Skip to main content

rs_matter/
acl.rs

1/*
2 *
3 *    Copyright (c) 2022-2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! This module contains the implementation of the `rs-matter` Access Control List (ACL)
19
20use core::fmt::Display;
21use core::num::NonZeroU8;
22use core::ops::RangeInclusive;
23
24use cfg_if::cfg_if;
25
26use num_derive::FromPrimitive;
27
28use crate::dm::clusters::acl::{
29    AccessControlAuxiliaryTypeEnum, AccessControlEntryAuthModeEnum,
30    AccessControlEntryPrivilegeEnum, AccessControlEntryStruct, AccessControlEntryStructBuilder,
31};
32use crate::dm::{Access, ClusterId, DeviceType, EndptId, NodeId, Privilege};
33use crate::error::{Error, ErrorCode};
34use crate::im::GenericPath;
35use crate::tlv::{FromTLV, Nullable, TLVBuilderParent, TLVElement, TLVTag, TLVWrite, ToTLV, TLV};
36use crate::transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC};
37use crate::utils::init::{init, Init, IntoFallibleInit};
38use crate::utils::storage::Vec;
39use crate::Matter;
40
41cfg_if! {
42    if #[cfg(feature = "max-subjects-per-acl-32")] {
43        /// Max subjects per ACL entry
44        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 32;
45    } else if #[cfg(feature = "max-subjects-per-acl-16")] {
46        /// Max subjects per ACL entry
47        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 16;
48    } else if #[cfg(feature = "max-subjects-per-acl-8")] {
49        /// Max subjects per ACL entry
50        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 8;
51    } else if #[cfg(feature = "max-subjects-per-acl-7")] {
52        /// Max subjects per ACL entry
53        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 7;
54    } else if #[cfg(feature = "max-subjects-per-acl-6")] {
55        /// Max subjects per ACL entry
56        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 6;
57    } else if #[cfg(feature = "max-subjects-per-acl-5")] {
58        /// Max subjects per ACL entry
59        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 5;
60    } else if #[cfg(feature = "max-subjects-per-acl-4")] {
61        /// Max subjects per ACL entry
62        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 4;
63    } else if #[cfg(feature = "max-subjects-per-acl-3")] {
64        /// Max subjects per ACL entry
65        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 3;
66    } else if #[cfg(feature = "max-subjects-per-acl-2")] {
67        /// Max subjects per ACL entry
68        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 2;
69    } else if #[cfg(feature = "max-subjects-per-acl-1")] {
70        /// Max subjects per ACL entry
71        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 1;
72    } else {
73        /// Max subjects per ACL entry
74        pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 4;
75    }
76}
77
78cfg_if! {
79    if #[cfg(feature = "max-targets-per-acl-32")] {
80        /// Max targets per ACL entry
81        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 32;
82    } else if #[cfg(feature = "max-targets-per-acl-16")] {
83        /// Max targets per ACL entry
84        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 16;
85    } else if #[cfg(feature = "max-targets-per-acl-8")] {
86        /// Max targets per ACL entry
87        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 8;
88    } else if #[cfg(feature = "max-targets-per-acl-7")] {
89        /// Max targets per ACL entry
90        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 7;
91    } else if #[cfg(feature = "max-targets-per-acl-6")] {
92        /// Max targets per ACL entry
93        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 6;
94    } else if #[cfg(feature = "max-targets-per-acl-5")] {
95        /// Max targets per ACL entry
96        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 5;
97    } else if #[cfg(feature = "max-targets-per-acl-4")] {
98        /// Max targets per ACL entry
99        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 4;
100    } else if #[cfg(feature = "max-targets-per-acl-3")] {
101        /// Max targets per ACL entry
102        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 3;
103    } else if #[cfg(feature = "max-targets-per-acl-2")] {
104        /// Max targets per ACL entry
105        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 2;
106    } else if #[cfg(feature = "max-targets-per-acl-1")] {
107        /// Max targets per ACL entry
108        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 1;
109    } else {
110        /// Max targets per ACL entry
111        pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 3;
112    }
113}
114
115cfg_if! {
116    if #[cfg(feature = "max-acls-per-fabric-32")] {
117        /// Max ACL entries per fabric
118        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 32;
119    } else if #[cfg(feature = "max-acls-per-fabric-16")] {
120        /// Max ACL entries per fabric
121        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 16;
122    } else if #[cfg(feature = "max-acls-per-fabric-8")] {
123        /// Max ACL entries per fabric
124        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 8;
125    } else if #[cfg(feature = "max-acls-per-fabric-7")] {
126        /// Max ACL entries per fabric
127        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 7;
128    } else if #[cfg(feature = "max-acls-per-fabric-6")] {
129        /// Max ACL entries per fabric
130        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 6;
131    } else if #[cfg(feature = "max-acls-per-fabric-5")] {
132        /// Max ACL entries per fabric
133        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 5;
134    } else if #[cfg(feature = "max-acls-per-fabric-4")] {
135        /// Max ACL entries per fabric
136        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 4;
137    } else if #[cfg(feature = "max-acls-per-fabric-3")] {
138        /// Max ACL entries per fabric
139        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 3;
140    } else if #[cfg(feature = "max-acls-per-fabric-2")] {
141        /// Max ACL entries per fabric
142        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 2;
143    } else if #[cfg(feature = "max-acls-per-fabric-1")] {
144        /// Max ACL entries per fabric
145        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 1;
146    } else {
147        /// Max ACL entries per fabric
148        pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 4;
149    }
150}
151
152/// An enum modeling the different authentication modes
153// TODO: Check if this and the SessionMode can be combined into some generic data structure
154#[derive(FromPrimitive, Copy, Clone, PartialEq, Debug)]
155#[cfg_attr(feature = "defmt", derive(defmt::Format))]
156#[repr(u8)]
157pub enum AuthMode {
158    /// PASE authentication
159    Pase = AccessControlEntryAuthModeEnum::PASE as _,
160    /// CASE authentication
161    Case = AccessControlEntryAuthModeEnum::CASE as _,
162    /// Group authentication
163    Group = AccessControlEntryAuthModeEnum::Group as _,
164}
165
166impl FromTLV<'_> for AuthMode {
167    fn from_tlv(t: &TLVElement) -> Result<Self, Error>
168    where
169        Self: Sized,
170    {
171        Ok(AccessControlEntryAuthModeEnum::from_tlv(t)?.into())
172    }
173}
174
175impl ToTLV for AuthMode {
176    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
177        AccessControlEntryAuthModeEnum::from(*self).to_tlv(tag, &mut tw)
178    }
179
180    fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
181        TLV::u8(tag, AccessControlEntryAuthModeEnum::from(*self) as _).into_tlv_iter()
182    }
183}
184
185impl From<AuthMode> for AccessControlEntryAuthModeEnum {
186    fn from(value: AuthMode) -> Self {
187        match value {
188            AuthMode::Pase => AccessControlEntryAuthModeEnum::PASE,
189            AuthMode::Case => AccessControlEntryAuthModeEnum::CASE,
190            AuthMode::Group => AccessControlEntryAuthModeEnum::Group,
191        }
192    }
193}
194
195impl From<AccessControlEntryAuthModeEnum> for AuthMode {
196    fn from(value: AccessControlEntryAuthModeEnum) -> Self {
197        match value {
198            AccessControlEntryAuthModeEnum::PASE => AuthMode::Pase,
199            AccessControlEntryAuthModeEnum::CASE => AuthMode::Case,
200            AccessControlEntryAuthModeEnum::Group => AuthMode::Group,
201        }
202    }
203}
204
205/// An accessor can have as many identities: one node id and up to MAX_CAT_IDS_PER_NOC
206const MAX_ACCESSOR_SUBJECTS: usize = 1 + MAX_CAT_IDS_PER_NOC;
207
208/// The CAT Prefix used in Subjects
209pub const NOC_CAT_SUBJECT_PREFIX: u64 = 0xFFFF_FFFD_0000_0000;
210pub const NOC_CAT_SUBJECT_MASK: u64 = 0xFFFF_FFFF_0000_0000;
211
212const NOC_CAT_ID_MASK: u64 = 0xFFFF_0000;
213const NOC_CAT_VERSION_MASK: u64 = 0xFFFF;
214
215/// The Node ID min range
216const NODE_ID_RANGE: RangeInclusive<u64> = 1..=0xFFFF_FFEF_FFFF_FFFF;
217
218/// Is this identifier a NOC CAT
219pub(crate) fn is_noc_cat(id: u64) -> bool {
220    ((id & NOC_CAT_SUBJECT_MASK) == NOC_CAT_SUBJECT_PREFIX)
221        && ((id & (NOC_CAT_ID_MASK | NOC_CAT_VERSION_MASK)) > 0)
222}
223
224/// Get the 16-bit NOC CAT id from the identifier
225fn get_noc_cat_id(id: u64) -> u64 {
226    (id & NOC_CAT_ID_MASK) >> 16
227}
228
229/// Get the 16-bit NOC CAT version from the identifier
230fn get_noc_cat_version(id: u64) -> u64 {
231    id & NOC_CAT_VERSION_MASK
232}
233
234/// Generate CAT that is embeddedable in the NoC
235/// This only generates the 32-bit CAT ID
236pub fn gen_noc_cat(id: u16, version: u16) -> u32 {
237    ((id as u32) << 16) | version as u32
238}
239
240/// Is this identifier a node id
241pub(crate) fn is_node(id: u64) -> bool {
242    NODE_ID_RANGE.contains(&id)
243}
244
245/// The Subjects that identify the Accessor
246pub struct AccessorSubjects([u64; MAX_ACCESSOR_SUBJECTS]);
247
248impl AccessorSubjects {
249    /// Create a new AccessorSubjects object
250    /// The first subject is the node id
251    pub fn new(id: u64) -> Self {
252        let mut a = Self(Default::default());
253        a.0[0] = id;
254        a
255    }
256
257    /// Add a CAT id to the AccessorSubjects
258    pub fn add_catid(&mut self, subject: u32) -> Result<(), Error> {
259        for (i, val) in self.0.iter().enumerate() {
260            if *val == 0 {
261                self.0[i] = NOC_CAT_SUBJECT_PREFIX | (subject as u64);
262                return Ok(());
263            }
264        }
265        Err(ErrorCode::ResourceExhausted.into())
266    }
267
268    /// Match the acl_subject with any of the current subjects
269    /// If a NOC CAT is specified, CAT aware matching is also performed
270    pub fn matches(&self, acl_subject: u64) -> bool {
271        for v in self.0.iter() {
272            if *v == 0 {
273                continue;
274            }
275
276            if *v == acl_subject {
277                return true;
278            } else {
279                // NOC CAT match
280                if is_noc_cat(*v)
281                    && is_noc_cat(acl_subject)
282                    && (get_noc_cat_id(*v) == get_noc_cat_id(acl_subject))
283                    && (get_noc_cat_version(*v) >= get_noc_cat_version(acl_subject))
284                {
285                    return true;
286                }
287            }
288        }
289
290        false
291    }
292}
293
294impl Display for AccessorSubjects {
295    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
296        write!(f, "[")?;
297        for i in self.0 {
298            if is_noc_cat(i) {
299                write!(f, "CAT({} - {})", get_noc_cat_id(i), get_noc_cat_version(i))?;
300            } else if i != 0 {
301                write!(f, "{}, ", i)?;
302            }
303        }
304        write!(f, "]")
305    }
306}
307
308#[cfg(feature = "defmt")]
309impl defmt::Format for AccessorSubjects {
310    fn format(&self, f: defmt::Formatter) {
311        defmt::write!(f, "[");
312        for i in self.0 {
313            if is_noc_cat(i) {
314                defmt::write!(f, "CAT({} - {})", get_noc_cat_id(i), get_noc_cat_version(i));
315            } else if i != 0 {
316                defmt::write!(f, "{}, ", i);
317            }
318        }
319        defmt::write!(f, "]")
320    }
321}
322
323/// The Accessor Object
324pub struct Accessor<'a> {
325    /// The fabric index of the accessor
326    pub(crate) fab_idx: u8,
327    /// Accessor's subject: could be node-id, NoC CAT, group id
328    subjects: AccessorSubjects,
329    /// The auth mode of this session. Might be `None` for plain-text sessions
330    auth_mode: Option<AuthMode>,
331    // Necessary so as to get access to the fabric manager to perform the access check in AccessReq::allow()
332    // as well as for a few other ACL related operations.
333    matter: &'a Matter<'a>,
334}
335
336impl<'a> Accessor<'a> {
337    /// Create a new Accessor object for the given session
338    pub fn for_session(session: &Session, matter: &'a Matter<'a>) -> Self {
339        match session.get_session_mode() {
340            SessionMode::Case {
341                fab_idx, cat_ids, ..
342            } => {
343                let mut subject =
344                    AccessorSubjects::new(session.get_peer_node_id().unwrap_or_default());
345                for i in *cat_ids {
346                    if i != 0 {
347                        let _ = subject.add_catid(i);
348                    }
349                }
350                Accessor::new(fab_idx.get(), subject, Some(AuthMode::Case), matter)
351            }
352            SessionMode::Pase { fab_idx } => Accessor::new(
353                *fab_idx,
354                AccessorSubjects::new(1),
355                Some(AuthMode::Pase),
356                matter,
357            ),
358            SessionMode::Group { fab_idx, group_id } => Accessor::new(
359                fab_idx.get(),
360                AccessorSubjects::new(*group_id as u64),
361                Some(AuthMode::Group),
362                matter,
363            ),
364            SessionMode::PlainText => Accessor::new(0, AccessorSubjects::new(1), None, matter),
365        }
366    }
367
368    /// Create a new Accessor object
369    ///
370    /// # Arguments
371    /// - `fab_idx`: The fabric index of the accessor (0 means no fabric index)
372    /// - `subjects`: The subjects of the accessor
373    /// - `auth_mode`: The auth mode of the accessor
374    /// - `matter`: The Matter instance
375    pub const fn new(
376        fab_idx: u8,
377        subjects: AccessorSubjects,
378        auth_mode: Option<AuthMode>,
379        matter: &'a Matter<'a>,
380    ) -> Self {
381        Self {
382            fab_idx,
383            subjects,
384            auth_mode,
385            matter,
386        }
387    }
388
389    pub fn fab_idx(&self) -> Result<NonZeroU8, Error> {
390        NonZeroU8::new(self.fab_idx).ok_or(ErrorCode::UnsupportedAccess.into())
391    }
392
393    /// Return the subjects of the accessor
394    pub const fn subjects(&self) -> &AccessorSubjects {
395        &self.subjects
396    }
397
398    /// Return the auth mode of the accessor
399    pub const fn auth_mode(&self) -> Option<AuthMode> {
400        self.auth_mode
401    }
402
403    /// Return whether the given endpoint is accessible for this accessor.
404    ///
405    /// For group sessions, only endpoints that are members of the group are accessible.
406    /// For all other session types, all endpoints are accessible.
407    pub fn is_endpoint_accessible(&self, endpoint_id: EndptId) -> bool {
408        if self.auth_mode != Some(AuthMode::Group) {
409            return true;
410        }
411
412        let group_id = self.subjects.0[0] as u16;
413
414        let Some(fab_idx) = core::num::NonZeroU8::new(self.fab_idx) else {
415            return false;
416        };
417
418        self.matter.with_state(|state| {
419            let Some(fabric) = state.fabrics.get(fab_idx) else {
420                return false;
421            };
422
423            fabric
424                .groups()
425                .get(group_id)
426                .is_some_and(|e| e.endpoints.contains(&endpoint_id))
427        })
428    }
429
430    /// Return the Operational Node ID of the accessor, if any
431    pub fn node_id(&self) -> Option<NodeId> {
432        let fab_idx = NonZeroU8::new(self.fab_idx)?;
433
434        self.matter
435            .with_state(|state| state.fabrics.get(fab_idx).map(|fabric| fabric.node_id()))
436    }
437
438    /// Return the peer node ID (admin node ID) for CASE sessions, or None for PASE/other sessions.
439    pub fn peer_node_id(&self) -> Option<u64> {
440        if matches!(self.auth_mode, Some(AuthMode::Case)) {
441            let id = self.subjects.0[0];
442            if is_node(id) {
443                Some(id)
444            } else {
445                None
446            }
447        } else {
448            None
449        }
450    }
451}
452
453/// Access Descriptor Object
454#[derive(Debug)]
455#[cfg_attr(feature = "defmt", derive(defmt::Format))]
456pub struct AccessDesc<'a> {
457    /// The object to be acted upon
458    path: GenericPath,
459    /// The target permissions
460    target_perms: Option<Access>,
461    // The operation being done
462    // TODO: Currently this is Access, but we need a way to represent the 'invoke' somehow too
463    operation: Access,
464    /// The device types of the endpoint hosting `path`. Used by ACL `Target`
465    /// entries that filter by `DeviceType` (Matter Core spec).
466    /// Empty when the access target's endpoint is unknown / not yet expanded.
467    device_types: &'a [DeviceType],
468}
469
470/// Access Request Object
471pub struct AccessReq<'a> {
472    /// The accessor requesting access
473    accessor: &'a Accessor<'a>,
474    /// The object being accessed
475    object: AccessDesc<'a>,
476}
477
478impl<'a> AccessReq<'a> {
479    /// Create an access request object.
480    ///
481    /// An access request specifies the _accessor_ attempting to access _path_
482    /// with _operation_. `device_types` lists the device types declared by
483    /// the endpoint that hosts `path`; pass an empty slice when this is not
484    /// applicable (e.g. for unit tests that don't exercise `DeviceType` ACL
485    /// targets).
486    pub const fn new(accessor: &'a Accessor, path: GenericPath, operation: Access) -> Self {
487        Self::new_with_device_types(accessor, path, operation, &[])
488    }
489
490    /// Create an access request object that also carries the device types of
491    /// the access target's endpoint, so that ACL entries with a `Target` of
492    /// kind `DeviceType` can be evaluated.
493    pub const fn new_with_device_types(
494        accessor: &'a Accessor,
495        path: GenericPath,
496        operation: Access,
497        device_types: &'a [DeviceType],
498    ) -> Self {
499        AccessReq {
500            accessor,
501            object: AccessDesc {
502                path,
503                target_perms: None,
504                operation,
505                device_types,
506            },
507        }
508    }
509
510    /// Return the accessor of the request
511    pub fn accessor(&self) -> &Accessor<'_> {
512        self.accessor
513    }
514
515    /// Return the operation of the request
516    pub fn operation(&self) -> Access {
517        self.object.operation
518    }
519
520    /// Add target's permissions to the request
521    ///
522    /// The permissions that are associated with the target (identified by the
523    /// path in the AccessReq) are added to the request
524    pub fn set_target_perms(&mut self, perms: Access) {
525        self.object.target_perms = Some(perms);
526    }
527
528    /// Check if access is allowed
529    ///
530    /// This checks all the ACL list to identify if any of the ACLs provides the
531    /// _accessor_ the necessary privileges to access the target as per its
532    /// permissions
533    pub fn allow(&self) -> bool {
534        self.accessor
535            .matter
536            .with_state(|state| state.fabrics.allow(self))
537    }
538}
539
540/// The target object
541#[derive(FromTLV, ToTLV, Clone, Debug, PartialEq)]
542#[cfg_attr(feature = "defmt", derive(defmt::Format))]
543pub struct Target {
544    pub cluster: Option<ClusterId>,
545    pub endpoint: Option<EndptId>,
546    pub device_type: Option<u32>,
547}
548
549impl Target {
550    /// Create a new target object
551    pub const fn new(
552        endpoint: Option<EndptId>,
553        cluster: Option<ClusterId>,
554        device_type: Option<u32>,
555    ) -> Self {
556        Self {
557            cluster,
558            endpoint,
559            device_type,
560        }
561    }
562}
563
564/// The ACL entry object
565#[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)]
566#[cfg_attr(feature = "defmt", derive(defmt::Format))]
567#[tlvargs(start = 1)]
568pub struct AclEntry {
569    /// The privilege of the entry
570    privilege: Privilege,
571    /// The auth mode of the entry
572    auth_mode: AuthMode,
573    /// The subjects of the entry
574    subjects: Nullable<Vec<u64, MAX_SUBJECTS_PER_ACL_ENTRY>>,
575    /// The targets of the entry
576    targets: Nullable<Vec<Target, MAX_TARGETS_PER_ACL_ENTRY>>,
577    // TODO: Figure out what this is, document and use
578    auxiliary_type: Option<AccessControlAuxiliaryTypeEnum>,
579    // Note that this field will always be `Some(NN)` when the entry is persisted in storage,
580    // however, it will be `None` when the entry is coming from the other peer.
581    #[tagval(crate::im::encoding::FABRIC_INDEX_TAG)]
582    pub fab_idx: Option<NonZeroU8>,
583}
584
585impl AclEntry {
586    /// Create a new ACL entry object
587    pub const fn new(
588        fab_idx: Option<NonZeroU8>,
589        privilege: Privilege,
590        auth_mode: AuthMode,
591    ) -> Self {
592        Self {
593            fab_idx,
594            privilege,
595            auth_mode,
596            subjects: Nullable::none(),
597            targets: Nullable::none(),
598            auxiliary_type: None,
599        }
600    }
601
602    /// Return an initializer for an ACL entry object
603    /// using the given fabric index, privilege and auth mode as input
604    pub fn init(
605        fab_idx: Option<NonZeroU8>,
606        privilege: Privilege,
607        auth_mode: AuthMode,
608    ) -> impl Init<Self> {
609        init!(Self {
610            fab_idx,
611            privilege,
612            auth_mode,
613            subjects <- Nullable::init_none(),
614            targets <- Nullable::init_none(),
615            auxiliary_type: None,
616        })
617    }
618
619    /// Return an initializer for an ACL entry object
620    /// using the given fabric index and TLV entry struct as input
621    pub fn init_with<'a>(
622        fab_idx: NonZeroU8,
623        entry: &'a AccessControlEntryStruct<'a>,
624    ) -> impl Init<Self, Error> + 'a {
625        Self::init(Some(fab_idx), Privilege::empty(), AuthMode::Pase)
626            .into_fallible()
627            .chain(|e| {
628                let auth_mode = entry.auth_mode().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
629                let privilege = entry.privilege().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
630                let subjects = entry.subjects().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
631                let targets = entry.targets().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
632                let auxiliary_type = entry.auxiliary_type().map_err(|_| ErrorCode::ConstraintError)?;
633
634                if
635                    // As per spec, PASE auth mode is reserved for future use
636                    matches!(auth_mode, AccessControlEntryAuthModeEnum::PASE)
637                    // As per spec, Group auth mode cannot have Admin privilege
638                    || matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) && matches!(privilege, AccessControlEntryPrivilegeEnum::Administer)
639                {
640                    Err(ErrorCode::ConstraintError)?;
641                }
642
643                e.privilege = privilege.into();
644                e.auth_mode = auth_mode.into();
645                e.auxiliary_type = auxiliary_type;
646
647                // Start with null subjects and targets
648                // so that we can keep those to null if we receive empty subjects' array or empty targets' array
649                // This is what the YAML tests expect
650                e.subjects.clear();
651                e.targets.clear();
652
653                if let Some(subjects) = subjects.into_option() {
654                    for subject in subjects {
655                        if e.subjects.is_none() {
656                            // Initialize our subjects to non-null lazily, only if we have at least one incoming subject
657                            // This ensures that if the incoming subjects is empty, we keep our subjects as null
658                            // which is what the YAML tests expect, even if we internally treat null and empty subjects the same way
659                            e.subjects.reinit(Nullable::init_some(Vec::init()));
660                        }
661
662                        let esubjects = unwrap!(e.subjects.as_opt_mut());
663
664                        let subject = subject?;
665
666                        if matches!(auth_mode, AccessControlEntryAuthModeEnum::CASE) && !is_node(subject) && !is_noc_cat(subject) {
667                            // As per spec, CASE auth mode only allows node ids and NOC CATs as subjects
668                            Err(ErrorCode::ConstraintError)?;
669                        }
670
671                        if matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) {
672                            // Per Matter Core spec: for Group auth mode, the
673                            // subject SHALL be a valid 16-bit Group ID. Group ID 0 is reserved
674                            // and MUST NOT be used; values larger than `u16::MAX` are also invalid.
675                            if subject == 0 || subject > u16::MAX as u64 {
676                                Err(ErrorCode::ConstraintError)?;
677                            }
678                        }
679
680                        // As per spec, on too many subjects we should return a FAILURE status code
681                        // `ErrorCode::BufferTooSmall` translates to a generic FAILURE status code
682                        esubjects
683                            .push(subject)
684                            .map_err(|_| ErrorCode::BufferTooSmall)?;
685                    }
686                }
687
688                if let Some(targets) = targets.into_option() {
689                    for target in targets {
690                        if e.targets.is_none() {
691                            // Initialize our targets to non-null lazily, only if we have at least one incoming target
692                            // This ensures that if the incoming targets is empty, we keep our targets as null
693                            // which is what the YAML tests expect, even if we internally treat null and empty targets the same way
694                            e.targets.reinit(Nullable::init_some(Vec::init()));
695                        }
696
697                        let etargets = unwrap!(e.targets.as_opt_mut());
698
699                        let target = target?;
700
701                        // Matter Core spec (AccessControlTargetStruct):
702                        // - At least one of cluster, endpoint or deviceType SHALL be present.
703                        // - If endpoint is present, deviceType SHALL NOT be present (and vice
704                        //   versa). cluster may be combined with either endpoint or deviceType.
705                        let has_endpoint = target.endpoint()?.is_some();
706                        let has_cluster = target.cluster()?.is_some();
707                        let has_device_type = target.device_type()?.is_some();
708
709                        if (!has_endpoint && !has_cluster && !has_device_type)
710                            || (has_endpoint && has_device_type)
711                        {
712                            Err(ErrorCode::ConstraintError)?;
713                        }
714
715                        // As per spec, on too many targets we should return a FAILURE status code
716                        // `ErrorCode::BufferTooSmall` translates to a generic FAILURE status code
717                        etargets
718                            .push(Target::new(
719                                target.endpoint()?.into_option(),
720                                target.cluster()?.into_option(),
721                                target.device_type()?.into_option(),
722                            ))
723                            .map_err(|_| ErrorCode::BufferTooSmall)?;
724                    }
725                }
726
727                Ok(())
728            })
729    }
730
731    /// Return the data of the ACL entry object
732    /// into the provided TLV builder
733    pub fn read_into<P: TLVBuilderParent>(
734        &self,
735        accessing_fab_idx: u8,
736        fab_idx: Option<u8>,
737        builder: AccessControlEntryStructBuilder<P>,
738    ) -> Result<P, Error> {
739        let same_fab_idx = Some(accessing_fab_idx) == fab_idx;
740
741        builder
742            .privilege(same_fab_idx.then(|| self.privilege.into()))?
743            .auth_mode(same_fab_idx.then(|| self.auth_mode.into()))?
744            .subjects()?
745            .with_some_if(same_fab_idx, |builder| {
746                builder.with_non_null(self.subjects(), |subjects, mut builder| {
747                    for subject in *subjects {
748                        builder = builder.push(subject)?;
749                    }
750
751                    builder.end()
752                })
753            })?
754            .targets()?
755            .with_some_if(same_fab_idx, |builder| {
756                builder.with_non_null(self.targets(), |targets, mut builder| {
757                    for target in *targets {
758                        builder = builder
759                            .push()?
760                            .cluster(Nullable::new(target.cluster))?
761                            .endpoint(Nullable::new(target.endpoint))?
762                            .device_type(Nullable::new(target.device_type))?
763                            .end()?;
764                    }
765
766                    builder.end()
767                })
768            })?
769            .auxiliary_type(self.auxiliary_type())?
770            .fabric_index(fab_idx)?
771            .end()
772    }
773
774    /// Normalize the ACL entry by converting non-null but empty
775    /// subjects/targets to null, as the spec and YAML tests expect
776    pub fn normalize(&mut self) {
777        if self
778            .subjects
779            .as_opt_ref()
780            .map(|subjects| subjects.is_empty())
781            .unwrap_or(false)
782        {
783            self.subjects.clear();
784        }
785
786        if self
787            .targets
788            .as_opt_ref()
789            .map(|targets| targets.is_empty())
790            .unwrap_or(false)
791        {
792            self.targets.clear();
793        }
794    }
795
796    /// Return the auth mode of the ACL entry
797    pub fn auth_mode(&self) -> AuthMode {
798        self.auth_mode
799    }
800
801    /// Return the subjects of the ACL entry
802    pub fn subjects(&self) -> Nullable<&[u64]> {
803        Nullable::new(self.subjects.as_opt_ref().map(|v| v.as_slice()))
804    }
805
806    /// Return the targets of the ACL entry
807    pub fn targets(&self) -> Nullable<&[Target]> {
808        Nullable::new(self.targets.as_opt_ref().map(|v| v.as_slice()))
809    }
810
811    pub fn auxiliary_type(&self) -> Option<AccessControlAuxiliaryTypeEnum> {
812        self.auxiliary_type
813    }
814
815    /// Check if the ACL entry allows access to the given accessor and object
816    pub fn allow(&self, req: &AccessReq) -> bool {
817        self.match_accessor(req.accessor) && self.match_access_desc(&req.object)
818    }
819
820    /// Add a subject to the ACL entry
821    pub fn add_subject(&mut self, subject: u64) -> Result<(), Error> {
822        if self.subjects.is_none() {
823            self.subjects.reinit(Nullable::init_some(Vec::init()));
824        }
825
826        unwrap!(self.subjects.as_opt_mut())
827            .push(subject)
828            .map_err(|_| ErrorCode::ResourceExhausted.into())
829    }
830
831    /// Add a CAT id to the ACL entry
832    pub fn add_subject_catid(&mut self, cat_id: u32) -> Result<(), Error> {
833        self.add_subject(NOC_CAT_SUBJECT_PREFIX | cat_id as u64)
834    }
835
836    /// Add a target to the ACL entry
837    pub fn add_target(&mut self, target: Target) -> Result<(), Error> {
838        if self.targets.is_none() {
839            self.targets.reinit(Nullable::init_some(Vec::init()));
840        }
841
842        unwrap!(self.targets.as_opt_mut())
843            .push(target)
844            .map_err(|_| ErrorCode::ResourceExhausted.into())
845    }
846
847    fn match_accessor(&self, accessor: &Accessor) -> bool {
848        if Some(self.auth_mode) != accessor.auth_mode {
849            return false;
850        }
851
852        let allow = self.subjects().as_opt_ref().is_none_or(|subjects| {
853            // Subjects array null or empty implies allow for all subjects
854            // Otherwise, check if the accessor's subject matches any of the ACL entry's subjects
855            subjects.is_empty() || subjects.iter().any(|s| accessor.subjects.matches(*s))
856        });
857
858        // true if both are true
859        allow
860            && self
861                .fab_idx
862                .map(|fab_idx| fab_idx.get() == accessor.fab_idx)
863                .unwrap_or(false)
864    }
865
866    fn match_access_desc(&self, object: &AccessDesc) -> bool {
867        let allow = self.targets.as_opt_ref().is_none_or(|targets| {
868            // Targets array null or empty implies allow for all targets
869            // Otherwise, check if the target matches any of the ACL entry's targets
870            targets.is_empty()
871                || targets.iter().any(|t| {
872                    let endpoint_match = t.endpoint.is_none() || t.endpoint == object.path.endpoint;
873                    let cluster_match = t.cluster.is_none() || t.cluster == object.path.cluster;
874                    // When `Target.device_type` is set, the access target's endpoint
875                    // must declare a matching device type in its `DeviceTypeList`
876                    // (Matter Core spec).
877                    let device_type_match = match t.device_type {
878                        Some(dt) => object
879                            .device_types
880                            .iter()
881                            .any(|endpoint_dt| endpoint_dt.dtype as u32 == dt),
882                        None => true,
883                    };
884                    endpoint_match && cluster_match && device_type_match
885                })
886        });
887
888        if allow {
889            // Check that the object's access allows this operation with this privilege
890            if let Some(access) = object.target_perms {
891                access.is_ok(object.operation, self.privilege)
892            } else {
893                false
894            }
895        } else {
896            false
897        }
898    }
899}
900
901#[cfg(test)]
902#[allow(clippy::bool_assert_comparison)]
903pub(crate) mod tests {
904    use core::num::NonZeroU8;
905
906    use crate::acl::{gen_noc_cat, AccessorSubjects};
907    use crate::dm::{Access, Privilege};
908    use crate::error::Error;
909    use crate::im::GenericPath;
910    use crate::test::test_matter;
911    use crate::Matter;
912
913    use super::{AccessReq, Accessor, AclEntry, AuthMode, Target};
914
915    pub(crate) const FAB_1: NonZeroU8 = match NonZeroU8::new(1) {
916        Some(f) => f,
917        None => ::core::unreachable!(),
918    };
919
920    pub(crate) const FAB_2: NonZeroU8 = match NonZeroU8::new(2) {
921        Some(f) => f,
922        None => ::core::unreachable!(),
923    };
924
925    fn add_fabric(matter: &Matter<'_>) {
926        matter.with_state(|state| {
927            // Add fabric with ID 1
928            state.fabrics.add_with_post_init(|_| Ok(())).unwrap();
929        })
930    }
931
932    fn add_acl(matter: &Matter<'_>, fab_idx: NonZeroU8, entry: AclEntry) -> Result<usize, Error> {
933        matter.with_state(|state| state.fabrics.fabric_mut(fab_idx)?.acl_add(entry))
934    }
935
936    fn remove_all_acl(matter: &Matter<'_>, fab_idx: NonZeroU8) {
937        matter.with_state(|state| state.fabrics.fabric_mut(fab_idx).unwrap().acl_remove_all())
938    }
939
940    #[test]
941    fn test_basic_empty_subject_target() {
942        let matter = test_matter();
943        let accessor = Accessor::new(
944            0,
945            AccessorSubjects::new(112233),
946            Some(AuthMode::Pase),
947            &matter,
948        );
949        let path = GenericPath::new(Some(1), Some(1234), None);
950        let mut req_pase = AccessReq::new(&accessor, path, Access::READ);
951        req_pase.set_target_perms(Access::RWVA);
952
953        // Always allow for PASE sessions
954        assert!(req_pase.allow());
955
956        let accessor = Accessor::new(
957            2,
958            AccessorSubjects::new(112233),
959            Some(AuthMode::Case),
960            &matter,
961        );
962        let path = GenericPath::new(Some(1), Some(1234), None);
963        let mut req = AccessReq::new(&accessor, path, Access::READ);
964        req.set_target_perms(Access::RWVA);
965
966        // Default deny for CASE
967        assert_eq!(req.allow(), false);
968
969        // Add fabric with ID 1
970        add_fabric(&matter);
971
972        // Deny adding invalid auth mode (PASE is reserved for future)
973        let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Pase);
974        assert!(add_acl(&matter, FAB_1, new).is_err());
975
976        // Deny for fab idx mismatch
977        let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
978        assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
979        assert_eq!(req.allow(), false);
980
981        // Always allow for PASE sessions
982        assert!(req_pase.allow());
983
984        // Add fabric with ID 2
985        add_fabric(&matter);
986
987        // Allow
988        let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
989        assert_eq!(add_acl(&matter, FAB_2, new).unwrap(), 0);
990        assert_eq!(req.allow(), true);
991    }
992
993    #[test]
994    fn test_subject() {
995        let matter = test_matter();
996
997        // Add fabric with ID 1
998        add_fabric(&matter);
999
1000        let accessor = Accessor::new(
1001            1,
1002            AccessorSubjects::new(112233),
1003            Some(AuthMode::Case),
1004            &matter,
1005        );
1006        let path = GenericPath::new(Some(1), Some(1234), None);
1007        let mut req = AccessReq::new(&accessor, path, Access::READ);
1008        req.set_target_perms(Access::RWVA);
1009
1010        // Deny for subject mismatch
1011        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1012        new.add_subject(112232).unwrap();
1013        assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
1014        assert_eq!(req.allow(), false);
1015
1016        // Allow for subject match - target is wildcard
1017        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1018        new.add_subject(112233).unwrap();
1019        assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 1);
1020        assert_eq!(req.allow(), true);
1021    }
1022
1023    #[test]
1024    fn test_cat() {
1025        let matter = test_matter();
1026
1027        // Add fabric with ID 1
1028        add_fabric(&matter);
1029
1030        let allow_cat = 0xABCD;
1031        let disallow_cat = 0xCAFE;
1032        let v2 = 2;
1033        let v3 = 3;
1034        // Accessor has nodeif and CAT 0xABCD_0002
1035        let mut subjects = AccessorSubjects::new(112233);
1036        subjects.add_catid(gen_noc_cat(allow_cat, v2)).unwrap();
1037
1038        let accessor = Accessor::new(1, subjects, Some(AuthMode::Case), &matter);
1039        let path = GenericPath::new(Some(1), Some(1234), None);
1040        let mut req = AccessReq::new(&accessor, path, Access::READ);
1041        req.set_target_perms(Access::RWVA);
1042
1043        // Deny for CAT id mismatch
1044        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1045        new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
1046            .unwrap();
1047        add_acl(&matter, FAB_1, new).unwrap();
1048        assert_eq!(req.allow(), false);
1049
1050        // Deny of CAT version mismatch
1051        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1052        new.add_subject_catid(gen_noc_cat(allow_cat, v3)).unwrap();
1053        add_acl(&matter, FAB_1, new).unwrap();
1054        assert_eq!(req.allow(), false);
1055
1056        // Allow for CAT match
1057        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1058        new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
1059        add_acl(&matter, FAB_1, new).unwrap();
1060        assert_eq!(req.allow(), true);
1061    }
1062
1063    #[test]
1064    fn test_cat_version() {
1065        let matter = test_matter();
1066
1067        // Add fabric with ID 1
1068        add_fabric(&matter);
1069
1070        let allow_cat = 0xABCD;
1071        let disallow_cat = 0xCAFE;
1072        let v2 = 2;
1073        let v3 = 3;
1074        // Accessor has nodeif and CAT 0xABCD_0003
1075        let mut subjects = AccessorSubjects::new(112233);
1076        subjects.add_catid(gen_noc_cat(allow_cat, v3)).unwrap();
1077
1078        let accessor = Accessor::new(1, subjects, Some(AuthMode::Case), &matter);
1079        let path = GenericPath::new(Some(1), Some(1234), None);
1080        let mut req = AccessReq::new(&accessor, path, Access::READ);
1081        req.set_target_perms(Access::RWVA);
1082
1083        // Deny for CAT id mismatch
1084        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1085        new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
1086            .unwrap();
1087        add_acl(&matter, FAB_1, new).unwrap();
1088        assert_eq!(req.allow(), false);
1089
1090        // Allow for CAT match and version more than ACL version
1091        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1092        new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
1093        add_acl(&matter, FAB_1, new).unwrap();
1094        assert_eq!(req.allow(), true);
1095    }
1096
1097    #[test]
1098    fn test_target() {
1099        let matter = test_matter();
1100
1101        // Add fabric with ID 1
1102        add_fabric(&matter);
1103
1104        let accessor = Accessor::new(
1105            1,
1106            AccessorSubjects::new(112233),
1107            Some(AuthMode::Case),
1108            &matter,
1109        );
1110        let path = GenericPath::new(Some(1), Some(1234), None);
1111        let mut req = AccessReq::new(&accessor, path, Access::READ);
1112        req.set_target_perms(Access::RWVA);
1113
1114        // Deny for target mismatch
1115        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1116        new.add_target(Target {
1117            cluster: Some(2),
1118            endpoint: Some(4567),
1119            device_type: None,
1120        })
1121        .unwrap();
1122        add_acl(&matter, FAB_1, new).unwrap();
1123        assert_eq!(req.allow(), false);
1124
1125        // Allow for cluster match - subject wildcard
1126        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1127        new.add_target(Target {
1128            cluster: Some(1234),
1129            endpoint: None,
1130            device_type: None,
1131        })
1132        .unwrap();
1133        add_acl(&matter, FAB_1, new).unwrap();
1134        assert_eq!(req.allow(), true);
1135
1136        // Clean state
1137        remove_all_acl(&matter, FAB_1);
1138
1139        // Allow for endpoint match - subject wildcard
1140        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1141        new.add_target(Target {
1142            cluster: None,
1143            endpoint: Some(1),
1144            device_type: None,
1145        })
1146        .unwrap();
1147        add_acl(&matter, FAB_1, new).unwrap();
1148        assert_eq!(req.allow(), true);
1149
1150        // Clean state
1151        remove_all_acl(&matter, FAB_1);
1152
1153        // Allow for exact match
1154        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1155        new.add_target(Target {
1156            cluster: Some(1234),
1157            endpoint: Some(1),
1158            device_type: None,
1159        })
1160        .unwrap();
1161        new.add_subject(112233).unwrap();
1162        add_acl(&matter, FAB_1, new).unwrap();
1163        assert_eq!(req.allow(), true);
1164    }
1165
1166    #[test]
1167    fn test_privilege() {
1168        let matter = test_matter();
1169
1170        // Add fabric with ID 1
1171        add_fabric(&matter);
1172
1173        let accessor = Accessor::new(
1174            1,
1175            AccessorSubjects::new(112233),
1176            Some(AuthMode::Case),
1177            &matter,
1178        );
1179        let path = GenericPath::new(Some(1), Some(1234), None);
1180
1181        // Create an Exact Match ACL with View privilege
1182        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1183        new.add_target(Target {
1184            cluster: Some(1234),
1185            endpoint: Some(1),
1186            device_type: None,
1187        })
1188        .unwrap();
1189        new.add_subject(112233).unwrap();
1190        add_acl(&matter, FAB_1, new).unwrap();
1191
1192        // Write on an RWVA without admin access - deny
1193        let mut req = AccessReq::new(&accessor, path.clone(), Access::WRITE);
1194        req.set_target_perms(Access::RWVA);
1195        assert_eq!(req.allow(), false);
1196
1197        // Create an Exact Match ACL with Admin privilege
1198        let mut new = AclEntry::new(None, Privilege::ADMIN, AuthMode::Case);
1199        new.add_target(Target {
1200            cluster: Some(1234),
1201            endpoint: Some(1),
1202            device_type: None,
1203        })
1204        .unwrap();
1205        new.add_subject(112233).unwrap();
1206        add_acl(&matter, FAB_1, new).unwrap();
1207
1208        // Write on an RWVA with admin access - allow
1209        let mut req = AccessReq::new(&accessor, path, Access::WRITE);
1210        req.set_target_perms(Access::RWVA);
1211        assert_eq!(req.allow(), true);
1212    }
1213
1214    #[test]
1215    fn test_delete_for_fabric() {
1216        let matter = test_matter();
1217
1218        // Add fabric with ID 1
1219        add_fabric(&matter);
1220
1221        // Add fabric with ID 2
1222        add_fabric(&matter);
1223
1224        let path = GenericPath::new(Some(1), Some(1234), None);
1225        let accessor2 = Accessor::new(
1226            1,
1227            AccessorSubjects::new(112233),
1228            Some(AuthMode::Case),
1229            &matter,
1230        );
1231        let mut req1 = AccessReq::new(&accessor2, path.clone(), Access::READ);
1232        req1.set_target_perms(Access::RWVA);
1233        let accessor3 = Accessor::new(
1234            2,
1235            AccessorSubjects::new(112233),
1236            Some(AuthMode::Case),
1237            &matter,
1238        );
1239        let mut req2 = AccessReq::new(&accessor3, path, Access::READ);
1240        req2.set_target_perms(Access::RWVA);
1241
1242        // Allow for subject match - target is wildcard - Fabric idx 2
1243        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1244        new.add_subject(112233).unwrap();
1245        assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
1246
1247        // Allow for subject match - target is wildcard - Fabric idx 3
1248        let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1249        new.add_subject(112233).unwrap();
1250        assert_eq!(add_acl(&matter, FAB_2, new).unwrap(), 0);
1251
1252        // Req for Fabric idx 1 gets denied, and that for Fabric idx 2 is allowed
1253        assert_eq!(req1.allow(), true);
1254        assert_eq!(req2.allow(), true);
1255        remove_all_acl(&matter, FAB_1);
1256        assert_eq!(req1.allow(), false);
1257        assert_eq!(req2.allow(), true);
1258    }
1259}