Skip to main content

matc/clusters/codec/
groupcast.rs

1//! Matter TLV encoders and decoders for Groupcast Cluster
2//! Cluster ID: 0x0065
3//!
4//! This file is automatically generated from Groupcast.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Enum definitions
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
16#[repr(u8)]
17pub enum GroupcastTestResult {
18    /// There was no failure
19    Success = 0,
20    /// Other error during processing not otherwise covered
21    Generalerror = 1,
22    /// Message counter was <= counter from last received
23    Messagereplay = 2,
24    /// Message authentication failed
25    Failedauth = 3,
26    /// No key was found to process the message
27    Noavailablekey = 4,
28    /// Issue with attempting to send a multicast IPv6 message
29    Sendfailure = 5,
30}
31
32impl GroupcastTestResult {
33    /// Convert from u8 value
34    pub fn from_u8(value: u8) -> Option<Self> {
35        match value {
36            0 => Some(GroupcastTestResult::Success),
37            1 => Some(GroupcastTestResult::Generalerror),
38            2 => Some(GroupcastTestResult::Messagereplay),
39            3 => Some(GroupcastTestResult::Failedauth),
40            4 => Some(GroupcastTestResult::Noavailablekey),
41            5 => Some(GroupcastTestResult::Sendfailure),
42            _ => None,
43        }
44    }
45
46    /// Convert to u8 value
47    pub fn to_u8(self) -> u8 {
48        self as u8
49    }
50}
51
52impl From<GroupcastTestResult> for u8 {
53    fn from(val: GroupcastTestResult) -> Self {
54        val as u8
55    }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
59#[repr(u8)]
60pub enum GroupcastTesting {
61    /// Disable all test event generation
62    Disabletesting = 0,
63    /// Enable Listener test event generation
64    Enablelistenertesting = 1,
65    /// Enable Sender test event generation
66    Enablesendertesting = 2,
67}
68
69impl GroupcastTesting {
70    /// Convert from u8 value
71    pub fn from_u8(value: u8) -> Option<Self> {
72        match value {
73            0 => Some(GroupcastTesting::Disabletesting),
74            1 => Some(GroupcastTesting::Enablelistenertesting),
75            2 => Some(GroupcastTesting::Enablesendertesting),
76            _ => None,
77        }
78    }
79
80    /// Convert to u8 value
81    pub fn to_u8(self) -> u8 {
82        self as u8
83    }
84}
85
86impl From<GroupcastTesting> for u8 {
87    fn from(val: GroupcastTesting) -> Self {
88        val as u8
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
93#[repr(u8)]
94pub enum MulticastAddrPolicy {
95    /// Indicates group uses the IANA-assigned multicast address (default)
96    Ianaaddr = 0,
97    /// Group uses multicast address scoped to Fabric ID and Group ID
98    Pergroup = 1,
99}
100
101impl MulticastAddrPolicy {
102    /// Convert from u8 value
103    pub fn from_u8(value: u8) -> Option<Self> {
104        match value {
105            0 => Some(MulticastAddrPolicy::Ianaaddr),
106            1 => Some(MulticastAddrPolicy::Pergroup),
107            _ => None,
108        }
109    }
110
111    /// Convert to u8 value
112    pub fn to_u8(self) -> u8 {
113        self as u8
114    }
115}
116
117impl From<MulticastAddrPolicy> for u8 {
118    fn from(val: MulticastAddrPolicy) -> Self {
119        val as u8
120    }
121}
122
123// Struct definitions
124
125#[derive(Debug, serde::Serialize)]
126pub struct Membership {
127    pub group_id: Option<u16>,
128    pub endpoints: Option<Vec<u16>>,
129    pub key_set_id: Option<u16>,
130    pub has_auxiliary_acl: Option<bool>,
131    pub mcast_addr_policy: Option<MulticastAddrPolicy>,
132}
133
134// Command encoders
135
136/// Encode JoinGroup command (0x00)
137pub fn encode_join_group(group_id: u16, endpoints: Vec<u16>, key_set_id: u16, key: Option<Vec<u8>>, use_auxiliary_acl: Option<bool>, replace_endpoints: Option<bool>, mcast_addr_policy: Option<MulticastAddrPolicy>) -> anyhow::Result<Vec<u8>> {
138    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
139    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt16(group_id)).into());
140    tlv_fields.push((1, tlv::TlvItemValueEnc::StructAnon(endpoints.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into());
141    tlv_fields.push((2, tlv::TlvItemValueEnc::UInt16(key_set_id)).into());
142    if let Some(x) = key { tlv_fields.push((3, tlv::TlvItemValueEnc::OctetString(x)).into()); }
143    if let Some(x) = use_auxiliary_acl { tlv_fields.push((4, tlv::TlvItemValueEnc::Bool(x)).into()); }
144    if let Some(x) = replace_endpoints { tlv_fields.push((5, tlv::TlvItemValueEnc::Bool(x)).into()); }
145    if let Some(x) = mcast_addr_policy { tlv_fields.push((6, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
146    let tlv = tlv::TlvItemEnc {
147        tag: 0,
148        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
149    };
150    Ok(tlv.encode()?)
151}
152
153/// Encode LeaveGroup command (0x01)
154pub fn encode_leave_group(group_id: u16, endpoints: Option<Vec<u16>>) -> anyhow::Result<Vec<u8>> {
155    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
156    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt16(group_id)).into());
157    if let Some(endpoints) = endpoints {
158        tlv_fields.push((1, tlv::TlvItemValueEnc::StructAnon(endpoints.into_iter().map(|v| (0, tlv::TlvItemValueEnc::UInt16(v)).into()).collect())).into());
159    }
160    let tlv = tlv::TlvItemEnc {
161        tag: 0,
162        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
163    };
164    Ok(tlv.encode()?)
165}
166
167/// Encode UpdateGroupKey command (0x03)
168pub fn encode_update_group_key(group_id: u16, key_set_id: u16, key: Option<Vec<u8>>) -> anyhow::Result<Vec<u8>> {
169    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
170    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt16(group_id)).into());
171    tlv_fields.push((1, tlv::TlvItemValueEnc::UInt16(key_set_id)).into());
172    if let Some(x) = key { tlv_fields.push((2, tlv::TlvItemValueEnc::OctetString(x)).into()); }
173    let tlv = tlv::TlvItemEnc {
174        tag: 0,
175        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
176    };
177    Ok(tlv.encode()?)
178}
179
180/// Encode ConfigureAuxiliaryACL command (0x04)
181pub fn encode_configure_auxiliary_acl(group_id: u16, use_auxiliary_acl: bool) -> anyhow::Result<Vec<u8>> {
182    let tlv = tlv::TlvItemEnc {
183        tag: 0,
184        value: tlv::TlvItemValueEnc::StructInvisible(vec![
185        (0, tlv::TlvItemValueEnc::UInt16(group_id)).into(),
186        (1, tlv::TlvItemValueEnc::Bool(use_auxiliary_acl)).into(),
187        ]),
188    };
189    Ok(tlv.encode()?)
190}
191
192/// Encode GroupcastTesting command (0x05)
193pub fn encode_groupcast_testing(test_operation: GroupcastTesting, duration_seconds: Option<u16>) -> anyhow::Result<Vec<u8>> {
194    let mut tlv_fields: Vec<tlv::TlvItemEnc> = Vec::new();
195    tlv_fields.push((0, tlv::TlvItemValueEnc::UInt8(test_operation.to_u8())).into());
196    if let Some(x) = duration_seconds { tlv_fields.push((1, tlv::TlvItemValueEnc::UInt16(x)).into()); }
197    let tlv = tlv::TlvItemEnc {
198        tag: 0,
199        value: tlv::TlvItemValueEnc::StructInvisible(tlv_fields),
200    };
201    Ok(tlv.encode()?)
202}
203
204// Attribute decoders
205
206/// Decode Membership attribute (0x0000)
207pub fn decode_membership(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Membership>> {
208    let mut res = Vec::new();
209    if let tlv::TlvItemValue::List(v) = inp {
210        for item in v {
211            res.push(Membership {
212                group_id: item.get_int(&[0]).map(|v| v as u16),
213                endpoints: {
214                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
215                        let items: Vec<u16> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u16) } else { None } }).collect();
216                        Some(items)
217                    } else {
218                        None
219                    }
220                },
221                key_set_id: item.get_int(&[2]).map(|v| v as u16),
222                has_auxiliary_acl: item.get_bool(&[3]),
223                mcast_addr_policy: item.get_int(&[4]).and_then(|v| MulticastAddrPolicy::from_u8(v as u8)),
224            });
225        }
226    }
227    Ok(res)
228}
229
230/// Decode MaxMembershipCount attribute (0x0001)
231pub fn decode_max_membership_count(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
232    if let tlv::TlvItemValue::Int(v) = inp {
233        Ok(*v as u16)
234    } else {
235        Err(anyhow::anyhow!("Expected UInt16"))
236    }
237}
238
239/// Decode MaxMcastAddrCount attribute (0x0002)
240pub fn decode_max_mcast_addr_count(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
241    if let tlv::TlvItemValue::Int(v) = inp {
242        Ok(*v as u16)
243    } else {
244        Err(anyhow::anyhow!("Expected UInt16"))
245    }
246}
247
248/// Decode UsedMcastAddrCount attribute (0x0003)
249pub fn decode_used_mcast_addr_count(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
250    if let tlv::TlvItemValue::Int(v) = inp {
251        Ok(*v as u16)
252    } else {
253        Err(anyhow::anyhow!("Expected UInt16"))
254    }
255}
256
257/// Decode FabricUnderTest attribute (0x0004)
258pub fn decode_fabric_under_test(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
259    if let tlv::TlvItemValue::Int(v) = inp {
260        Ok(*v as u8)
261    } else {
262        Err(anyhow::anyhow!("Expected UInt8"))
263    }
264}
265
266
267// JSON dispatcher function
268
269/// Decode attribute value and return as JSON string
270///
271/// # Parameters
272/// * `cluster_id` - The cluster identifier
273/// * `attribute_id` - The attribute identifier
274/// * `tlv_value` - The TLV value to decode
275///
276/// # Returns
277/// JSON string representation of the decoded value or error
278pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
279    // Verify this is the correct cluster
280    if cluster_id != 0x0065 {
281        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0065, got {}\"}}", cluster_id);
282    }
283
284    match attribute_id {
285        0x0000 => {
286            match decode_membership(tlv_value) {
287                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
288                Err(e) => format!("{{\"error\": \"{}\"}}", e),
289            }
290        }
291        0x0001 => {
292            match decode_max_membership_count(tlv_value) {
293                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
294                Err(e) => format!("{{\"error\": \"{}\"}}", e),
295            }
296        }
297        0x0002 => {
298            match decode_max_mcast_addr_count(tlv_value) {
299                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
300                Err(e) => format!("{{\"error\": \"{}\"}}", e),
301            }
302        }
303        0x0003 => {
304            match decode_used_mcast_addr_count(tlv_value) {
305                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
306                Err(e) => format!("{{\"error\": \"{}\"}}", e),
307            }
308        }
309        0x0004 => {
310            match decode_fabric_under_test(tlv_value) {
311                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
312                Err(e) => format!("{{\"error\": \"{}\"}}", e),
313            }
314        }
315        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
316    }
317}
318
319/// Get list of all attributes supported by this cluster
320///
321/// # Returns
322/// Vector of tuples containing (attribute_id, attribute_name)
323pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
324    vec![
325        (0x0000, "Membership"),
326        (0x0001, "MaxMembershipCount"),
327        (0x0002, "MaxMcastAddrCount"),
328        (0x0003, "UsedMcastAddrCount"),
329        (0x0004, "FabricUnderTest"),
330    ]
331}
332
333// Command listing
334
335pub fn get_command_list() -> Vec<(u32, &'static str)> {
336    vec![
337        (0x00, "JoinGroup"),
338        (0x01, "LeaveGroup"),
339        (0x03, "UpdateGroupKey"),
340        (0x04, "ConfigureAuxiliaryACL"),
341        (0x05, "GroupcastTesting"),
342    ]
343}
344
345pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
346    match cmd_id {
347        0x00 => Some("JoinGroup"),
348        0x01 => Some("LeaveGroup"),
349        0x03 => Some("UpdateGroupKey"),
350        0x04 => Some("ConfigureAuxiliaryACL"),
351        0x05 => Some("GroupcastTesting"),
352        _ => None,
353    }
354}
355
356pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
357    match cmd_id {
358        0x00 => Some(vec![
359            crate::clusters::codec::CommandField { tag: 0, name: "group_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
360            crate::clusters::codec::CommandField { tag: 1, name: "endpoints", kind: crate::clusters::codec::FieldKind::List { entry_type: "endpoint-no" }, optional: false, nullable: false },
361            crate::clusters::codec::CommandField { tag: 2, name: "key_set_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
362            crate::clusters::codec::CommandField { tag: 3, name: "key", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
363            crate::clusters::codec::CommandField { tag: 4, name: "use_auxiliary_acl", kind: crate::clusters::codec::FieldKind::Bool, optional: true, nullable: false },
364            crate::clusters::codec::CommandField { tag: 5, name: "replace_endpoints", kind: crate::clusters::codec::FieldKind::Bool, optional: true, nullable: false },
365            crate::clusters::codec::CommandField { tag: 6, name: "mcast_addr_policy", kind: crate::clusters::codec::FieldKind::Enum { name: "MulticastAddrPolicy", variants: &[(0, "Ianaaddr"), (1, "Pergroup")] }, optional: true, nullable: false },
366        ]),
367        0x01 => Some(vec![
368            crate::clusters::codec::CommandField { tag: 0, name: "group_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
369            crate::clusters::codec::CommandField { tag: 1, name: "endpoints", kind: crate::clusters::codec::FieldKind::List { entry_type: "endpoint-no" }, optional: true, nullable: false },
370        ]),
371        0x03 => Some(vec![
372            crate::clusters::codec::CommandField { tag: 0, name: "group_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
373            crate::clusters::codec::CommandField { tag: 1, name: "key_set_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
374            crate::clusters::codec::CommandField { tag: 2, name: "key", kind: crate::clusters::codec::FieldKind::OctetString, optional: true, nullable: false },
375        ]),
376        0x04 => Some(vec![
377            crate::clusters::codec::CommandField { tag: 0, name: "group_id", kind: crate::clusters::codec::FieldKind::U16, optional: false, nullable: false },
378            crate::clusters::codec::CommandField { tag: 1, name: "use_auxiliary_acl", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
379        ]),
380        0x05 => Some(vec![
381            crate::clusters::codec::CommandField { tag: 0, name: "test_operation", kind: crate::clusters::codec::FieldKind::Enum { name: "GroupcastTesting", variants: &[(0, "Disabletesting"), (1, "Enablelistenertesting"), (2, "Enablesendertesting")] }, optional: false, nullable: false },
382            crate::clusters::codec::CommandField { tag: 1, name: "duration_seconds", kind: crate::clusters::codec::FieldKind::U16, optional: true, nullable: false },
383        ]),
384        _ => None,
385    }
386}
387
388pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
389    match cmd_id {
390        0x00 => Err(anyhow::anyhow!("command \"JoinGroup\" has complex args: use raw mode")),
391        0x01 => Err(anyhow::anyhow!("command \"LeaveGroup\" has complex args: use raw mode")),
392        0x03 => {
393        let group_id = crate::clusters::codec::json_util::get_u16(args, "group_id")?;
394        let key_set_id = crate::clusters::codec::json_util::get_u16(args, "key_set_id")?;
395        let key = crate::clusters::codec::json_util::get_opt_octstr(args, "key")?;
396        encode_update_group_key(group_id, key_set_id, key)
397        }
398        0x04 => {
399        let group_id = crate::clusters::codec::json_util::get_u16(args, "group_id")?;
400        let use_auxiliary_acl = crate::clusters::codec::json_util::get_bool(args, "use_auxiliary_acl")?;
401        encode_configure_auxiliary_acl(group_id, use_auxiliary_acl)
402        }
403        0x05 => {
404        let test_operation = {
405            let n = crate::clusters::codec::json_util::get_u64(args, "test_operation")?;
406            GroupcastTesting::from_u8(n as u8).ok_or_else(|| anyhow::anyhow!("invalid GroupcastTesting: {}", n))?
407        };
408        let duration_seconds = crate::clusters::codec::json_util::get_opt_u16(args, "duration_seconds")?;
409        encode_groupcast_testing(test_operation, duration_seconds)
410        }
411        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
412    }
413}
414
415#[derive(Debug, serde::Serialize)]
416pub struct LeaveGroupResponse {
417    pub group_id: Option<u16>,
418    pub endpoints: Option<Vec<u16>>,
419}
420
421// Command response decoders
422
423/// Decode LeaveGroupResponse command response (02)
424pub fn decode_leave_group_response(inp: &tlv::TlvItemValue) -> anyhow::Result<LeaveGroupResponse> {
425    if let tlv::TlvItemValue::List(_fields) = inp {
426        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
427        Ok(LeaveGroupResponse {
428                group_id: item.get_int(&[0]).map(|v| v as u16),
429                endpoints: {
430                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[1]) {
431                        let items: Vec<u16> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v as u16) } else { None } }).collect();
432                        Some(items)
433                    } else {
434                        None
435                    }
436                },
437        })
438    } else {
439        Err(anyhow::anyhow!("Expected struct fields"))
440    }
441}
442
443// Typed facade (invokes + reads)
444
445/// Invoke `JoinGroup` command on cluster `Groupcast`.
446pub async fn join_group(conn: &crate::controller::Connection, endpoint: u16, group_id: u16, endpoints: Vec<u16>, key_set_id: u16, key: Option<Vec<u8>>, use_auxiliary_acl: Option<bool>, replace_endpoints: Option<bool>, mcast_addr_policy: Option<MulticastAddrPolicy>) -> anyhow::Result<()> {
447    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_CMD_ID_JOINGROUP, &encode_join_group(group_id, endpoints, key_set_id, key, use_auxiliary_acl, replace_endpoints, mcast_addr_policy)?).await?;
448    Ok(())
449}
450
451/// Invoke `LeaveGroup` command on cluster `Groupcast`.
452pub async fn leave_group(conn: &crate::controller::Connection, endpoint: u16, group_id: u16, endpoints: Option<Vec<u16>>) -> anyhow::Result<LeaveGroupResponse> {
453    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_CMD_ID_LEAVEGROUP, &encode_leave_group(group_id, endpoints)?).await?;
454    decode_leave_group_response(&tlv)
455}
456
457/// Invoke `UpdateGroupKey` command on cluster `Groupcast`.
458pub async fn update_group_key(conn: &crate::controller::Connection, endpoint: u16, group_id: u16, key_set_id: u16, key: Option<Vec<u8>>) -> anyhow::Result<()> {
459    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_CMD_ID_UPDATEGROUPKEY, &encode_update_group_key(group_id, key_set_id, key)?).await?;
460    Ok(())
461}
462
463/// Invoke `ConfigureAuxiliaryACL` command on cluster `Groupcast`.
464pub async fn configure_auxiliary_acl(conn: &crate::controller::Connection, endpoint: u16, group_id: u16, use_auxiliary_acl: bool) -> anyhow::Result<()> {
465    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_CMD_ID_CONFIGUREAUXILIARYACL, &encode_configure_auxiliary_acl(group_id, use_auxiliary_acl)?).await?;
466    Ok(())
467}
468
469/// Invoke `GroupcastTesting` command on cluster `Groupcast`.
470pub async fn groupcast_testing(conn: &crate::controller::Connection, endpoint: u16, test_operation: GroupcastTesting, duration_seconds: Option<u16>) -> anyhow::Result<()> {
471    conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_CMD_ID_GROUPCASTTESTING, &encode_groupcast_testing(test_operation, duration_seconds)?).await?;
472    Ok(())
473}
474
475/// Read `Membership` attribute from cluster `Groupcast`.
476pub async fn read_membership(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Membership>> {
477    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_ATTR_ID_MEMBERSHIP).await?;
478    decode_membership(&tlv)
479}
480
481/// Read `MaxMembershipCount` attribute from cluster `Groupcast`.
482pub async fn read_max_membership_count(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
483    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_ATTR_ID_MAXMEMBERSHIPCOUNT).await?;
484    decode_max_membership_count(&tlv)
485}
486
487/// Read `MaxMcastAddrCount` attribute from cluster `Groupcast`.
488pub async fn read_max_mcast_addr_count(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
489    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_ATTR_ID_MAXMCASTADDRCOUNT).await?;
490    decode_max_mcast_addr_count(&tlv)
491}
492
493/// Read `UsedMcastAddrCount` attribute from cluster `Groupcast`.
494pub async fn read_used_mcast_addr_count(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
495    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_ATTR_ID_USEDMCASTADDRCOUNT).await?;
496    decode_used_mcast_addr_count(&tlv)
497}
498
499/// Read `FabricUnderTest` attribute from cluster `Groupcast`.
500pub async fn read_fabric_under_test(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
501    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_GROUPCAST, crate::clusters::defs::CLUSTER_GROUPCAST_ATTR_ID_FABRICUNDERTEST).await?;
502    decode_fabric_under_test(&tlv)
503}
504
505#[derive(Debug, serde::Serialize)]
506pub struct GroupcastTestingEvent {
507    pub source_ip_address: Option<u8>,
508    pub destination_ip_address: Option<u8>,
509    pub group_id: Option<u16>,
510    pub endpoint_id: Option<u8>,
511    pub cluster_id: Option<u32>,
512    pub element_id: Option<u32>,
513    pub access_allowed: Option<bool>,
514    pub groupcast_test_result: Option<GroupcastTestResult>,
515}
516
517// Event decoders
518
519/// Decode GroupcastTesting event (0x00, priority: info)
520pub fn decode_groupcast_testing_event(inp: &tlv::TlvItemValue) -> anyhow::Result<GroupcastTestingEvent> {
521    if let tlv::TlvItemValue::List(_fields) = inp {
522        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
523        Ok(GroupcastTestingEvent {
524                                source_ip_address: item.get_int(&[0]).map(|v| v as u8),
525                                destination_ip_address: item.get_int(&[1]).map(|v| v as u8),
526                                group_id: item.get_int(&[2]).map(|v| v as u16),
527                                endpoint_id: item.get_int(&[3]).map(|v| v as u8),
528                                cluster_id: item.get_int(&[4]).map(|v| v as u32),
529                                element_id: item.get_int(&[5]).map(|v| v as u32),
530                                access_allowed: item.get_bool(&[6]),
531                                groupcast_test_result: item.get_int(&[7]).and_then(|v| GroupcastTestResult::from_u8(v as u8)),
532        })
533    } else {
534        Err(anyhow::anyhow!("Expected struct fields"))
535    }
536}
537
538
539// Event JSON dispatcher
540
541/// Decode event value and return as JSON string
542pub fn decode_event_json(cluster_id: u32, event_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
543    if cluster_id != 0x0065 {
544        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0065, got {}\"}}", cluster_id);
545    }
546
547    match event_id {
548        0x00 => {
549            match decode_groupcast_testing_event(tlv_value) {
550                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
551                Err(e) => format!("{{\"error\": \"{}\"}}", e),
552            }
553        }
554        _ => format!("{{\"error\": \"Unknown event ID: {}\"}}", event_id),
555    }
556}
557
558/// Get list of all events supported by this cluster
559///
560/// # Returns
561/// Vector of tuples containing (event_id, event_name)
562pub fn get_event_list() -> Vec<(u32, &'static str)> {
563    vec![
564        (0x00, "GroupcastTesting"),
565    ]
566}
567