1use 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 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 32;
45 } else if #[cfg(feature = "max-subjects-per-acl-16")] {
46 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 16;
48 } else if #[cfg(feature = "max-subjects-per-acl-8")] {
49 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 8;
51 } else if #[cfg(feature = "max-subjects-per-acl-7")] {
52 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 7;
54 } else if #[cfg(feature = "max-subjects-per-acl-6")] {
55 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 6;
57 } else if #[cfg(feature = "max-subjects-per-acl-5")] {
58 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 5;
60 } else if #[cfg(feature = "max-subjects-per-acl-4")] {
61 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 4;
63 } else if #[cfg(feature = "max-subjects-per-acl-3")] {
64 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 3;
66 } else if #[cfg(feature = "max-subjects-per-acl-2")] {
67 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 2;
69 } else if #[cfg(feature = "max-subjects-per-acl-1")] {
70 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 1;
72 } else {
73 pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 4;
75 }
76}
77
78cfg_if! {
79 if #[cfg(feature = "max-targets-per-acl-32")] {
80 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 32;
82 } else if #[cfg(feature = "max-targets-per-acl-16")] {
83 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 16;
85 } else if #[cfg(feature = "max-targets-per-acl-8")] {
86 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 8;
88 } else if #[cfg(feature = "max-targets-per-acl-7")] {
89 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 7;
91 } else if #[cfg(feature = "max-targets-per-acl-6")] {
92 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 6;
94 } else if #[cfg(feature = "max-targets-per-acl-5")] {
95 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 5;
97 } else if #[cfg(feature = "max-targets-per-acl-4")] {
98 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 4;
100 } else if #[cfg(feature = "max-targets-per-acl-3")] {
101 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 3;
103 } else if #[cfg(feature = "max-targets-per-acl-2")] {
104 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 2;
106 } else if #[cfg(feature = "max-targets-per-acl-1")] {
107 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 1;
109 } else {
110 pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 3;
112 }
113}
114
115cfg_if! {
116 if #[cfg(feature = "max-acls-per-fabric-32")] {
117 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 32;
119 } else if #[cfg(feature = "max-acls-per-fabric-16")] {
120 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 16;
122 } else if #[cfg(feature = "max-acls-per-fabric-8")] {
123 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 8;
125 } else if #[cfg(feature = "max-acls-per-fabric-7")] {
126 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 7;
128 } else if #[cfg(feature = "max-acls-per-fabric-6")] {
129 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 6;
131 } else if #[cfg(feature = "max-acls-per-fabric-5")] {
132 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 5;
134 } else if #[cfg(feature = "max-acls-per-fabric-4")] {
135 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 4;
137 } else if #[cfg(feature = "max-acls-per-fabric-3")] {
138 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 3;
140 } else if #[cfg(feature = "max-acls-per-fabric-2")] {
141 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 2;
143 } else if #[cfg(feature = "max-acls-per-fabric-1")] {
144 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 1;
146 } else {
147 pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 4;
149 }
150}
151
152#[derive(FromPrimitive, Copy, Clone, PartialEq, Debug)]
155#[cfg_attr(feature = "defmt", derive(defmt::Format))]
156#[repr(u8)]
157pub enum AuthMode {
158 Pase = AccessControlEntryAuthModeEnum::PASE as _,
160 Case = AccessControlEntryAuthModeEnum::CASE as _,
162 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
205const MAX_ACCESSOR_SUBJECTS: usize = 1 + MAX_CAT_IDS_PER_NOC;
207
208pub 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
215const NODE_ID_RANGE: RangeInclusive<u64> = 1..=0xFFFF_FFEF_FFFF_FFFF;
217
218pub(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
224fn get_noc_cat_id(id: u64) -> u64 {
226 (id & NOC_CAT_ID_MASK) >> 16
227}
228
229fn get_noc_cat_version(id: u64) -> u64 {
231 id & NOC_CAT_VERSION_MASK
232}
233
234pub fn gen_noc_cat(id: u16, version: u16) -> u32 {
237 ((id as u32) << 16) | version as u32
238}
239
240pub(crate) fn is_node(id: u64) -> bool {
242 NODE_ID_RANGE.contains(&id)
243}
244
245pub struct AccessorSubjects([u64; MAX_ACCESSOR_SUBJECTS]);
247
248impl AccessorSubjects {
249 pub fn new(id: u64) -> Self {
252 let mut a = Self(Default::default());
253 a.0[0] = id;
254 a
255 }
256
257 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 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 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
323pub struct Accessor<'a> {
325 pub(crate) fab_idx: u8,
327 aux_acl_enabled: bool,
329 subjects: AccessorSubjects,
331 auth_mode: Option<AuthMode>,
333 matter: &'a Matter<'a>,
336}
337
338impl<'a> Accessor<'a> {
339 pub fn for_session(session: &Session, matter: &'a Matter<'a>, aux_acl_enabled: bool) -> Self {
341 match session.get_session_mode() {
342 SessionMode::Case {
343 fab_idx, cat_ids, ..
344 } => {
345 let mut subject =
346 AccessorSubjects::new(session.get_peer_node_id().unwrap_or_default());
347 for i in *cat_ids {
348 if i != 0 {
349 let _ = subject.add_catid(i);
350 }
351 }
352 Accessor::new(
353 fab_idx.get(),
354 aux_acl_enabled,
355 subject,
356 Some(AuthMode::Case),
357 matter,
358 )
359 }
360 SessionMode::Pase { fab_idx } => Accessor::new(
361 *fab_idx,
362 aux_acl_enabled,
363 AccessorSubjects::new(1),
364 Some(AuthMode::Pase),
365 matter,
366 ),
367 SessionMode::Group { fab_idx, group_id } => Accessor::new(
368 fab_idx.get(),
369 aux_acl_enabled,
370 AccessorSubjects::new(*group_id as u64),
371 Some(AuthMode::Group),
372 matter,
373 ),
374 SessionMode::PlainText => {
375 Accessor::new(0, aux_acl_enabled, AccessorSubjects::new(1), None, matter)
376 }
377 }
378 }
379
380 pub const fn new(
389 fab_idx: u8,
390 aux_acl_enabled: bool,
391 subjects: AccessorSubjects,
392 auth_mode: Option<AuthMode>,
393 matter: &'a Matter<'a>,
394 ) -> Self {
395 Self {
396 fab_idx,
397 aux_acl_enabled,
398 subjects,
399 auth_mode,
400 matter,
401 }
402 }
403
404 pub fn fab_idx(&self) -> Result<NonZeroU8, Error> {
405 NonZeroU8::new(self.fab_idx).ok_or(ErrorCode::UnsupportedAccess.into())
406 }
407
408 pub const fn aux_acl_enabled(&self) -> bool {
410 self.aux_acl_enabled
411 }
412
413 pub const fn subjects(&self) -> &AccessorSubjects {
415 &self.subjects
416 }
417
418 pub const fn auth_mode(&self) -> Option<AuthMode> {
420 self.auth_mode
421 }
422
423 pub fn is_endpoint_accessible(&self, endpoint_id: EndptId) -> bool {
428 if self.auth_mode != Some(AuthMode::Group) {
429 return true;
430 }
431
432 #[cfg(feature = "groups")]
435 {
436 let group_id = self.subjects.0[0] as u16;
437
438 let Some(fab_idx) = core::num::NonZeroU8::new(self.fab_idx) else {
439 return false;
440 };
441
442 self.matter.with_state(|state| {
443 let Some(fabric) = state.fabrics.get(fab_idx) else {
444 return false;
445 };
446
447 fabric
448 .groups()
449 .get(group_id)
450 .is_some_and(|e| e.endpoints.contains(&endpoint_id))
451 })
452 }
453
454 #[cfg(not(feature = "groups"))]
459 {
460 let _ = endpoint_id;
461 false
462 }
463 }
464
465 pub fn node_id(&self) -> Option<NodeId> {
467 let fab_idx = NonZeroU8::new(self.fab_idx)?;
468
469 self.matter
470 .with_state(|state| state.fabrics.get(fab_idx).map(|fabric| fabric.node_id()))
471 }
472
473 pub fn peer_node_id(&self) -> Option<u64> {
475 if matches!(self.auth_mode, Some(AuthMode::Case)) {
476 let id = self.subjects.0[0];
477 if is_node(id) {
478 Some(id)
479 } else {
480 None
481 }
482 } else {
483 None
484 }
485 }
486}
487
488#[derive(Debug)]
490#[cfg_attr(feature = "defmt", derive(defmt::Format))]
491pub struct AccessDesc<'a> {
492 path: GenericPath,
494 target_perms: Option<Access>,
496 operation: Access,
498 device_types: &'a [DeviceType],
502}
503
504pub struct AccessReq<'a> {
506 accessor: &'a Accessor<'a>,
508 object: AccessDesc<'a>,
510}
511
512impl<'a> AccessReq<'a> {
513 pub const fn new(
521 accessor: &'a Accessor<'a>,
522 path: GenericPath,
523 operation: Access,
524 device_types: &'a [DeviceType],
525 ) -> Self {
526 AccessReq {
527 accessor,
528 object: AccessDesc {
529 path,
530 target_perms: None,
531 operation,
532 device_types,
533 },
534 }
535 }
536
537 pub fn accessor(&self) -> &Accessor<'_> {
539 self.accessor
540 }
541
542 pub fn operation(&self) -> Access {
544 self.object.operation
545 }
546
547 pub fn set_target_perms(&mut self, perms: Access) {
552 self.object.target_perms = Some(perms);
553 }
554
555 pub fn allow(&self) -> bool {
561 self.accessor.matter.with_state(|state| {
562 let allow = state.fabrics.allow(self, self.accessor.aux_acl_enabled());
563
564 #[cfg(feature = "groups")]
565 let allow = allow || self.allow_groupcast_auxiliary(&state.fabrics);
566
567 allow
568 })
569 }
570
571 #[cfg(feature = "groups")]
577 fn allow_groupcast_auxiliary(&self, fabrics: &crate::fabric::Fabrics) -> bool {
578 if !self.accessor.aux_acl_enabled() {
579 return false;
580 }
581
582 if self.accessor.auth_mode != Some(AuthMode::Group) {
583 return false;
584 }
585
586 let Ok(fab_idx) = self.accessor.fab_idx() else {
587 return false;
588 };
589
590 let Some(fabric) = fabrics.get(fab_idx) else {
591 return false;
592 };
593
594 let Some(endpoint) = self.object.path.endpoint else {
596 return false;
597 };
598
599 let granted = fabric.groups().iter().any(|entry| {
600 entry.has_aux_acl()
601 && entry.endpoints.contains(&endpoint)
602 && self.accessor.subjects.matches(entry.group_id as u64)
603 });
604
605 granted
606 && self
607 .object
608 .target_perms
609 .is_some_and(|access| access.is_ok(self.object.operation, Privilege::OPERATE))
610 }
611}
612
613#[derive(FromTLV, ToTLV, Clone, Debug, PartialEq)]
615#[cfg_attr(feature = "defmt", derive(defmt::Format))]
616pub struct Target {
617 pub cluster: Option<ClusterId>,
618 pub endpoint: Option<EndptId>,
619 pub device_type: Option<u32>,
620}
621
622impl Target {
623 pub const fn new(
625 endpoint: Option<EndptId>,
626 cluster: Option<ClusterId>,
627 device_type: Option<u32>,
628 ) -> Self {
629 Self {
630 cluster,
631 endpoint,
632 device_type,
633 }
634 }
635}
636
637#[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)]
639#[cfg_attr(feature = "defmt", derive(defmt::Format))]
640#[tlvargs(start = 1)]
641pub struct AclEntry {
642 privilege: Privilege,
644 auth_mode: AuthMode,
646 subjects: Nullable<Vec<u64, MAX_SUBJECTS_PER_ACL_ENTRY>>,
648 targets: Nullable<Vec<Target, MAX_TARGETS_PER_ACL_ENTRY>>,
650 auxiliary_type: Option<AccessControlAuxiliaryTypeEnum>,
652 #[tagval(crate::im::encoding::FABRIC_INDEX_TAG)]
655 pub fab_idx: Option<NonZeroU8>,
656}
657
658impl AclEntry {
659 pub const fn new(
661 fab_idx: Option<NonZeroU8>,
662 privilege: Privilege,
663 auth_mode: AuthMode,
664 ) -> Self {
665 Self {
666 fab_idx,
667 privilege,
668 auth_mode,
669 subjects: Nullable::none(),
670 targets: Nullable::none(),
671 auxiliary_type: None,
672 }
673 }
674
675 pub fn init(
678 fab_idx: Option<NonZeroU8>,
679 privilege: Privilege,
680 auth_mode: AuthMode,
681 ) -> impl Init<Self> {
682 init!(Self {
683 fab_idx,
684 privilege,
685 auth_mode,
686 subjects <- Nullable::init_none(),
687 targets <- Nullable::init_none(),
688 auxiliary_type: None,
689 })
690 }
691
692 pub fn init_with<'a>(
695 fab_idx: NonZeroU8,
696 entry: &'a AccessControlEntryStruct<'a>,
697 ) -> impl Init<Self, Error> + 'a {
698 Self::init(Some(fab_idx), Privilege::empty(), AuthMode::Pase)
699 .into_fallible()
700 .chain(|e| {
701 let auth_mode = entry.auth_mode().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
702 let privilege = entry.privilege().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
703 let subjects = entry.subjects().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
704 let targets = entry.targets().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
705 let auxiliary_type = entry.auxiliary_type().map_err(|_| ErrorCode::ConstraintError)?;
706
707 if auxiliary_type.is_some() {
712 Err(ErrorCode::ConstraintError)?;
713 }
714
715 if
716 matches!(auth_mode, AccessControlEntryAuthModeEnum::PASE)
718 || matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) && matches!(privilege, AccessControlEntryPrivilegeEnum::Administer)
720 {
721 Err(ErrorCode::ConstraintError)?;
722 }
723
724 e.privilege = privilege.into();
725 e.auth_mode = auth_mode.into();
726
727 e.subjects.clear();
731 e.targets.clear();
732
733 if let Some(subjects) = subjects.into_option() {
734 for subject in subjects {
735 if e.subjects.is_none() {
736 e.subjects.reinit(Nullable::init_some(Vec::init()));
740 }
741
742 let esubjects = unwrap!(e.subjects.as_opt_mut());
743
744 let subject = subject?;
745
746 if matches!(auth_mode, AccessControlEntryAuthModeEnum::CASE) && !is_node(subject) && !is_noc_cat(subject) {
747 Err(ErrorCode::ConstraintError)?;
749 }
750
751 if matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) {
752 if subject == 0 || subject > u16::MAX as u64 {
756 Err(ErrorCode::ConstraintError)?;
757 }
758 }
759
760 esubjects
763 .push(subject)
764 .map_err(|_| ErrorCode::BufferTooSmall)?;
765 }
766 }
767
768 if let Some(targets) = targets.into_option() {
769 for target in targets {
770 if e.targets.is_none() {
771 e.targets.reinit(Nullable::init_some(Vec::init()));
775 }
776
777 let etargets = unwrap!(e.targets.as_opt_mut());
778
779 let target = target?;
780
781 let has_endpoint = target.endpoint()?.is_some();
786 let has_cluster = target.cluster()?.is_some();
787 let has_device_type = target.device_type()?.is_some();
788
789 if (!has_endpoint && !has_cluster && !has_device_type)
790 || (has_endpoint && has_device_type)
791 {
792 Err(ErrorCode::ConstraintError)?;
793 }
794
795 etargets
798 .push(Target::new(
799 target.endpoint()?.into_option(),
800 target.cluster()?.into_option(),
801 target.device_type()?.into_option(),
802 ))
803 .map_err(|_| ErrorCode::BufferTooSmall)?;
804 }
805 }
806
807 Ok(())
808 })
809 }
810
811 pub fn read_into<P: TLVBuilderParent>(
814 &self,
815 accessing_fab_idx: u8,
816 fab_idx: Option<u8>,
817 builder: AccessControlEntryStructBuilder<P>,
818 ) -> Result<P, Error> {
819 let same_fab_idx = Some(accessing_fab_idx) == fab_idx;
820
821 builder
822 .privilege(same_fab_idx.then(|| self.privilege.into()))?
823 .auth_mode(same_fab_idx.then(|| self.auth_mode.into()))?
824 .subjects()?
825 .with_some_if(same_fab_idx, |builder| {
826 builder.with_non_null(self.subjects(), |subjects, mut builder| {
827 for subject in *subjects {
828 builder = builder.push(subject)?;
829 }
830
831 builder.end()
832 })
833 })?
834 .targets()?
835 .with_some_if(same_fab_idx, |builder| {
836 builder.with_non_null(self.targets(), |targets, mut builder| {
837 for target in *targets {
838 builder = builder
839 .push()?
840 .cluster(Nullable::new(target.cluster))?
841 .endpoint(Nullable::new(target.endpoint))?
842 .device_type(Nullable::new(target.device_type))?
843 .end()?;
844 }
845
846 builder.end()
847 })
848 })?
849 .auxiliary_type(self.auxiliary_type())?
850 .fabric_index(fab_idx)?
851 .end()
852 }
853
854 pub fn normalize(&mut self) {
857 if self
858 .subjects
859 .as_opt_ref()
860 .map(|subjects| subjects.is_empty())
861 .unwrap_or(false)
862 {
863 self.subjects.clear();
864 }
865
866 if self
867 .targets
868 .as_opt_ref()
869 .map(|targets| targets.is_empty())
870 .unwrap_or(false)
871 {
872 self.targets.clear();
873 }
874 }
875
876 pub fn auth_mode(&self) -> AuthMode {
878 self.auth_mode
879 }
880
881 pub fn subjects(&self) -> Nullable<&[u64]> {
883 Nullable::new(self.subjects.as_opt_ref().map(|v| v.as_slice()))
884 }
885
886 pub fn targets(&self) -> Nullable<&[Target]> {
888 Nullable::new(self.targets.as_opt_ref().map(|v| v.as_slice()))
889 }
890
891 pub fn auxiliary_type(&self) -> Option<AccessControlAuxiliaryTypeEnum> {
892 self.auxiliary_type
893 }
894
895 pub fn allow(&self, req: &AccessReq, aux_acl_enabled: bool) -> bool {
901 self.match_accessor(req.accessor) && self.match_access_desc(&req.object, aux_acl_enabled)
902 }
903
904 pub fn add_subject(&mut self, subject: u64) -> Result<(), Error> {
906 if self.subjects.is_none() {
907 self.subjects.reinit(Nullable::init_some(Vec::init()));
908 }
909
910 unwrap!(self.subjects.as_opt_mut())
911 .push(subject)
912 .map_err(|_| ErrorCode::ResourceExhausted.into())
913 }
914
915 pub fn add_subject_catid(&mut self, cat_id: u32) -> Result<(), Error> {
917 self.add_subject(NOC_CAT_SUBJECT_PREFIX | cat_id as u64)
918 }
919
920 pub fn add_target(&mut self, target: Target) -> Result<(), Error> {
922 if self.targets.is_none() {
923 self.targets.reinit(Nullable::init_some(Vec::init()));
924 }
925
926 unwrap!(self.targets.as_opt_mut())
927 .push(target)
928 .map_err(|_| ErrorCode::ResourceExhausted.into())
929 }
930
931 fn match_accessor(&self, accessor: &Accessor) -> bool {
932 if Some(self.auth_mode) != accessor.auth_mode {
933 return false;
934 }
935
936 let allow = self.subjects().as_opt_ref().is_none_or(|subjects| {
937 subjects.is_empty() || subjects.iter().any(|s| accessor.subjects.matches(*s))
940 });
941
942 allow
944 && self
945 .fab_idx
946 .map(|fab_idx| fab_idx.get() == accessor.fab_idx)
947 .unwrap_or(false)
948 }
949
950 fn match_access_desc(&self, object: &AccessDesc, aux_acl_enabled: bool) -> bool {
951 if aux_acl_enabled
957 && matches!(self.auth_mode, AuthMode::Group)
958 && object.path.endpoint == Some(crate::dm::endpoints::ROOT_ENDPOINT_ID)
959 && self
960 .targets
961 .as_opt_ref()
962 .is_none_or(|targets| targets.is_empty())
963 {
964 return false;
965 }
966
967 let allow = self.targets.as_opt_ref().is_none_or(|targets| {
968 targets.is_empty()
971 || targets.iter().any(|t| {
972 let endpoint_match = t.endpoint.is_none() || t.endpoint == object.path.endpoint;
973 let cluster_match = t.cluster.is_none() || t.cluster == object.path.cluster;
974 let device_type_match = match t.device_type {
978 Some(dt) => object
979 .device_types
980 .iter()
981 .any(|endpoint_dt| endpoint_dt.dtype as u32 == dt),
982 None => true,
983 };
984 endpoint_match && cluster_match && device_type_match
985 })
986 });
987
988 if allow {
989 if let Some(access) = object.target_perms {
991 access.is_ok(object.operation, self.privilege)
992 } else {
993 false
994 }
995 } else {
996 false
997 }
998 }
999}
1000
1001#[cfg(test)]
1002#[allow(clippy::bool_assert_comparison)]
1003pub(crate) mod tests {
1004 use core::num::NonZeroU8;
1005
1006 use crate::acl::{gen_noc_cat, AccessorSubjects};
1007 use crate::dm::{Access, Privilege};
1008 use crate::error::Error;
1009 use crate::im::GenericPath;
1010 use crate::test::test_matter;
1011 use crate::Matter;
1012
1013 use super::{AccessReq, Accessor, AclEntry, AuthMode, Target};
1014
1015 pub(crate) const FAB_1: NonZeroU8 = match NonZeroU8::new(1) {
1016 Some(f) => f,
1017 None => ::core::unreachable!(),
1018 };
1019
1020 pub(crate) const FAB_2: NonZeroU8 = match NonZeroU8::new(2) {
1021 Some(f) => f,
1022 None => ::core::unreachable!(),
1023 };
1024
1025 fn add_fabric(matter: &Matter<'_>) {
1026 matter.with_state(|state| {
1027 state.fabrics.add_with_post_init(|_| Ok(())).unwrap();
1029 })
1030 }
1031
1032 fn add_acl(matter: &Matter<'_>, fab_idx: NonZeroU8, entry: AclEntry) -> Result<usize, Error> {
1033 matter.with_state(|state| state.fabrics.fabric_mut(fab_idx)?.acl_add(entry))
1034 }
1035
1036 fn remove_all_acl(matter: &Matter<'_>, fab_idx: NonZeroU8) {
1037 matter.with_state(|state| state.fabrics.fabric_mut(fab_idx).unwrap().acl_remove_all())
1038 }
1039
1040 #[test]
1041 fn test_basic_empty_subject_target() {
1042 let matter = test_matter();
1043 let accessor = Accessor::new(
1044 0,
1045 false,
1046 AccessorSubjects::new(112233),
1047 Some(AuthMode::Pase),
1048 &matter,
1049 );
1050 let path = GenericPath::new(Some(1), Some(1234), None);
1051 let mut req_pase = AccessReq::new(&accessor, path, Access::READ, &[]);
1052 req_pase.set_target_perms(Access::RWVA);
1053
1054 assert!(req_pase.allow());
1056
1057 let accessor = Accessor::new(
1058 2,
1059 false,
1060 AccessorSubjects::new(112233),
1061 Some(AuthMode::Case),
1062 &matter,
1063 );
1064 let path = GenericPath::new(Some(1), Some(1234), None);
1065 let mut req = AccessReq::new(&accessor, path, Access::READ, &[]);
1066 req.set_target_perms(Access::RWVA);
1067
1068 assert_eq!(req.allow(), false);
1070
1071 add_fabric(&matter);
1073
1074 let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Pase);
1076 assert!(add_acl(&matter, FAB_1, new).is_err());
1077
1078 let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1080 assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
1081 assert_eq!(req.allow(), false);
1082
1083 assert!(req_pase.allow());
1085
1086 add_fabric(&matter);
1088
1089 let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1091 assert_eq!(add_acl(&matter, FAB_2, new).unwrap(), 0);
1092 assert_eq!(req.allow(), true);
1093 }
1094
1095 #[test]
1096 fn test_subject() {
1097 let matter = test_matter();
1098
1099 add_fabric(&matter);
1101
1102 let accessor = Accessor::new(
1103 1,
1104 false,
1105 AccessorSubjects::new(112233),
1106 Some(AuthMode::Case),
1107 &matter,
1108 );
1109 let path = GenericPath::new(Some(1), Some(1234), None);
1110 let mut req = AccessReq::new(&accessor, path, Access::READ, &[]);
1111 req.set_target_perms(Access::RWVA);
1112
1113 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1115 new.add_subject(112232).unwrap();
1116 assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
1117 assert_eq!(req.allow(), false);
1118
1119 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1121 new.add_subject(112233).unwrap();
1122 assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 1);
1123 assert_eq!(req.allow(), true);
1124 }
1125
1126 #[test]
1127 fn test_cat() {
1128 let matter = test_matter();
1129
1130 add_fabric(&matter);
1132
1133 let allow_cat = 0xABCD;
1134 let disallow_cat = 0xCAFE;
1135 let v2 = 2;
1136 let v3 = 3;
1137 let mut subjects = AccessorSubjects::new(112233);
1139 subjects.add_catid(gen_noc_cat(allow_cat, v2)).unwrap();
1140
1141 let accessor = Accessor::new(1, false, subjects, Some(AuthMode::Case), &matter);
1142 let path = GenericPath::new(Some(1), Some(1234), None);
1143 let mut req = AccessReq::new(&accessor, path, Access::READ, &[]);
1144 req.set_target_perms(Access::RWVA);
1145
1146 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1148 new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
1149 .unwrap();
1150 add_acl(&matter, FAB_1, new).unwrap();
1151 assert_eq!(req.allow(), false);
1152
1153 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1155 new.add_subject_catid(gen_noc_cat(allow_cat, v3)).unwrap();
1156 add_acl(&matter, FAB_1, new).unwrap();
1157 assert_eq!(req.allow(), false);
1158
1159 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1161 new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
1162 add_acl(&matter, FAB_1, new).unwrap();
1163 assert_eq!(req.allow(), true);
1164 }
1165
1166 #[test]
1167 fn test_cat_version() {
1168 let matter = test_matter();
1169
1170 add_fabric(&matter);
1172
1173 let allow_cat = 0xABCD;
1174 let disallow_cat = 0xCAFE;
1175 let v2 = 2;
1176 let v3 = 3;
1177 let mut subjects = AccessorSubjects::new(112233);
1179 subjects.add_catid(gen_noc_cat(allow_cat, v3)).unwrap();
1180
1181 let accessor = Accessor::new(1, false, subjects, Some(AuthMode::Case), &matter);
1182 let path = GenericPath::new(Some(1), Some(1234), None);
1183 let mut req = AccessReq::new(&accessor, path, Access::READ, &[]);
1184 req.set_target_perms(Access::RWVA);
1185
1186 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1188 new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
1189 .unwrap();
1190 add_acl(&matter, FAB_1, new).unwrap();
1191 assert_eq!(req.allow(), false);
1192
1193 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1195 new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
1196 add_acl(&matter, FAB_1, new).unwrap();
1197 assert_eq!(req.allow(), true);
1198 }
1199
1200 #[test]
1201 fn test_target() {
1202 let matter = test_matter();
1203
1204 add_fabric(&matter);
1206
1207 let accessor = Accessor::new(
1208 1,
1209 false,
1210 AccessorSubjects::new(112233),
1211 Some(AuthMode::Case),
1212 &matter,
1213 );
1214 let path = GenericPath::new(Some(1), Some(1234), None);
1215 let mut req = AccessReq::new(&accessor, path, Access::READ, &[]);
1216 req.set_target_perms(Access::RWVA);
1217
1218 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1220 new.add_target(Target {
1221 cluster: Some(2),
1222 endpoint: Some(4567),
1223 device_type: None,
1224 })
1225 .unwrap();
1226 add_acl(&matter, FAB_1, new).unwrap();
1227 assert_eq!(req.allow(), false);
1228
1229 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1231 new.add_target(Target {
1232 cluster: Some(1234),
1233 endpoint: None,
1234 device_type: None,
1235 })
1236 .unwrap();
1237 add_acl(&matter, FAB_1, new).unwrap();
1238 assert_eq!(req.allow(), true);
1239
1240 remove_all_acl(&matter, FAB_1);
1242
1243 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1245 new.add_target(Target {
1246 cluster: None,
1247 endpoint: Some(1),
1248 device_type: None,
1249 })
1250 .unwrap();
1251 add_acl(&matter, FAB_1, new).unwrap();
1252 assert_eq!(req.allow(), true);
1253
1254 remove_all_acl(&matter, FAB_1);
1256
1257 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1259 new.add_target(Target {
1260 cluster: Some(1234),
1261 endpoint: Some(1),
1262 device_type: None,
1263 })
1264 .unwrap();
1265 new.add_subject(112233).unwrap();
1266 add_acl(&matter, FAB_1, new).unwrap();
1267 assert_eq!(req.allow(), true);
1268 }
1269
1270 #[test]
1271 fn test_privilege() {
1272 let matter = test_matter();
1273
1274 add_fabric(&matter);
1276
1277 let accessor = Accessor::new(
1278 1,
1279 false,
1280 AccessorSubjects::new(112233),
1281 Some(AuthMode::Case),
1282 &matter,
1283 );
1284 let path = GenericPath::new(Some(1), Some(1234), None);
1285
1286 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1288 new.add_target(Target {
1289 cluster: Some(1234),
1290 endpoint: Some(1),
1291 device_type: None,
1292 })
1293 .unwrap();
1294 new.add_subject(112233).unwrap();
1295 add_acl(&matter, FAB_1, new).unwrap();
1296
1297 let mut req = AccessReq::new(&accessor, path.clone(), Access::WRITE, &[]);
1299 req.set_target_perms(Access::RWVA);
1300 assert_eq!(req.allow(), false);
1301
1302 let mut new = AclEntry::new(None, Privilege::ADMIN, AuthMode::Case);
1304 new.add_target(Target {
1305 cluster: Some(1234),
1306 endpoint: Some(1),
1307 device_type: None,
1308 })
1309 .unwrap();
1310 new.add_subject(112233).unwrap();
1311 add_acl(&matter, FAB_1, new).unwrap();
1312
1313 let mut req = AccessReq::new(&accessor, path, Access::WRITE, &[]);
1315 req.set_target_perms(Access::RWVA);
1316 assert_eq!(req.allow(), true);
1317 }
1318
1319 #[test]
1320 fn test_delete_for_fabric() {
1321 let matter = test_matter();
1322
1323 add_fabric(&matter);
1325
1326 add_fabric(&matter);
1328
1329 let path = GenericPath::new(Some(1), Some(1234), None);
1330 let accessor2 = Accessor::new(
1331 1,
1332 false,
1333 AccessorSubjects::new(112233),
1334 Some(AuthMode::Case),
1335 &matter,
1336 );
1337 let mut req1 = AccessReq::new(&accessor2, path.clone(), Access::READ, &[]);
1338 req1.set_target_perms(Access::RWVA);
1339 let accessor3 = Accessor::new(
1340 2,
1341 false,
1342 AccessorSubjects::new(112233),
1343 Some(AuthMode::Case),
1344 &matter,
1345 );
1346 let mut req2 = AccessReq::new(&accessor3, path, Access::READ, &[]);
1347 req2.set_target_perms(Access::RWVA);
1348
1349 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1351 new.add_subject(112233).unwrap();
1352 assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
1353
1354 let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
1356 new.add_subject(112233).unwrap();
1357 assert_eq!(add_acl(&matter, FAB_2, new).unwrap(), 0);
1358
1359 assert_eq!(req1.allow(), true);
1361 assert_eq!(req2.allow(), true);
1362 remove_all_acl(&matter, FAB_1);
1363 assert_eq!(req1.allow(), false);
1364 assert_eq!(req2.allow(), true);
1365 }
1366
1367 #[test]
1371 fn test_aux_wildcard_group_excludes_root_endpoint() {
1372 let matter = test_matter();
1373 add_fabric(&matter);
1374
1375 const GROUP_ID: u64 = 0x12AB;
1376
1377 let mut entry = AclEntry::new(None, Privilege::OPERATE, AuthMode::Group);
1379 entry.add_subject(GROUP_ID).unwrap();
1380 add_acl(&matter, FAB_1, entry).unwrap();
1381
1382 let accessor = Accessor::new(
1383 FAB_1.get(),
1384 false,
1385 AccessorSubjects::new(GROUP_ID),
1386 Some(AuthMode::Group),
1387 &matter,
1388 );
1389
1390 let ep0 = GenericPath::new(Some(0), Some(1234), None);
1391 let ep1 = GenericPath::new(Some(1), Some(1234), None);
1392
1393 for path in [ep0.clone(), ep1.clone()] {
1395 let mut req = AccessReq::new(&accessor, path, Access::WRITE, &[]);
1396 req.set_target_perms(Access::WO);
1397 assert!(req.allow());
1398 }
1399
1400 let accessor = Accessor::new(
1401 FAB_1.get(),
1402 true,
1403 AccessorSubjects::new(GROUP_ID),
1404 Some(AuthMode::Group),
1405 &matter,
1406 );
1407
1408 let mut req = AccessReq::new(&accessor, ep0.clone(), Access::WRITE, &[]);
1410 req.set_target_perms(Access::WO);
1411 assert!(!req.allow());
1412
1413 let mut req = AccessReq::new(&accessor, ep1, Access::WRITE, &[]);
1415 req.set_target_perms(Access::WO);
1416 assert!(req.allow());
1417
1418 remove_all_acl(&matter, FAB_1);
1420 let mut entry = AclEntry::new(None, Privilege::OPERATE, AuthMode::Group);
1421 entry.add_subject(GROUP_ID).unwrap();
1422 entry.add_target(Target::new(Some(0), None, None)).unwrap();
1423 add_acl(&matter, FAB_1, entry).unwrap();
1424
1425 let mut req = AccessReq::new(&accessor, ep0, Access::WRITE, &[]);
1426 req.set_target_perms(Access::WO);
1427 assert!(req.allow());
1428 }
1429}