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
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Import serialization helpers for octet strings
12use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14// Enum definitions
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum AccessControlEntryAuthMode {
19    /// Passcode authenticated session
20    Pase = 1,
21    /// Certificate authenticated session
22    Case = 2,
23    /// Group authenticated session
24    Group = 3,
25}
26
27impl AccessControlEntryAuthMode {
28    /// Convert from u8 value
29    pub fn from_u8(value: u8) -> Option<Self> {
30        match value {
31            1 => Some(AccessControlEntryAuthMode::Pase),
32            2 => Some(AccessControlEntryAuthMode::Case),
33            3 => Some(AccessControlEntryAuthMode::Group),
34            _ => None,
35        }
36    }
37
38    /// Convert to u8 value
39    pub fn to_u8(self) -> u8 {
40        self as u8
41    }
42}
43
44impl From<AccessControlEntryAuthMode> for u8 {
45    fn from(val: AccessControlEntryAuthMode) -> Self {
46        val as u8
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51#[repr(u8)]
52pub enum AccessControlEntryPrivilege {
53    /// Can read and observe all (except Access Control Cluster)
54    View = 1,
55    Proxyview = 2,
56    /// View privileges, and can perform the primary function of this Node (except Access Control Cluster)
57    Operate = 3,
58    /// Operate privileges, and can modify persistent configuration of this Node (except Access Control Cluster)
59    Manage = 4,
60    /// Manage privileges, and can observe and modify the Access Control Cluster
61    Administer = 5,
62}
63
64impl AccessControlEntryPrivilege {
65    /// Convert from u8 value
66    pub fn from_u8(value: u8) -> Option<Self> {
67        match value {
68            1 => Some(AccessControlEntryPrivilege::View),
69            2 => Some(AccessControlEntryPrivilege::Proxyview),
70            3 => Some(AccessControlEntryPrivilege::Operate),
71            4 => Some(AccessControlEntryPrivilege::Manage),
72            5 => Some(AccessControlEntryPrivilege::Administer),
73            _ => None,
74        }
75    }
76
77    /// Convert to u8 value
78    pub fn to_u8(self) -> u8 {
79        self as u8
80    }
81}
82
83impl From<AccessControlEntryPrivilege> for u8 {
84    fn from(val: AccessControlEntryPrivilege) -> Self {
85        val as u8
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[repr(u8)]
91pub enum AccessRestrictionType {
92    /// Clients on this fabric are currently forbidden from reading and writing an attribute
93    Attributeaccessforbidden = 0,
94    /// Clients on this fabric are currently forbidden from writing an attribute
95    Attributewriteforbidden = 1,
96    /// Clients on this fabric are currently forbidden from invoking a command
97    Commandforbidden = 2,
98    /// Clients on this fabric are currently forbidden from reading an event
99    Eventforbidden = 3,
100}
101
102impl AccessRestrictionType {
103    /// Convert from u8 value
104    pub fn from_u8(value: u8) -> Option<Self> {
105        match value {
106            0 => Some(AccessRestrictionType::Attributeaccessforbidden),
107            1 => Some(AccessRestrictionType::Attributewriteforbidden),
108            2 => Some(AccessRestrictionType::Commandforbidden),
109            3 => Some(AccessRestrictionType::Eventforbidden),
110            _ => None,
111        }
112    }
113
114    /// Convert to u8 value
115    pub fn to_u8(self) -> u8 {
116        self as u8
117    }
118}
119
120impl From<AccessRestrictionType> for u8 {
121    fn from(val: AccessRestrictionType) -> Self {
122        val as u8
123    }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
127#[repr(u8)]
128pub enum ChangeType {
129    /// Entry or extension was changed
130    Changed = 0,
131    /// Entry or extension was added
132    Added = 1,
133    /// Entry or extension was removed
134    Removed = 2,
135}
136
137impl ChangeType {
138    /// Convert from u8 value
139    pub fn from_u8(value: u8) -> Option<Self> {
140        match value {
141            0 => Some(ChangeType::Changed),
142            1 => Some(ChangeType::Added),
143            2 => Some(ChangeType::Removed),
144            _ => None,
145        }
146    }
147
148    /// Convert to u8 value
149    pub fn to_u8(self) -> u8 {
150        self as u8
151    }
152}
153
154impl From<ChangeType> for u8 {
155    fn from(val: ChangeType) -> Self {
156        val as u8
157    }
158}
159
160// Struct definitions
161
162#[derive(Debug, serde::Serialize)]
163pub struct AccessControlEntry {
164    pub privilege: Option<AccessControlEntryPrivilege>,
165    pub auth_mode: Option<AccessControlEntryAuthMode>,
166    pub subjects: Option<Vec<u64>>,
167    pub targets: Option<Vec<AccessControlTarget>>,
168}
169
170#[derive(Debug, serde::Serialize)]
171pub struct AccessControlExtension {
172    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
173    pub data: Option<Vec<u8>>,
174}
175
176#[derive(Debug, serde::Serialize)]
177pub struct AccessControlTarget {
178    pub cluster: Option<u32>,
179    pub endpoint: Option<u16>,
180    pub device_type: Option<u32>,
181}
182
183#[derive(Debug, serde::Serialize)]
184pub struct AccessRestrictionEntry {
185    pub endpoint: Option<u16>,
186    pub cluster: Option<u32>,
187    pub restrictions: Option<Vec<AccessRestriction>>,
188}
189
190#[derive(Debug, serde::Serialize)]
191pub struct AccessRestriction {
192    pub type_: Option<AccessRestrictionType>,
193    pub id: Option<u32>,
194}
195
196#[derive(Debug, serde::Serialize)]
197pub struct CommissioningAccessRestrictionEntry {
198    pub endpoint: Option<u16>,
199    pub cluster: Option<u32>,
200    pub restrictions: Option<Vec<AccessRestriction>>,
201}
202
203// Command encoders
204
205/// Encode ReviewFabricRestrictions command (0x00)
206pub fn encode_review_fabric_restrictions(arl: Vec<CommissioningAccessRestrictionEntry>) -> anyhow::Result<Vec<u8>> {
207    let tlv = tlv::TlvItemEnc {
208        tag: 0,
209        value: tlv::TlvItemValueEnc::StructInvisible(vec![
210        (0, tlv::TlvItemValueEnc::Array(arl.into_iter().map(|v| {
211                    let mut fields = Vec::new();
212                    if let Some(x) = v.endpoint { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
213                    if let Some(x) = v.cluster { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
214                    if let Some(listv) = v.restrictions {
215                        let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
216                            let mut nested_fields = Vec::new();
217                                if let Some(x) = inner.type_ { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
218                                if let Some(x) = inner.id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
219                            (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
220                        }).collect();
221                        fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
222                    }
223                    (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
224                }).collect())).into(),
225        ]),
226    };
227    Ok(tlv.encode()?)
228}
229
230// Attribute decoders
231
232/// Decode ACL attribute (0x0000)
233pub fn decode_acl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlEntry>> {
234    let mut res = Vec::new();
235    if let tlv::TlvItemValue::List(v) = inp {
236        for item in v {
237            res.push(AccessControlEntry {
238                privilege: item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
239                auth_mode: item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
240                subjects: {
241                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
242                        let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
243                        Some(items)
244                    } else {
245                        None
246                    }
247                },
248                targets: {
249                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
250                        let mut items = Vec::new();
251                        for list_item in l {
252                            items.push(AccessControlTarget {
253                cluster: list_item.get_int(&[0]).map(|v| v as u32),
254                endpoint: list_item.get_int(&[1]).map(|v| v as u16),
255                device_type: list_item.get_int(&[2]).map(|v| v as u32),
256                            });
257                        }
258                        Some(items)
259                    } else {
260                        None
261                    }
262                },
263            });
264        }
265    }
266    Ok(res)
267}
268
269/// Decode Extension attribute (0x0001)
270pub fn decode_extension(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlExtension>> {
271    let mut res = Vec::new();
272    if let tlv::TlvItemValue::List(v) = inp {
273        for item in v {
274            res.push(AccessControlExtension {
275                data: item.get_octet_string_owned(&[1]),
276            });
277        }
278    }
279    Ok(res)
280}
281
282/// Decode SubjectsPerAccessControlEntry attribute (0x0002)
283pub fn decode_subjects_per_access_control_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
284    if let tlv::TlvItemValue::Int(v) = inp {
285        Ok(*v as u16)
286    } else {
287        Err(anyhow::anyhow!("Expected UInt16"))
288    }
289}
290
291/// Decode TargetsPerAccessControlEntry attribute (0x0003)
292pub fn decode_targets_per_access_control_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
293    if let tlv::TlvItemValue::Int(v) = inp {
294        Ok(*v as u16)
295    } else {
296        Err(anyhow::anyhow!("Expected UInt16"))
297    }
298}
299
300/// Decode AccessControlEntriesPerFabric attribute (0x0004)
301pub fn decode_access_control_entries_per_fabric(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
302    if let tlv::TlvItemValue::Int(v) = inp {
303        Ok(*v as u16)
304    } else {
305        Err(anyhow::anyhow!("Expected UInt16"))
306    }
307}
308
309/// Decode CommissioningARL attribute (0x0005)
310pub fn decode_commissioning_arl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<CommissioningAccessRestrictionEntry>> {
311    let mut res = Vec::new();
312    if let tlv::TlvItemValue::List(v) = inp {
313        for item in v {
314            res.push(CommissioningAccessRestrictionEntry {
315                endpoint: item.get_int(&[0]).map(|v| v as u16),
316                cluster: item.get_int(&[1]).map(|v| v as u32),
317                restrictions: {
318                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
319                        let mut items = Vec::new();
320                        for list_item in l {
321                            items.push(AccessRestriction {
322                type_: list_item.get_int(&[0]).and_then(|v| AccessRestrictionType::from_u8(v as u8)),
323                id: list_item.get_int(&[1]).map(|v| v as u32),
324                            });
325                        }
326                        Some(items)
327                    } else {
328                        None
329                    }
330                },
331            });
332        }
333    }
334    Ok(res)
335}
336
337/// Decode ARL attribute (0x0006)
338pub fn decode_arl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessRestrictionEntry>> {
339    let mut res = Vec::new();
340    if let tlv::TlvItemValue::List(v) = inp {
341        for item in v {
342            res.push(AccessRestrictionEntry {
343                endpoint: item.get_int(&[0]).map(|v| v as u16),
344                cluster: item.get_int(&[1]).map(|v| v as u32),
345                restrictions: {
346                    if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
347                        let mut items = Vec::new();
348                        for list_item in l {
349                            items.push(AccessRestriction {
350                type_: list_item.get_int(&[0]).and_then(|v| AccessRestrictionType::from_u8(v as u8)),
351                id: list_item.get_int(&[1]).map(|v| v as u32),
352                            });
353                        }
354                        Some(items)
355                    } else {
356                        None
357                    }
358                },
359            });
360        }
361    }
362    Ok(res)
363}
364
365
366// JSON dispatcher function
367
368/// Decode attribute value and return as JSON string
369///
370/// # Parameters
371/// * `cluster_id` - The cluster identifier
372/// * `attribute_id` - The attribute identifier
373/// * `tlv_value` - The TLV value to decode
374///
375/// # Returns
376/// JSON string representation of the decoded value or error
377pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
378    // Verify this is the correct cluster
379    if cluster_id != 0x001F {
380        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x001F, got {}\"}}", cluster_id);
381    }
382
383    match attribute_id {
384        0x0000 => {
385            match decode_acl(tlv_value) {
386                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
387                Err(e) => format!("{{\"error\": \"{}\"}}", e),
388            }
389        }
390        0x0001 => {
391            match decode_extension(tlv_value) {
392                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
393                Err(e) => format!("{{\"error\": \"{}\"}}", e),
394            }
395        }
396        0x0002 => {
397            match decode_subjects_per_access_control_entry(tlv_value) {
398                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
399                Err(e) => format!("{{\"error\": \"{}\"}}", e),
400            }
401        }
402        0x0003 => {
403            match decode_targets_per_access_control_entry(tlv_value) {
404                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
405                Err(e) => format!("{{\"error\": \"{}\"}}", e),
406            }
407        }
408        0x0004 => {
409            match decode_access_control_entries_per_fabric(tlv_value) {
410                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
411                Err(e) => format!("{{\"error\": \"{}\"}}", e),
412            }
413        }
414        0x0005 => {
415            match decode_commissioning_arl(tlv_value) {
416                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
417                Err(e) => format!("{{\"error\": \"{}\"}}", e),
418            }
419        }
420        0x0006 => {
421            match decode_arl(tlv_value) {
422                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
423                Err(e) => format!("{{\"error\": \"{}\"}}", e),
424            }
425        }
426        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
427    }
428}
429
430/// Get list of all attributes supported by this cluster
431///
432/// # Returns
433/// Vector of tuples containing (attribute_id, attribute_name)
434pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
435    vec![
436        (0x0000, "ACL"),
437        (0x0001, "Extension"),
438        (0x0002, "SubjectsPerAccessControlEntry"),
439        (0x0003, "TargetsPerAccessControlEntry"),
440        (0x0004, "AccessControlEntriesPerFabric"),
441        (0x0005, "CommissioningARL"),
442        (0x0006, "ARL"),
443    ]
444}
445
446#[derive(Debug, serde::Serialize)]
447pub struct ReviewFabricRestrictionsResponse {
448    pub token: Option<u64>,
449}
450
451// Command response decoders
452
453/// Decode ReviewFabricRestrictionsResponse command response (01)
454pub fn decode_review_fabric_restrictions_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ReviewFabricRestrictionsResponse> {
455    if let tlv::TlvItemValue::List(_fields) = inp {
456        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
457        Ok(ReviewFabricRestrictionsResponse {
458                token: item.get_int(&[0]),
459        })
460    } else {
461        Err(anyhow::anyhow!("Expected struct fields"))
462    }
463}
464
465#[derive(Debug, serde::Serialize)]
466pub struct AccessControlEntryChangedEvent {
467    pub admin_node_id: Option<u64>,
468    pub admin_passcode_id: Option<u16>,
469    pub change_type: Option<ChangeType>,
470    pub latest_value: Option<AccessControlEntry>,
471}
472
473#[derive(Debug, serde::Serialize)]
474pub struct AccessControlExtensionChangedEvent {
475    pub admin_node_id: Option<u64>,
476    pub admin_passcode_id: Option<u16>,
477    pub change_type: Option<ChangeType>,
478    pub latest_value: Option<AccessControlExtension>,
479}
480
481#[derive(Debug, serde::Serialize)]
482pub struct FabricRestrictionReviewUpdateEvent {
483    pub token: Option<u64>,
484    pub instruction: Option<String>,
485    pub arl_request_flow_url: Option<String>,
486}
487
488// Event decoders
489
490/// Decode AccessControlEntryChanged event (0x00, priority: info)
491pub fn decode_access_control_entry_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AccessControlEntryChangedEvent> {
492    if let tlv::TlvItemValue::List(_fields) = inp {
493        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
494        Ok(AccessControlEntryChangedEvent {
495                                admin_node_id: item.get_int(&[1]),
496                                admin_passcode_id: item.get_int(&[2]).map(|v| v as u16),
497                                change_type: item.get_int(&[3]).and_then(|v| ChangeType::from_u8(v as u8)),
498                                latest_value: {
499                    if let Some(nested_tlv) = item.get(&[4]) {
500                        if let tlv::TlvItemValue::List(_) = nested_tlv {
501                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
502                            Some(AccessControlEntry {
503                privilege: nested_item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
504                auth_mode: nested_item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
505                subjects: {
506                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[3]) {
507                        let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
508                        Some(items)
509                    } else {
510                        None
511                    }
512                },
513                targets: {
514                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[4]) {
515                        let mut items = Vec::new();
516                        for list_item in l {
517                            items.push(AccessControlTarget {
518                cluster: list_item.get_int(&[0]).map(|v| v as u32),
519                endpoint: list_item.get_int(&[1]).map(|v| v as u16),
520                device_type: list_item.get_int(&[2]).map(|v| v as u32),
521                            });
522                        }
523                        Some(items)
524                    } else {
525                        None
526                    }
527                },
528                            })
529                        } else {
530                            None
531                        }
532                    } else {
533                        None
534                    }
535                },
536        })
537    } else {
538        Err(anyhow::anyhow!("Expected struct fields"))
539    }
540}
541
542/// Decode AccessControlExtensionChanged event (0x01, priority: info)
543pub fn decode_access_control_extension_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AccessControlExtensionChangedEvent> {
544    if let tlv::TlvItemValue::List(_fields) = inp {
545        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
546        Ok(AccessControlExtensionChangedEvent {
547                                admin_node_id: item.get_int(&[1]),
548                                admin_passcode_id: item.get_int(&[2]).map(|v| v as u16),
549                                change_type: item.get_int(&[3]).and_then(|v| ChangeType::from_u8(v as u8)),
550                                latest_value: {
551                    if let Some(nested_tlv) = item.get(&[4]) {
552                        if let tlv::TlvItemValue::List(_) = nested_tlv {
553                            let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
554                            Some(AccessControlExtension {
555                data: nested_item.get_octet_string_owned(&[1]),
556                            })
557                        } else {
558                            None
559                        }
560                    } else {
561                        None
562                    }
563                },
564        })
565    } else {
566        Err(anyhow::anyhow!("Expected struct fields"))
567    }
568}
569
570/// Decode FabricRestrictionReviewUpdate event (0x02, priority: info)
571pub fn decode_fabric_restriction_review_update_event(inp: &tlv::TlvItemValue) -> anyhow::Result<FabricRestrictionReviewUpdateEvent> {
572    if let tlv::TlvItemValue::List(_fields) = inp {
573        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
574        Ok(FabricRestrictionReviewUpdateEvent {
575                                token: item.get_int(&[0]),
576                                instruction: item.get_string_owned(&[1]),
577                                arl_request_flow_url: item.get_string_owned(&[2]),
578        })
579    } else {
580        Err(anyhow::anyhow!("Expected struct fields"))
581    }
582}
583