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 subjects: AccessorSubjects,
329 auth_mode: Option<AuthMode>,
331 matter: &'a Matter<'a>,
334}
335
336impl<'a> Accessor<'a> {
337 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 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 pub const fn subjects(&self) -> &AccessorSubjects {
395 &self.subjects
396 }
397
398 pub const fn auth_mode(&self) -> Option<AuthMode> {
400 self.auth_mode
401 }
402
403 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 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 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#[derive(Debug)]
455#[cfg_attr(feature = "defmt", derive(defmt::Format))]
456pub struct AccessDesc<'a> {
457 path: GenericPath,
459 target_perms: Option<Access>,
461 operation: Access,
464 device_types: &'a [DeviceType],
468}
469
470pub struct AccessReq<'a> {
472 accessor: &'a Accessor<'a>,
474 object: AccessDesc<'a>,
476}
477
478impl<'a> AccessReq<'a> {
479 pub const fn new(accessor: &'a Accessor, path: GenericPath, operation: Access) -> Self {
487 Self::new_with_device_types(accessor, path, operation, &[])
488 }
489
490 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 pub fn accessor(&self) -> &Accessor<'_> {
512 self.accessor
513 }
514
515 pub fn operation(&self) -> Access {
517 self.object.operation
518 }
519
520 pub fn set_target_perms(&mut self, perms: Access) {
525 self.object.target_perms = Some(perms);
526 }
527
528 pub fn allow(&self) -> bool {
534 self.accessor
535 .matter
536 .with_state(|state| state.fabrics.allow(self))
537 }
538}
539
540#[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 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#[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)]
566#[cfg_attr(feature = "defmt", derive(defmt::Format))]
567#[tlvargs(start = 1)]
568pub struct AclEntry {
569 privilege: Privilege,
571 auth_mode: AuthMode,
573 subjects: Nullable<Vec<u64, MAX_SUBJECTS_PER_ACL_ENTRY>>,
575 targets: Nullable<Vec<Target, MAX_TARGETS_PER_ACL_ENTRY>>,
577 auxiliary_type: Option<AccessControlAuxiliaryTypeEnum>,
579 #[tagval(crate::im::encoding::FABRIC_INDEX_TAG)]
582 pub fab_idx: Option<NonZeroU8>,
583}
584
585impl AclEntry {
586 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 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 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 matches!(auth_mode, AccessControlEntryAuthModeEnum::PASE)
637 || 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 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 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 Err(ErrorCode::ConstraintError)?;
669 }
670
671 if matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) {
672 if subject == 0 || subject > u16::MAX as u64 {
676 Err(ErrorCode::ConstraintError)?;
677 }
678 }
679
680 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 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 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 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 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 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 pub fn auth_mode(&self) -> AuthMode {
798 self.auth_mode
799 }
800
801 pub fn subjects(&self) -> Nullable<&[u64]> {
803 Nullable::new(self.subjects.as_opt_ref().map(|v| v.as_slice()))
804 }
805
806 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 pub fn allow(&self, req: &AccessReq) -> bool {
817 self.match_accessor(req.accessor) && self.match_access_desc(&req.object)
818 }
819
820 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 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 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.is_empty() || subjects.iter().any(|s| accessor.subjects.matches(*s))
856 });
857
858 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.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 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 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 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 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 assert_eq!(req.allow(), false);
968
969 add_fabric(&matter);
971
972 let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Pase);
974 assert!(add_acl(&matter, FAB_1, new).is_err());
975
976 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 assert!(req_pase.allow());
983
984 add_fabric(&matter);
986
987 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(&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 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 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(&matter);
1029
1030 let allow_cat = 0xABCD;
1031 let disallow_cat = 0xCAFE;
1032 let v2 = 2;
1033 let v3 = 3;
1034 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 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 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 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(&matter);
1069
1070 let allow_cat = 0xABCD;
1071 let disallow_cat = 0xCAFE;
1072 let v2 = 2;
1073 let v3 = 3;
1074 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 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 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(&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 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 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 remove_all_acl(&matter, FAB_1);
1138
1139 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 remove_all_acl(&matter, FAB_1);
1152
1153 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(&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 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 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 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 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(&matter);
1220
1221 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 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 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 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}