use core::fmt::Display;
use core::num::NonZeroU8;
use core::ops::RangeInclusive;
use cfg_if::cfg_if;
use num_derive::FromPrimitive;
use crate::dm::clusters::acl::{
AccessControlAuxiliaryTypeEnum, AccessControlEntryAuthModeEnum,
AccessControlEntryPrivilegeEnum, AccessControlEntryStruct, AccessControlEntryStructBuilder,
};
use crate::dm::{Access, ClusterId, DeviceType, EndptId, NodeId, Privilege};
use crate::error::{Error, ErrorCode};
use crate::im::GenericPath;
use crate::tlv::{FromTLV, Nullable, TLVBuilderParent, TLVElement, TLVTag, TLVWrite, ToTLV, TLV};
use crate::transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC};
use crate::utils::init::{init, Init, IntoFallibleInit};
use crate::utils::storage::Vec;
use crate::Matter;
cfg_if! {
if #[cfg(feature = "max-subjects-per-acl-32")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 32;
} else if #[cfg(feature = "max-subjects-per-acl-16")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 16;
} else if #[cfg(feature = "max-subjects-per-acl-8")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 8;
} else if #[cfg(feature = "max-subjects-per-acl-7")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 7;
} else if #[cfg(feature = "max-subjects-per-acl-6")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 6;
} else if #[cfg(feature = "max-subjects-per-acl-5")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 5;
} else if #[cfg(feature = "max-subjects-per-acl-4")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 4;
} else if #[cfg(feature = "max-subjects-per-acl-3")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 3;
} else if #[cfg(feature = "max-subjects-per-acl-2")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 2;
} else if #[cfg(feature = "max-subjects-per-acl-1")] {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 1;
} else {
pub const MAX_SUBJECTS_PER_ACL_ENTRY: usize = 4;
}
}
cfg_if! {
if #[cfg(feature = "max-targets-per-acl-32")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 32;
} else if #[cfg(feature = "max-targets-per-acl-16")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 16;
} else if #[cfg(feature = "max-targets-per-acl-8")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 8;
} else if #[cfg(feature = "max-targets-per-acl-7")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 7;
} else if #[cfg(feature = "max-targets-per-acl-6")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 6;
} else if #[cfg(feature = "max-targets-per-acl-5")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 5;
} else if #[cfg(feature = "max-targets-per-acl-4")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 4;
} else if #[cfg(feature = "max-targets-per-acl-3")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 3;
} else if #[cfg(feature = "max-targets-per-acl-2")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 2;
} else if #[cfg(feature = "max-targets-per-acl-1")] {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 1;
} else {
pub const MAX_TARGETS_PER_ACL_ENTRY: usize = 3;
}
}
cfg_if! {
if #[cfg(feature = "max-acls-per-fabric-32")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 32;
} else if #[cfg(feature = "max-acls-per-fabric-16")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 16;
} else if #[cfg(feature = "max-acls-per-fabric-8")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 8;
} else if #[cfg(feature = "max-acls-per-fabric-7")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 7;
} else if #[cfg(feature = "max-acls-per-fabric-6")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 6;
} else if #[cfg(feature = "max-acls-per-fabric-5")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 5;
} else if #[cfg(feature = "max-acls-per-fabric-4")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 4;
} else if #[cfg(feature = "max-acls-per-fabric-3")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 3;
} else if #[cfg(feature = "max-acls-per-fabric-2")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 2;
} else if #[cfg(feature = "max-acls-per-fabric-1")] {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 1;
} else {
pub const MAX_ACL_ENTRIES_PER_FABRIC: usize = 4;
}
}
#[derive(FromPrimitive, Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum AuthMode {
Pase = AccessControlEntryAuthModeEnum::PASE as _,
Case = AccessControlEntryAuthModeEnum::CASE as _,
Group = AccessControlEntryAuthModeEnum::Group as _,
}
impl FromTLV<'_> for AuthMode {
fn from_tlv(t: &TLVElement) -> Result<Self, Error>
where
Self: Sized,
{
Ok(AccessControlEntryAuthModeEnum::from_tlv(t)?.into())
}
}
impl ToTLV for AuthMode {
fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
AccessControlEntryAuthModeEnum::from(*self).to_tlv(tag, &mut tw)
}
fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
TLV::u8(tag, AccessControlEntryAuthModeEnum::from(*self) as _).into_tlv_iter()
}
}
impl From<AuthMode> for AccessControlEntryAuthModeEnum {
fn from(value: AuthMode) -> Self {
match value {
AuthMode::Pase => AccessControlEntryAuthModeEnum::PASE,
AuthMode::Case => AccessControlEntryAuthModeEnum::CASE,
AuthMode::Group => AccessControlEntryAuthModeEnum::Group,
}
}
}
impl From<AccessControlEntryAuthModeEnum> for AuthMode {
fn from(value: AccessControlEntryAuthModeEnum) -> Self {
match value {
AccessControlEntryAuthModeEnum::PASE => AuthMode::Pase,
AccessControlEntryAuthModeEnum::CASE => AuthMode::Case,
AccessControlEntryAuthModeEnum::Group => AuthMode::Group,
}
}
}
const MAX_ACCESSOR_SUBJECTS: usize = 1 + MAX_CAT_IDS_PER_NOC;
pub const NOC_CAT_SUBJECT_PREFIX: u64 = 0xFFFF_FFFD_0000_0000;
pub const NOC_CAT_SUBJECT_MASK: u64 = 0xFFFF_FFFF_0000_0000;
const NOC_CAT_ID_MASK: u64 = 0xFFFF_0000;
const NOC_CAT_VERSION_MASK: u64 = 0xFFFF;
const NODE_ID_RANGE: RangeInclusive<u64> = 1..=0xFFFF_FFEF_FFFF_FFFF;
pub(crate) fn is_noc_cat(id: u64) -> bool {
((id & NOC_CAT_SUBJECT_MASK) == NOC_CAT_SUBJECT_PREFIX)
&& ((id & (NOC_CAT_ID_MASK | NOC_CAT_VERSION_MASK)) > 0)
}
fn get_noc_cat_id(id: u64) -> u64 {
(id & NOC_CAT_ID_MASK) >> 16
}
fn get_noc_cat_version(id: u64) -> u64 {
id & NOC_CAT_VERSION_MASK
}
pub fn gen_noc_cat(id: u16, version: u16) -> u32 {
((id as u32) << 16) | version as u32
}
pub(crate) fn is_node(id: u64) -> bool {
NODE_ID_RANGE.contains(&id)
}
pub struct AccessorSubjects([u64; MAX_ACCESSOR_SUBJECTS]);
impl AccessorSubjects {
pub fn new(id: u64) -> Self {
let mut a = Self(Default::default());
a.0[0] = id;
a
}
pub fn add_catid(&mut self, subject: u32) -> Result<(), Error> {
for (i, val) in self.0.iter().enumerate() {
if *val == 0 {
self.0[i] = NOC_CAT_SUBJECT_PREFIX | (subject as u64);
return Ok(());
}
}
Err(ErrorCode::ResourceExhausted.into())
}
pub fn matches(&self, acl_subject: u64) -> bool {
for v in self.0.iter() {
if *v == 0 {
continue;
}
if *v == acl_subject {
return true;
} else {
if is_noc_cat(*v)
&& is_noc_cat(acl_subject)
&& (get_noc_cat_id(*v) == get_noc_cat_id(acl_subject))
&& (get_noc_cat_version(*v) >= get_noc_cat_version(acl_subject))
{
return true;
}
}
}
false
}
}
impl Display for AccessorSubjects {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
write!(f, "[")?;
for i in self.0 {
if is_noc_cat(i) {
write!(f, "CAT({} - {})", get_noc_cat_id(i), get_noc_cat_version(i))?;
} else if i != 0 {
write!(f, "{}, ", i)?;
}
}
write!(f, "]")
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for AccessorSubjects {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "[");
for i in self.0 {
if is_noc_cat(i) {
defmt::write!(f, "CAT({} - {})", get_noc_cat_id(i), get_noc_cat_version(i));
} else if i != 0 {
defmt::write!(f, "{}, ", i);
}
}
defmt::write!(f, "]")
}
}
pub struct Accessor<'a> {
pub(crate) fab_idx: u8,
subjects: AccessorSubjects,
auth_mode: Option<AuthMode>,
matter: &'a Matter<'a>,
}
impl<'a> Accessor<'a> {
pub fn for_session(session: &Session, matter: &'a Matter<'a>) -> Self {
match session.get_session_mode() {
SessionMode::Case {
fab_idx, cat_ids, ..
} => {
let mut subject =
AccessorSubjects::new(session.get_peer_node_id().unwrap_or_default());
for i in *cat_ids {
if i != 0 {
let _ = subject.add_catid(i);
}
}
Accessor::new(fab_idx.get(), subject, Some(AuthMode::Case), matter)
}
SessionMode::Pase { fab_idx } => Accessor::new(
*fab_idx,
AccessorSubjects::new(1),
Some(AuthMode::Pase),
matter,
),
SessionMode::Group { fab_idx, group_id } => Accessor::new(
fab_idx.get(),
AccessorSubjects::new(*group_id as u64),
Some(AuthMode::Group),
matter,
),
SessionMode::PlainText => Accessor::new(0, AccessorSubjects::new(1), None, matter),
}
}
pub const fn new(
fab_idx: u8,
subjects: AccessorSubjects,
auth_mode: Option<AuthMode>,
matter: &'a Matter<'a>,
) -> Self {
Self {
fab_idx,
subjects,
auth_mode,
matter,
}
}
pub fn fab_idx(&self) -> Result<NonZeroU8, Error> {
NonZeroU8::new(self.fab_idx).ok_or(ErrorCode::UnsupportedAccess.into())
}
pub const fn subjects(&self) -> &AccessorSubjects {
&self.subjects
}
pub const fn auth_mode(&self) -> Option<AuthMode> {
self.auth_mode
}
pub fn is_endpoint_accessible(&self, endpoint_id: EndptId) -> bool {
if self.auth_mode != Some(AuthMode::Group) {
return true;
}
let group_id = self.subjects.0[0] as u16;
let Some(fab_idx) = core::num::NonZeroU8::new(self.fab_idx) else {
return false;
};
self.matter.with_state(|state| {
let Some(fabric) = state.fabrics.get(fab_idx) else {
return false;
};
fabric
.groups()
.get(group_id)
.is_some_and(|e| e.endpoints.contains(&endpoint_id))
})
}
pub fn node_id(&self) -> Option<NodeId> {
let fab_idx = NonZeroU8::new(self.fab_idx)?;
self.matter
.with_state(|state| state.fabrics.get(fab_idx).map(|fabric| fabric.node_id()))
}
pub fn peer_node_id(&self) -> Option<u64> {
if matches!(self.auth_mode, Some(AuthMode::Case)) {
let id = self.subjects.0[0];
if is_node(id) {
Some(id)
} else {
None
}
} else {
None
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AccessDesc<'a> {
path: GenericPath,
target_perms: Option<Access>,
operation: Access,
device_types: &'a [DeviceType],
}
pub struct AccessReq<'a> {
accessor: &'a Accessor<'a>,
object: AccessDesc<'a>,
}
impl<'a> AccessReq<'a> {
pub const fn new(accessor: &'a Accessor, path: GenericPath, operation: Access) -> Self {
Self::new_with_device_types(accessor, path, operation, &[])
}
pub const fn new_with_device_types(
accessor: &'a Accessor,
path: GenericPath,
operation: Access,
device_types: &'a [DeviceType],
) -> Self {
AccessReq {
accessor,
object: AccessDesc {
path,
target_perms: None,
operation,
device_types,
},
}
}
pub fn accessor(&self) -> &Accessor<'_> {
self.accessor
}
pub fn operation(&self) -> Access {
self.object.operation
}
pub fn set_target_perms(&mut self, perms: Access) {
self.object.target_perms = Some(perms);
}
pub fn allow(&self) -> bool {
self.accessor
.matter
.with_state(|state| state.fabrics.allow(self))
}
}
#[derive(FromTLV, ToTLV, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Target {
pub cluster: Option<ClusterId>,
pub endpoint: Option<EndptId>,
pub device_type: Option<u32>,
}
impl Target {
pub const fn new(
endpoint: Option<EndptId>,
cluster: Option<ClusterId>,
device_type: Option<u32>,
) -> Self {
Self {
cluster,
endpoint,
device_type,
}
}
}
#[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[tlvargs(start = 1)]
pub struct AclEntry {
privilege: Privilege,
auth_mode: AuthMode,
subjects: Nullable<Vec<u64, MAX_SUBJECTS_PER_ACL_ENTRY>>,
targets: Nullable<Vec<Target, MAX_TARGETS_PER_ACL_ENTRY>>,
auxiliary_type: Option<AccessControlAuxiliaryTypeEnum>,
#[tagval(crate::im::encoding::FABRIC_INDEX_TAG)]
pub fab_idx: Option<NonZeroU8>,
}
impl AclEntry {
pub const fn new(
fab_idx: Option<NonZeroU8>,
privilege: Privilege,
auth_mode: AuthMode,
) -> Self {
Self {
fab_idx,
privilege,
auth_mode,
subjects: Nullable::none(),
targets: Nullable::none(),
auxiliary_type: None,
}
}
pub fn init(
fab_idx: Option<NonZeroU8>,
privilege: Privilege,
auth_mode: AuthMode,
) -> impl Init<Self> {
init!(Self {
fab_idx,
privilege,
auth_mode,
subjects <- Nullable::init_none(),
targets <- Nullable::init_none(),
auxiliary_type: None,
})
}
pub fn init_with<'a>(
fab_idx: NonZeroU8,
entry: &'a AccessControlEntryStruct<'a>,
) -> impl Init<Self, Error> + 'a {
Self::init(Some(fab_idx), Privilege::empty(), AuthMode::Pase)
.into_fallible()
.chain(|e| {
let auth_mode = entry.auth_mode().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
let privilege = entry.privilege().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
let subjects = entry.subjects().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
let targets = entry.targets().map_err(|_| ErrorCode::ConstraintError)?.ok_or(ErrorCode::ConstraintError)?;
let auxiliary_type = entry.auxiliary_type().map_err(|_| ErrorCode::ConstraintError)?;
if
matches!(auth_mode, AccessControlEntryAuthModeEnum::PASE)
|| matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) && matches!(privilege, AccessControlEntryPrivilegeEnum::Administer)
{
Err(ErrorCode::ConstraintError)?;
}
e.privilege = privilege.into();
e.auth_mode = auth_mode.into();
e.auxiliary_type = auxiliary_type;
e.subjects.clear();
e.targets.clear();
if let Some(subjects) = subjects.into_option() {
for subject in subjects {
if e.subjects.is_none() {
e.subjects.reinit(Nullable::init_some(Vec::init()));
}
let esubjects = unwrap!(e.subjects.as_opt_mut());
let subject = subject?;
if matches!(auth_mode, AccessControlEntryAuthModeEnum::CASE) && !is_node(subject) && !is_noc_cat(subject) {
Err(ErrorCode::ConstraintError)?;
}
if matches!(auth_mode, AccessControlEntryAuthModeEnum::Group) {
if subject == 0 || subject > u16::MAX as u64 {
Err(ErrorCode::ConstraintError)?;
}
}
esubjects
.push(subject)
.map_err(|_| ErrorCode::BufferTooSmall)?;
}
}
if let Some(targets) = targets.into_option() {
for target in targets {
if e.targets.is_none() {
e.targets.reinit(Nullable::init_some(Vec::init()));
}
let etargets = unwrap!(e.targets.as_opt_mut());
let target = target?;
let has_endpoint = target.endpoint()?.is_some();
let has_cluster = target.cluster()?.is_some();
let has_device_type = target.device_type()?.is_some();
if (!has_endpoint && !has_cluster && !has_device_type)
|| (has_endpoint && has_device_type)
{
Err(ErrorCode::ConstraintError)?;
}
etargets
.push(Target::new(
target.endpoint()?.into_option(),
target.cluster()?.into_option(),
target.device_type()?.into_option(),
))
.map_err(|_| ErrorCode::BufferTooSmall)?;
}
}
Ok(())
})
}
pub fn read_into<P: TLVBuilderParent>(
&self,
accessing_fab_idx: u8,
fab_idx: Option<u8>,
builder: AccessControlEntryStructBuilder<P>,
) -> Result<P, Error> {
let same_fab_idx = Some(accessing_fab_idx) == fab_idx;
builder
.privilege(same_fab_idx.then(|| self.privilege.into()))?
.auth_mode(same_fab_idx.then(|| self.auth_mode.into()))?
.subjects()?
.with_some_if(same_fab_idx, |builder| {
builder.with_non_null(self.subjects(), |subjects, mut builder| {
for subject in *subjects {
builder = builder.push(subject)?;
}
builder.end()
})
})?
.targets()?
.with_some_if(same_fab_idx, |builder| {
builder.with_non_null(self.targets(), |targets, mut builder| {
for target in *targets {
builder = builder
.push()?
.cluster(Nullable::new(target.cluster))?
.endpoint(Nullable::new(target.endpoint))?
.device_type(Nullable::new(target.device_type))?
.end()?;
}
builder.end()
})
})?
.auxiliary_type(self.auxiliary_type())?
.fabric_index(fab_idx)?
.end()
}
pub fn normalize(&mut self) {
if self
.subjects
.as_opt_ref()
.map(|subjects| subjects.is_empty())
.unwrap_or(false)
{
self.subjects.clear();
}
if self
.targets
.as_opt_ref()
.map(|targets| targets.is_empty())
.unwrap_or(false)
{
self.targets.clear();
}
}
pub fn auth_mode(&self) -> AuthMode {
self.auth_mode
}
pub fn subjects(&self) -> Nullable<&[u64]> {
Nullable::new(self.subjects.as_opt_ref().map(|v| v.as_slice()))
}
pub fn targets(&self) -> Nullable<&[Target]> {
Nullable::new(self.targets.as_opt_ref().map(|v| v.as_slice()))
}
pub fn auxiliary_type(&self) -> Option<AccessControlAuxiliaryTypeEnum> {
self.auxiliary_type
}
pub fn allow(&self, req: &AccessReq) -> bool {
self.match_accessor(req.accessor) && self.match_access_desc(&req.object)
}
pub fn add_subject(&mut self, subject: u64) -> Result<(), Error> {
if self.subjects.is_none() {
self.subjects.reinit(Nullable::init_some(Vec::init()));
}
unwrap!(self.subjects.as_opt_mut())
.push(subject)
.map_err(|_| ErrorCode::ResourceExhausted.into())
}
pub fn add_subject_catid(&mut self, cat_id: u32) -> Result<(), Error> {
self.add_subject(NOC_CAT_SUBJECT_PREFIX | cat_id as u64)
}
pub fn add_target(&mut self, target: Target) -> Result<(), Error> {
if self.targets.is_none() {
self.targets.reinit(Nullable::init_some(Vec::init()));
}
unwrap!(self.targets.as_opt_mut())
.push(target)
.map_err(|_| ErrorCode::ResourceExhausted.into())
}
fn match_accessor(&self, accessor: &Accessor) -> bool {
if Some(self.auth_mode) != accessor.auth_mode {
return false;
}
let allow = self.subjects().as_opt_ref().is_none_or(|subjects| {
subjects.is_empty() || subjects.iter().any(|s| accessor.subjects.matches(*s))
});
allow
&& self
.fab_idx
.map(|fab_idx| fab_idx.get() == accessor.fab_idx)
.unwrap_or(false)
}
fn match_access_desc(&self, object: &AccessDesc) -> bool {
let allow = self.targets.as_opt_ref().is_none_or(|targets| {
targets.is_empty()
|| targets.iter().any(|t| {
let endpoint_match = t.endpoint.is_none() || t.endpoint == object.path.endpoint;
let cluster_match = t.cluster.is_none() || t.cluster == object.path.cluster;
let device_type_match = match t.device_type {
Some(dt) => object
.device_types
.iter()
.any(|endpoint_dt| endpoint_dt.dtype as u32 == dt),
None => true,
};
endpoint_match && cluster_match && device_type_match
})
});
if allow {
if let Some(access) = object.target_perms {
access.is_ok(object.operation, self.privilege)
} else {
false
}
} else {
false
}
}
}
#[cfg(test)]
#[allow(clippy::bool_assert_comparison)]
pub(crate) mod tests {
use core::num::NonZeroU8;
use crate::acl::{gen_noc_cat, AccessorSubjects};
use crate::dm::{Access, Privilege};
use crate::error::Error;
use crate::im::GenericPath;
use crate::test::test_matter;
use crate::Matter;
use super::{AccessReq, Accessor, AclEntry, AuthMode, Target};
pub(crate) const FAB_1: NonZeroU8 = match NonZeroU8::new(1) {
Some(f) => f,
None => ::core::unreachable!(),
};
pub(crate) const FAB_2: NonZeroU8 = match NonZeroU8::new(2) {
Some(f) => f,
None => ::core::unreachable!(),
};
fn add_fabric(matter: &Matter<'_>) {
matter.with_state(|state| {
state.fabrics.add_with_post_init(|_| Ok(())).unwrap();
})
}
fn add_acl(matter: &Matter<'_>, fab_idx: NonZeroU8, entry: AclEntry) -> Result<usize, Error> {
matter.with_state(|state| state.fabrics.fabric_mut(fab_idx)?.acl_add(entry))
}
fn remove_all_acl(matter: &Matter<'_>, fab_idx: NonZeroU8) {
matter.with_state(|state| state.fabrics.fabric_mut(fab_idx).unwrap().acl_remove_all())
}
#[test]
fn test_basic_empty_subject_target() {
let matter = test_matter();
let accessor = Accessor::new(
0,
AccessorSubjects::new(112233),
Some(AuthMode::Pase),
&matter,
);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut req_pase = AccessReq::new(&accessor, path, Access::READ);
req_pase.set_target_perms(Access::RWVA);
assert!(req_pase.allow());
let accessor = Accessor::new(
2,
AccessorSubjects::new(112233),
Some(AuthMode::Case),
&matter,
);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut req = AccessReq::new(&accessor, path, Access::READ);
req.set_target_perms(Access::RWVA);
assert_eq!(req.allow(), false);
add_fabric(&matter);
let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Pase);
assert!(add_acl(&matter, FAB_1, new).is_err());
let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
assert_eq!(req.allow(), false);
assert!(req_pase.allow());
add_fabric(&matter);
let new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
assert_eq!(add_acl(&matter, FAB_2, new).unwrap(), 0);
assert_eq!(req.allow(), true);
}
#[test]
fn test_subject() {
let matter = test_matter();
add_fabric(&matter);
let accessor = Accessor::new(
1,
AccessorSubjects::new(112233),
Some(AuthMode::Case),
&matter,
);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut req = AccessReq::new(&accessor, path, Access::READ);
req.set_target_perms(Access::RWVA);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject(112232).unwrap();
assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject(112233).unwrap();
assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 1);
assert_eq!(req.allow(), true);
}
#[test]
fn test_cat() {
let matter = test_matter();
add_fabric(&matter);
let allow_cat = 0xABCD;
let disallow_cat = 0xCAFE;
let v2 = 2;
let v3 = 3;
let mut subjects = AccessorSubjects::new(112233);
subjects.add_catid(gen_noc_cat(allow_cat, v2)).unwrap();
let accessor = Accessor::new(1, subjects, Some(AuthMode::Case), &matter);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut req = AccessReq::new(&accessor, path, Access::READ);
req.set_target_perms(Access::RWVA);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
.unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(allow_cat, v3)).unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_cat_version() {
let matter = test_matter();
add_fabric(&matter);
let allow_cat = 0xABCD;
let disallow_cat = 0xCAFE;
let v2 = 2;
let v3 = 3;
let mut subjects = AccessorSubjects::new(112233);
subjects.add_catid(gen_noc_cat(allow_cat, v3)).unwrap();
let accessor = Accessor::new(1, subjects, Some(AuthMode::Case), &matter);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut req = AccessReq::new(&accessor, path, Access::READ);
req.set_target_perms(Access::RWVA);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
.unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_target() {
let matter = test_matter();
add_fabric(&matter);
let accessor = Accessor::new(
1,
AccessorSubjects::new(112233),
Some(AuthMode::Case),
&matter,
);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut req = AccessReq::new(&accessor, path, Access::READ);
req.set_target_perms(Access::RWVA);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(2),
endpoint: Some(4567),
device_type: None,
})
.unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: None,
device_type: None,
})
.unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), true);
remove_all_acl(&matter, FAB_1);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: None,
endpoint: Some(1),
device_type: None,
})
.unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), true);
remove_all_acl(&matter, FAB_1);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: Some(1),
device_type: None,
})
.unwrap();
new.add_subject(112233).unwrap();
add_acl(&matter, FAB_1, new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_privilege() {
let matter = test_matter();
add_fabric(&matter);
let accessor = Accessor::new(
1,
AccessorSubjects::new(112233),
Some(AuthMode::Case),
&matter,
);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: Some(1),
device_type: None,
})
.unwrap();
new.add_subject(112233).unwrap();
add_acl(&matter, FAB_1, new).unwrap();
let mut req = AccessReq::new(&accessor, path.clone(), Access::WRITE);
req.set_target_perms(Access::RWVA);
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(None, Privilege::ADMIN, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: Some(1),
device_type: None,
})
.unwrap();
new.add_subject(112233).unwrap();
add_acl(&matter, FAB_1, new).unwrap();
let mut req = AccessReq::new(&accessor, path, Access::WRITE);
req.set_target_perms(Access::RWVA);
assert_eq!(req.allow(), true);
}
#[test]
fn test_delete_for_fabric() {
let matter = test_matter();
add_fabric(&matter);
add_fabric(&matter);
let path = GenericPath::new(Some(1), Some(1234), None);
let accessor2 = Accessor::new(
1,
AccessorSubjects::new(112233),
Some(AuthMode::Case),
&matter,
);
let mut req1 = AccessReq::new(&accessor2, path.clone(), Access::READ);
req1.set_target_perms(Access::RWVA);
let accessor3 = Accessor::new(
2,
AccessorSubjects::new(112233),
Some(AuthMode::Case),
&matter,
);
let mut req2 = AccessReq::new(&accessor3, path, Access::READ);
req2.set_target_perms(Access::RWVA);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject(112233).unwrap();
assert_eq!(add_acl(&matter, FAB_1, new).unwrap(), 0);
let mut new = AclEntry::new(None, Privilege::VIEW, AuthMode::Case);
new.add_subject(112233).unwrap();
assert_eq!(add_acl(&matter, FAB_2, new).unwrap(), 0);
assert_eq!(req1.allow(), true);
assert_eq!(req2.allow(), true);
remove_all_acl(&matter, FAB_1);
assert_eq!(req1.allow(), false);
assert_eq!(req2.allow(), true);
}
}