use core::{cell::RefCell, fmt::Display};
use crate::{
data_model::objects::{Access, ClusterId, EndptId, Privilege},
error::{Error, ErrorCode},
fabric,
interaction_model::messages::GenericPath,
tlv::{self, FromTLV, TLVElement, TLVList, TLVWriter, TagType, ToTLV},
transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC},
utils::writebuf::WriteBuf,
};
use log::error;
use num_derive::FromPrimitive;
pub const SUBJECTS_PER_ENTRY: usize = 4;
pub const TARGETS_PER_ENTRY: usize = 3;
pub const ENTRIES_PER_FABRIC: usize = 3;
#[derive(FromPrimitive, Copy, Clone, PartialEq, Debug)]
pub enum AuthMode {
Pase = 1,
Case = 2,
Group = 3,
Invalid = 4,
}
impl FromTLV<'_> for AuthMode {
fn from_tlv(t: &TLVElement) -> Result<Self, Error>
where
Self: Sized,
{
num::FromPrimitive::from_u32(t.u32()?)
.filter(|a| *a != AuthMode::Invalid)
.ok_or_else(|| ErrorCode::Invalid.into())
}
}
impl ToTLV for AuthMode {
fn to_tlv(
&self,
tw: &mut crate::tlv::TLVWriter,
tag: crate::tlv::TagType,
) -> Result<(), Error> {
match self {
AuthMode::Invalid => Ok(()),
_ => tw.u8(tag, *self as u8),
}
}
}
const MAX_ACCESSOR_SUBJECTS: usize = 1 + MAX_CAT_IDS_PER_NOC;
pub const NOC_CAT_SUBJECT_PREFIX: u64 = 0xFFFF_FFFD_0000_0000;
const NOC_CAT_ID_MASK: u64 = 0xFFFF_0000;
const NOC_CAT_VERSION_MASK: u64 = 0xFFFF;
fn is_noc_cat(id: u64) -> bool {
(id & NOC_CAT_SUBJECT_PREFIX) == NOC_CAT_SUBJECT_PREFIX
}
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 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::NoSpace.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, "]")
}
}
pub struct Accessor<'a> {
pub fab_idx: u8,
subjects: AccessorSubjects,
auth_mode: AuthMode,
acl_mgr: &'a RefCell<AclMgr>,
}
impl<'a> Accessor<'a> {
pub fn for_session(session: &Session, acl_mgr: &'a RefCell<AclMgr>) -> Self {
match session.get_session_mode() {
SessionMode::Case(c) => {
let mut subject =
AccessorSubjects::new(session.get_peer_node_id().unwrap_or_default());
for i in c.cat_ids {
if i != 0 {
let _ = subject.add_catid(i);
}
}
Accessor::new(c.fab_idx, subject, AuthMode::Case, acl_mgr)
}
SessionMode::Pase => {
Accessor::new(0, AccessorSubjects::new(1), AuthMode::Pase, acl_mgr)
}
SessionMode::PlainText => {
Accessor::new(0, AccessorSubjects::new(1), AuthMode::Invalid, acl_mgr)
}
}
}
pub const fn new(
fab_idx: u8,
subjects: AccessorSubjects,
auth_mode: AuthMode,
acl_mgr: &'a RefCell<AclMgr>,
) -> Self {
Self {
fab_idx,
subjects,
auth_mode,
acl_mgr,
}
}
}
#[derive(Debug)]
pub struct AccessDesc {
path: GenericPath,
target_perms: Option<Access>,
operation: Access,
}
pub struct AccessReq<'a> {
accessor: &'a Accessor<'a>,
object: AccessDesc,
}
impl<'a> AccessReq<'a> {
pub fn new(accessor: &'a Accessor, path: GenericPath, operation: Access) -> Self {
AccessReq {
accessor,
object: AccessDesc {
path,
target_perms: None,
operation,
},
}
}
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.acl_mgr.borrow().allow(self)
}
}
#[derive(FromTLV, ToTLV, Clone, Debug, PartialEq)]
pub struct Target {
cluster: Option<ClusterId>,
endpoint: Option<EndptId>,
device_type: Option<u32>,
}
impl Target {
pub fn new(
endpoint: Option<EndptId>,
cluster: Option<ClusterId>,
device_type: Option<u32>,
) -> Self {
Self {
cluster,
endpoint,
device_type,
}
}
}
type Subjects = [Option<u64>; SUBJECTS_PER_ENTRY];
type Targets = [Option<Target>; TARGETS_PER_ENTRY];
#[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)]
#[tlvargs(start = 1)]
pub struct AclEntry {
privilege: Privilege,
auth_mode: AuthMode,
subjects: Subjects,
targets: Targets,
#[tagval(0xFE)]
pub fab_idx: Option<u8>,
}
impl AclEntry {
pub fn new(fab_idx: u8, privilege: Privilege, auth_mode: AuthMode) -> Self {
const INIT_SUBJECTS: Option<u64> = None;
const INIT_TARGETS: Option<Target> = None;
let privilege = privilege;
Self {
fab_idx: Some(fab_idx),
privilege,
auth_mode,
subjects: [INIT_SUBJECTS; SUBJECTS_PER_ENTRY],
targets: [INIT_TARGETS; TARGETS_PER_ENTRY],
}
}
pub fn add_subject(&mut self, subject: u64) -> Result<(), Error> {
let index = self
.subjects
.iter()
.position(|s| s.is_none())
.ok_or(ErrorCode::NoSpace)?;
self.subjects[index] = Some(subject);
Ok(())
}
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> {
let index = self
.targets
.iter()
.position(|s| s.is_none())
.ok_or(ErrorCode::NoSpace)?;
self.targets[index] = Some(target);
Ok(())
}
fn match_accessor(&self, accessor: &Accessor) -> bool {
if self.auth_mode != accessor.auth_mode {
return false;
}
let mut allow = false;
let mut entries_exist = false;
for i in self.subjects.iter().flatten() {
entries_exist = true;
if accessor.subjects.matches(*i) {
allow = true;
}
}
if !entries_exist {
allow = true;
}
allow && self.fab_idx == Some(accessor.fab_idx)
}
fn match_access_desc(&self, object: &AccessDesc) -> bool {
let mut allow = false;
let mut entries_exist = false;
for t in self.targets.iter().flatten() {
entries_exist = true;
if (t.endpoint.is_none() || t.endpoint == object.path.endpoint)
&& (t.cluster.is_none() || t.cluster == object.path.cluster)
{
allow = true
}
}
if !entries_exist {
allow = true;
}
if allow {
if let Some(access) = object.target_perms {
access.is_ok(object.operation, self.privilege)
} else {
false
}
} else {
false
}
}
pub fn allow(&self, req: &AccessReq) -> bool {
self.match_accessor(req.accessor) && self.match_access_desc(&req.object)
}
}
const MAX_ACL_ENTRIES: usize = ENTRIES_PER_FABRIC * fabric::MAX_SUPPORTED_FABRICS;
type AclEntries = heapless::Vec<Option<AclEntry>, MAX_ACL_ENTRIES>;
pub struct AclMgr {
entries: AclEntries,
changed: bool,
}
impl AclMgr {
#[inline(always)]
pub const fn new() -> Self {
Self {
entries: AclEntries::new(),
changed: false,
}
}
pub fn erase_all(&mut self) -> Result<(), Error> {
self.entries.clear();
self.changed = true;
Ok(())
}
pub fn add(&mut self, entry: AclEntry) -> Result<(), Error> {
let cnt = self
.entries
.iter()
.flatten()
.filter(|a| a.fab_idx == entry.fab_idx)
.count();
if cnt >= ENTRIES_PER_FABRIC {
Err(ErrorCode::NoSpace)?;
}
let slot = self.entries.iter().position(|a| a.is_none());
if slot.is_some() || self.entries.len() < MAX_ACL_ENTRIES {
if let Some(index) = slot {
self.entries[index] = Some(entry);
} else {
self.entries
.push(Some(entry))
.map_err(|_| ErrorCode::NoSpace)
.unwrap();
}
self.changed = true;
}
Ok(())
}
pub fn edit(&mut self, index: u8, fab_idx: u8, new: AclEntry) -> Result<(), Error> {
let old = self.for_index_in_fabric(index, fab_idx)?;
*old = Some(new);
self.changed = true;
Ok(())
}
pub fn delete(&mut self, index: u8, fab_idx: u8) -> Result<(), Error> {
let old = self.for_index_in_fabric(index, fab_idx)?;
*old = None;
self.changed = true;
Ok(())
}
pub fn delete_for_fabric(&mut self, fab_idx: u8) -> Result<(), Error> {
for entry in &mut self.entries {
if entry
.as_ref()
.map(|e| e.fab_idx == Some(fab_idx))
.unwrap_or(false)
{
*entry = None;
self.changed = true;
}
}
Ok(())
}
pub fn for_each_acl<T>(&self, mut f: T) -> Result<(), Error>
where
T: FnMut(&AclEntry) -> Result<(), Error>,
{
for entry in self.entries.iter().flatten() {
f(entry)?;
}
Ok(())
}
pub fn allow(&self, req: &AccessReq) -> bool {
if req.accessor.auth_mode == AuthMode::Pase {
return true;
}
for e in self.entries.iter().flatten() {
if e.allow(req) {
return true;
}
}
error!(
"ACL Disallow for subjects {} fab idx {}",
req.accessor.subjects, req.accessor.fab_idx
);
error!("{}", self);
false
}
pub fn load(&mut self, data: &[u8]) -> Result<(), Error> {
let root = TLVList::new(data).iter().next().ok_or(ErrorCode::Invalid)?;
tlv::from_tlv(&mut self.entries, &root)?;
self.changed = false;
Ok(())
}
pub fn store<'a>(&mut self, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
if self.changed {
let mut wb = WriteBuf::new(buf);
let mut tw = TLVWriter::new(&mut wb);
self.entries
.as_slice()
.to_tlv(&mut tw, TagType::Anonymous)?;
self.changed = false;
let len = tw.get_tail();
Ok(Some(&buf[..len]))
} else {
Ok(None)
}
}
pub fn is_changed(&self) -> bool {
self.changed
}
fn for_index_in_fabric(
&mut self,
index: u8,
fab_idx: u8,
) -> Result<&mut Option<AclEntry>, Error> {
for (curr_index, entry) in self
.entries
.iter_mut()
.filter(|e| {
e.as_ref()
.filter(|e1| e1.fab_idx == Some(fab_idx))
.is_some()
})
.enumerate()
{
if curr_index == index as usize {
return Ok(entry);
}
}
Err(ErrorCode::NotFound.into())
}
}
impl core::fmt::Display for AclMgr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "ACLS: [")?;
for i in self.entries.iter().flatten() {
write!(f, " {{ {:?} }}, ", i)?;
}
write!(f, "]")
}
}
#[cfg(test)]
#[allow(clippy::bool_assert_comparison)]
mod tests {
use core::cell::RefCell;
use crate::{
acl::{gen_noc_cat, AccessorSubjects},
data_model::objects::{Access, Privilege},
interaction_model::messages::GenericPath,
};
use super::{AccessReq, Accessor, AclEntry, AclMgr, AuthMode, Target};
#[test]
fn test_basic_empty_subject_target() {
let am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &am);
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);
let new = AclEntry::new(1, Privilege::VIEW, AuthMode::Pase);
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let new = AclEntry::new(1, Privilege::VIEW, AuthMode::Case);
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_subject() {
let am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &am);
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(2, Privilege::VIEW, AuthMode::Case);
new.add_subject(112232).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_subject(112233).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_cat() {
let am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
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(2, subjects, AuthMode::Case, &am);
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(2, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
.unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(allow_cat, v3)).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_cat_version() {
let am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
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(2, subjects, AuthMode::Case, &am);
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(2, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(disallow_cat, v2))
.unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_target() {
let am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &am);
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(2, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(2),
endpoint: Some(4567),
device_type: None,
})
.unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), false);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: None,
device_type: None,
})
.unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
am.borrow_mut().erase_all().unwrap();
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: None,
endpoint: Some(1),
device_type: None,
})
.unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
am.borrow_mut().erase_all().unwrap();
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: Some(1),
device_type: None,
})
.unwrap();
new.add_subject(112233).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req.allow(), true);
}
#[test]
fn test_privilege() {
let am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &am);
let path = GenericPath::new(Some(1), Some(1234), None);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: Some(1),
device_type: None,
})
.unwrap();
new.add_subject(112233).unwrap();
am.borrow_mut().add(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(2, Privilege::ADMIN, AuthMode::Case);
new.add_target(Target {
cluster: Some(1234),
endpoint: Some(1),
device_type: None,
})
.unwrap();
new.add_subject(112233).unwrap();
am.borrow_mut().add(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 am = RefCell::new(AclMgr::new());
am.borrow_mut().erase_all().unwrap();
let path = GenericPath::new(Some(1), Some(1234), None);
let accessor2 = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &am);
let mut req2 = AccessReq::new(&accessor2, path.clone(), Access::READ);
req2.set_target_perms(Access::RWVA);
let accessor3 = Accessor::new(3, AccessorSubjects::new(112233), AuthMode::Case, &am);
let mut req3 = AccessReq::new(&accessor3, path, Access::READ);
req3.set_target_perms(Access::RWVA);
let mut new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case);
new.add_subject(112233).unwrap();
am.borrow_mut().add(new).unwrap();
let mut new = AclEntry::new(3, Privilege::VIEW, AuthMode::Case);
new.add_subject(112233).unwrap();
am.borrow_mut().add(new).unwrap();
assert_eq!(req2.allow(), true);
assert_eq!(req3.allow(), true);
am.borrow_mut().delete_for_fabric(2).unwrap();
assert_eq!(req2.allow(), false);
assert_eq!(req3.allow(), true);
}
}