Skip to main content

matc/clusters/codec/
acl_cluster.rs

1//! Matter TLV encoders and decoders for Access Control Cluster
2//! Cluster ID: 0x001F
3//!
4//! This file is automatically generated from ACL-Cluster.xml
5
6#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13// Import serialization helpers for octet strings
14use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16// Enum definitions
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum AccessControlAuxiliaryType {
21    /// This ACL entry exists because of some system reason and is likely non-revocable
22    System = 0,
23    /// Synthesized via Groupcast Cluster administrator-configured group membership
24    Groupcast = 1,
25}
26
27impl AccessControlAuxiliaryType {
28    /// Convert from u8 value
29    pub fn from_u8(value: u8) -> Option<Self> {
30        match value {
31            0 => Some(AccessControlAuxiliaryType::System),
32            1 => Some(AccessControlAuxiliaryType::Groupcast),
33            _ => None,
34        }
35    }
36
37    /// Convert to u8 value
38    pub fn to_u8(self) -> u8 {
39        self as u8
40    }
41}
42
43impl From<AccessControlAuxiliaryType> for u8 {
44    fn from(val: AccessControlAuxiliaryType) -> Self {
45        val as u8
46    }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
50#[repr(u8)]
51pub enum AccessControlEntryAuthMode {
52    /// Passcode authenticated session
53    Pase = 1,
54    /// Certificate authenticated session
55    Case = 2,
56    /// Group authenticated session
57    Group = 3,
58}
59
60impl AccessControlEntryAuthMode {
61    /// Convert from u8 value
62    pub fn from_u8(value: u8) -> Option<Self> {
63        match value {
64            1 => Some(AccessControlEntryAuthMode::Pase),
65            2 => Some(AccessControlEntryAuthMode::Case),
66            3 => Some(AccessControlEntryAuthMode::Group),
67            _ => None,
68        }
69    }
70
71    /// Convert to u8 value
72    pub fn to_u8(self) -> u8 {
73        self as u8
74    }
75}
76
77impl From<AccessControlEntryAuthMode> for u8 {
78    fn from(val: AccessControlEntryAuthMode) -> Self {
79        val as u8
80    }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84#[repr(u8)]
85pub enum AccessControlEntryPrivilege {
86    /// Can read and observe all (except Access Control Cluster)
87    View = 1,
88    Proxyview = 2,
89    /// View privileges, and can perform the primary function of this Node (except Access Control Cluster)
90    Operate = 3,
91    /// Operate privileges, and can modify persistent configuration of this Node (except Access Control Cluster)
92    Manage = 4,
93    /// Manage privileges, and can observe and modify the Access Control Cluster
94    Administer = 5,
95}
96
97impl AccessControlEntryPrivilege {
98    /// Convert from u8 value
99    pub fn from_u8(value: u8) -> Option<Self> {
100        match value {
101            1 => Some(AccessControlEntryPrivilege::View),
102            2 => Some(AccessControlEntryPrivilege::Proxyview),
103            3 => Some(AccessControlEntryPrivilege::Operate),
104            4 => Some(AccessControlEntryPrivilege::Manage),
105            5 => Some(AccessControlEntryPrivilege::Administer),
106            _ => None,
107        }
108    }
109
110    /// Convert to u8 value
111    pub fn to_u8(self) -> u8 {
112        self as u8
113    }
114}
115
116impl From<AccessControlEntryPrivilege> for u8 {
117    fn from(val: AccessControlEntryPrivilege) -> Self {
118        val as u8
119    }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
123#[repr(u8)]
124pub enum AccessRestrictionType {
125    /// Clients on this fabric are currently forbidden from reading and writing an attribute
126    Attributeaccessforbidden = 0,
127    /// Clients on this fabric are currently forbidden from writing an attribute
128    Attributewriteforbidden = 1,
129    /// Clients on this fabric are currently forbidden from invoking a command
130    Commandforbidden = 2,
131    /// Clients on this fabric are currently forbidden from reading an event
132    Eventforbidden = 3,
133}
134
135impl AccessRestrictionType {
136    /// Convert from u8 value
137    pub fn from_u8(value: u8) -> Option<Self> {
138        match value {
139            0 => Some(AccessRestrictionType::Attributeaccessforbidden),
140            1 => Some(AccessRestrictionType::Attributewriteforbidden),
141            2 => Some(AccessRestrictionType::Commandforbidden),
142            3 => Some(AccessRestrictionType::Eventforbidden),
143            _ => None,
144        }
145    }
146
147    /// Convert to u8 value
148    pub fn to_u8(self) -> u8 {
149        self as u8
150    }
151}
152
153impl From<AccessRestrictionType> for u8 {
154    fn from(val: AccessRestrictionType) -> Self {
155        val as u8
156    }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
160#[repr(u8)]
161pub enum ChangeType {
162    /// Entry or extension was changed
163    Changed = 0,
164    /// Entry or extension was added
165    Added = 1,
166    /// Entry or extension was removed
167    Removed = 2,
168}
169
170impl ChangeType {
171    /// Convert from u8 value
172    pub fn from_u8(value: u8) -> Option<Self> {
173        match value {
174            0 => Some(ChangeType::Changed),
175            1 => Some(ChangeType::Added),
176            2 => Some(ChangeType::Removed),
177            _ => None,
178        }
179    }
180
181    /// Convert to u8 value
182    pub fn to_u8(self) -> u8 {
183        self as u8
184    }
185}
186
187impl From<ChangeType> for u8 {
188    fn from(val: ChangeType) -> Self {
189        val as u8
190    }
191}
192
193// Struct definitions
194
195#[derive(Debug, serde::Serialize)]
196pub struct AccessControlEntry {
197    pub privilege: Option<AccessControlEntryPrivilege>,
198    pub auth_mode: Option<AccessControlEntryAuthMode>,
199    pub subjects: Option<Vec<u64>>,
200    pub targets: Option<Vec<AccessControlTarget>>,
201    pub auxiliary_type: Option<AccessControlAuxiliaryType>,
202}
203
204#[derive(Debug, serde::Serialize)]
205pub struct AccessControlExtension {
206    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
207    pub data: Option<Vec<u8>>,
208}
209
210#[derive(Debug, serde::Serialize)]
211pub struct AccessControlTarget {
212    pub cluster: Option<u32>,
213    pub endpoint: Option<u16>,
214    pub device_type: Option<u32>,
215}
216
217#[derive(Debug, serde::Serialize)]
218pub struct AccessRestrictionEntry {
219    pub endpoint: Option<u16>,
220    pub cluster: Option<u32>,
221    pub restrictions: Option<Vec<AccessRestriction>>,
222}
223
224#[derive(Debug, serde::Serialize)]
225pub struct AccessRestriction {
226    pub type_: Option<AccessRestrictionType>,
227    pub id: Option<u32>,
228}
229
230#[derive(Debug, serde::Serialize)]
231pub struct CommissioningAccessRestrictionEntry {
232    pub endpoint: Option<u16>,
233    pub cluster: Option<u32>,
234    pub restrictions: Option<Vec<AccessRestriction>>,
235}
236
237// Command encoders
238
239/// Encode ReviewFabricRestrictions command (0x00)
240pub fn encode_review_fabric_restrictions(arl: Vec<CommissioningAccessRestrictionEntry>) -> anyhow::Result<Vec<u8>> {
241    let tlv = tlv::TlvItemEnc {
242        tag: 0,
243        value: tlv::TlvItemValueEnc::StructInvisible(vec![
244        (0, tlv::TlvItemValueEnc::Array(arl.into_iter().map(|v| {
245                    let mut fields = Vec::new();
246                    if let Some(x) = v.endpoint { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
247                    if let Some(x) = v.cluster { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
248                    if let Some(listv) = v.restrictions {
249                        let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
250                            let mut nested_fields = Vec::new();
251                                if let Some(x) = inner.type_ { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
252                                if let Some(x) = inner.id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
253                            (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
254                        }).collect();
255                        fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
256                    }
257                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
258                }).collect())).into(),
259        ]),
260    };
261    Ok(tlv.encode()?)
262}
263
264// Attribute decoders
265
266/// Decode ACL attribute (0x0000)
267pub fn decode_acl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlEntry>> {
268    let mut res = Vec::new();
269    if let tlv::TlvItemValue::List(v) = inp {
270        for item in v {
271            res.push(AccessControlEntry {
272                privilege: item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
273                auth_mode: item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
274                subjects: {
275                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
276                        let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
277                        Some(items)
278                    } else {
279                        None
280                    }
281                },
282                targets: {
283                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
284                        let mut items = Vec::new();
285                        for list_item in l {
286                            items.push(AccessControlTarget {
287                cluster: list_item.get_int(&[0]).map(|v| v as u32),
288                endpoint: list_item.get_int(&[1]).map(|v| v as u16),
289                device_type: list_item.get_int(&[2]).map(|v| v as u32),
290                            });
291                        }
292                        Some(items)
293                    } else {
294                        None
295                    }
296                },
297                auxiliary_type: item.get_int(&[5]).and_then(|v| AccessControlAuxiliaryType::from_u8(v as u8)),
298            });
299        }
300    }
301    Ok(res)
302}
303
304/// Decode Extension attribute (0x0001)
305pub fn decode_extension(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlExtension>> {
306    let mut res = Vec::new();
307    if let tlv::TlvItemValue::List(v) = inp {
308        for item in v {
309            res.push(AccessControlExtension {
310                data: item.get_octet_string_owned(&[1]),
311            });
312        }
313    }
314    Ok(res)
315}
316
317/// Decode SubjectsPerAccessControlEntry attribute (0x0002)
318pub fn decode_subjects_per_access_control_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
319    if let tlv::TlvItemValue::Int(v) = inp {
320        Ok(*v as u16)
321    } else {
322        Err(anyhow::anyhow!("Expected UInt16"))
323    }
324}
325
326/// Decode TargetsPerAccessControlEntry attribute (0x0003)
327pub fn decode_targets_per_access_control_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
328    if let tlv::TlvItemValue::Int(v) = inp {
329        Ok(*v as u16)
330    } else {
331        Err(anyhow::anyhow!("Expected UInt16"))
332    }
333}
334
335/// Decode AccessControlEntriesPerFabric attribute (0x0004)
336pub fn decode_access_control_entries_per_fabric(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
337    if let tlv::TlvItemValue::Int(v) = inp {
338        Ok(*v as u16)
339    } else {
340        Err(anyhow::anyhow!("Expected UInt16"))
341    }
342}
343
344/// Decode CommissioningARL attribute (0x0005)
345pub fn decode_commissioning_arl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<CommissioningAccessRestrictionEntry>> {
346    let mut res = Vec::new();
347    if let tlv::TlvItemValue::List(v) = inp {
348        for item in v {
349            res.push(CommissioningAccessRestrictionEntry {
350                endpoint: item.get_int(&[0]).map(|v| v as u16),
351                cluster: item.get_int(&[1]).map(|v| v as u32),
352                restrictions: {
353                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
354                        let mut items = Vec::new();
355                        for list_item in l {
356                            items.push(AccessRestriction {
357                type_: list_item.get_int(&[0]).and_then(|v| AccessRestrictionType::from_u8(v as u8)),
358                id: list_item.get_int(&[1]).map(|v| v as u32),
359                            });
360                        }
361                        Some(items)
362                    } else {
363                        None
364                    }
365                },
366            });
367        }
368    }
369    Ok(res)
370}
371
372/// Decode ARL attribute (0x0006)
373pub fn decode_arl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessRestrictionEntry>> {
374    let mut res = Vec::new();
375    if let tlv::TlvItemValue::List(v) = inp {
376        for item in v {
377            res.push(AccessRestrictionEntry {
378                endpoint: item.get_int(&[0]).map(|v| v as u16),
379                cluster: item.get_int(&[1]).map(|v| v as u32),
380                restrictions: {
381                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
382                        let mut items = Vec::new();
383                        for list_item in l {
384                            items.push(AccessRestriction {
385                type_: list_item.get_int(&[0]).and_then(|v| AccessRestrictionType::from_u8(v as u8)),
386                id: list_item.get_int(&[1]).map(|v| v as u32),
387                            });
388                        }
389                        Some(items)
390                    } else {
391                        None
392                    }
393                },
394            });
395        }
396    }
397    Ok(res)
398}
399
400/// Decode AuxiliaryACL attribute (0x0007)
401pub fn decode_auxiliary_acl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlEntry>> {
402    let mut res = Vec::new();
403    if let tlv::TlvItemValue::List(v) = inp {
404        for item in v {
405            res.push(AccessControlEntry {
406                privilege: item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
407                auth_mode: item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
408                subjects: {
409                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
410                        let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
411                        Some(items)
412                    } else {
413                        None
414                    }
415                },
416                targets: {
417                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
418                        let mut items = Vec::new();
419                        for list_item in l {
420                            items.push(AccessControlTarget {
421                cluster: list_item.get_int(&[0]).map(|v| v as u32),
422                endpoint: list_item.get_int(&[1]).map(|v| v as u16),
423                device_type: list_item.get_int(&[2]).map(|v| v as u32),
424                            });
425                        }
426                        Some(items)
427                    } else {
428                        None
429                    }
430                },
431                auxiliary_type: item.get_int(&[5]).and_then(|v| AccessControlAuxiliaryType::from_u8(v as u8)),
432            });
433        }
434    }
435    Ok(res)
436}
437
438
439// JSON dispatcher function
440
441/// Decode attribute value and return as JSON string
442///
443/// # Parameters
444/// * `cluster_id` - The cluster identifier
445/// * `attribute_id` - The attribute identifier
446/// * `tlv_value` - The TLV value to decode
447///
448/// # Returns
449/// JSON string representation of the decoded value or error
450pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
451    // Verify this is the correct cluster
452    if cluster_id != 0x001F {
453        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x001F, got {}\"}}", cluster_id);
454    }
455
456    match attribute_id {
457        0x0000 => {
458            match decode_acl(tlv_value) {
459                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
460                Err(e) => format!("{{\"error\": \"{}\"}}", e),
461            }
462        }
463        0x0001 => {
464            match decode_extension(tlv_value) {
465                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
466                Err(e) => format!("{{\"error\": \"{}\"}}", e),
467            }
468        }
469        0x0002 => {
470            match decode_subjects_per_access_control_entry(tlv_value) {
471                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
472                Err(e) => format!("{{\"error\": \"{}\"}}", e),
473            }
474        }
475        0x0003 => {
476            match decode_targets_per_access_control_entry(tlv_value) {
477                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
478                Err(e) => format!("{{\"error\": \"{}\"}}", e),
479            }
480        }
481        0x0004 => {
482            match decode_access_control_entries_per_fabric(tlv_value) {
483                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
484                Err(e) => format!("{{\"error\": \"{}\"}}", e),
485            }
486        }
487        0x0005 => {
488            match decode_commissioning_arl(tlv_value) {
489                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
490                Err(e) => format!("{{\"error\": \"{}\"}}", e),
491            }
492        }
493        0x0006 => {
494            match decode_arl(tlv_value) {
495                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
496                Err(e) => format!("{{\"error\": \"{}\"}}", e),
497            }
498        }
499        0x0007 => {
500            match decode_auxiliary_acl(tlv_value) {
501                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
502                Err(e) => format!("{{\"error\": \"{}\"}}", e),
503            }
504        }
505        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
506    }
507}
508
509/// Get list of all attributes supported by this cluster
510///
511/// # Returns
512/// Vector of tuples containing (attribute_id, attribute_name)
513pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
514    vec![
515        (0x0000, "ACL"),
516        (0x0001, "Extension"),
517        (0x0002, "SubjectsPerAccessControlEntry"),
518        (0x0003, "TargetsPerAccessControlEntry"),
519        (0x0004, "AccessControlEntriesPerFabric"),
520        (0x0005, "CommissioningARL"),
521        (0x0006, "ARL"),
522        (0x0007, "AuxiliaryACL"),
523    ]
524}
525
526// Command listing
527
528pub fn get_command_list() -> Vec<(u32, &'static str)> {
529    vec![
530        (0x00, "ReviewFabricRestrictions"),
531    ]
532}
533
534pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
535    match cmd_id {
536        0x00 => Some("ReviewFabricRestrictions"),
537        _ => None,
538    }
539}
540
541pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
542    match cmd_id {
543        0x00 => Some(vec![
544            crate::clusters::codec::CommandField { tag: 0, name: "arl", kind: crate::clusters::codec::FieldKind::List { entry_type: "CommissioningAccessRestrictionEntryStruct" }, optional: false, nullable: false },
545        ]),
546        _ => None,
547    }
548}
549
550pub fn encode_command_json(cmd_id: u32, _args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
551    match cmd_id {
552        0x00 => Err(anyhow::anyhow!("command \"ReviewFabricRestrictions\" has complex args: use raw mode")),
553        _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
554    }
555}
556
557#[derive(Debug, serde::Serialize)]
558pub struct ReviewFabricRestrictionsResponse {
559    pub token: Option<u64>,
560}
561
562// Command response decoders
563
564/// Decode ReviewFabricRestrictionsResponse command response (01)
565pub fn decode_review_fabric_restrictions_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ReviewFabricRestrictionsResponse> {
566    if let tlv::TlvItemValue::List(_fields) = inp {
567        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
568        Ok(ReviewFabricRestrictionsResponse {
569                token: item.get_int(&[0]),
570        })
571    } else {
572        Err(anyhow::anyhow!("Expected struct fields"))
573    }
574}
575
576// Typed facade (invokes + reads)
577
578/// Invoke `ReviewFabricRestrictions` command on cluster `Access Control`.
579pub async fn review_fabric_restrictions(conn: &crate::controller::Connection, endpoint: u16, arl: Vec<CommissioningAccessRestrictionEntry>) -> anyhow::Result<ReviewFabricRestrictionsResponse> {
580    let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_CMD_ID_REVIEWFABRICRESTRICTIONS, &encode_review_fabric_restrictions(arl)?).await?;
581    decode_review_fabric_restrictions_response(&tlv)
582}
583
584/// Read `ACL` attribute from cluster `Access Control`.
585pub async fn read_acl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessControlEntry>> {
586    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_ACL).await?;
587    decode_acl(&tlv)
588}
589
590/// Read `Extension` attribute from cluster `Access Control`.
591pub async fn read_extension(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessControlExtension>> {
592    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_EXTENSION).await?;
593    decode_extension(&tlv)
594}
595
596/// Read `SubjectsPerAccessControlEntry` attribute from cluster `Access Control`.
597pub async fn read_subjects_per_access_control_entry(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
598    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_SUBJECTSPERACCESSCONTROLENTRY).await?;
599    decode_subjects_per_access_control_entry(&tlv)
600}
601
602/// Read `TargetsPerAccessControlEntry` attribute from cluster `Access Control`.
603pub async fn read_targets_per_access_control_entry(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
604    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_TARGETSPERACCESSCONTROLENTRY).await?;
605    decode_targets_per_access_control_entry(&tlv)
606}
607
608/// Read `AccessControlEntriesPerFabric` attribute from cluster `Access Control`.
609pub async fn read_access_control_entries_per_fabric(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
610    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_ACCESSCONTROLENTRIESPERFABRIC).await?;
611    decode_access_control_entries_per_fabric(&tlv)
612}
613
614/// Read `CommissioningARL` attribute from cluster `Access Control`.
615pub async fn read_commissioning_arl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<CommissioningAccessRestrictionEntry>> {
616    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_COMMISSIONINGARL).await?;
617    decode_commissioning_arl(&tlv)
618}
619
620/// Read `ARL` attribute from cluster `Access Control`.
621pub async fn read_arl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessRestrictionEntry>> {
622    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_ARL).await?;
623    decode_arl(&tlv)
624}
625
626/// Read `AuxiliaryACL` attribute from cluster `Access Control`.
627pub async fn read_auxiliary_acl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessControlEntry>> {
628    let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_AUXILIARYACL).await?;
629    decode_auxiliary_acl(&tlv)
630}
631
632#[derive(Debug, serde::Serialize)]
633pub struct AccessControlEntryChangedEvent {
634    pub admin_node_id: Option<u64>,
635    pub admin_passcode_id: Option<u16>,
636    pub change_type: Option<ChangeType>,
637    pub latest_value: Option<AccessControlEntry>,
638}
639
640#[derive(Debug, serde::Serialize)]
641pub struct AccessControlExtensionChangedEvent {
642    pub admin_node_id: Option<u64>,
643    pub admin_passcode_id: Option<u16>,
644    pub change_type: Option<ChangeType>,
645    pub latest_value: Option<AccessControlExtension>,
646}
647
648#[derive(Debug, serde::Serialize)]
649pub struct FabricRestrictionReviewUpdateEvent {
650    pub token: Option<u64>,
651    pub instruction: Option<String>,
652    pub arl_request_flow_url: Option<String>,
653}
654
655#[derive(Debug, serde::Serialize)]
656pub struct AuxiliaryAccessUpdatedEvent {
657    pub admin_node_id: Option<u64>,
658}
659
660// Event decoders
661
662/// Decode AccessControlEntryChanged event (0x00, priority: info)
663pub fn decode_access_control_entry_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AccessControlEntryChangedEvent> {
664    if let tlv::TlvItemValue::List(_fields) = inp {
665        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
666        Ok(AccessControlEntryChangedEvent {
667                                admin_node_id: item.get_int(&[1]),
668                                admin_passcode_id: item.get_int(&[2]).map(|v| v as u16),
669                                change_type: item.get_int(&[3]).and_then(|v| ChangeType::from_u8(v as u8)),
670                                latest_value: {
671                    if let Some(nested_tlv) = item.get(&[4]) {
672                        if let tlv::TlvItemValue::List(_) = nested_tlv {
673                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
674                            Some(AccessControlEntry {
675                privilege: nested_item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
676                auth_mode: nested_item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
677                subjects: {
678                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[3]) {
679                        let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
680                        Some(items)
681                    } else {
682                        None
683                    }
684                },
685                targets: {
686                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[4]) {
687                        let mut items = Vec::new();
688                        for list_item in l {
689                            items.push(AccessControlTarget {
690                cluster: list_item.get_int(&[0]).map(|v| v as u32),
691                endpoint: list_item.get_int(&[1]).map(|v| v as u16),
692                device_type: list_item.get_int(&[2]).map(|v| v as u32),
693                            });
694                        }
695                        Some(items)
696                    } else {
697                        None
698                    }
699                },
700                auxiliary_type: nested_item.get_int(&[5]).and_then(|v| AccessControlAuxiliaryType::from_u8(v as u8)),
701                            })
702                        } else {
703                            None
704                        }
705                    } else {
706                        None
707                    }
708                },
709        })
710    } else {
711        Err(anyhow::anyhow!("Expected struct fields"))
712    }
713}
714
715/// Decode AccessControlExtensionChanged event (0x01, priority: info)
716pub fn decode_access_control_extension_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AccessControlExtensionChangedEvent> {
717    if let tlv::TlvItemValue::List(_fields) = inp {
718        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
719        Ok(AccessControlExtensionChangedEvent {
720                                admin_node_id: item.get_int(&[1]),
721                                admin_passcode_id: item.get_int(&[2]).map(|v| v as u16),
722                                change_type: item.get_int(&[3]).and_then(|v| ChangeType::from_u8(v as u8)),
723                                latest_value: {
724                    if let Some(nested_tlv) = item.get(&[4]) {
725                        if let tlv::TlvItemValue::List(_) = nested_tlv {
726                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
727                            Some(AccessControlExtension {
728                data: nested_item.get_octet_string_owned(&[1]),
729                            })
730                        } else {
731                            None
732                        }
733                    } else {
734                        None
735                    }
736                },
737        })
738    } else {
739        Err(anyhow::anyhow!("Expected struct fields"))
740    }
741}
742
743/// Decode FabricRestrictionReviewUpdate event (0x02, priority: info)
744pub fn decode_fabric_restriction_review_update_event(inp: &tlv::TlvItemValue) -> anyhow::Result<FabricRestrictionReviewUpdateEvent> {
745    if let tlv::TlvItemValue::List(_fields) = inp {
746        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
747        Ok(FabricRestrictionReviewUpdateEvent {
748                                token: item.get_int(&[0]),
749                                instruction: item.get_string_owned(&[1]),
750                                arl_request_flow_url: item.get_string_owned(&[2]),
751        })
752    } else {
753        Err(anyhow::anyhow!("Expected struct fields"))
754    }
755}
756
757/// Decode AuxiliaryAccessUpdated event (0x03, priority: info)
758pub fn decode_auxiliary_access_updated_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AuxiliaryAccessUpdatedEvent> {
759    if let tlv::TlvItemValue::List(_fields) = inp {
760        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
761        Ok(AuxiliaryAccessUpdatedEvent {
762                                admin_node_id: item.get_int(&[0]),
763        })
764    } else {
765        Err(anyhow::anyhow!("Expected struct fields"))
766    }
767}
768
769
770// Event JSON dispatcher
771
772/// Decode event value and return as JSON string
773pub fn decode_event_json(cluster_id: u32, event_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
774    if cluster_id != 0x001F {
775        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x001F, got {}\"}}", cluster_id);
776    }
777
778    match event_id {
779        0x00 => {
780            match decode_access_control_entry_changed_event(tlv_value) {
781                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
782                Err(e) => format!("{{\"error\": \"{}\"}}", e),
783            }
784        }
785        0x01 => {
786            match decode_access_control_extension_changed_event(tlv_value) {
787                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
788                Err(e) => format!("{{\"error\": \"{}\"}}", e),
789            }
790        }
791        0x02 => {
792            match decode_fabric_restriction_review_update_event(tlv_value) {
793                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
794                Err(e) => format!("{{\"error\": \"{}\"}}", e),
795            }
796        }
797        0x03 => {
798            match decode_auxiliary_access_updated_event(tlv_value) {
799                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
800                Err(e) => format!("{{\"error\": \"{}\"}}", e),
801            }
802        }
803        _ => format!("{{\"error\": \"Unknown event ID: {}\"}}", event_id),
804    }
805}
806
807/// Get list of all events supported by this cluster
808///
809/// # Returns
810/// Vector of tuples containing (event_id, event_name)
811pub fn get_event_list() -> Vec<(u32, &'static str)> {
812    vec![
813        (0x00, "AccessControlEntryChanged"),
814        (0x01, "AccessControlExtensionChanged"),
815        (0x02, "FabricRestrictionReviewUpdate"),
816        (0x03, "AuxiliaryAccessUpdated"),
817    ]
818}
819