diff --git a/examples/onoff_light/src/main.rs b/examples/onoff_light/src/main.rs index 3ba694b2..efa4b853 100644 --- a/examples/onoff_light/src/main.rs +++ b/examples/onoff_light/src/main.rs @@ -16,6 +16,7 @@ */ use core::pin::pin; + use std::net::UdpSocket; use embassy_futures::select::{select, select4}; @@ -78,7 +79,7 @@ fn main() -> Result<(), Error> { // e.g., an opt-level of "0" will require a several times' larger stack. // // Optimizing/lowering `rs-matter` memory consumption is an ongoing topic. - .stack_size(54 * 1024) + .stack_size(45 * 1024) .spawn(run) .unwrap(); @@ -91,9 +92,10 @@ fn run() -> Result<(), Error> { ); info!( - "Matter memory: Matter={}B, IM Buffers={}B", + "Matter memory: Matter (BSS)={}B, IM Buffers (BSS)={}B, Subscriptions (BSS)={}B", core::mem::size_of::(), - core::mem::size_of::>() + core::mem::size_of::>(), + core::mem::size_of::>() ); let matter = MATTER.uninit().init_with(Matter::init( @@ -115,8 +117,6 @@ fn run() -> Result<(), Error> { info!("IM buffers initialized"); - let mut mdns = pin!(run_mdns(&matter)); - let on_off = cluster_on_off::OnOffCluster::new(Dataver::new_rand(matter.rand())); let subscriptions = SUBSCRIPTIONS.uninit().init_with(Subscriptions::init()); @@ -128,7 +128,7 @@ fn run() -> Result<(), Error> { // All other subscription requests will be turned down with "resource exhausted" let responder = DefaultResponder::new(&matter, buffers, &subscriptions, dm_handler); info!( - "Responder memory: Responder={}B, Runner={}B", + "Responder memory: Responder (stack)={}B, Runner fut (stack)={}B", core::mem::size_of_val(&responder), core::mem::size_of_val(&responder.run::<4, 4>()) ); @@ -136,6 +136,7 @@ fn run() -> Result<(), Error> { // Run the responder with up to 4 handlers (i.e. 4 exchanges can be handled simultenously) // Clients trying to open more exchanges than the ones currently running will get "I'm busy, please try again later" let mut respond = pin!(responder.run::<4, 4>()); + //let mut respond = responder_fut(responder); // This is a sample code that simulates state changes triggered by the HAL // Changes will be properly communicated to the Matter controllers and other Matter apps (i.e. Google Home, Alexa), thanks to subscriptions @@ -156,6 +157,25 @@ fn run() -> Result<(), Error> { let socket = async_io::Async::::bind(MATTER_SOCKET_BIND_ADDR)?; // Run the Matter and mDNS transports + info!( + "Transport memory: Transport fut (stack)={}B, mDNS fut (stack)={}B", + core::mem::size_of_val(&matter.run( + &socket, + &socket, + Some(( + CommissioningData { + // TODO: Hard-coded for now + verifier: VerifierData::new_with_pw(123456, matter.rand()), + discriminator: 250, + }, + Default::default(), + )), + )), + core::mem::size_of_val(&run_mdns(&matter)) + ); + + let mut mdns = pin!(run_mdns(&matter)); + let mut transport = pin!(matter.run( &socket, &socket, @@ -171,9 +191,14 @@ fn run() -> Result<(), Error> { // NOTE: // Replace with your own persister for e.g. `no_std` environments - let psm = PSM.uninit().init_with(Psm::init()); + info!( + "Persist memory: Persist (BSS)={}B, Persist fut (stack)={}B", + core::mem::size_of::>(), + core::mem::size_of_val(&psm.run(std::env::temp_dir().join("rs-matter"), &matter)) + ); + let mut persist = pin!(psm.run(std::env::temp_dir().join("rs-matter"), &matter)); // Combine all async tasks in a single one @@ -189,6 +214,15 @@ fn run() -> Result<(), Error> { futures_lite::future::block_on(all.coalesce()) } +// #[inline(never)] +// pub fn responder_fut(responder: &'static DefaultResponder) -> Box>> +// where +// B: BufferAccess, +// T: DataModelHandler, +// { +// Box::new(responder.run::<4, 4>()) +// } + const NODE: Node<'static> = Node { id: 0, endpoints: &[ @@ -235,6 +269,7 @@ async fn run_mdns(matter: &Matter<'_>) -> Result<(), Error> { // NOTE: // Replace with your own network initialization for e.g. `no_std` environments + #[inline(never)] fn initialize_network() -> Result<(Ipv4Addr, Ipv6Addr, u32), Error> { use log::error; use nix::{net::if_::InterfaceFlags, sys::socket::SockaddrIn6}; diff --git a/rs-matter/src/acl.rs b/rs-matter/src/acl.rs index ad274102..35e5ce57 100644 --- a/rs-matter/src/acl.rs +++ b/rs-matter/src/acl.rs @@ -17,25 +17,28 @@ use core::{fmt::Display, num::NonZeroU8}; -use log::error; - use num_derive::FromPrimitive; use crate::data_model::objects::{Access, ClusterId, EndptId, Privilege}; use crate::error::{Error, ErrorCode}; -use crate::fabric; +use crate::fabric::FabricMgr; use crate::interaction_model::messages::GenericPath; -use crate::tlv::{ - EitherIter, FromTLV, Nullable, TLVElement, TLVTag, TLVWrite, TLVWriter, ToTLV, TLV, -}; +use crate::tlv::{EitherIter, FromTLV, Nullable, TLVElement, TLVTag, TLVWrite, ToTLV, TLV}; use crate::transport::session::{Session, SessionMode, MAX_CAT_IDS_PER_NOC}; use crate::utils::cell::RefCell; use crate::utils::init::{init, Init}; -use crate::utils::storage::WriteBuf; +use crate::utils::storage::Vec; -// Matter Minimum Requirements +/// Max subjects per ACL entry +// TODO: Make this configurable via a cargo feature pub const SUBJECTS_PER_ENTRY: usize = 4; + +/// Max targets per ACL entry +// TODO: Make this configurable via a cargo feature pub const TARGETS_PER_ENTRY: usize = 3; + +/// Max ACL entries per fabric +// TODO: Make this configurable via a cargo feature pub const ENTRIES_PER_FABRIC: usize = 3; // TODO: Check if this and the SessionMode can be combined into some generic data structure @@ -74,10 +77,12 @@ impl ToTLV for AuthMode { } } -/// An accessor can have as many identities: one node id and Upto MAX_CAT_IDS_PER_NOC +/// An accessor can have as many identities: one node id and up to MAX_CAT_IDS_PER_NOC const MAX_ACCESSOR_SUBJECTS: usize = 1 + MAX_CAT_IDS_PER_NOC; + /// The CAT Prefix used in Subjects 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; @@ -171,11 +176,11 @@ pub struct Accessor<'a> { /// The Authmode of this session auth_mode: AuthMode, // TODO: Is this the right place for this though, or should we just use a global-acl-handle-get - acl_mgr: &'a RefCell, + fabric_mgr: &'a RefCell, } impl<'a> Accessor<'a> { - pub fn for_session(session: &Session, acl_mgr: &'a RefCell) -> Self { + pub fn for_session(session: &Session, fabric_mgr: &'a RefCell) -> Self { match session.get_session_mode() { SessionMode::Case { fab_idx, cat_ids, .. @@ -187,14 +192,17 @@ impl<'a> Accessor<'a> { let _ = subject.add_catid(i); } } - Accessor::new(fab_idx.get(), subject, AuthMode::Case, acl_mgr) - } - SessionMode::Pase { fab_idx } => { - Accessor::new(*fab_idx, AccessorSubjects::new(1), AuthMode::Pase, acl_mgr) + Accessor::new(fab_idx.get(), subject, AuthMode::Case, fabric_mgr) } + SessionMode::Pase { fab_idx } => Accessor::new( + *fab_idx, + AccessorSubjects::new(1), + AuthMode::Pase, + fabric_mgr, + ), SessionMode::PlainText => { - Accessor::new(0, AccessorSubjects::new(1), AuthMode::Invalid, acl_mgr) + Accessor::new(0, AccessorSubjects::new(1), AuthMode::Invalid, fabric_mgr) } } } @@ -203,15 +211,23 @@ impl<'a> Accessor<'a> { fab_idx: u8, subjects: AccessorSubjects, auth_mode: AuthMode, - acl_mgr: &'a RefCell, + fabric_mgr: &'a RefCell, ) -> Self { Self { fab_idx, subjects, auth_mode, - acl_mgr, + fabric_mgr, } } + + pub fn subjects(&self) -> &AccessorSubjects { + &self.subjects + } + + pub fn auth_mode(&self) -> AuthMode { + self.auth_mode + } } #[derive(Debug)] @@ -247,6 +263,10 @@ impl<'a> AccessReq<'a> { } } + pub fn accessor(&self) -> &Accessor { + self.accessor + } + pub fn operation(&self) -> Access { self.object.operation } @@ -265,7 +285,7 @@ impl<'a> AccessReq<'a> { /// _accessor_ the necessary privileges to access the target as per its /// permissions pub fn allow(&self) -> bool { - self.accessor.acl_mgr.borrow().allow(self) + self.accessor.fabric_mgr.borrow().allow(self) } } @@ -290,23 +310,13 @@ impl Target { } } -type Subjects = [Option; SUBJECTS_PER_ENTRY]; - -type Targets = Nullable<[Option; TARGETS_PER_ENTRY]>; -impl Targets { - fn init_notnull() -> Self { - const INIT_TARGETS: Option = None; - Nullable::some([INIT_TARGETS; TARGETS_PER_ENTRY]) - } -} - #[derive(ToTLV, FromTLV, Clone, Debug, PartialEq)] #[tlvargs(start = 1)] pub struct AclEntry { privilege: Privilege, auth_mode: AuthMode, - subjects: Subjects, - targets: Targets, + subjects: Vec, + targets: Nullable>, // TODO: Instead of the direct value, we should consider GlobalElements::FabricIndex // Note that this field will always be `Some(NN)` when the entry is persisted in storage, // however, it will be `None` when the entry is coming from the other peer @@ -315,25 +325,30 @@ pub struct AclEntry { } impl AclEntry { - pub fn new(fab_idx: NonZeroU8, privilege: Privilege, auth_mode: AuthMode) -> Self { - const INIT_SUBJECTS: Option = None; + pub fn new(privilege: Privilege, auth_mode: AuthMode) -> Self { Self { - fab_idx: Some(fab_idx), + fab_idx: None, privilege, auth_mode, - subjects: [INIT_SUBJECTS; SUBJECTS_PER_ENTRY], - targets: Targets::init_notnull(), + subjects: Vec::new(), + targets: Nullable::some(Vec::new()), } } + pub fn init(privilege: Privilege, auth_mode: AuthMode) -> impl Init { + init!(Self { + fab_idx: None, + privilege, + auth_mode, + subjects <- Vec::init(), + targets <- Nullable::init_some(Vec::init()), + }) + } + 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(()) + self.subjects + .push(subject) + .map_err(|_| ErrorCode::NoSpace.into()) } pub fn add_subject_catid(&mut self, cat_id: u32) -> Result<(), Error> { @@ -342,19 +357,18 @@ impl AclEntry { pub fn add_target(&mut self, target: Target) -> Result<(), Error> { if self.targets.is_none() { - self.targets = Targets::init_notnull(); + self.targets.reinit(Nullable::init_some(Vec::init())); } - let index = self - .targets - .as_ref() - .unwrap() - .iter() - .position(|s| s.is_none()) - .ok_or(ErrorCode::NoSpace)?; - self.targets.as_mut().unwrap()[index] = Some(target); + self.targets + .as_mut() + .unwrap() + .push(target) + .map_err(|_| ErrorCode::NoSpace.into()) + } - Ok(()) + pub fn auth_mode(&self) -> AuthMode { + self.auth_mode } fn match_accessor(&self, accessor: &Accessor) -> bool { @@ -364,9 +378,9 @@ impl AclEntry { let mut allow = false; let mut entries_exist = false; - for i in self.subjects.iter().flatten() { + for s in &self.subjects { entries_exist = true; - if accessor.subjects.matches(*i) { + if accessor.subjects.matches(*s) { allow = true; } } @@ -389,7 +403,7 @@ impl AclEntry { match self.targets.as_ref() { None => allow = true, // Allow if targets are NULL Some(targets) => { - for t in targets.iter().flatten() { + for t in targets { entries_exist = true; if (t.endpoint.is_none() || t.endpoint == object.path.endpoint) && (t.cluster.is_none() || t.cluster == object.path.cluster) @@ -421,296 +435,36 @@ impl AclEntry { } } -const MAX_ACL_ENTRIES: usize = ENTRIES_PER_FABRIC * fabric::MAX_SUPPORTED_FABRICS; - -type AclEntries = crate::utils::storage::Vec, MAX_ACL_ENTRIES>; - -pub struct AclMgr { - entries: AclEntries, - changed: bool, -} - -impl Default for AclMgr { - fn default() -> Self { - Self::new() - } -} - -impl AclMgr { - /// Create a new ACL Manager - #[inline(always)] - pub const fn new() -> Self { - Self { - entries: AclEntries::new(), - changed: false, - } - } - - /// Return an in-place initializer for ACL Manager - pub fn init() -> impl Init { - init!(Self { - entries <- AclEntries::init(), - 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 { - let Some(fab_idx) = entry.fab_idx else { - // When persisting entries, the `fab_idx` should always be set - return Err(ErrorCode::Invalid.into()); - }; - - if entry.auth_mode == AuthMode::Pase { - // Reserved for future use - // TODO: Should be something that results in IMStatusCode::ConstraintError - Err(ErrorCode::Invalid)?; - } - - let cnt = self.get_index_in_fabric(MAX_ACL_ENTRIES, fab_idx); - if cnt >= ENTRIES_PER_FABRIC as u8 { - Err(ErrorCode::NoSpace)?; - } - - let slot = self.entries.iter().position(|a| a.is_none()); - - if slot.is_some() || self.entries.len() < MAX_ACL_ENTRIES { - let slot = if let Some(slot) = slot { - self.entries[slot] = Some(entry); - - slot - } else { - self.entries - .push(Some(entry)) - .map_err(|_| ErrorCode::NoSpace) - .unwrap(); - - self.entries.len() - 1 - }; - - self.changed = true; - - Ok(self.get_index_in_fabric(slot, fab_idx)) - } else { - Err(ErrorCode::NoSpace.into()) - } - } - - // Since the entries are fabric-scoped, the index is only for entries with the matching fabric index - pub fn edit(&mut self, index: u8, fab_idx: NonZeroU8, 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: NonZeroU8) -> 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: NonZeroU8) -> 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(&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 { - // PASE Sessions with no fabric index have implicit access grant, - // but only as long as the ACL list is empty - // - // As per the spec: - // The Access Control List is able to have an initial entry added because the Access Control Privilege - // Granting algorithm behaves as if, over a PASE commissioning channel during the commissioning - // phase, the following implicit Access Control Entry were present on the Commissionee (but not on - // the Commissioner): - // Access Control Cluster: { - // ACL: [ - // 0: { - // // implicit entry only; does not explicitly exist! - // FabricIndex: 0, // not fabric-specific - // Privilege: Administer, - // AuthMode: PASE, - // Subjects: [], - // Targets: [] // entire node - // } - // ], - // Extension: [] - // } - 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 entries = TLVElement::new(data).array()?.iter(); - - self.entries.clear(); - - for entry in entries { - let entry = entry?; - - self.entries - .push(Option::::from_tlv(&entry)?) - .map_err(|_| ErrorCode::NoSpace)?; - } - - self.changed = false; - - Ok(()) - } - - pub fn store<'a>(&mut self, buf: &'a mut [u8]) -> Result, Error> { - if self.changed { - let mut wb = WriteBuf::new(buf); - let mut tw = TLVWriter::new(&mut wb); - self.entries - .as_slice() - .to_tlv(&TLVTag::Anonymous, &mut tw)?; - - self.changed = false; - - let len = tw.get_tail(); - - Ok(Some(&buf[..len])) - } else { - Ok(None) - } - } - - pub fn is_changed(&self) -> bool { - self.changed - } - - /// Traverse fabric specific entries to find the index - /// - /// If the ACL Mgr has 3 entries with fabric indexes, 1, 2, 1, then the list - /// index 1 for Fabric 1 in the ACL Mgr will be the actual index 2 (starting from 0) - fn for_index_in_fabric( - &mut self, - index: u8, - fab_idx: NonZeroU8, - ) -> Result<&mut Option, Error> { - // Can't use flatten as we need to borrow the Option<> not the 'AclEntry' - 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()) - } - - /// Traverse fabric specific entries to find the index of an entry relative to its fabric. - /// - /// If the ACL Mgr has 3 entries with fabric indexes, 1, 2, 1, then the actual - /// index 2 in the ACL Mgr will be the list index 1 for Fabric 1 - fn get_index_in_fabric(&self, till_slot_index: usize, fab_idx: NonZeroU8) -> u8 { - self.entries - .iter() - .take(till_slot_index) - .flatten() - .filter(|e| e.fab_idx == Some(fab_idx)) - .count() as u8 - } -} - -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)] pub(crate) mod tests { use core::num::NonZeroU8; - use crate::{ - acl::{gen_noc_cat, AccessorSubjects}, - data_model::objects::{Access, Privilege}, - interaction_model::messages::GenericPath, - }; - + use crate::acl::{gen_noc_cat, AccessorSubjects}; + use crate::crypto::KeyPair; + use crate::data_model::objects::{Access, Privilege}; + use crate::fabric::FabricMgr; + use crate::interaction_model::messages::GenericPath; use crate::utils::cell::RefCell; + use crate::utils::rand::sys_rand; - use super::{AccessReq, Accessor, AclEntry, AclMgr, AuthMode, Target}; + use super::{AccessReq, Accessor, AclEntry, AuthMode, Target}; pub(crate) const FAB_1: NonZeroU8 = match NonZeroU8::new(1) { Some(f) => f, None => unreachable!(), }; + pub(crate) const FAB_2: NonZeroU8 = match NonZeroU8::new(2) { Some(f) => f, None => unreachable!(), }; - pub(crate) const FAB_3: NonZeroU8 = match NonZeroU8::new(3) { - Some(f) => f, - None => unreachable!(), - }; #[test] fn test_basic_empty_subject_target() { - let am = RefCell::new(AclMgr::new()); - am.borrow_mut().erase_all().unwrap(); + let fm = RefCell::new(FabricMgr::new()); - let accessor = Accessor::new(0, AccessorSubjects::new(112233), AuthMode::Pase, &am); + let accessor = Accessor::new(0, AccessorSubjects::new(112233), AuthMode::Pase, &fm); 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); @@ -718,7 +472,7 @@ pub(crate) mod tests { // Always allow for PASE sessions assert!(req_pase.allow()); - let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &am); + let accessor = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); @@ -726,50 +480,69 @@ pub(crate) mod tests { // Default deny for CASE assert_eq!(req.allow(), false); + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); + // Deny adding invalid auth mode (PASE is reserved for future) - let new = AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Pase); - assert!(am.borrow_mut().add(new).is_err()); + let new = AclEntry::new(Privilege::VIEW, AuthMode::Pase); + assert!(fm.borrow_mut().acl_add(FAB_1, new).is_err()); // Deny for fab idx mismatch - let new = AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case); - assert_eq!(am.borrow_mut().add(new).unwrap(), 0); + let new = AclEntry::new(Privilege::VIEW, AuthMode::Case); + assert_eq!(fm.borrow_mut().acl_add(FAB_1, new).unwrap(), 0); assert_eq!(req.allow(), false); // Always allow for PASE sessions assert!(req_pase.allow()); + // Add fabric with ID 2 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); + // Allow - let new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); - assert_eq!(am.borrow_mut().add(new).unwrap(), 0); + let new = AclEntry::new(Privilege::VIEW, AuthMode::Case); + assert_eq!(fm.borrow_mut().acl_add(FAB_2, new).unwrap(), 0); 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 fm = RefCell::new(FabricMgr::new()); + + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); + + let accessor = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); // Deny for subject mismatch - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject(112232).unwrap(); - assert_eq!(am.borrow_mut().add(new).unwrap(), 0); + assert_eq!(fm.borrow_mut().acl_add(FAB_1, new).unwrap(), 0); assert_eq!(req.allow(), false); // Allow for subject match - target is wildcard - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject(112233).unwrap(); - assert_eq!(am.borrow_mut().add(new).unwrap(), 1); + assert_eq!(fm.borrow_mut().acl_add(FAB_1, new).unwrap(), 1); assert_eq!(req.allow(), true); } #[test] fn test_cat() { - let am = RefCell::new(AclMgr::new()); - am.borrow_mut().erase_all().unwrap(); + let fm = RefCell::new(FabricMgr::new()); + + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); let allow_cat = 0xABCD; let disallow_cat = 0xCAFE; @@ -779,35 +552,39 @@ pub(crate) mod tests { 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 accessor = Accessor::new(1, subjects, AuthMode::Case, &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); // Deny for CAT id mismatch - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject_catid(gen_noc_cat(disallow_cat, v2)) .unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); assert_eq!(req.allow(), false); // Deny of CAT version mismatch - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject_catid(gen_noc_cat(allow_cat, v3)).unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); assert_eq!(req.allow(), false); // Allow for CAT match - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, 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 fm = RefCell::new(FabricMgr::new()); + + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); let allow_cat = 0xABCD; let disallow_cat = 0xCAFE; @@ -817,75 +594,80 @@ pub(crate) mod tests { 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 accessor = Accessor::new(1, subjects, AuthMode::Case, &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); // Deny for CAT id mismatch - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject_catid(gen_noc_cat(disallow_cat, v2)) .unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); assert_eq!(req.allow(), false); // Allow for CAT match and version more than ACL version - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject_catid(gen_noc_cat(allow_cat, v2)).unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, 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 fm = RefCell::new(FabricMgr::new()); + + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); + + let accessor = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); let path = GenericPath::new(Some(1), Some(1234), None); let mut req = AccessReq::new(&accessor, path, Access::READ); req.set_target_perms(Access::RWVA); // Deny for target mismatch - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_target(Target { cluster: Some(2), endpoint: Some(4567), device_type: None, }) .unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); assert_eq!(req.allow(), false); // Allow for cluster match - subject wildcard - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_target(Target { cluster: Some(1234), endpoint: None, device_type: None, }) .unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); assert_eq!(req.allow(), true); - // Clean Slate - am.borrow_mut().erase_all().unwrap(); + // Clean state + fm.borrow_mut().get_mut(FAB_1).unwrap().acl_remove_all(); // Allow for endpoint match - subject wildcard - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_target(Target { cluster: None, endpoint: Some(1), device_type: None, }) .unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); assert_eq!(req.allow(), true); - // Clean Slate - am.borrow_mut().erase_all().unwrap(); + // Clean state + fm.borrow_mut().get_mut(FAB_1).unwrap().acl_remove_all(); // Allow for exact match - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_target(Target { cluster: Some(1234), endpoint: Some(1), @@ -893,19 +675,24 @@ pub(crate) mod tests { }) .unwrap(); new.add_subject(112233).unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, 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 fm = RefCell::new(FabricMgr::new()); + + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); + + let accessor = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); let path = GenericPath::new(Some(1), Some(1234), None); // Create an Exact Match ACL with View privilege - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_target(Target { cluster: Some(1234), endpoint: Some(1), @@ -913,7 +700,7 @@ pub(crate) mod tests { }) .unwrap(); new.add_subject(112233).unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); // Write on an RWVA without admin access - deny let mut req = AccessReq::new(&accessor, path.clone(), Access::WRITE); @@ -921,7 +708,7 @@ pub(crate) mod tests { assert_eq!(req.allow(), false); // Create an Exact Match ACL with Admin privilege - let mut new = AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case); + let mut new = AclEntry::new(Privilege::ADMIN, AuthMode::Case); new.add_target(Target { cluster: Some(1234), endpoint: Some(1), @@ -929,7 +716,7 @@ pub(crate) mod tests { }) .unwrap(); new.add_subject(112233).unwrap(); - am.borrow_mut().add(new).unwrap(); + fm.borrow_mut().acl_add(FAB_1, new).unwrap(); // Write on an RWVA with admin access - allow let mut req = AccessReq::new(&accessor, path, Access::WRITE); @@ -939,31 +726,41 @@ pub(crate) mod tests { #[test] fn test_delete_for_fabric() { - let am = RefCell::new(AclMgr::new()); - am.borrow_mut().erase_all().unwrap(); + let fm = RefCell::new(FabricMgr::new()); + + // Add fabric with ID 1 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .unwrap(); + + // Add fabric with ID 2 + fm.borrow_mut() + .add_with_post_init(KeyPair::new(sys_rand).unwrap(), |_| Ok(())) + .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); + let accessor2 = Accessor::new(1, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let mut req1 = AccessReq::new(&accessor2, path.clone(), Access::READ); + req1.set_target_perms(Access::RWVA); + let accessor3 = Accessor::new(2, AccessorSubjects::new(112233), AuthMode::Case, &fm); + let mut req2 = AccessReq::new(&accessor3, path, 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); // Allow for subject match - target is wildcard - Fabric idx 2 - let mut new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject(112233).unwrap(); - assert_eq!(am.borrow_mut().add(new).unwrap(), 0); + assert_eq!(fm.borrow_mut().acl_add(FAB_1, new).unwrap(), 0); // Allow for subject match - target is wildcard - Fabric idx 3 - let mut new = AclEntry::new(FAB_3, Privilege::VIEW, AuthMode::Case); + let mut new = AclEntry::new(Privilege::VIEW, AuthMode::Case); new.add_subject(112233).unwrap(); - assert_eq!(am.borrow_mut().add(new).unwrap(), 0); + assert_eq!(fm.borrow_mut().acl_add(FAB_2, new).unwrap(), 0); - // Req for Fabric idx 2 gets denied, and that for Fabric idx 3 is allowed + // Req for Fabric idx 1 gets denied, and that for Fabric idx 2 is allowed + assert_eq!(req1.allow(), true); + assert_eq!(req2.allow(), true); + fm.borrow_mut().acl_remove_all(FAB_1).unwrap(); + assert_eq!(req1.allow(), false); assert_eq!(req2.allow(), true); - assert_eq!(req3.allow(), true); - am.borrow_mut().delete_for_fabric(FAB_2).unwrap(); - assert_eq!(req2.allow(), false); - assert_eq!(req3.allow(), true); } } diff --git a/rs-matter/src/core.rs b/rs-matter/src/core.rs index c68db6ce..06919039 100644 --- a/rs-matter/src/core.rs +++ b/rs-matter/src/core.rs @@ -17,7 +17,6 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use crate::acl::AclMgr; use crate::data_model::{ cluster_basic_information::BasicInfoConfig, sdm::{dev_att::DevAttDataFetcher, failsafe::FailSafe}, @@ -50,8 +49,7 @@ pub struct CommissioningData { /// The primary Matter Object pub struct Matter<'a> { - pub(crate) fabric_mgr: RefCell, - pub acl_mgr: RefCell, // Public for tests + pub fabric_mgr: RefCell, // Public for tests pub(crate) pase_mgr: RefCell, pub(crate) failsafe: RefCell, pub transport_mgr: TransportMgr<'a>, // Public for tests @@ -112,9 +110,8 @@ impl<'a> Matter<'a> { ) -> Self { Self { fabric_mgr: RefCell::new(FabricMgr::new()), - acl_mgr: RefCell::new(AclMgr::new()), pase_mgr: RefCell::new(PaseMgr::new(epoch, rand)), - failsafe: RefCell::new(FailSafe::new()), + failsafe: RefCell::new(FailSafe::new(epoch, rand)), transport_mgr: TransportMgr::new(mdns, dev_det, port, epoch, rand), persist_notification: Notification::new(), epoch, @@ -173,9 +170,8 @@ impl<'a> Matter<'a> { init!( Self { fabric_mgr <- RefCell::init(FabricMgr::init()), - acl_mgr <- RefCell::init(AclMgr::init()), pase_mgr <- RefCell::init(PaseMgr::init(epoch, rand)), - failsafe: RefCell::new(FailSafe::new()), + failsafe: RefCell::new(FailSafe::new(epoch, rand)), transport_mgr <- TransportMgr::init(mdns, dev_det, port, epoch, rand), persist_notification: Notification::new(), epoch, @@ -249,20 +245,12 @@ impl<'a> Matter<'a> { .load(data, &self.transport_mgr.mdns) } - pub fn load_acls(&self, data: &[u8]) -> Result<(), Error> { - self.acl_mgr.borrow_mut().load(data) - } - pub fn store_fabrics<'b>(&self, buf: &'b mut [u8]) -> Result, Error> { self.fabric_mgr.borrow_mut().store(buf) } - pub fn store_acls<'b>(&self, buf: &'b mut [u8]) -> Result, Error> { - self.acl_mgr.borrow_mut().store(buf) - } - - pub fn is_changed(&self) -> bool { - self.acl_mgr.borrow().is_changed() || self.fabric_mgr.borrow().is_changed() + pub fn fabrics_changed(&self) -> bool { + self.fabric_mgr.borrow().is_changed() } /// Return `true` if there is at least one commissioned fabric @@ -277,7 +265,7 @@ impl<'a> Matter<'a> { // after we receive `CommissioningComplete` on behalf of a Case session // for the fabric in question. pub fn is_commissioned(&self) -> bool { - self.fabric_mgr.borrow().used_count() > 0 + self.fabric_mgr.borrow().iter().count() > 0 } fn start_comissioning( @@ -286,7 +274,8 @@ impl<'a> Matter<'a> { discovery_capabilities: DiscoveryCapabilities, buf: &mut [u8], ) -> Result { - if !self.pase_mgr.borrow().is_pase_session_enabled() && self.fabric_mgr.borrow().is_empty() + if !self.pase_mgr.borrow().is_pase_session_enabled() + && self.fabric_mgr.borrow().iter().count() == 0 { print_pairing_code_and_qr(self.dev_det, &dev_comm, discovery_capabilities, buf)?; @@ -354,8 +343,8 @@ impl<'a> Matter<'a> { /// The default IM and SC handlers (`DataModel` and `SecureChannel`) do call this method after processing the messages. /// /// TODO: Fix the method name as it is not clear enough. Potentially revamp the whole persistence notification logic - pub fn notify_changed(&self) { - if self.is_changed() { + pub fn notify_fabrics_maybe_changed(&self) { + if self.fabrics_changed() { self.persist_notification.notify(); } } @@ -365,7 +354,7 @@ impl<'a> Matter<'a> { /// if there are changes, persist them. /// /// TODO: Fix the method name as it is not clear enough. Potentially revamp the whole persistence notification logic - pub async fn wait_changed(&self) { + pub async fn wait_fabrics_changed(&self) { self.persist_notification.wait().await } } diff --git a/rs-matter/src/data_model/core.rs b/rs-matter/src/data_model/core.rs index 64a6f60b..3b0b88b9 100644 --- a/rs-matter/src/data_model/core.rs +++ b/rs-matter/src/data_model/core.rs @@ -133,7 +133,7 @@ where } exchange.acknowledge().await?; - exchange.matter().notify_changed(); + exchange.matter().notify_fabrics_maybe_changed(); Ok(()) } diff --git a/rs-matter/src/data_model/objects/cluster.rs b/rs-matter/src/data_model/objects/cluster.rs index e5575aca..84463aee 100644 --- a/rs-matter/src/data_model/objects/cluster.rs +++ b/rs-matter/src/data_model/objects/cluster.rs @@ -90,7 +90,7 @@ impl<'a> AttrDetails<'a> { endpoint: Some(self.endpoint_id), cluster: Some(self.cluster_id), attr: Some(self.attr_id), - list_index: self.list_index, + list_index: self.list_index.clone(), ..Default::default() } } diff --git a/rs-matter/src/data_model/objects/node.rs b/rs-matter/src/data_model/objects/node.rs index 4b27fa09..5d092e30 100644 --- a/rs-matter/src/data_model/objects/node.rs +++ b/rs-matter/src/data_model/objects/node.rs @@ -114,7 +114,7 @@ impl<'a> Node<'a> { endpoint_id: ep.id, cluster_id: cl.id, attr_id: attr.id, - list_index: path.list_index, + list_index: path.list_index.clone(), fab_idx: accessor.fab_idx, fab_filter: fabric_filtered, dataver, @@ -204,7 +204,7 @@ impl<'a> Node<'a> { endpoint_id: ep.id, cluster_id: cl.id, attr_id: attr.id, - list_index: attr_data.path.list_index, + list_index: attr_data.path.list_index.clone(), fab_idx: accessor.fab_idx, fab_filter: false, dataver: attr_data.data_ver, diff --git a/rs-matter/src/data_model/sdm/admin_commissioning.rs b/rs-matter/src/data_model/sdm/admin_commissioning.rs index a5cc825a..492b343f 100644 --- a/rs-matter/src/data_model/sdm/admin_commissioning.rs +++ b/rs-matter/src/data_model/sdm/admin_commissioning.rs @@ -37,7 +37,7 @@ pub enum WindowStatus { BasicWindowOpen = 2, } -#[derive(Copy, Clone, Debug, FromRepr, EnumDiscriminants)] +#[derive(Clone, Debug, FromRepr, EnumDiscriminants)] #[repr(u16)] pub enum Attributes { WindowStatus(AttrType) = 0, diff --git a/rs-matter/src/data_model/sdm/failsafe.rs b/rs-matter/src/data_model/sdm/failsafe.rs index 449428c0..7cab3a20 100644 --- a/rs-matter/src/data_model/sdm/failsafe.rs +++ b/rs-matter/src/data_model/sdm/failsafe.rs @@ -16,27 +16,44 @@ */ use core::num::NonZeroU8; +use core::time::Duration; + +use bitflags::bitflags; -use crate::{ - error::{Error, ErrorCode}, - transport::session::SessionMode, -}; use log::error; -#[derive(PartialEq, Clone)] -#[allow(dead_code)] -#[allow(clippy::enum_variant_names)] -enum NocState { - NocNotRecvd, - // This is the local fabric index - AddNocRecvd(NonZeroU8), - UpdateNocRecvd(NonZeroU8), +use crate::cert::{CertRef, MAX_CERT_TLV_LEN}; +use crate::crypto::KeyPair; +use crate::error::{Error, ErrorCode}; +use crate::fabric::FabricMgr; +use crate::interaction_model::core::IMStatusCode; +use crate::mdns::Mdns; +use crate::tlv::TLVElement; +use crate::transport::session::SessionMode; +use crate::utils::cell::RefCell; +use crate::utils::epoch::Epoch; +use crate::utils::init::{init, Init}; +use crate::utils::rand::Rand; +use crate::utils::storage::Vec; + +bitflags! { + #[repr(transparent)] + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct NocFlags: u8 { + const ADD_CSR_REQ_RECVD = 0x01; + const UPDATE_CSR_REQ_RECVD = 0x02; + const ADD_ROOT_CERT_RECVD = 0x04; + const ADD_NOC_RECVD = 0x08; + const UPDATE_NOC_RECVD = 0x10; + } } #[derive(PartialEq)] pub struct ArmedCtx { - timeout: u16, - noc_state: NocState, + armed_at: Duration, + timeout_secs: u16, + fab_idx: u8, + flags: NocFlags, } #[derive(PartialEq)] @@ -45,108 +62,378 @@ pub enum State { Armed(ArmedCtx), } +pub enum IMError { + Error(Error), + Status(IMStatusCode), +} + +impl From for IMError { + fn from(e: Error) -> Self { + IMError::Error(e) + } +} + +impl From for IMError { + fn from(e: IMStatusCode) -> Self { + IMError::Status(e) + } +} + pub struct FailSafe { state: State, + key_pair: Option, + root_ca: Vec, + epoch: Epoch, + rand: Rand, } impl FailSafe { #[inline(always)] - pub const fn new() -> Self { - Self { state: State::Idle } + pub const fn new(epoch: Epoch, rand: Rand) -> Self { + Self { + state: State::Idle, + key_pair: None, + root_ca: Vec::new(), + epoch, + rand, + } } - pub fn arm(&mut self, timeout: u16, session_mode: SessionMode) -> Result<(), Error> { - match &mut self.state { - State::Idle => { - self.state = State::Armed(ArmedCtx { - timeout, - noc_state: NocState::NocNotRecvd, - }) - } - State::Armed(c) => { - match c.noc_state { - NocState::NocNotRecvd => (), - NocState::AddNocRecvd(fab_idx) | NocState::UpdateNocRecvd(fab_idx) => { - if let Some(sess_fab_idx) = NonZeroU8::new(session_mode.fab_idx()) { - if sess_fab_idx != fab_idx { - error!("Received Fail-Safe Re-arm with a different fabric index from a previous Add/Update NOC"); - Err(ErrorCode::Invalid)?; - } - } else { - error!("Received Fail-Safe Re-arm from a session that does not have a fabric index"); - Err(ErrorCode::Invalid)?; - } - } - } + pub fn init(epoch: Epoch, rand: Rand) -> impl Init { + init!(Self { + state: State::Idle, + key_pair: None, + root_ca <- Vec::init(), + epoch, + rand, + }) + } - // re-arm - c.timeout = timeout; + pub fn arm(&mut self, timeout_secs: u16, session_mode: &SessionMode) -> Result<(), Error> { + self.update_state_timeout(); + + if matches!(self.state, State::Idle) { + if matches!(session_mode, SessionMode::PlainText) { + // Only PASE and CASE sessions supported + return Err(ErrorCode::GennCommInvalidAuthentication)?; } + + self.state = State::Armed(ArmedCtx { + armed_at: (self.epoch)(), + timeout_secs, + fab_idx: session_mode.fab_idx(), + flags: NocFlags::empty(), + }); + + return Ok(()); } + + // Re-arm + + self.check_state( + session_mode, + NocFlags::empty(), + NocFlags::empty(), + NocFlags::empty(), + )?; + + let State::Armed(ctx) = &mut self.state else { + // Impossible, as we checked for Idle above + unreachable!(); + }; + + ctx.armed_at = (self.epoch)(); + ctx.timeout_secs = timeout_secs; + Ok(()) } - pub fn disarm(&mut self, session_mode: SessionMode) -> Result<(), Error> { - match &mut self.state { - State::Idle => { - error!("Received Fail-Safe Disarm without it being armed"); - Err(ErrorCode::Invalid)?; - } - State::Armed(c) => { - match c.noc_state { - NocState::NocNotRecvd => { - error!("Received Fail-Safe Disarm, yet the failsafe has not received Add/Update NOC first"); - Err(ErrorCode::Invalid)?; - } - NocState::AddNocRecvd(fab_idx) | NocState::UpdateNocRecvd(fab_idx) => { - if let Some(sess_fab_idx) = NonZeroU8::new(session_mode.fab_idx()) { - if sess_fab_idx != fab_idx { - error!("Received disarm with different fabric index from a previous Add/Update NOC"); - Err(ErrorCode::Invalid)?; - } - } else { - error!( - "Received disarm from a session that does not have a fabric index" - ); - Err(ErrorCode::Invalid)?; - } - } - } - self.state = State::Idle; - } + pub fn disarm(&mut self, session_mode: &SessionMode) -> Result<(), Error> { + self.update_state_timeout(); + + if matches!(self.state, State::Idle) { + error!("Received Fail-Safe Disarm without it being armed"); + return Err(ErrorCode::ConstraintError)?; } + + // Has to be a CASE session + Self::get_case_fab_idx(session_mode)?; + + self.check_state( + session_mode, + NocFlags::empty(), + NocFlags::empty(), + NocFlags::empty(), + )?; + self.state = State::Idle; + Ok(()) } - pub fn is_armed(&self) -> bool { - self.state != State::Idle + pub fn add_trusted_root_cert( + &mut self, + session_mode: &SessionMode, + root_ca: &[u8], + ) -> Result<(), Error> { + self.update_state_timeout(); + + self.check_state( + session_mode, + NocFlags::empty(), + NocFlags::ADD_ROOT_CERT_RECVD, + NocFlags::ADD_ROOT_CERT_RECVD, + )?; + + self.root_ca.clear(); + self.root_ca + .extend_from_slice(root_ca) + .map_err(|_| ErrorCode::InvalidCommand)?; + + self.add_flags(NocFlags::ADD_ROOT_CERT_RECVD); + + Ok(()) } - pub fn record_add_noc(&mut self, fabric_index: NonZeroU8) -> Result<(), Error> { - match &mut self.state { - State::Idle => Err(ErrorCode::Invalid.into()), - State::Armed(c) => { - if c.noc_state == NocState::NocNotRecvd { - c.noc_state = NocState::AddNocRecvd(fabric_index); - Ok(()) + pub fn add_csr_req(&mut self, session_mode: &SessionMode) -> Result<&KeyPair, Error> { + self.update_state_timeout(); + + self.check_state( + session_mode, + NocFlags::empty(), + NocFlags::ADD_CSR_REQ_RECVD | NocFlags::UPDATE_CSR_REQ_RECVD, + NocFlags::ADD_CSR_REQ_RECVD, + )?; + + self.key_pair = Some(KeyPair::new(self.rand)?); + + self.add_flags(NocFlags::ADD_CSR_REQ_RECVD); + + Ok(self.key_pair.as_ref().unwrap()) + } + + pub fn update_csr_req(&mut self, session_mode: &SessionMode) -> Result<&KeyPair, Error> { + self.update_state_timeout(); + + // Must be a CASE session + Self::get_case_fab_idx(session_mode)?; + + self.check_state( + session_mode, + NocFlags::empty(), + NocFlags::ADD_CSR_REQ_RECVD | NocFlags::UPDATE_CSR_REQ_RECVD, + NocFlags::UPDATE_CSR_REQ_RECVD, + )?; + + self.key_pair = Some(KeyPair::new(self.rand)?); + + self.add_flags(NocFlags::UPDATE_CSR_REQ_RECVD); + + Ok(self.key_pair.as_ref().unwrap()) + } + + #[allow(clippy::too_many_arguments)] + pub fn update_noc( + &mut self, + session_mode: &SessionMode, + fabric_mgr: &RefCell, + vendor_id: u16, + icac: Option<&[u8]>, + noc: &[u8], + ipk: &[u8], + buf: &mut [u8], + mdns: &dyn Mdns, + ) -> Result<(), Error> { + self.update_state_timeout(); + + let fab_idx = Self::get_case_fab_idx(session_mode)?; + + self.check_state( + session_mode, + NocFlags::ADD_ROOT_CERT_RECVD | NocFlags::UPDATE_CSR_REQ_RECVD, + NocFlags::ADD_NOC_RECVD | NocFlags::ADD_CSR_REQ_RECVD | NocFlags::UPDATE_NOC_RECVD, + NocFlags::UPDATE_NOC_RECVD, + )?; + + Self::validate_certs( + &CertRef::new(TLVElement::new(noc)), + icac.map(|icac| CertRef::new(TLVElement::new(icac))) + .as_ref(), + &CertRef::new(TLVElement::new(&self.root_ca)), + buf, + )?; + + fabric_mgr.borrow_mut().update( + fab_idx, + self.key_pair.take().unwrap(), + &self.root_ca, + noc, + icac.unwrap_or(&[]), + ipk, + vendor_id, + mdns, + )?; + + self.add_flags(NocFlags::ADD_NOC_RECVD); + + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + pub fn add_noc( + &mut self, + fabric_mgr: &RefCell, + session_mode: &SessionMode, + vendor_id: u16, + icac: Option<&[u8]>, + noc: &[u8], + ipk: &[u8], + case_admin_subject: u64, + buf: &mut [u8], + mdns: &dyn Mdns, + ) -> Result { + self.update_state_timeout(); + + self.check_state( + session_mode, + NocFlags::ADD_ROOT_CERT_RECVD | NocFlags::ADD_CSR_REQ_RECVD, + NocFlags::ADD_NOC_RECVD | NocFlags::UPDATE_CSR_REQ_RECVD | NocFlags::UPDATE_NOC_RECVD, + NocFlags::ADD_NOC_RECVD, + )?; + + Self::validate_certs( + &CertRef::new(TLVElement::new(noc)), + icac.map(|icac| CertRef::new(TLVElement::new(icac))) + .as_ref(), + &CertRef::new(TLVElement::new(&self.root_ca)), + buf, + )?; + + let fab_idx = fabric_mgr + .borrow_mut() + .add( + self.key_pair.take().unwrap(), + &self.root_ca, + noc, + icac.unwrap_or(&[]), + ipk, + vendor_id, + case_admin_subject, + mdns, + ) + .map_err(|e| { + if e.code() == ErrorCode::NoSpace { + ErrorCode::NocFabricTableFull.into() } else { - Err(ErrorCode::Invalid.into()) + e + } + })? + .fab_idx(); + + let State::Armed(ctx) = &mut self.state else { + // Impossible to be in any other state because otherwise + // check_state would have failed + unreachable!(); + }; + + ctx.fab_idx = fab_idx.get(); + self.add_flags(NocFlags::ADD_NOC_RECVD); + + Ok(fab_idx) + } + + fn validate_certs( + noc: &CertRef, + icac: Option<&CertRef>, + root: &CertRef, + buf: &mut [u8], + ) -> Result<(), Error> { + let mut verifier = noc.verify_chain_start(); + + if let Some(icac) = icac { + // If ICAC is present handle it + verifier = verifier.add_cert(icac, buf)?; + } + + verifier.add_cert(root, buf)?.finalise(buf) + } + + fn get_case_fab_idx(session_mode: &SessionMode) -> Result { + if let SessionMode::Case { fab_idx, .. } = session_mode { + Ok(*fab_idx) + } else { + // Only CASE session supported + Err(ErrorCode::GennCommInvalidAuthentication.into()) + } + } + + fn check_state( + &self, + session_mode: &SessionMode, + present: NocFlags, + absent: NocFlags, + op: NocFlags, + ) -> Result<(), Error> { + if let State::Armed(ctx) = &self.state { + if matches!(session_mode, SessionMode::PlainText) { + // Session is plain text + Err(ErrorCode::GennCommInvalidAuthentication)?; + } + + if ctx.fab_idx != session_mode.fab_idx() { + // Fabric index does not match + Err(ErrorCode::NocInvalidFabricIndex)?; + } + + if !ctx.flags.contains(present) { + // State is not what is expected for that concrete command + + if op == NocFlags::ADD_NOC_RECVD + && !ctx.flags.contains(NocFlags::UPDATE_CSR_REQ_RECVD) + || op == NocFlags::UPDATE_NOC_RECVD + && !ctx.flags.contains(NocFlags::UPDATE_CSR_REQ_RECVD) + { + // Return a more concrete error if the problem is that the CSR request is missing + Err(ErrorCode::NocMissingCsr)?; } + + Err(ErrorCode::ConstraintError)?; } + + if !ctx.flags.intersection(absent).is_empty() { + // State is not what is expected for that concrete command + + if op == NocFlags::ADD_NOC_RECVD + && ctx.flags.contains(NocFlags::UPDATE_CSR_REQ_RECVD) + || op == NocFlags::UPDATE_NOC_RECVD + && ctx.flags.contains(NocFlags::ADD_CSR_REQ_RECVD) + { + // Return a more concrete error if the problem is an add/update NOC mismatch + Err(ErrorCode::NocFabricConflict)?; + } + + Err(ErrorCode::ConstraintError)?; + } + } else { + // Fail-safe is not armed + Err(ErrorCode::FailSafeRequired)?; } + + Ok(()) } - pub fn allow_noc_change(&self) -> Result { - let allow = match &self.state { - State::Idle => false, - State::Armed(c) => c.noc_state == NocState::NocNotRecvd, - }; - Ok(allow) + fn add_flags(&mut self, flags: NocFlags) { + match &mut self.state { + State::Armed(ctx) => ctx.flags |= flags, + _ => panic!("Not armed"), + } } -} -impl Default for FailSafe { - fn default() -> Self { - Self::new() + fn update_state_timeout(&mut self) { + if let State::Armed(ctx) = &mut self.state { + let now = (self.epoch)(); + if now >= ctx.armed_at + Duration::from_secs(ctx.timeout_secs as u64) { + self.state = State::Idle; + } + } } } diff --git a/rs-matter/src/data_model/sdm/general_commissioning.rs b/rs-matter/src/data_model/sdm/general_commissioning.rs index 30befea1..7a84484c 100644 --- a/rs-matter/src/data_model/sdm/general_commissioning.rs +++ b/rs-matter/src/data_model/sdm/general_commissioning.rs @@ -24,7 +24,6 @@ use strum::{EnumDiscriminants, FromRepr}; use crate::data_model::objects::*; use crate::tlv::{FromTLV, TLVElement, ToTLV, Utf8Str}; use crate::transport::exchange::Exchange; -use crate::transport::session::SessionMode; use crate::{attribute_enum, cmd_enter}; use crate::{command_enum, error::*}; @@ -56,13 +55,66 @@ pub enum RespCommands { CommissioningCompleteResp = 0x05, } -#[derive(FromTLV, ToTLV)] +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] #[tlvargs(lifetime = "'a")] struct CommonResponse<'a> { error_code: u8, debug_txt: Utf8Str<'a>, } +impl CommissioningErrorEnum { + fn map(result: Result<(), Error>) -> Result { + match result { + Ok(()) => Ok(CommissioningErrorEnum::OK), + Err(err) => match err.code() { + ErrorCode::Busy | ErrorCode::NocInvalidFabricIndex => { + Ok(CommissioningErrorEnum::BusyWithOtherAdmin) + } + ErrorCode::GennCommInvalidAuthentication => { + Ok(CommissioningErrorEnum::InvalidAuthentication) + } + ErrorCode::FailSafeRequired => Ok(CommissioningErrorEnum::NoFailSafe), + _ => Err(err), + }, + } + } +} + +#[derive(Debug, FromTLV, ToTLV, Eq, PartialEq, Hash)] +struct FailSafeParams { + expiry_len: u16, + bread_crumb: u64, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +pub struct BasicCommissioningInfo { + pub expiry_len: u16, + pub max_cmltv_failsafe_secs: u16, +} + +impl BasicCommissioningInfo { + pub const fn new() -> Self { + // TODO: Arch-Specific + Self { + expiry_len: 120, + max_cmltv_failsafe_secs: 120, + } + } +} + +impl Default for BasicCommissioningInfo { + fn default() -> Self { + BasicCommissioningInfo::new() + } +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +#[tlvargs(lifetime = "'a")] +struct RegulatoryConfig<'a> { + #[tagval(1)] + country_code: Utf8Str<'a>, +} + pub const CLUSTER: Cluster<'static> = Cluster { id: ID as _, feature_map: 0, @@ -102,34 +154,6 @@ pub const CLUSTER: Cluster<'static> = Cluster { ], }; -#[derive(FromTLV, ToTLV)] -struct FailSafeParams { - expiry_len: u16, - bread_crumb: u64, -} - -#[derive(FromTLV, ToTLV, Debug, Clone)] -pub struct BasicCommissioningInfo { - pub expiry_len: u16, - pub max_cmltv_failsafe_secs: u16, -} - -impl BasicCommissioningInfo { - pub const fn new() -> Self { - // TODO: Arch-Specific - Self { - expiry_len: 120, - max_cmltv_failsafe_secs: 120, - } - } -} - -impl Default for BasicCommissioningInfo { - fn default() -> Self { - BasicCommissioningInfo::new() - } -} - #[derive(Debug, Clone)] pub struct GenCommCluster { data_ver: Dataver, @@ -215,25 +239,19 @@ impl GenCommCluster { ) -> Result<(), Error> { cmd_enter!("ARM Fail Safe"); - let p = FailSafeParams::from_tlv(data)?; - - let status = if exchange - .matter() - .failsafe - .borrow_mut() - .arm( - p.expiry_len, - exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?, - ) - .is_err() - { - CommissioningErrorEnum::BusyWithOtherAdmin as u8 - } else { - CommissioningErrorEnum::OK as u8 - }; + let p = FailSafeParams::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received fail safe params: {:?}", p); + + let status = CommissioningErrorEnum::map(exchange.with_session(|sess| { + exchange + .matter() + .failsafe + .borrow_mut() + .arm(p.expiry_len, sess.get_session_mode()) + }))?; let cmd_data = CommonResponse { - error_code: status, + error_code: status as _, debug_txt: "", }; @@ -251,14 +269,9 @@ impl GenCommCluster { encoder: CmdDataEncoder, ) -> Result<(), Error> { cmd_enter!("Set Regulatory Config"); - let country_code = data - .r#struct() - .map_err(|_| ErrorCode::InvalidCommand)? - .find_ctx(1) - .map_err(|_| ErrorCode::InvalidCommand)? - .utf8() - .map_err(|_| ErrorCode::InvalidCommand)?; - info!("Received country code: {}", country_code); + + let cfg = RegulatoryConfig::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received reg cfg: {:?}", cfg); let cmd_data = CommonResponse { error_code: 0, @@ -278,29 +291,17 @@ impl GenCommCluster { encoder: CmdDataEncoder, ) -> Result<(), Error> { cmd_enter!("Commissioning Complete"); - let mut status: u8 = CommissioningErrorEnum::OK as u8; - - // Has to be a Case Session - if !exchange - .with_session(|sess| Ok(matches!(sess.get_session_mode(), SessionMode::Case { .. })))? - { - status = CommissioningErrorEnum::InvalidAuthentication as u8; - } - // AddNOC or UpdateNOC must have happened, and that too for the same fabric - // scope that is for this session - if exchange - .matter() - .failsafe - .borrow_mut() - .disarm(exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))?) - .is_err() - { - status = CommissioningErrorEnum::InvalidAuthentication as u8; - } + let status = CommissioningErrorEnum::map(exchange.with_session(|sess| { + exchange + .matter() + .failsafe + .borrow_mut() + .disarm(sess.get_session_mode()) + }))?; let cmd_data = CommonResponse { - error_code: status, + error_code: status as _, debug_txt: "", }; diff --git a/rs-matter/src/data_model/sdm/noc.rs b/rs-matter/src/data_model/sdm/noc.rs index f3a13730..eb269f31 100644 --- a/rs-matter/src/data_model/sdm/noc.rs +++ b/rs-matter/src/data_model/sdm/noc.rs @@ -23,19 +23,17 @@ use log::{error, info, warn}; use strum::{EnumDiscriminants, FromRepr}; -use crate::acl::{AclEntry, AuthMode}; -use crate::cert::{CertRef, MAX_CERT_TLV_LEN}; +use crate::cert::CertRef; use crate::crypto::{self, KeyPair}; use crate::data_model::objects::*; use crate::data_model::sdm::dev_att; -use crate::fabric::{Fabric, MAX_SUPPORTED_FABRICS}; -use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, TLVWriter, ToTLV, UtfStr}; +use crate::fabric::MAX_SUPPORTED_FABRICS; +use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, ToTLV, UtfStr}; use crate::transport::exchange::Exchange; use crate::transport::session::SessionMode; -use crate::utils::epoch::Epoch; use crate::utils::init::InitMaybeUninit; use crate::utils::storage::WriteBuf; -use crate::{attribute_enum, cmd_enter, command_enum, error::*}; +use crate::{alloc, attribute_enum, cmd_enter, command_enum, error::*}; use super::dev_att::{DataType, DevAttDataFetcher}; @@ -43,38 +41,21 @@ use super::dev_att::{DataType, DevAttDataFetcher}; #[derive(Clone, Copy)] #[allow(dead_code)] -enum NocStatus { +pub enum NocStatus { Ok = 0, InvalidPublicKey = 1, InvalidNodeOpId = 2, InvalidNOC = 3, MissingCsr = 4, TableFull = 5, - MissingAcl = 6, - MissingIpk = 7, - InsufficientPrivlege = 8, + InvalidAdminSubject = 6, + Reserved1 = 7, + Reserved2 = 8, FabricConflict = 9, LabelConflict = 10, InvalidFabricIndex = 11, } -enum NocError { - Status(NocStatus), - Error(Error), -} - -impl From for NocError { - fn from(value: NocStatus) -> Self { - Self::Status(value) - } -} - -impl From for NocError { - fn from(value: Error) -> Self { - Self::Error(value) - } -} - pub const ID: u32 = 0x003E; #[derive(FromRepr)] @@ -112,6 +93,67 @@ pub enum Attributes { attribute_enum!(Attributes); +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +#[tlvargs(lifetime = "'a")] +struct NocResp<'a> { + status_code: u8, + fab_idx: u8, + debug_txt: UtfStr<'a>, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +#[tlvargs(lifetime = "'a")] +struct AddNocReq<'a> { + noc_value: OctetStr<'a>, + icac_value: Option>, + ipk_value: OctetStr<'a>, + case_admin_subject: u64, + vendor_id: u16, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +#[tlvargs(lifetime = "'a")] +struct CsrReq<'a> { + nonce: OctetStr<'a>, + for_update_noc: Option, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +#[tlvargs(lifetime = "'a")] +struct CommonReq<'a> { + str: OctetStr<'a>, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +#[tlvargs(lifetime = "'a")] +struct UpdateFabricLabelReq<'a> { + label: UtfStr<'a>, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +struct CertChainReq { + cert_type: u8, +} + +#[derive(Debug, Clone, FromTLV, ToTLV, Eq, PartialEq, Hash)] +struct RemoveFabricReq { + fab_idx: NonZeroU8, +} + +impl NocStatus { + fn map(result: Result<(), Error>) -> Result { + match result { + Ok(()) => Ok(NocStatus::Ok), + Err(err) => match err.code() { + ErrorCode::NocFabricTableFull => Ok(NocStatus::TableFull), + ErrorCode::NocInvalidFabricIndex => Ok(NocStatus::InvalidFabricIndex), + ErrorCode::ConstraintError => Ok(NocStatus::MissingCsr), + _ => Err(err), + }, + } + } +} + pub const CLUSTER: Cluster<'static> = Cluster { id: ID as _, feature_map: 0, @@ -150,59 +192,6 @@ pub const CLUSTER: Cluster<'static> = Cluster { ], }; -pub struct NocData { - pub key_pair: KeyPair, - pub root_ca: crate::utils::storage::Vec, -} - -impl NocData { - pub fn new(key_pair: KeyPair) -> Self { - Self { - key_pair, - root_ca: crate::utils::storage::Vec::new(), - } - } -} - -#[derive(ToTLV)] -struct NocResp<'a> { - status_code: u8, - fab_idx: u8, - debug_txt: UtfStr<'a>, -} - -#[derive(FromTLV)] -#[tlvargs(lifetime = "'a")] -struct AddNocReq<'a> { - noc_value: OctetStr<'a>, - icac_value: Option>, - ipk_value: OctetStr<'a>, - case_admin_subject: u64, - vendor_id: u16, -} - -#[derive(FromTLV)] -#[tlvargs(lifetime = "'a")] -struct CommonReq<'a> { - str: OctetStr<'a>, -} - -#[derive(FromTLV)] -#[tlvargs(lifetime = "'a")] -struct UpdateFabricLabelReq<'a> { - label: UtfStr<'a>, -} - -#[derive(FromTLV)] -struct CertChainReq { - cert_type: u8, -} - -#[derive(FromTLV)] -struct RemoveFabricReq { - fab_idx: NonZeroU8, -} - #[derive(Debug, Clone)] pub struct NocCluster { data_ver: Dataver, @@ -230,28 +219,26 @@ impl NocCluster { Attributes::CurrentFabricIndex(codec) => codec.encode(writer, attr.fab_idx), Attributes::Fabrics(_) => { writer.start_array(&AttrDataWriter::TAG)?; - exchange - .matter() - .fabric_mgr - .borrow() - .for_each(|entry, fab_idx| { - if !attr.fab_filter || attr.fab_idx == fab_idx.get() { - let root_ca_cert = entry.get_root_ca()?; - - entry - .get_fabric_desc(fab_idx, &root_ca_cert)? - .to_tlv(&TLVTag::Anonymous, &mut *writer)?; - } - - Ok(()) - })?; + for fabric in exchange.matter().fabric_mgr.borrow().iter() { + if (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get()) + && !fabric.root_ca().is_empty() + { + // Empty `root_ca` might happen in the E2E tests + let root_ca_cert = CertRef::new(TLVElement::new(fabric.root_ca())); + + fabric + .descriptor(&root_ca_cert)? + .to_tlv(&TLVTag::Anonymous, &mut *writer)?; + } + } + writer.end_container()?; writer.complete() } Attributes::CommissionedFabrics(codec) => codec.encode( writer, - exchange.matter().fabric_mgr.borrow().used_count() as _, + exchange.matter().fabric_mgr.borrow().iter().count() as _, ), _ => { error!("Attribute not supported: this shouldn't happen"); @@ -292,147 +279,6 @@ impl NocCluster { Ok(()) } - fn _handle_command_addnoc( - &self, - exchange: &Exchange, - data: &TLVElement, - ) -> Result { - let noc_data = exchange - .with_session(|sess| Ok(sess.take_noc_data()))? - .ok_or(NocStatus::MissingCsr)?; - - if !exchange - .matter() - .failsafe - .borrow_mut() - .allow_noc_change() - .map_err(|_| NocStatus::InsufficientPrivlege)? - { - error!("AddNOC not allowed by Fail Safe"); - Err(NocStatus::InsufficientPrivlege)?; - } - - let r = AddNocReq::from_tlv(data).map_err(|_| NocStatus::InvalidNOC)?; - - info!( - "Received NOC as: {}", - CertRef::new(TLVElement::new(r.noc_value.0)) - ); - - let noc = crate::utils::storage::Vec::from_slice(r.noc_value.0) - .map_err(|_| NocStatus::InvalidNOC)?; - - let icac = if let Some(icac_value) = r.icac_value { - if !icac_value.0.is_empty() { - info!( - "Received ICAC as: {}", - CertRef::new(TLVElement::new(icac_value.0)) - ); - - let icac = crate::utils::storage::Vec::from_slice(icac_value.0) - .map_err(|_| NocStatus::InvalidNOC)?; - Some(icac) - } else { - None - } - } else { - None - }; - - let fabric = Fabric::new( - noc_data.key_pair, - noc_data.root_ca, - icac, - noc, - r.ipk_value.0, - r.vendor_id, - "", - ) - .map_err(|_| NocStatus::TableFull)?; - - let fab_idx = exchange - .matter() - .fabric_mgr - .borrow_mut() - .add(fabric, &exchange.matter().transport_mgr.mdns) - .map_err(|_| NocStatus::TableFull)?; - - let succeeded = Cell::new(false); - - let _fab_guard = scopeguard::guard(fab_idx, |fab_idx| { - if !succeeded.get() { - // Remove the fabric if we fail further down this function - warn!("Removing fabric {} due to failure", fab_idx.get()); - - exchange - .matter() - .fabric_mgr - .borrow_mut() - .remove(fab_idx, &exchange.matter().transport_mgr.mdns) - .unwrap(); - } - }); - - let mut acl = AclEntry::new(fab_idx, Privilege::ADMIN, AuthMode::Case); - acl.add_subject(r.case_admin_subject)?; - let acl_entry_index = exchange.matter().acl_mgr.borrow_mut().add(acl)?; - - let _acl_guard = scopeguard::guard(fab_idx, |fab_idx| { - if !succeeded.get() { - // Remove the ACL entry if we fail further down this function - warn!( - "Removing ACL entry {}/{} due to failure", - acl_entry_index, - fab_idx.get() - ); - - exchange - .matter() - .acl_mgr - .borrow_mut() - .delete(acl_entry_index, fab_idx) - .unwrap(); - } - }); - - exchange - .matter() - .failsafe - .borrow_mut() - .record_add_noc(fab_idx)?; - - // Finally, upgrade our session with the new fabric index - exchange.with_session(|sess| { - if matches!(sess.get_session_mode(), SessionMode::Pase { .. }) { - sess.upgrade_fabric_idx(fab_idx)?; - } - - Ok(()) - })?; - - // Leave the fabric and its ACLs in place now that we've updated everything - succeeded.set(true); - - Ok(fab_idx) - } - - fn create_nocresponse( - encoder: CmdDataEncoder, - status_code: NocStatus, - fab_idx: u8, - debug_txt: &str, - ) -> Result<(), Error> { - let cmd_data = NocResp { - status_code: status_code as u8, - fab_idx, - debug_txt, - }; - - encoder - .with_command(RespCommands::NOCResp as _)? - .set(cmd_data) - } - fn handle_command_updatefablabel( &self, exchange: &Exchange, @@ -440,29 +286,33 @@ impl NocCluster { encoder: CmdDataEncoder, ) -> Result<(), Error> { cmd_enter!("Update Fabric Label"); - let req = UpdateFabricLabelReq::from_tlv(data).map_err(Error::map_invalid_data_type)?; - let (result, fab_idx) = if let SessionMode::Case { fab_idx, .. } = - exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? - { - if exchange + let req = UpdateFabricLabelReq::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received Fabric Label: {:?}", req); + + let mut updated_fab_idx = 0; + + let status = NocStatus::map(exchange.with_session(|sess| { + let SessionMode::Case { fab_idx, .. } = sess.get_session_mode() else { + return Err(ErrorCode::GennCommInvalidAuthentication.into()); + }; + + updated_fab_idx = fab_idx.get(); + + exchange .matter() .fabric_mgr .borrow_mut() - .set_label(fab_idx, req.label) - .is_err() - { - (NocStatus::LabelConflict, fab_idx.get()) - } else { - (NocStatus::Ok, fab_idx.get()) - } - } else { - // Update Fabric Label not allowed - (NocStatus::InvalidFabricIndex, 0) - }; - - Self::create_nocresponse(encoder, result, fab_idx, "")?; + .update_label(*fab_idx, req.label) + .map_err(|e| { + if e.code() == ErrorCode::Invalid { + ErrorCode::NocLabelConflict.into() + } else { + e + } + }) + }))?; - Ok(()) + Self::create_nocresponse(encoder, status as _, updated_fab_idx, "") } fn handle_command_rmfabric( @@ -472,7 +322,9 @@ impl NocCluster { encoder: CmdDataEncoder, ) -> Result<(), Error> { cmd_enter!("Remove Fabric"); - let req = RemoveFabricReq::from_tlv(data).map_err(Error::map_invalid_data_type)?; + let req = RemoveFabricReq::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received Fabric Index: {:?}", req); + if exchange .matter() .fabric_mgr @@ -480,11 +332,6 @@ impl NocCluster { .remove(req.fab_idx, &exchange.matter().transport_mgr.mdns) .is_ok() { - let _ = exchange - .matter() - .acl_mgr - .borrow_mut() - .delete_for_fabric(req.fab_idx); exchange .matter() .transport_mgr @@ -498,6 +345,8 @@ impl NocCluster { Ok(()) } else { + // TODO + Self::create_nocresponse( encoder, NocStatus::InvalidFabricIndex, @@ -515,13 +364,66 @@ impl NocCluster { ) -> Result<(), Error> { cmd_enter!("AddNOC"); - let (status, fab_idx) = match self._handle_command_addnoc(exchange, data) { - Ok(fab_idx) => (NocStatus::Ok, fab_idx.get()), - Err(NocError::Status(status)) => (status, 0), - Err(NocError::Error(error)) => Err(error)?, - }; + let r = AddNocReq::from_tlv(data).map_err(Error::map_invalid_command)?; - Self::create_nocresponse(encoder, status, fab_idx, "")?; + info!( + "Received NOC as: {}", + CertRef::new(TLVElement::new(r.noc_value.0)) + ); + + if let Some(icac_value) = r.icac_value { + info!( + "Received ICAC as: {}", + CertRef::new(TLVElement::new(icac_value.0)) + ); + } + + let mut added_fab_idx = 0; + + let mut buf = alloc!([0; 800]); // TODO LARGE BUFFER + let buf = &mut buf[..]; + + let status = NocStatus::map(exchange.with_session(|sess| { + let fab_idx = exchange.matter().failsafe.borrow_mut().add_noc( + &exchange.matter().fabric_mgr, + sess.get_session_mode(), + r.vendor_id, + r.icac_value.as_ref().map(|icac| icac.0), + r.noc_value.0, + r.ipk_value.0, + r.case_admin_subject, + buf, + &exchange.matter().transport_mgr.mdns, + )?; + + let succeeded = Cell::new(false); + + let _fab_guard = scopeguard::guard(fab_idx, |fab_idx| { + if !succeeded.get() { + // Remove the fabric if we fail further down this function + warn!("Removing fabric {} due to failure", fab_idx.get()); + + exchange + .matter() + .fabric_mgr + .borrow_mut() + .remove(fab_idx, &exchange.matter().transport_mgr.mdns) + .unwrap(); + } + }); + + if matches!(sess.get_session_mode(), SessionMode::Pase { .. }) { + sess.upgrade_fabric_idx(fab_idx)?; + } + + succeeded.set(true); + + added_fab_idx = fab_idx.get(); + + Ok(()) + }))?; + + Self::create_nocresponse(encoder, status, added_fab_idx, "")?; Ok(()) } @@ -537,27 +439,47 @@ impl NocCluster { let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; info!("Received Attestation Nonce:{:?}", req.str); - let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; exchange.with_session(|sess| { - attest_challenge.copy_from_slice(sess.get_att_challenge()); - Ok(()) - })?; + let mut writer = encoder.with_command(RespCommands::AttReqResp as _)?; - let mut writer = encoder.with_command(RespCommands::AttReqResp as _)?; + writer.start_struct(&CmdDataWriter::TAG)?; - writer.start_struct(&CmdDataWriter::TAG)?; - add_attestation_element( - exchange.matter().epoch(), - exchange.matter().dev_att(), - req.str.0, - &attest_challenge, - &mut writer, - )?; - writer.end_container()?; + let epoch = (exchange.matter().epoch())().as_secs() as u32; - writer.complete()?; + let mut signature_buf = MaybeUninit::<[u8; crypto::EC_SIGNATURE_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER + let signature_buf = signature_buf.init_zeroed(); + let mut signature_len = 0; - Ok(()) + writer.str_cb(&TLVTag::Context(0), |buf| { + let dev_att = exchange.matter().dev_att(); + + let mut wb = WriteBuf::new(buf); + wb.start_struct(&TLVTag::Anonymous)?; + wb.str_cb(&TLVTag::Context(1), |buf| { + dev_att.get_devatt_data(dev_att::DataType::CertDeclaration, buf) + })?; + wb.str(&TLVTag::Context(2), req.str.0)?; + wb.u32(&TLVTag::Context(3), epoch)?; + wb.end_container()?; + + let len = wb.get_tail(); + + signature_len = Self::compute_attestation_signature( + dev_att, + &mut wb, + sess.get_att_challenge(), + signature_buf, + )? + .len(); + + Ok(len) + })?; + writer.str(&TLVTag::Context(1), &signature_buf[..signature_len])?; + + writer.end_container()?; + + writer.complete() + }) } fn handle_command_certchainrequest( @@ -569,7 +491,8 @@ impl NocCluster { cmd_enter!("CertChainRequest"); info!("Received data: {}", data); - let cert_type = get_certchainrequest_params(data).map_err(Error::map_invalid_command)?; + let cert_type = + Self::get_certchainrequest_params(data).map_err(Error::map_invalid_command)?; let mut writer = encoder.with_command(RespCommands::CertChainResp as _)?; @@ -590,56 +513,51 @@ impl NocCluster { ) -> Result<(), Error> { cmd_enter!("CSRRequest"); - let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; - info!("Received CSR Nonce:{:?}", req.str); - - if !exchange.matter().failsafe.borrow().is_armed() { - Err(ErrorCode::UnsupportedAccess)?; - } + let req = CsrReq::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received CSR: {:?}", req); - let noc_keypair = KeyPair::new(exchange.matter().rand())?; - let mut attest_challenge = [0u8; crypto::SYMM_KEY_LEN_BYTES]; exchange.with_session(|sess| { - attest_challenge.copy_from_slice(sess.get_att_challenge()); - Ok(()) - })?; + let mut failsafe = exchange.matter().failsafe.borrow_mut(); - let mut writer = encoder.with_command(RespCommands::CSRResp as _)?; + let key_pair = if req.for_update_noc.unwrap_or(false) { + failsafe.update_csr_req(sess.get_session_mode()) + } else { + failsafe.add_csr_req(sess.get_session_mode()) + }?; - writer.start_struct(&CmdDataWriter::TAG)?; - add_nocsrelement( - exchange.matter().dev_att(), - &noc_keypair, - req.str.0, - &attest_challenge, - &mut writer, - )?; - writer.end_container()?; + let mut writer = encoder.with_command(RespCommands::CSRResp as _)?; - writer.complete()?; + writer.start_struct(&CmdDataWriter::TAG)?; - let noc_data = NocData::new(noc_keypair); - // Store this in the session data instead of cluster data, so it gets cleared - // if the session goes away for some reason - exchange.with_session(|sess| { - sess.set_noc_data(noc_data); - Ok(()) - })?; + let mut signature_buf = MaybeUninit::<[u8; crypto::EC_SIGNATURE_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER + let signature_buf = signature_buf.init_zeroed(); + let mut signature_len = 0; - Ok(()) - } + writer.str_cb(&TLVTag::Context(0), |buf| { + let mut wb = WriteBuf::new(buf); - fn add_rca_to_session_noc_data(exchange: &Exchange, data: &TLVElement) -> Result<(), Error> { - exchange.with_session(|sess| { - let noc_data = sess.get_noc_data().ok_or(ErrorCode::NoSession)?; + wb.start_struct(&TLVTag::Anonymous)?; + wb.str_cb(&TLVTag::Context(1), |buf| Ok(key_pair.get_csr(buf)?.len()))?; + wb.str(&TLVTag::Context(2), req.nonce.0)?; + wb.end_container()?; - let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; - info!("Received Trusted Cert:{:x?}", req.str); + let len = wb.get_tail(); - noc_data.root_ca = crate::utils::storage::Vec::from_slice(req.str.0) - .map_err(|_| ErrorCode::BufferTooSmall)?; + signature_len = Self::compute_attestation_signature( + exchange.matter().dev_att(), + &mut wb, + sess.get_att_challenge(), + signature_buf, + )? + .len(); - Ok(()) + Ok(len) + })?; + writer.str(&TLVTag::Context(1), &signature_buf[..signature_len])?; + + writer.end_container()?; + + writer.complete() }) } @@ -649,23 +567,73 @@ impl NocCluster { data: &TLVElement, ) -> Result<(), Error> { cmd_enter!("AddTrustedRootCert"); - if !exchange.matter().failsafe.borrow().is_armed() { - Err(ErrorCode::UnsupportedAccess)?; - } - // This may happen on CASE or PASE. For PASE, the existence of NOC Data is necessary - match exchange.with_session(|sess| Ok(sess.get_session_mode().clone()))? { - SessionMode::Case { .. } => { - // TODO - Updating the Trusted RCA of an existing Fabric - Self::add_rca_to_session_noc_data(exchange, data)?; - } - SessionMode::Pase { .. } => { - Self::add_rca_to_session_noc_data(exchange, data)?; - } - _ => (), - } + let req = CommonReq::from_tlv(data).map_err(Error::map_invalid_command)?; + info!("Received Trusted Cert: {:x?}", req.str); - Ok(()) + exchange.with_session(|sess| { + exchange + .matter() + .failsafe + .borrow_mut() + .add_trusted_root_cert(sess.get_session_mode(), req.str.0) + }) + } + + fn create_nocresponse( + encoder: CmdDataEncoder, + status_code: NocStatus, + fab_idx: u8, + debug_txt: &str, + ) -> Result<(), Error> { + let cmd_data = NocResp { + status_code: status_code as u8, + fab_idx, + debug_txt, + }; + + encoder + .with_command(RespCommands::NOCResp as _)? + .set(cmd_data) + } + + fn compute_attestation_signature<'a>( + dev_att: &dyn DevAttDataFetcher, + attest_element: &mut WriteBuf, + attest_challenge: &[u8], + signature_buf: &'a mut [u8], + ) -> Result<&'a [u8], Error> { + let dac_key = { + let mut pubkey_buf = MaybeUninit::<[u8; crypto::EC_POINT_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER + let pubkey_buf = pubkey_buf.init_zeroed(); + + let mut privkey_buf = MaybeUninit::<[u8; crypto::BIGNUM_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER + let privkey_buf = privkey_buf.init_zeroed(); + + let pubkey_len = dev_att.get_devatt_data(dev_att::DataType::DACPubKey, pubkey_buf)?; + let privkey_len = + dev_att.get_devatt_data(dev_att::DataType::DACPrivKey, privkey_buf)?; + + KeyPair::new_from_components(&pubkey_buf[..pubkey_len], &privkey_buf[..privkey_len]) + }?; + + attest_element.copy_from_slice(attest_challenge)?; + let len = dac_key.sign_msg(attest_element.as_slice(), signature_buf)?; + + Ok(&signature_buf[..len]) + } + + fn get_certchainrequest_params(data: &TLVElement) -> Result { + let cert_type = CertChainReq::from_tlv(data)?.cert_type; + + const CERT_TYPE_DAC: u8 = 1; + const CERT_TYPE_PAI: u8 = 2; + info!("Received Cert Type:{:?}", cert_type); + match cert_type { + CERT_TYPE_DAC => Ok(dev_att::DataType::DAC), + CERT_TYPE_PAI => Ok(dev_att::DataType::PAI), + _ => Err(ErrorCode::Invalid.into()), + } } } @@ -697,101 +665,3 @@ impl ChangeNotifier<()> for NocCluster { self.data_ver.consume_change(()) } } - -fn add_attestation_element( - epoch: Epoch, - dev_att: &dyn DevAttDataFetcher, - att_nonce: &[u8], - attest_challenge: &[u8], - t: &mut TLVWriter, -) -> Result<(), Error> { - let epoch = epoch().as_secs() as u32; - - let mut signature_buf = MaybeUninit::<[u8; crypto::EC_SIGNATURE_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER - let signature_buf = signature_buf.init_zeroed(); - let mut signature_len = 0; - - t.str_cb(&TLVTag::Context(0), |buf| { - let mut wb = WriteBuf::new(buf); - wb.start_struct(&TLVTag::Anonymous)?; - wb.str_cb(&TLVTag::Context(1), |buf| { - dev_att.get_devatt_data(dev_att::DataType::CertDeclaration, buf) - })?; - wb.str(&TLVTag::Context(2), att_nonce)?; - wb.u32(&TLVTag::Context(3), epoch)?; - wb.end_container()?; - - let len = wb.get_tail(); - - signature_len = - compute_attestation_signature(dev_att, &mut wb, attest_challenge, signature_buf)?.len(); - - Ok(len) - })?; - t.str(&TLVTag::Context(1), &signature_buf[..signature_len]) -} - -fn add_nocsrelement( - dev_att: &dyn DevAttDataFetcher, - noc_keypair: &KeyPair, - csr_nonce: &[u8], - attest_challenge: &[u8], - t: &mut TLVWriter, -) -> Result<(), Error> { - let mut signature_buf = MaybeUninit::<[u8; crypto::EC_SIGNATURE_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER - let signature_buf = signature_buf.init_zeroed(); - let mut signature_len = 0; - - t.str_cb(&TLVTag::Context(0), |buf| { - let mut wb = WriteBuf::new(buf); - - wb.start_struct(&TLVTag::Anonymous)?; - wb.str_cb(&TLVTag::Context(1), |buf| { - Ok(noc_keypair.get_csr(buf)?.len()) - })?; - wb.str(&TLVTag::Context(2), csr_nonce)?; - wb.end_container()?; - - let len = wb.get_tail(); - - signature_len = - compute_attestation_signature(dev_att, &mut wb, attest_challenge, signature_buf)?.len(); - - Ok(len) - })?; - t.str(&TLVTag::Context(1), &signature_buf[..signature_len]) -} - -fn compute_attestation_signature<'a>( - dev_att: &dyn DevAttDataFetcher, - attest_element: &mut WriteBuf, - attest_challenge: &[u8], - signature_buf: &'a mut [u8], -) -> Result<&'a [u8], Error> { - let dac_key = { - let mut pubkey_buf = MaybeUninit::<[u8; crypto::EC_POINT_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER - let pubkey_buf = pubkey_buf.init_zeroed(); - let mut privkey_buf = MaybeUninit::<[u8; crypto::BIGNUM_LEN_BYTES]>::uninit(); // TODO MEDIUM BUFFER - let privkey_buf = privkey_buf.init_zeroed(); - let pubkey_len = dev_att.get_devatt_data(dev_att::DataType::DACPubKey, pubkey_buf)?; - let privkey_len = dev_att.get_devatt_data(dev_att::DataType::DACPrivKey, privkey_buf)?; - KeyPair::new_from_components(&pubkey_buf[..pubkey_len], &privkey_buf[..privkey_len]) - }?; - attest_element.copy_from_slice(attest_challenge)?; - let len = dac_key.sign_msg(attest_element.as_slice(), signature_buf)?; - - Ok(&signature_buf[..len]) -} - -fn get_certchainrequest_params(data: &TLVElement) -> Result { - let cert_type = CertChainReq::from_tlv(data)?.cert_type; - - const CERT_TYPE_DAC: u8 = 1; - const CERT_TYPE_PAI: u8 = 2; - info!("Received Cert Type:{:?}", cert_type); - match cert_type { - CERT_TYPE_DAC => Ok(dev_att::DataType::DAC), - CERT_TYPE_PAI => Ok(dev_att::DataType::PAI), - _ => Err(ErrorCode::Invalid.into()), - } -} diff --git a/rs-matter/src/data_model/system_model/access_control.rs b/rs-matter/src/data_model/system_model/access_control.rs index 30f86439..587dc733 100644 --- a/rs-matter/src/data_model/system_model/access_control.rs +++ b/rs-matter/src/data_model/system_model/access_control.rs @@ -21,8 +21,9 @@ use strum::{EnumDiscriminants, FromRepr}; use log::{error, info}; -use crate::acl::{self, AclEntry, AclMgr}; +use crate::acl::{self, AclEntry}; use crate::data_model::objects::*; +use crate::fabric::FabricMgr; use crate::interaction_model::messages::ib::{attr_list_write, ListOperation}; use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, ToTLV}; use crate::transport::exchange::Exchange; @@ -93,7 +94,7 @@ impl AccessControlCluster { attr: &AttrDetails, encoder: AttrDataEncoder, ) -> Result<(), Error> { - self.read_acl_attr(&exchange.matter().acl_mgr.borrow(), attr, encoder) + self.read_acl_attr(&exchange.matter().fabric_mgr.borrow(), attr, encoder) } pub fn write( @@ -106,7 +107,7 @@ impl AccessControlCluster { Attributes::Acl(_) => { attr_list_write(attr, data.with_dataver(self.data_ver.get())?, |op, data| { self.write_acl_attr( - &mut exchange.matter().acl_mgr.borrow_mut(), + &mut exchange.matter().fabric_mgr.borrow_mut(), &op, data, NonZeroU8::new(attr.fab_idx).ok_or(ErrorCode::Invalid)?, @@ -122,7 +123,7 @@ impl AccessControlCluster { fn read_acl_attr( &self, - acl_mgr: &AclMgr, + fabric_mgr: &FabricMgr, attr: &AttrDetails, encoder: AttrDataEncoder, ) -> Result<(), Error> { @@ -133,18 +134,13 @@ impl AccessControlCluster { match attr.attr_id.try_into()? { Attributes::Acl(_) => { writer.start_array(&AttrDataWriter::TAG)?; - acl_mgr.for_each_acl(|entry| { - if !attr.fab_filter - || entry - .fab_idx - .map(|fi| fi.get() == attr.fab_idx) - .unwrap_or(false) - { - entry.to_tlv(&TLVTag::Anonymous, &mut *writer)?; + for fabric in fabric_mgr.iter() { + if !attr.fab_filter || fabric.fab_idx().get() == attr.fab_idx { + for entry in fabric.acl_iter() { + entry.to_tlv(&TLVTag::Anonymous, &mut *writer)?; + } } - - Ok(()) - })?; + } writer.end_container()?; writer.complete() @@ -178,7 +174,7 @@ impl AccessControlCluster { /// Care about fabric-scoped behaviour is taken fn write_acl_attr( &self, - acl_mgr: &mut AclMgr, + fabric_mgr: &mut FabricMgr, op: &ListOperation, data: &TLVElement, fab_idx: NonZeroU8, @@ -186,21 +182,19 @@ impl AccessControlCluster { info!("Performing ACL operation {:?}", op); match op { ListOperation::AddItem | ListOperation::EditItem(_) => { - let mut acl_entry = AclEntry::from_tlv(data)?; + let acl_entry = AclEntry::from_tlv(data)?; info!("ACL {:?}", acl_entry); - // Overwrite the fabric index with our accessing fabric index - acl_entry.fab_idx = Some(fab_idx); if let ListOperation::EditItem(index) = op { - acl_mgr.edit(*index as u8, fab_idx, acl_entry)?; + fabric_mgr.acl_update(fab_idx, *index as _, acl_entry)?; } else { - acl_mgr.add(acl_entry)?; + fabric_mgr.acl_add(fab_idx, acl_entry)?; } Ok(()) } - ListOperation::DeleteItem(index) => acl_mgr.delete(*index as u8, fab_idx), - ListOperation::DeleteList => acl_mgr.delete_for_fabric(fab_idx), + ListOperation::DeleteItem(index) => fabric_mgr.acl_remove(fab_idx, *index as _), + ListOperation::DeleteList => fabric_mgr.acl_remove_all(fab_idx), } } } @@ -228,249 +222,249 @@ impl ChangeNotifier<()> for AccessControlCluster { } } -#[cfg(test)] -mod tests { - use crate::acl::{AclEntry, AclMgr, AuthMode}; - use crate::data_model::objects::{AttrDataEncoder, AttrDetails, Node, Privilege}; - use crate::data_model::system_model::access_control::Dataver; - use crate::interaction_model::messages::ib::ListOperation; - use crate::tlv::{ - get_root_node_struct, TLVControl, TLVElement, TLVTag, TLVTagType, TLVValueType, TLVWriter, - ToTLV, - }; - use crate::utils::storage::WriteBuf; - - use super::AccessControlCluster; - - use crate::acl::tests::{FAB_1, FAB_2}; - - #[test] - /// Add an ACL entry - fn acl_cluster_add() { - let mut buf: [u8; 100] = [0; 100]; - let mut writebuf = WriteBuf::new(&mut buf); - let mut tw = TLVWriter::new(&mut writebuf); - - let mut acl_mgr = AclMgr::new(); - let acl = AccessControlCluster::new(Dataver::new(0)); - - let new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); - new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap(); - let data = get_root_node_struct(writebuf.as_slice()).unwrap(); - - // Test, ACL has fabric index 2, but the accessing fabric is 1 - // the fabric index in the TLV should be ignored and the ACL should be created with entry 1 - let result = acl.write_acl_attr(&mut acl_mgr, &ListOperation::AddItem, &data, FAB_1); - assert!(result.is_ok()); - - let verifier = AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case); - acl_mgr - .for_each_acl(|a| { - assert_eq!(*a, verifier); - Ok(()) - }) - .unwrap(); - } - - #[test] - /// - The listindex used for edit should be relative to the current fabric - fn acl_cluster_edit() { - let mut buf: [u8; 100] = [0; 100]; - let mut writebuf = WriteBuf::new(&mut buf); - let mut tw = TLVWriter::new(&mut writebuf); - - // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order - let mut acl_mgr = AclMgr::new(); - let mut verifier = [ - AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case), - AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case), - AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case), - ]; - for i in &verifier { - acl_mgr.add(i.clone()).unwrap(); - } - let acl = AccessControlCluster::new(Dataver::new(0)); - - let new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); - new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap(); - let data = get_root_node_struct(writebuf.as_slice()).unwrap(); - - // Test, Edit Fabric 2's index 1 - with accessing fabring as 2 - allow - let result = acl.write_acl_attr(&mut acl_mgr, &ListOperation::EditItem(1), &data, FAB_2); - // Fabric 2's index 1, is actually our index 2, update the verifier - verifier[2] = new; - assert!(result.is_ok()); - - // Also validate in the acl_mgr that the entries are in the right order - let mut index = 0; - acl_mgr - .for_each_acl(|a| { - assert_eq!(*a, verifier[index]); - index += 1; - Ok(()) - }) - .unwrap(); - } - - #[test] - /// - The listindex used for delete should be relative to the current fabric - fn acl_cluster_delete() { - // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order - let mut acl_mgr = AclMgr::new(); - let input = [ - AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case), - AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case), - AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case), - ]; - for i in &input { - acl_mgr.add(i.clone()).unwrap(); - } - let acl = AccessControlCluster::new(Dataver::new(0)); - // data is don't-care actually - let data = &[TLVControl::new(TLVTagType::Anonymous, TLVValueType::Null).as_raw()]; - let data = TLVElement::new(data.as_slice()); - - // Test , Delete Fabric 1's index 0 - let result = acl.write_acl_attr(&mut acl_mgr, &ListOperation::DeleteItem(0), &data, FAB_1); - assert!(result.is_ok()); - - let verifier = [input[0].clone(), input[2].clone()]; - // Also validate in the acl_mgr that the entries are in the right order - let mut index = 0; - acl_mgr - .for_each_acl(|a| { - assert_eq!(*a, verifier[index]); - index += 1; - Ok(()) - }) - .unwrap(); - } - - #[test] - /// - acl read with and without fabric filtering - fn acl_cluster_read() { - let mut buf: [u8; 100] = [0; 100]; - let mut writebuf = WriteBuf::new(&mut buf); - - // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order - let mut acl_mgr = AclMgr::new(); - let input = [ - AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case), - AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case), - AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case), - ]; - for i in input { - acl_mgr.add(i).unwrap(); - } - let acl = AccessControlCluster::new(Dataver::new(0)); - // Test 1, all 3 entries are read in the response without fabric filtering - { - let attr = AttrDetails { - node: &Node { - id: 0, - endpoints: &[], - }, - endpoint_id: 0, - cluster_id: 0, - attr_id: 0, - list_index: None, - fab_idx: 1, - fab_filter: false, - dataver: None, - wildcard: false, - }; - - let mut tw = TLVWriter::new(&mut writebuf); - let encoder = AttrDataEncoder::new(&attr, &mut tw); - - acl.read_acl_attr(&acl_mgr, &attr, encoder).unwrap(); - assert_eq!( - // &[ - // 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, - // 4, 24, 36, 254, 2, 24, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, - // 1, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, - // 24 - // ], - &[ - 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, - 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 21, 36, 1, 1, 36, 2, 2, 54, - 3, 24, 54, 4, 24, 36, 254, 1, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, - 36, 254, 2, 24, 24, 24, 24 - ], - writebuf.as_slice() - ); - } - writebuf.reset(); - - // Test 2, only single entry is read in the response with fabric filtering and fabric idx 1 - { - let attr = AttrDetails { - node: &Node { - id: 0, - endpoints: &[], - }, - endpoint_id: 0, - cluster_id: 0, - attr_id: 0, - list_index: None, - fab_idx: 1, - fab_filter: true, - dataver: None, - wildcard: false, - }; - - let mut tw = TLVWriter::new(&mut writebuf); - let encoder = AttrDataEncoder::new(&attr, &mut tw); - - acl.read_acl_attr(&acl_mgr, &attr, encoder).unwrap(); - assert_eq!( - // &[ - // 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, - // 4, 24, 36, 254, 1, 24, 24, 24, 24 - // ], - &[ - 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, - 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 1, 24, 24, 24, 24 - ], - writebuf.as_slice() - ); - } - writebuf.reset(); - - // Test 3, only single entry is read in the response with fabric filtering and fabric idx 2 - { - let attr = AttrDetails { - node: &Node { - id: 0, - endpoints: &[], - }, - endpoint_id: 0, - cluster_id: 0, - attr_id: 0, - list_index: None, - fab_idx: 2, - fab_filter: true, - dataver: None, - wildcard: false, - }; - - let mut tw = TLVWriter::new(&mut writebuf); - let encoder = AttrDataEncoder::new(&attr, &mut tw); - - acl.read_acl_attr(&acl_mgr, &attr, encoder).unwrap(); - assert_eq!( - // &[ - // 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, - // 4, 24, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, - // 2, 24, 24, 24, 24 - // ], - &[ - 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, - 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 54, - 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, 24 - ], - writebuf.as_slice() - ); - } - } -} +// #[cfg(test)] +// mod tests { +// use crate::acl::{AclEntry, AclMgr, AuthMode}; +// use crate::data_model::objects::{AttrDataEncoder, AttrDetails, Node, Privilege}; +// use crate::data_model::system_model::access_control::Dataver; +// use crate::interaction_model::messages::ib::ListOperation; +// use crate::tlv::{ +// get_root_node_struct, TLVControl, TLVElement, TLVTag, TLVTagType, TLVValueType, TLVWriter, +// ToTLV, +// }; +// use crate::utils::storage::WriteBuf; + +// use super::AccessControlCluster; + +// use crate::acl::tests::{FAB_1, FAB_2}; + +// #[test] +// /// Add an ACL entry +// fn acl_cluster_add() { +// let mut buf: [u8; 100] = [0; 100]; +// let mut writebuf = WriteBuf::new(&mut buf); +// let mut tw = TLVWriter::new(&mut writebuf); + +// let mut acl_mgr = AclMgr::new(); +// let acl = AccessControlCluster::new(Dataver::new(0)); + +// let new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); +// new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap(); +// let data = get_root_node_struct(writebuf.as_slice()).unwrap(); + +// // Test, ACL has fabric index 2, but the accessing fabric is 1 +// // the fabric index in the TLV should be ignored and the ACL should be created with entry 1 +// let result = acl.write_acl_attr(&mut acl_mgr, &ListOperation::AddItem, &data, FAB_1); +// assert!(result.is_ok()); + +// let verifier = AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case); +// acl_mgr +// .for_each_acl(|a| { +// assert_eq!(*a, verifier); +// Ok(()) +// }) +// .unwrap(); +// } + +// #[test] +// /// - The listindex used for edit should be relative to the current fabric +// fn acl_cluster_edit() { +// let mut buf: [u8; 100] = [0; 100]; +// let mut writebuf = WriteBuf::new(&mut buf); +// let mut tw = TLVWriter::new(&mut writebuf); + +// // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order +// let mut acl_mgr = AclMgr::new(); +// let mut verifier = [ +// AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case), +// AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case), +// AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case), +// ]; +// for i in &verifier { +// acl_mgr.add(i.clone()).unwrap(); +// } +// let acl = AccessControlCluster::new(Dataver::new(0)); + +// let new = AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case); +// new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap(); +// let data = get_root_node_struct(writebuf.as_slice()).unwrap(); + +// // Test, Edit Fabric 2's index 1 - with accessing fabring as 2 - allow +// let result = acl.write_acl_attr(&mut acl_mgr, &ListOperation::EditItem(1), &data, FAB_2); +// // Fabric 2's index 1, is actually our index 2, update the verifier +// verifier[2] = new; +// assert!(result.is_ok()); + +// // Also validate in the acl_mgr that the entries are in the right order +// let mut index = 0; +// acl_mgr +// .for_each_acl(|a| { +// assert_eq!(*a, verifier[index]); +// index += 1; +// Ok(()) +// }) +// .unwrap(); +// } + +// #[test] +// /// - The listindex used for delete should be relative to the current fabric +// fn acl_cluster_delete() { +// // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order +// let mut acl_mgr = AclMgr::new(); +// let input = [ +// AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case), +// AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case), +// AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case), +// ]; +// for i in &input { +// acl_mgr.add(i.clone()).unwrap(); +// } +// let acl = AccessControlCluster::new(Dataver::new(0)); +// // data is don't-care actually +// let data = &[TLVControl::new(TLVTagType::Anonymous, TLVValueType::Null).as_raw()]; +// let data = TLVElement::new(data.as_slice()); + +// // Test , Delete Fabric 1's index 0 +// let result = acl.write_acl_attr(&mut acl_mgr, &ListOperation::DeleteItem(0), &data, FAB_1); +// assert!(result.is_ok()); + +// let verifier = [input[0].clone(), input[2].clone()]; +// // Also validate in the acl_mgr that the entries are in the right order +// let mut index = 0; +// acl_mgr +// .for_each_acl(|a| { +// assert_eq!(*a, verifier[index]); +// index += 1; +// Ok(()) +// }) +// .unwrap(); +// } + +// #[test] +// /// - acl read with and without fabric filtering +// fn acl_cluster_read() { +// let mut buf: [u8; 100] = [0; 100]; +// let mut writebuf = WriteBuf::new(&mut buf); + +// // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order +// let mut acl_mgr = AclMgr::new(); +// let input = [ +// AclEntry::new(FAB_2, Privilege::VIEW, AuthMode::Case), +// AclEntry::new(FAB_1, Privilege::VIEW, AuthMode::Case), +// AclEntry::new(FAB_2, Privilege::ADMIN, AuthMode::Case), +// ]; +// for i in input { +// acl_mgr.add(i).unwrap(); +// } +// let acl = AccessControlCluster::new(Dataver::new(0)); +// // Test 1, all 3 entries are read in the response without fabric filtering +// { +// let attr = AttrDetails { +// node: &Node { +// id: 0, +// endpoints: &[], +// }, +// endpoint_id: 0, +// cluster_id: 0, +// attr_id: 0, +// list_index: None, +// fab_idx: 1, +// fab_filter: false, +// dataver: None, +// wildcard: false, +// }; + +// let mut tw = TLVWriter::new(&mut writebuf); +// let encoder = AttrDataEncoder::new(&attr, &mut tw); + +// acl.read_acl_attr(&acl_mgr, &attr, encoder).unwrap(); +// assert_eq!( +// // &[ +// // 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, +// // 4, 24, 36, 254, 2, 24, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, +// // 1, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, +// // 24 +// // ], +// &[ +// 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, +// 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 21, 36, 1, 1, 36, 2, 2, 54, +// 3, 24, 54, 4, 24, 36, 254, 1, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, +// 36, 254, 2, 24, 24, 24, 24 +// ], +// writebuf.as_slice() +// ); +// } +// writebuf.reset(); + +// // Test 2, only single entry is read in the response with fabric filtering and fabric idx 1 +// { +// let attr = AttrDetails { +// node: &Node { +// id: 0, +// endpoints: &[], +// }, +// endpoint_id: 0, +// cluster_id: 0, +// attr_id: 0, +// list_index: None, +// fab_idx: 1, +// fab_filter: true, +// dataver: None, +// wildcard: false, +// }; + +// let mut tw = TLVWriter::new(&mut writebuf); +// let encoder = AttrDataEncoder::new(&attr, &mut tw); + +// acl.read_acl_attr(&acl_mgr, &attr, encoder).unwrap(); +// assert_eq!( +// // &[ +// // 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, +// // 4, 24, 36, 254, 1, 24, 24, 24, 24 +// // ], +// &[ +// 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, +// 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 1, 24, 24, 24, 24 +// ], +// writebuf.as_slice() +// ); +// } +// writebuf.reset(); + +// // Test 3, only single entry is read in the response with fabric filtering and fabric idx 2 +// { +// let attr = AttrDetails { +// node: &Node { +// id: 0, +// endpoints: &[], +// }, +// endpoint_id: 0, +// cluster_id: 0, +// attr_id: 0, +// list_index: None, +// fab_idx: 2, +// fab_filter: true, +// dataver: None, +// wildcard: false, +// }; + +// let mut tw = TLVWriter::new(&mut writebuf); +// let encoder = AttrDataEncoder::new(&attr, &mut tw); + +// acl.read_acl_attr(&acl_mgr, &attr, encoder).unwrap(); +// assert_eq!( +// // &[ +// // 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, +// // 4, 24, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, +// // 2, 24, 24, 24, 24 +// // ], +// &[ +// 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1, +// 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 54, +// 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, 24 +// ], +// writebuf.as_slice() +// ); +// } +// } +// } diff --git a/rs-matter/src/error.rs b/rs-matter/src/error.rs index 41d3ccba..16812f87 100644 --- a/rs-matter/src/error.rs +++ b/rs-matter/src/error.rs @@ -17,7 +17,15 @@ use core::{array::TryFromSliceError, fmt, str::Utf8Error}; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +// TODO: The error code enum is in a need of an overhaul +// +// We need separate error enums per chunks of functionality +// and a way to map them to concrete IM and SC status codes +// +// This is a non-trivial effort though as we need to also generify +// the returned error type of all APIs that take callbacks that return errors +// (i.e., `Exchange::with_*`, `WriteBuf::append_with_buf` etc.) +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum ErrorCode { AttributeNotFound, AttributeIsCustom, @@ -28,6 +36,8 @@ pub enum ErrorCode { EndpointNotFound, InvalidAction, InvalidCommand, + FailSafeRequired, + ConstraintError, InvalidDataType, UnsupportedAccess, ResourceExhausted, @@ -74,6 +84,13 @@ pub enum ErrorCode { TLVTypeMismatch, TruncatedPacket, Utf8Fail, + GennCommInvalidAuthentication, + NocInvalidNoc, + NocMissingCsr, + NocFabricTableFull, + NocFabricConflict, + NocLabelConflict, + NocInvalidFabricIndex, } impl From for Error { diff --git a/rs-matter/src/fabric.rs b/rs-matter/src/fabric.rs index 3b2eb9d3..edbb02dc 100644 --- a/rs-matter/src/fabric.rs +++ b/rs-matter/src/fabric.rs @@ -19,19 +19,19 @@ use core::fmt::Write; use core::mem::MaybeUninit; use core::num::NonZeroU8; -use byteorder::{BigEndian, ByteOrder}; - use heapless::String; -use log::info; +use log::{error, info}; +use crate::acl::{self, AccessReq, AclEntry, AuthMode}; use crate::cert::{CertRef, MAX_CERT_TLV_LEN}; use crate::crypto::{self, hkdf_sha256, HmacSha256, KeyPair}; +use crate::data_model::objects::Privilege; use crate::error::{Error, ErrorCode}; use crate::group_keys::KeySet; use crate::mdns::{Mdns, ServiceMode}; -use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVWriter, TagType, ToTLV, UtfStr}; -use crate::utils::init::{init, Init, InitMaybeUninit}; +use crate::tlv::{FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, TagType, ToTLV, UtfStr}; +use crate::utils::init::{init, Init, InitMaybeUninit, IntoFallibleInit}; use crate::utils::storage::{Vec, WriteBuf}; const COMPRESSED_FABRIC_ID_LEN: usize = 8; @@ -49,90 +49,149 @@ pub struct FabricDescriptor<'a> { pub fab_idx: NonZeroU8, } +/// Fabric type #[derive(Debug, ToTLV, FromTLV)] pub struct Fabric { + /// Fabric local index + fab_idx: NonZeroU8, + /// Fabric node ID node_id: u64, + /// Fabric ID fabric_id: u64, + /// Vendor ID vendor_id: u16, + /// Fabric key pair key_pair: KeyPair, - pub root_ca: Vec, - pub icac: Option>, - pub noc: Vec, - pub ipk: KeySet, + /// Root CA certificate to be used when verifying the node's certificate + /// + /// Note that we deviate from the Matter spec here, in that we store the + /// root certificate in the Fabric type itself, rather than - as the + /// spec mandates - in a separate Root CA store + /// + /// This simplifies the implementation, but results in potentially multiple + /// copies of the same Root CA used accross multiple fabrics. + root_ca: Vec, + /// Intermediate CA certificate + icac: Vec, + /// Node Operational Certificate + noc: Vec, + /// Intermediate Public Key + ipk: KeySet, + /// Fabric label; unique accross all fabrics on the device label: String<32>, + /// Fabric mDNS service name mdns_service_name: String<33>, + /// Access Control List + acl: Vec, } impl Fabric { - pub fn new( - key_pair: KeyPair, - root_ca: Vec, - icac: Option>, - noc: Vec, + /// Return an in-place-initializer for a Fabric type, with the + /// provided Fabric Index and KeyPair + /// + /// All other fields are initialized to default values, which are NOT + /// valid for the operation of the fabric. + /// + /// The Fabric must be updated with the correct values before it can be + /// used, via `Fabric::update`. + fn init(fab_idx: NonZeroU8, key_pair: KeyPair) -> impl Init { + init!(Self { + fab_idx, + node_id: 0, + fabric_id: 0, + vendor_id: 0, + key_pair, + root_ca <- Vec::init(), + icac <- Vec::init(), + noc <- Vec::init(), + ipk <- KeySet::init(), + label: String::new(), + mdns_service_name: String::new(), + acl <- Vec::init(), + }) + } + + /// Update the fabric with the provided data so that it can operate. + /// + /// This method is supposed to be called right after `Fabric::init` or + /// when the NOC of the fabric needs to be updated. + #[allow(clippy::too_many_arguments)] + fn update( + &mut self, + root_ca: &[u8], + noc: &[u8], + icac: &[u8], ipk: &[u8], vendor_id: u16, - label: &str, - ) -> Result { - let (node_id, fabric_id) = { - let noc_p = CertRef::new(TLVElement::new(&noc)); - (noc_p.get_node_id()?, noc_p.get_fabric_id()?) - }; + case_admin_subject: Option, + mdns: &dyn Mdns, + ) -> Result<(), Error> { + if !self.mdns_service_name.is_empty() { + mdns.remove(&self.mdns_service_name)?; + } + + self.root_ca + .extend_from_slice(root_ca) + .map_err(|_| ErrorCode::NoSpace)?; + self.icac + .extend_from_slice(icac) + .map_err(|_| ErrorCode::NoSpace)?; + self.noc + .extend_from_slice(noc) + .map_err(|_| ErrorCode::NoSpace)?; + + let noc_p = CertRef::new(TLVElement::new(noc)); + + self.node_id = noc_p.get_node_id()?; + self.fabric_id = noc_p.get_fabric_id()?; + self.vendor_id = vendor_id; + + let root_ca_p = CertRef::new(TLVElement::new(root_ca)); let mut compressed_id = [0_u8; COMPRESSED_FABRIC_ID_LEN]; + Fabric::compute_compressed_id(root_ca_p.pubkey()?, self.fabric_id, &mut compressed_id)?; - let ipk = { - let root_ca_p = CertRef::new(TLVElement::new(&root_ca)); - Fabric::get_compressed_id(root_ca_p.pubkey()?, fabric_id, &mut compressed_id)?; - KeySet::new(ipk, &compressed_id)? - }; + self.ipk = KeySet::new(ipk, &compressed_id)?; - let mut mdns_service_name = heapless::String::<33>::new(); + self.mdns_service_name.clear(); for c in compressed_id { let mut hex = heapless::String::<4>::new(); write!(&mut hex, "{:02X}", c).unwrap(); - mdns_service_name.push_str(&hex).unwrap(); + self.mdns_service_name.push_str(&hex).unwrap(); } - mdns_service_name.push('-').unwrap(); - let mut node_id_be: [u8; 8] = [0; 8]; - BigEndian::write_u64(&mut node_id_be, node_id); - for c in node_id_be { + self.mdns_service_name.push('-').unwrap(); + for c in self.node_id.to_be_bytes() { let mut hex = heapless::String::<4>::new(); write!(&mut hex, "{:02X}", c).unwrap(); - mdns_service_name.push_str(&hex).unwrap(); + self.mdns_service_name.push_str(&hex).unwrap(); } - info!("MDNS Service Name: {}", mdns_service_name); - Ok(Self { - node_id, - fabric_id, - vendor_id, - key_pair, - root_ca, - icac, - noc, - ipk, - label: label.try_into().unwrap(), - mdns_service_name, - }) - } + info!("mDNS Service name: {}", self.mdns_service_name); + + mdns.add(&self.mdns_service_name, ServiceMode::Commissioned)?; + + if let Some(case_admin_subject) = case_admin_subject { + self.acl.clear(); + self.acl.push_init( + AclEntry::init(Privilege::ADMIN, AuthMode::Case) + .into_fallible() + .chain(|e| { + e.fab_idx = Some(self.fab_idx); + e.add_subject(case_admin_subject) + }), + || ErrorCode::NoSpace.into(), + )?; + } - fn get_compressed_id(root_pubkey: &[u8], fabric_id: u64, out: &mut [u8]) -> Result<(), Error> { - let root_pubkey = &root_pubkey[1..]; - let mut fabric_id_be: [u8; 8] = [0; 8]; - BigEndian::write_u64(&mut fabric_id_be, fabric_id); - const COMPRESSED_FABRIC_ID_INFO: [u8; 16] = [ - 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x46, 0x61, 0x62, 0x72, - 0x69, 0x63, - ]; - hkdf_sha256(&fabric_id_be, root_pubkey, &COMPRESSED_FABRIC_ID_INFO, out) - .map_err(|_| Error::from(ErrorCode::NoSpace)) + Ok(()) } - pub fn match_dest_id(&self, random: &[u8], target: &[u8]) -> Result<(), Error> { + /// Is the fabric matching the privided destination ID + pub fn is_dest_id(&self, random: &[u8], target: &[u8]) -> Result<(), Error> { let mut mac = HmacSha256::new(self.ipk.op_key())?; mac.update(random)?; - mac.update(self.get_root_ca()?.pubkey()?)?; + mac.update(CertRef::new(TLVElement::new(self.root_ca())).pubkey()?)?; mac.update(&self.fabric_id.to_le_bytes())?; mac.update(&self.node_id.to_le_bytes())?; @@ -147,25 +206,61 @@ impl Fabric { } } + /// Sign a message with the fabric's key pair pub fn sign_msg(&self, msg: &[u8], signature: &mut [u8]) -> Result { self.key_pair.sign_msg(msg, signature) } - pub fn get_node_id(&self) -> u64 { + /// Return the key pair of the fabric + pub fn key_pair(&self) -> &KeyPair { + &self.key_pair + } + + /// Return the fabric's node ID + pub fn node_id(&self) -> u64 { self.node_id } - pub fn get_fabric_id(&self) -> u64 { + /// Return the fabric's fabric ID + pub fn fabric_id(&self) -> u64 { self.fabric_id } - pub fn get_root_ca(&self) -> Result, Error> { - Ok(CertRef::new(TLVElement::new(&self.root_ca))) + /// Return the fabric's local index + pub fn fab_idx(&self) -> NonZeroU8 { + self.fab_idx } - pub fn get_fabric_desc<'a>( + /// Return the fabric's Root CA in encoded TLV form + /// + /// Use `CertRef` to decode on the fly + pub fn root_ca(&self) -> &[u8] { + &self.root_ca + } + + /// Return the fabric's ICAC in encoded TLV form + /// + /// Use `CertRef` to decode on the fly. + /// + /// Note that this method might return an empty slice, + /// which indicates that this fabric does not have an ICAC. + pub fn icac(&self) -> &[u8] { + &self.icac + } + + /// Return the fabric's NOC + pub fn noc(&self) -> &[u8] { + &self.noc + } + + /// Return the fabric's IPK + pub fn ipk(&self) -> &KeySet { + &self.ipk + } + + /// Return the fabric's descriptor + pub fn descriptor<'a>( &'a self, - fab_idx: NonZeroU8, root_ca_cert: &'a CertRef<'a>, ) -> Result, Error> { let desc = FabricDescriptor { @@ -174,19 +269,112 @@ impl Fabric { fabric_id: self.fabric_id, node_id: self.node_id, label: self.label.as_str(), - fab_idx, + fab_idx: self.fab_idx, }; Ok(desc) } + + /// Return an iterator over the ACL entries of the fabric + pub fn acl_iter(&self) -> impl Iterator { + self.acl.iter() + } + + /// Add a new ACL entry to the fabric. + /// + /// Return the index of the added entry. + fn acl_add(&mut self, mut entry: AclEntry) -> Result { + if entry.auth_mode() == AuthMode::Pase { + // Reserved for future use + Err(ErrorCode::ConstraintError)?; + } + + // Overwrite the fabric index with our accessing fabric index + entry.fab_idx = Some(self.fab_idx); + + self.acl.push(entry).map_err(|_| ErrorCode::NoSpace)?; + + Ok(self.acl.len() - 1) + } + + /// Update an existing ACL entry in the fabric + fn acl_update(&mut self, idx: usize, mut entry: AclEntry) -> Result<(), Error> { + if self.acl.len() <= idx { + return Err(ErrorCode::NotFound.into()); + } + + // Overwrite the fabric index with our accessing fabric index + entry.fab_idx = Some(self.fab_idx); + + self.acl[idx] = entry; + + Ok(()) + } + + /// Remove an ACL entry from the fabric + fn acl_remove(&mut self, idx: usize) -> Result<(), Error> { + if self.acl.len() <= idx { + return Err(ErrorCode::NotFound.into()); + } + + self.acl.remove(idx); + + Ok(()) + } + + /// Remove all ACL entries from the fabric + pub fn acl_remove_all(&mut self) { + // pub for tests + self.acl.clear(); + } + + /// Check if the fabric allows the given access request + /// + /// Note that the fabric index in the access request needs to be checked before that. + fn allow(&self, req: &AccessReq) -> bool { + for e in &self.acl { + if e.allow(req) { + return true; + } + } + + error!( + "ACL Disallow for subjects {} fab idx {}", + req.accessor().subjects(), + req.accessor().fab_idx + ); + + false + } + + /// Compute the compressed fabric ID + fn compute_compressed_id( + root_pubkey: &[u8], + fabric_id: u64, + out: &mut [u8], + ) -> Result<(), Error> { + let root_pubkey = &root_pubkey[1..]; + const COMPRESSED_FABRIC_ID_INFO: [u8; 16] = [ + 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x46, 0x61, 0x62, 0x72, + 0x69, 0x63, + ]; + hkdf_sha256( + &fabric_id.to_be_bytes(), + root_pubkey, + &COMPRESSED_FABRIC_ID_INFO, + out, + ) + .map_err(|_| Error::from(ErrorCode::NoSpace)) + } } +/// Max number of supported fabrics +// TODO: Make this configurable via a cargo feature pub const MAX_SUPPORTED_FABRICS: usize = 3; -type FabricEntries = Vec, MAX_SUPPORTED_FABRICS>; - +/// Fabric manager type pub struct FabricMgr { - fabrics: FabricEntries, + fabrics: Vec, changed: bool, } @@ -197,37 +385,45 @@ impl Default for FabricMgr { } impl FabricMgr { + /// Create a new Fabric Manager #[inline(always)] pub const fn new() -> Self { Self { - fabrics: FabricEntries::new(), + fabrics: Vec::new(), changed: false, } } + /// Return an in-place-initializer for a Fabric Manager pub fn init() -> impl Init { init!(Self { - fabrics <- FabricEntries::init(), + fabrics <- Vec::init(), changed: false, }) } - pub fn load(&mut self, data: &[u8], mdns: &dyn Mdns) -> Result<(), Error> { - let entries = TLVElement::new(data).array()?.iter(); + /// Removes all fabrics + pub fn reset(&mut self) { + self.fabrics.clear(); + self.changed = false; + } - for fabric in self.fabrics.iter().flatten() { + /// Load the fabrics from the provided TLV data + pub fn load(&mut self, data: &[u8], mdns: &dyn Mdns) -> Result<(), Error> { + for fabric in self.iter() { mdns.remove(&fabric.mdns_service_name)?; } - for entry in entries { + self.fabrics.clear(); + + for entry in TLVElement::new(data).array()?.iter() { let entry = entry?; self.fabrics - .push(Option::::from_tlv(&entry)?) - .map_err(|_| ErrorCode::NoSpace)?; + .push_init(Fabric::init_from_tlv(entry), || ErrorCode::NoSpace.into())?; } - for fabric in self.fabrics.iter().flatten() { + for fabric in &self.fabrics { mdns.add(&fabric.mdns_service_name, ServiceMode::Commissioned)?; } @@ -236,128 +432,285 @@ impl FabricMgr { Ok(()) } + /// Store the fabrics into the provided buffer as TLV data + /// + /// If the fabrics have not changed since the last store operation, the + /// function returns `None` and does not store the fabrics. pub fn store<'a>(&mut self, buf: &'a mut [u8]) -> Result, Error> { - if self.changed { - let mut wb = WriteBuf::new(buf); - let mut tw = TLVWriter::new(&mut wb); - - self.fabrics - .as_slice() - .to_tlv(&TagType::Anonymous, &mut tw)?; + if !self.changed { + return Ok(None); + } - self.changed = false; + let mut wb = WriteBuf::new(buf); - let len = wb.get_tail(); + wb.start_array(&TLVTag::Anonymous)?; - Ok(Some(&buf[..len])) - } else { - Ok(None) + for fabric in self.iter() { + fabric + .to_tlv(&TagType::Anonymous, &mut wb) + .map_err(|_| ErrorCode::NoSpace)?; } + + wb.end_container()?; + + self.changed = false; + + let len = wb.get_tail(); + + Ok(Some(&buf[..len])) } + /// Check if the fabrics have changed since the last store operation pub fn is_changed(&self) -> bool { self.changed } - pub fn add(&mut self, f: Fabric, mdns: &dyn Mdns) -> Result { - // Do not re-use slots (if possible) because currently we use the - // position of the fabric in the array as a `fabric_index` as per the Matter Core spec - // TODO: In future introduce a new field in Fabric to store the fabric index, as - // we do for session indexes. - let slot = (self.fabrics.len() == MAX_SUPPORTED_FABRICS) - .then(|| self.fabrics.iter().position(|x| x.is_none())) - .flatten(); - - if slot.is_some() || self.fabrics.len() < MAX_SUPPORTED_FABRICS { - mdns.add(&f.mdns_service_name, ServiceMode::Commissioned)?; - self.changed = true; - - if let Some(index) = slot { - self.fabrics[index] = Some(f); - - // Unwrapping is safe because we explicitly add + 1 here - Ok(NonZeroU8::new(index as u8 + 1).unwrap()) - } else { - self.fabrics - .push(Some(f)) - .map_err(|_| ErrorCode::NoSpace) - .unwrap(); - - // Unwrapping is safe because we just added the entry - Ok(NonZeroU8::new(self.fabrics.len() as u8).unwrap()) - } + /// Add a new fabric to the manager with the provided data and immediately updates it with the provided post-init updater. + /// + /// This method is unlikely to be useful outside of tests. + /// + /// If this operation succeeds, the fabric immediately becomes operational. + pub fn add_with_post_init( + &mut self, + key_pair: KeyPair, + post_init: F, + ) -> Result<&mut Fabric, Error> + where + F: FnOnce(&mut Fabric) -> Result<(), Error>, + { + let max_fab_idx = self + .iter() + .map(|fabric| fabric.fab_idx().get()) + .max() + .unwrap_or(0); + let fab_idx = NonZeroU8::new(if max_fab_idx < u8::MAX - 1 { + // First try with the next available fabric index larger than all currently used + max_fab_idx + 1 } else { - Err(ErrorCode::NoSpace.into()) + // If there is already a fabric with index 254, try to find the first unused one + let Some(fab_idx) = (1..u8::MAX) + .find(|fab_idx| self.iter().all(|fabric| fabric.fab_idx().get() != *fab_idx)) + else { + return Err(ErrorCode::NoSpace.into()); + }; + + fab_idx + }) + .unwrap(); // We never use 0 as a fabric index, nor u8::MAX + + self.fabrics.push_init( + Fabric::init(fab_idx, key_pair) + .into_fallible::() + .chain(post_init), + || ErrorCode::NoSpace.into(), + )?; + + let fabric = self.fabrics.last_mut().unwrap(); + self.changed = true; + + Ok(fabric) + } + + /// Add a new fabric to the manager with the provided data. + /// + /// If this operation succeeds, the fabric immediately becomes operational. + #[allow(clippy::too_many_arguments)] + pub fn add( + &mut self, + key_pair: KeyPair, + root_ca: &[u8], + noc: &[u8], + icac: &[u8], + ipk: &[u8], + vendor_id: u16, + case_admin_subject: u64, + mdns: &dyn Mdns, + ) -> Result<&mut Fabric, Error> { + self.add_with_post_init(key_pair, |fabric| { + fabric.update( + root_ca, + noc, + icac, + ipk, + vendor_id, + Some(case_admin_subject), + mdns, + ) + }) + } + + /// Update an existing fabric with the provided data (usually, as a result of an `UpdateNOC` IM command). + /// + /// If this operation succeeds, the fabric immediately becomes operational. + /// Note however, that the caller is expected to remove all sessions associated with the fabric, as they would + /// contain invalid keys after the NOC update. + #[allow(clippy::too_many_arguments)] + pub fn update( + &mut self, + fab_idx: NonZeroU8, + key_pair: KeyPair, + root_ca: &[u8], + noc: &[u8], + icac: &[u8], + ipk: &[u8], + vendor_id: u16, + mdns: &dyn Mdns, + ) -> Result<&mut Fabric, Error> { + let Some(fabric) = self + .fabrics + .iter_mut() + .find(|fabric| fabric.fab_idx == fab_idx) + else { + return Err(ErrorCode::NotFound.into()); + }; + + fabric.key_pair = key_pair; + + fabric.update(root_ca, noc, icac, ipk, vendor_id, None, mdns)?; + + self.changed = true; + + Ok(fabric) + } + + pub fn update_label(&mut self, fab_idx: NonZeroU8, label: &str) -> Result<(), Error> { + if self.iter().any(|fabric| { + fabric.fab_idx != fab_idx && !fabric.label.is_empty() && fabric.label == label + }) { + return Err(ErrorCode::Invalid.into()); } + + let fabric = self.get_mut(fab_idx).ok_or(ErrorCode::NotFound)?; + fabric.label.clear(); + fabric + .label + .push_str(label) + .map_err(|_| ErrorCode::NoSpace)?; + + Ok(()) } + /// Remove a fabric from the manager pub fn remove(&mut self, fab_idx: NonZeroU8, mdns: &dyn Mdns) -> Result<(), Error> { - if fab_idx.get() as usize <= self.fabrics.len() { - if let Some(f) = self.fabrics[(fab_idx.get() - 1) as usize].take() { - mdns.remove(&f.mdns_service_name)?; - self.changed = true; - Ok(()) - } else { - Err(ErrorCode::NotFound.into()) - } - } else { - Err(ErrorCode::NotFound.into()) - } + let Some(fabric) = self.get(fab_idx) else { + return Ok(()); + }; + + mdns.remove(&fabric.mdns_service_name)?; + + self.fabrics.retain(|fabric| fabric.fab_idx != fab_idx); + + Ok(()) } - pub fn match_dest_id(&self, random: &[u8], target: &[u8]) -> Result { - for (index, fabric) in self.fabrics.iter().enumerate() { - if let Some(fabric) = fabric { - if fabric.match_dest_id(random, target).is_ok() { - // Unwrapping is safe because we explicitly add + 1 here - return Ok(NonZeroU8::new(index as u8 + 1).unwrap()); - } - } - } - Err(ErrorCode::NotFound.into()) + /// Get a fabric that matches the provided destination ID + pub fn get_by_dest_id(&self, random: &[u8], target: &[u8]) -> Option<&Fabric> { + self.iter() + .find(|fabric| fabric.is_dest_id(random, target).is_ok()) } - pub fn get_fabric(&self, idx: NonZeroU8) -> Option<&Fabric> { - self.fabrics[idx.get() as usize - 1].as_ref() + /// Get a fabric by its local index + pub fn get(&self, fab_idx: NonZeroU8) -> Option<&Fabric> { + self.iter().find(|fabric| fabric.fab_idx == fab_idx) } - pub fn is_empty(&self) -> bool { - !self.fabrics.iter().any(Option::is_some) + /// Get a mutable fabric reference by its local index + pub fn get_mut(&mut self, fab_idx: NonZeroU8) -> Option<&mut Fabric> { + // pub for testing + self.fabrics + .iter_mut() + .find(|fabric| fabric.fab_idx == fab_idx) } - pub fn used_count(&self) -> usize { - self.fabrics.iter().filter(|f| f.is_some()).count() + /// Iterate over the fabrics + pub fn iter(&self) -> impl Iterator { + self.fabrics.iter() } - // Parameters to T are the Fabric and its Fabric Index - pub fn for_each(&self, mut f: T) -> Result<(), Error> - where - T: FnMut(&Fabric, NonZeroU8) -> Result<(), Error>, - { - for (index, fabric) in self.fabrics.iter().enumerate() { - if let Some(fabric) = fabric { - f(fabric, NonZeroU8::new(index as u8 + 1).unwrap())?; - } + /// Check if the given access request should be allowed, based on all operational fabrics + /// and their ACLs + pub fn allow(&self, req: &AccessReq) -> bool { + // PASE Sessions with no fabric index have implicit access grant, + // but only as long as the ACL list is empty + // + // As per the spec: + // The Access Control List is able to have an initial entry added because the Access Control Privilege + // Granting algorithm behaves as if, over a PASE commissioning channel during the commissioning + // phase, the following implicit Access Control Entry were present on the Commissionee (but not on + // the Commissioner): + // Access Control Cluster: { + // ACL: [ + // 0: { + // // implicit entry only; does not explicitly exist! + // FabricIndex: 0, // not fabric-specific + // Privilege: Administer, + // AuthMode: PASE, + // Subjects: [], + // Targets: [] // entire node + // } + // ], + // Extension: [] + // } + if req.accessor().auth_mode() == AuthMode::Pase { + return true; } + + let Some(fab_idx) = NonZeroU8::new(req.accessor().fab_idx) else { + return false; + }; + + let Some(fabric) = self.get(fab_idx) else { + return false; + }; + + fabric.allow(req) + } + + /// Add a new ACL entry to the fabric with the provided local index + /// + /// Return the index of the added entry. + pub fn acl_add(&mut self, fab_idx: NonZeroU8, entry: AclEntry) -> Result { + let index = self + .get_mut(fab_idx) + .ok_or(ErrorCode::NotFound)? + .acl_add(entry)?; + self.changed = true; + + Ok(index) + } + + /// Update an existing ACL entry in the fabric with the provided local index + pub fn acl_update( + &mut self, + fab_idx: NonZeroU8, + idx: usize, + entry: AclEntry, + ) -> Result<(), Error> { + self.get_mut(fab_idx) + .ok_or(ErrorCode::NotFound)? + .acl_update(idx, entry)?; + self.changed = true; + Ok(()) } - pub fn set_label(&mut self, index: NonZeroU8, label: &str) -> Result<(), Error> { - if !label.is_empty() - && self - .fabrics - .iter() - .filter_map(|f| f.as_ref()) - .any(|f| f.label == label) - { - return Err(ErrorCode::Invalid.into()); - } + /// Remove an ACL entry from the fabric with the provided local index + pub fn acl_remove(&mut self, fab_idx: NonZeroU8, idx: usize) -> Result<(), Error> { + self.get_mut(fab_idx) + .ok_or(ErrorCode::NotFound)? + .acl_remove(idx)?; + self.changed = true; + + Ok(()) + } + + /// Remove all ACL entries from the fabric with the provided local index + pub fn acl_remove_all(&mut self, fab_idx: NonZeroU8) -> Result<(), Error> { + self.get_mut(fab_idx) + .ok_or(ErrorCode::NotFound)? + .acl_remove_all(); + self.changed = true; - let index = (index.get() - 1) as usize; - if let Some(fabric) = &mut self.fabrics[index] { - fabric.label = label.try_into().unwrap(); - self.changed = true; - } Ok(()) } } diff --git a/rs-matter/src/interaction_model/core.rs b/rs-matter/src/interaction_model/core.rs index 897509a1..fc12bef9 100644 --- a/rs-matter/src/interaction_model/core.rs +++ b/rs-matter/src/interaction_model/core.rs @@ -80,6 +80,8 @@ impl From for IMStatusCode { ErrorCode::Busy => IMStatusCode::Busy, ErrorCode::DataVersionMismatch => IMStatusCode::DataVersionMismatch, ErrorCode::ResourceExhausted => IMStatusCode::ResourceExhausted, + ErrorCode::FailSafeRequired => IMStatusCode::FailSafeRequired, + ErrorCode::ConstraintError => IMStatusCode::ConstraintError, _ => IMStatusCode::Failure, } } @@ -151,27 +153,29 @@ pub const PROTO_ID_INTERACTION_MODEL: u16 = 0x01; pub enum ReportDataReq<'a> { Read(&'a ReadReqRef<'a>), Subscribe(&'a SubscribeReqRef<'a>), + SubscribeReport(&'a SubscribeReqRef<'a>), } impl<'a> ReportDataReq<'a> { pub fn attr_requests(&self) -> Result>, Error> { match self { - ReportDataReq::Read(req) => req.attr_requests(), - ReportDataReq::Subscribe(req) => req.attr_requests(), + Self::Read(req) => req.attr_requests(), + Self::Subscribe(req) | Self::SubscribeReport(req) => req.attr_requests(), } } pub fn dataver_filters(&self) -> Result>, Error> { match self { - ReportDataReq::Read(req) => req.dataver_filters(), - ReportDataReq::Subscribe(req) => req.dataver_filters(), + Self::Read(req) => req.dataver_filters(), + Self::Subscribe(req) => req.dataver_filters(), + Self::SubscribeReport(_) => Ok(None), } } pub fn fabric_filtered(&self) -> Result { match self { - ReportDataReq::Read(req) => req.fabric_filtered(), - ReportDataReq::Subscribe(req) => req.fabric_filtered(), + Self::Read(req) => req.fabric_filtered(), + Self::Subscribe(req) | Self::SubscribeReport(req) => req.fabric_filtered(), } } } diff --git a/rs-matter/src/interaction_model/messages.rs b/rs-matter/src/interaction_model/messages.rs index 1ab36089..1d51dc41 100644 --- a/rs-matter/src/interaction_model/messages.rs +++ b/rs-matter/src/interaction_model/messages.rs @@ -601,7 +601,7 @@ pub mod ib { where F: FnMut(ListOperation, &TLVElement) -> Result<(), Error>, { - if let Some(Some(index)) = attr.list_index.map(Into::into) { + if let Some(Some(index)) = attr.list_index.clone().map(Into::into) { // If list index is valid, // - this is a modify item or delete item operation if data.null().is_ok() { diff --git a/rs-matter/src/persist.rs b/rs-matter/src/persist.rs index 4936c544..84d9d44a 100644 --- a/rs-matter/src/persist.rs +++ b/rs-matter/src/persist.rs @@ -58,11 +58,6 @@ pub mod fileio { pub fn load(&mut self, dir: &Path, matter: &Matter) -> Result<(), Error> { fs::create_dir_all(dir)?; - if let Some(data) = Self::load_key(dir, "acls", unsafe { self.buf.assume_init_mut() })? - { - matter.load_acls(data)?; - } - if let Some(data) = Self::load_key(dir, "fabrics", unsafe { self.buf.assume_init_mut() })? { @@ -73,13 +68,9 @@ pub mod fileio { } pub fn store(&mut self, dir: &Path, matter: &Matter) -> Result<(), Error> { - if matter.is_changed() { + if matter.fabrics_changed() { fs::create_dir_all(dir)?; - if let Some(data) = matter.store_acls(unsafe { self.buf.assume_init_mut() })? { - Self::store_key(dir, "acls", data)?; - } - if let Some(data) = matter.store_fabrics(unsafe { self.buf.assume_init_mut() })? { Self::store_key(dir, "fabrics", data)?; } @@ -98,7 +89,7 @@ pub mod fileio { self.load(dir, matter)?; loop { - matter.wait_changed().await; + matter.wait_fabrics_changed().await; self.store(dir, matter)?; } diff --git a/rs-matter/src/secure_channel/case.rs b/rs-matter/src/secure_channel/case.rs index 01630be0..3705281d 100644 --- a/rs-matter/src/secure_channel/case.rs +++ b/rs-matter/src/secure_channel/case.rs @@ -31,7 +31,11 @@ use crate::{ exchange::Exchange, session::{NocCatIds, ReservedSession, SessionMode}, }, - utils::{init::InitMaybeUninit, rand::Rand, storage::WriteBuf}, + utils::{ + init::{init, zeroed, Init, InitMaybeUninit}, + rand::Rand, + storage::WriteBuf, + }, }; #[derive(Debug, Clone)] @@ -64,6 +68,18 @@ impl CaseSession { local_fabric_idx: 0, } } + + pub fn init() -> impl Init { + init!(Self { + peer_sessid: 0, + local_sessid: 0, + tt_hash: None, + shared_secret <- zeroed(), + our_pub_key <- zeroed(), + peer_pub_key <- zeroed(), + local_fabric_idx: 0, + }) + } } pub struct Case(()); @@ -89,7 +105,7 @@ impl Case { .await?; exchange.acknowledge().await?; - exchange.matter().notify_changed(); + exchange.matter().notify_fabrics_maybe_changed(); Ok(()) } @@ -106,7 +122,7 @@ impl Case { let fabric_mgr = exchange.matter().fabric_mgr.borrow(); let fabric = NonZeroU8::new(case_session.local_fabric_idx) - .and_then(|fabric_idx| fabric_mgr.get_fabric(fabric_idx)); + .and_then(|fabric_idx| fabric_mgr.get(fabric_idx)); if let Some(fabric) = fabric { let root = get_root_node_struct(exchange.rx()?.payload())?; let encrypted = root.structure()?.ctx(1)?.str()?; @@ -120,7 +136,7 @@ impl Case { decrypted.copy_from_slice(encrypted); let len = - Case::get_sigma3_decryption(fabric.ipk.op_key(), case_session, decrypted)?; + Case::get_sigma3_decryption(fabric.ipk().op_key(), case_session, decrypted)?; let decrypted = &decrypted[..len]; let root = get_root_node_struct(decrypted)?; @@ -131,14 +147,11 @@ impl Case { .initiator_icac .map(|icac| CertRef::new(TLVElement::new(icac.0))); - let mut validate_certs_buf = alloc!([0; 800]); // TODO LARGE BUFFER - let validate_certs_buf = &mut validate_certs_buf[..]; - if let Err(e) = Case::validate_certs( - fabric, - &initiator_noc, - initiator_icac.as_ref(), - validate_certs_buf, - ) { + let mut buf = alloc!([0; 800]); // TODO LARGE BUFFER + let buf = &mut buf[..]; + if let Err(e) = + Case::validate_certs(fabric, &initiator_noc, initiator_icac.as_ref(), buf) + { error!("Certificate Chain doesn't match: {}", e); SCStatusCodes::InvalidParameter } else if let Err(e) = Case::validate_sigma3_sign( @@ -147,6 +160,7 @@ impl Case { &initiator_noc, d.signature.0, case_session, + buf, ) { error!("Sigma3 Signature doesn't match: {}", e); SCStatusCodes::InvalidParameter @@ -164,7 +178,7 @@ impl Case { MaybeUninit::<[u8; 3 * crypto::SYMM_KEY_LEN_BYTES]>::uninit(); // TODO MEDIM BUFFER let session_keys = session_keys.init_zeroed(); Case::get_session_keys( - fabric.ipk.op_key(), + fabric.ipk().op_key(), case_session.tt_hash.as_ref().unwrap(), &case_session.shared_secret, session_keys, @@ -173,7 +187,7 @@ impl Case { let peer_addr = exchange.with_session(|sess| Ok(sess.get_peer_addr()))?; session.update( - fabric.get_node_id(), + fabric.node_id(), initiator_noc.get_node_id()?, case_session.peer_sessid, case_session.local_sessid, @@ -220,9 +234,10 @@ impl Case { let local_fabric_idx = exchange .matter() .fabric_mgr - .borrow_mut() - .match_dest_id(r.initiator_random.0, r.dest_id.0); - if local_fabric_idx.is_err() { + .borrow() + .get_by_dest_id(r.initiator_random.0, r.dest_id.0) + .map(|fabric| fabric.fab_idx()); + if local_fabric_idx.is_none() { error!("Fabric Index mismatch"); complete_with_status(exchange, SCStatusCodes::NoSharedTrustRoots, &[]).await?; @@ -243,7 +258,7 @@ impl Case { .as_mut() .unwrap() .update(exchange.rx()?.payload())?; - case_session.local_fabric_idx = local_fabric_idx?.get(); + case_session.local_fabric_idx = local_fabric_idx.unwrap().get(); if r.peer_pub_key.0.len() != crypto::EC_POINT_LEN_BYTES { error!("Invalid public key length"); Err(ErrorCode::Invalid)?; @@ -276,7 +291,7 @@ impl Case { let fabric_mgr = exchange.matter().fabric_mgr.borrow(); let fabric = NonZeroU8::new(case_session.local_fabric_idx) - .and_then(|fabric_idx| fabric_mgr.get_fabric(fabric_idx)); + .and_then(|fabric_idx| fabric_mgr.get(fabric_idx)); let Some(fabric) = fabric else { return sc_write(tw, SCStatusCodes::NoSharedTrustRoots, &[]); @@ -335,10 +350,9 @@ impl Case { initiator_noc_cert: &CertRef, sign: &[u8], case_session: &CaseSession, + buf: &mut [u8], ) -> Result<(), Error> { - const MAX_TBS_SIZE: usize = 800; - let mut buf = [0; MAX_TBS_SIZE]; - let mut write_buf = WriteBuf::new(&mut buf); + let mut write_buf = WriteBuf::new(buf); let tw = &mut write_buf; tw.start_struct(&TLVTag::Anonymous)?; tw.str(&TLVTag::Context(1), initiator_noc)?; @@ -362,14 +376,14 @@ impl Case { ) -> Result<(), Error> { let mut verifier = noc.verify_chain_start(); - if fabric.get_fabric_id() != noc.get_fabric_id()? { + if fabric.fabric_id() != noc.get_fabric_id()? { Err(ErrorCode::Invalid)?; } if let Some(icac) = icac { // If ICAC is present handle it if let Ok(fid) = icac.get_fabric_id() { - if fid != fabric.get_fabric_id() { + if fid != fabric.fabric_id() { Err(ErrorCode::Invalid)?; } } @@ -377,7 +391,7 @@ impl Case { } verifier - .add_cert(&CertRef::new(TLVElement::new(&fabric.root_ca)), buf)? + .add_cert(&CertRef::new(TLVElement::new(fabric.root_ca())), buf)? .finalise(buf)?; Ok(()) } @@ -501,7 +515,7 @@ impl Case { let mut sigma2_key = [0_u8; crypto::SYMM_KEY_LEN_BYTES]; Case::get_sigma2_key( - fabric.ipk.op_key(), + fabric.ipk().op_key(), our_random, case_session, &mut sigma2_key, @@ -510,9 +524,9 @@ impl Case { let mut write_buf = WriteBuf::new(out); let tw = &mut write_buf; tw.start_struct(&TLVTag::Anonymous)?; - tw.str(&TLVTag::Context(1), &fabric.noc)?; - if let Some(icac_cert) = fabric.icac.as_ref() { - tw.str(&TLVTag::Context(2), icac_cert)? + tw.str(&TLVTag::Context(1), fabric.noc())?; + if !fabric.icac().is_empty() { + tw.str(&TLVTag::Context(2), fabric.icac())? }; tw.str(&TLVTag::Context(3), signature)?; @@ -550,9 +564,9 @@ impl Case { let mut write_buf = WriteBuf::new(buf); let tw = &mut write_buf; tw.start_struct(&TLVTag::Anonymous)?; - tw.str(&TLVTag::Context(1), &fabric.noc)?; - if let Some(icac_cert) = fabric.icac.as_deref() { - tw.str(&TLVTag::Context(2), icac_cert)?; + tw.str(&TLVTag::Context(1), fabric.noc())?; + if !fabric.icac().is_empty() { + tw.str(&TLVTag::Context(2), fabric.icac())?; } tw.str(&TLVTag::Context(3), our_pub_key)?; tw.str(&TLVTag::Context(4), peer_pub_key)?; diff --git a/rs-matter/src/secure_channel/core.rs b/rs-matter/src/secure_channel/core.rs index 6dc3069f..b93c5d77 100644 --- a/rs-matter/src/secure_channel/core.rs +++ b/rs-matter/src/secure_channel/core.rs @@ -15,14 +15,16 @@ * limitations under the License. */ +use core::mem::MaybeUninit; + use log::error; use crate::{ - alloc, error::*, respond::ExchangeHandler, secure_channel::{common::*, pake::Pake}, transport::exchange::Exchange, + utils::init::InitMaybeUninit, }; use super::{ @@ -53,12 +55,14 @@ impl SecureChannel { match meta.opcode()? { OpCode::PBKDFParamRequest => { - let mut spake2p = alloc!(Spake2P::new()); // TODO LARGE BUFFER - Pake::new().handle(exchange, &mut spake2p).await + let mut spake2p = MaybeUninit::uninit(); // TODO LARGE BUFFER + let spake2p = spake2p.init_with(Spake2P::init()); + Pake::new().handle(exchange, spake2p).await } OpCode::CASESigma1 => { - let mut case_session = alloc!(CaseSession::new()); // TODO LARGE BUFFER - Case::new().handle(exchange, &mut case_session).await + let mut case_session = MaybeUninit::uninit(); // TODO LARGE BUFFER + let case_session = case_session.init_with(CaseSession::init()); + Case::new().handle(exchange, case_session).await } opcode => { error!("Invalid opcode: {:?}", opcode); diff --git a/rs-matter/src/secure_channel/pake.rs b/rs-matter/src/secure_channel/pake.rs index d999914e..2da65abf 100644 --- a/rs-matter/src/secure_channel/pake.rs +++ b/rs-matter/src/secure_channel/pake.rs @@ -181,7 +181,9 @@ impl Pake { self.handle_pasepake3(exchange, session, spake2p).await?; exchange.acknowledge().await?; - exchange.matter().notify_changed(); + exchange.matter().notify_fabrics_maybe_changed(); + + self.clear_timeout(exchange); Ok(()) } @@ -362,6 +364,12 @@ impl Pake { .await } + fn clear_timeout(&mut self, exchange: &Exchange) { + let mut pase = exchange.matter().pase_mgr.borrow_mut(); + + pase.timeout = None; + } + async fn update_timeout( &mut self, exchange: &mut Exchange<'_>, diff --git a/rs-matter/src/secure_channel/spake2p.rs b/rs-matter/src/secure_channel/spake2p.rs index 05219e4d..cfd40e6c 100644 --- a/rs-matter/src/secure_channel/spake2p.rs +++ b/rs-matter/src/secure_channel/spake2p.rs @@ -15,18 +15,16 @@ * limitations under the License. */ -use crate::{ - crypto::{self, HmacSha256}, - utils::rand::Rand, -}; use byteorder::{ByteOrder, LittleEndian}; + use log::error; + use subtle::ConstantTimeEq; -use crate::{ - crypto::{pbkdf2_hmac, Sha256}, - error::{Error, ErrorCode}, -}; +use crate::crypto::{self, pbkdf2_hmac, HmacSha256, Sha256}; +use crate::error::{Error, ErrorCode}; +use crate::utils::init::{init, zeroed, Init}; +use crate::utils::rand::Rand; use super::{common::SCStatusCodes, crypto::CryptoSpake2}; @@ -134,7 +132,7 @@ impl VerifierData { impl Spake2P { pub const fn new() -> Self { - Spake2P { + Self { mode: Spake2Mode::Unknown, context: None, crypto_spake2: None, @@ -144,6 +142,18 @@ impl Spake2P { } } + #[allow(non_snake_case)] + pub fn init() -> impl Init { + init!(Self { + mode: Spake2Mode::Unknown, + context: None, + crypto_spake2: None, + Ke <- zeroed(), + cA <- zeroed(), + app_data: 0, + }) + } + pub fn set_app_data(&mut self, data: u32) { self.app_data = data; } diff --git a/rs-matter/src/tlv/read.rs b/rs-matter/src/tlv/read.rs index 995c2258..cf485027 100644 --- a/rs-matter/src/tlv/read.rs +++ b/rs-matter/src/tlv/read.rs @@ -77,7 +77,7 @@ pub struct TLVElement<'a>(TLVSequence<'a>); impl<'a> TLVElement<'a> { /// Create a new `TLVElement` from a byte slice, where the byte slice contains an encoded TLV stream (a TLV element). #[inline(always)] - pub fn new(data: &'a [u8]) -> Self { + pub const fn new(data: &'a [u8]) -> Self { Self(TLVSequence(data)) } diff --git a/rs-matter/src/transport/core.rs b/rs-matter/src/transport/core.rs index 04c0a7a1..a0f6a038 100644 --- a/rs-matter/src/transport/core.rs +++ b/rs-matter/src/transport/core.rs @@ -814,8 +814,6 @@ impl<'m> TransportMgr<'m> { // No existing session: we either have to create one, or return an error - let mut error_code = ErrorCode::NoSession; - if !packet.header.plain.is_encrypted() { // Unencrypted packets can be decoded without a session, and we need to anyway do that // in order to determine (based on proto hdr data) whether to create a new session or not @@ -829,22 +827,18 @@ impl<'m> TransportMgr<'m> { // As per spec, new unencrypted sessions are only created for // `PBKDFParamRequest` or `CASESigma1` unencrypted messages - if let Some(session) = - session_mgr.add(false, packet.peer, packet.header.plain.get_src_nodeid()) - { - // Session created successfully: decode, indicate packet payload slice and process further - return session.post_recv(&packet.header, epoch); - } else { - // We tried to create a new PASE session, but there was no space - error_code = ErrorCode::NoSpaceSessions; - } + let session = + session_mgr.add(false, packet.peer, packet.header.plain.get_src_nodeid())?; + + // Session created successfully: decode, indicate packet payload slice and process further + return session.post_recv(&packet.header, epoch); } } else { // Packet cannot be decoded, set packet payload to empty set_payload(packet, (0, 0)); } - Err(error_code.into()) + Err(ErrorCode::NoSession.into()) } fn encode_packet( diff --git a/rs-matter/src/transport/exchange.rs b/rs-matter/src/transport/exchange.rs index 5ec3d10a..cd26ea5d 100644 --- a/rs-matter/src/transport/exchange.rs +++ b/rs-matter/src/transport/exchange.rs @@ -213,7 +213,7 @@ impl ExchangeId { fn accessor<'a>(&self, matter: &'a Matter<'a>) -> Result, Error> { self.with_session(matter, |sess| { - Ok(Accessor::for_session(sess, &matter.acl_mgr)) + Ok(Accessor::for_session(sess, &matter.fabric_mgr)) }) } diff --git a/rs-matter/src/transport/session.rs b/rs-matter/src/transport/session.rs index e0891268..7dfc0144 100644 --- a/rs-matter/src/transport/session.rs +++ b/rs-matter/src/transport/session.rs @@ -21,13 +21,12 @@ use core::time::Duration; use log::{error, info, trace, warn}; -use crate::data_model::sdm::noc::NocData; use crate::error::*; use crate::transport::exchange::ExchangeId; use crate::transport::mrp::ReliableMessage; use crate::utils::cell::RefCell; use crate::utils::epoch::Epoch; -use crate::utils::init::{init, Init}; +use crate::utils::init::{init, zeroed, Init, IntoFallibleInit}; use crate::utils::rand::Rand; use crate::utils::storage::{ParseBuf, WriteBuf}; use crate::Matter; @@ -89,8 +88,7 @@ pub struct Session { msg_ctr: u32, rx_ctr_state: RxCtrState, mode: SessionMode, - data: Option, - pub(crate) exchanges: heapless::Vec, MAX_EXCHANGES>, + pub(crate) exchanges: crate::utils::storage::Vec, MAX_EXCHANGES>, last_use: Duration, reserved: bool, } @@ -118,26 +116,36 @@ impl Session { msg_ctr: Self::rand_msg_ctr(rand), rx_ctr_state: RxCtrState::new(0), mode: SessionMode::PlainText, - data: None, - exchanges: heapless::Vec::new(), + exchanges: crate::utils::storage::Vec::new(), last_use: epoch(), } } - pub fn set_noc_data(&mut self, data: NocData) { - self.data = Some(data); - } - - pub fn clear_noc_data(&mut self) { - self.data = None; - } - - pub fn get_noc_data(&mut self) -> Option<&mut NocData> { - self.data.as_mut() - } - - pub fn take_noc_data(&mut self) -> Option { - self.data.take() + pub fn init( + id: u32, + reserved: bool, + peer_addr: Address, + peer_nodeid: Option, + epoch: Epoch, + rand: Rand, + ) -> impl Init { + init!(Self { + id, + reserved, + peer_addr, + local_nodeid: 0, + peer_nodeid, + dec_key <- zeroed(), + enc_key <- zeroed(), + att_challenge <- zeroed(), + peer_sess_id: 0, + local_sess_id: 0, + msg_ctr: Self::rand_msg_ctr(rand), + rx_ctr_state: RxCtrState::new(0), + mode: SessionMode::PlainText, + exchanges: crate::utils::storage::Vec::new(), + last_use: epoch(), + }) } pub fn get_local_sess_id(&self) -> u16 { @@ -450,10 +458,7 @@ impl<'a> ReservedSession<'a> { pub fn reserve_now(matter: &'a Matter<'a>) -> Result { let mut mgr = matter.transport_mgr.session_mgr.borrow_mut(); - let id = mgr - .add(true, Address::new(), None) - .ok_or(ErrorCode::NoSpaceSessions)? - .id; + let id = mgr.add(true, Address::new(), None)?.id; Ok(Self { id, @@ -643,7 +648,7 @@ impl SessionMgr { reserved: bool, peer_addr: Address, peer_nodeid: Option, - ) -> Option<&mut Session> { + ) -> Result<&mut Session, Error> { let session_id = self.next_sess_unique_id; self.next_sess_unique_id += 1; @@ -652,7 +657,7 @@ impl SessionMgr { self.next_sess_unique_id = 0; } - let session = Session::new( + let session = Session::init( session_id, reserved, peer_addr, @@ -661,9 +666,12 @@ impl SessionMgr { self.rand, ); - self.sessions.push(session).ok()?; + self.sessions + .push_init(session.into_fallible::(), || { + ErrorCode::NoSpaceSessions.into() + })?; - Some(self.sessions.last_mut().unwrap()) + Ok(self.sessions.last_mut().unwrap()) } /// This assumes that the higher layer has taken care of doing anything required diff --git a/rs-matter/src/utils/init.rs b/rs-matter/src/utils/init.rs index 68b12c7d..25290c8f 100644 --- a/rs-matter/src/utils/init.rs +++ b/rs-matter/src/utils/init.rs @@ -46,14 +46,6 @@ pub trait IntoFallibleInit: Init { impl IntoFallibleInit for I where I: Init {} -/// An extension trait for re-setting an already instantiated `T` with the given initializer. -pub trait ApplyInit: Init { - fn apply(self, to: &mut T) -> Result<(), E> { - unsafe { Self::__init(self, to as *mut T) } - } -} - -impl ApplyInit for I where I: Init {} /// An extension trait for retrofitting `UnsafeCell` with an initializer. pub trait UnsafeCellInit { /// Create a new in-place initializer for `UnsafeCell` diff --git a/rs-matter/src/utils/maybe.rs b/rs-matter/src/utils/maybe.rs index b79cf206..9eef0605 100644 --- a/rs-matter/src/utils/maybe.rs +++ b/rs-matter/src/utils/maybe.rs @@ -101,6 +101,40 @@ impl Maybe { } } + /// Sets the `Maybe` value to "none". + pub fn clear(&mut self) { + if self.some { + unsafe { + let slot = addr_of_mut!(*self); + + addr_of_mut!((*slot).some).write(false); + + let value = addr_of_mut!((*slot).value) as *mut T; + + core::ptr::drop_in_place(value); + } + } + } + + /// Re-initialize the `Maybe` value with a new in-place initializer. + pub fn reinit>(&mut self, value: I) { + // Unwrap is safe because the initializer is infallible + Self::try_reinit(self, value).unwrap(); + } + + /// Try to re-initialize the `Maybe` value with a new in-place initializer. + /// + /// If the re-initialization fails, the `Maybe` value is left to `none`. + pub fn try_reinit, E>(&mut self, value: I) -> Result<(), E> { + self.clear(); + + unsafe { + let slot = addr_of_mut!(*self); + + value.__init(slot) + } + } + /// Return a mutable reference to the wrapped value, if it exists. pub fn as_mut(&mut self) -> Option<&mut T> { if self.some { @@ -146,12 +180,21 @@ impl Maybe { /// Note that this method is not efficient when the wrapped value is large /// (might result in big stack memory usage due to moves), hence its usage /// is not recommended when the wrapped value is large. - pub fn into_option(self) -> Option { - if self.some { - Some(unsafe { self.value.assume_init() }) - } else { - None + pub fn into_option(mut self) -> Option { + if !self.some { + return None; } + + Some(unsafe { + let slot = addr_of_mut!(self); + + let ret = core::ptr::read(addr_of_mut!((*slot).value) as *mut _); + + // So that `T` is not double-dropped on dtor + self.some = false; + + ret + }) } /// Return whether the `Maybe` value is empty. @@ -165,6 +208,15 @@ impl Maybe { } } +impl Drop for Maybe { + fn drop(&mut self) { + // Explicit drop to ensure that the wrapped value is dropped + // The compiler won't drop it automatically, because it is tracked as `MaybeUninit` + // (even if it is initialized in the meantime, i.e. `self.some == true`) + self.clear(); + } +} + impl Default for Maybe { fn default() -> Self { Self::none() @@ -192,8 +244,6 @@ where } } -impl Copy for Maybe where T: Copy {} - impl PartialEq for Maybe where T: PartialEq, @@ -213,3 +263,97 @@ where self.as_ref().hash(state) } } + +#[cfg(test)] +mod tests { + use super::Maybe; + + macro_rules! droppable { + () => { + static COUNT: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0); + + #[derive(Eq, Ord, PartialEq, PartialOrd)] + struct Droppable(()); + + impl Droppable { + fn new() -> Self { + COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + Droppable(()) + } + + fn count() -> i32 { + COUNT.load(core::sync::atomic::Ordering::Relaxed) + } + } + + impl Drop for Droppable { + fn drop(&mut self) { + COUNT.fetch_sub(1, core::sync::atomic::Ordering::Relaxed); + } + } + + impl Clone for Droppable { + fn clone(&self) -> Self { + COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + + Self(()) + } + } + }; + } + + #[test] + fn drop() { + droppable!(); + + // Test dropping none + + assert_eq!(Droppable::count(), 0); + + { + let _m: Maybe = Maybe::none(); + } + + assert_eq!(Droppable::count(), 0); + + // Test dropping some + + { + let _m: Maybe = Maybe::some(Droppable::new()); + } + + assert_eq!(Droppable::count(), 0); + + // Test `into_option` destructuring + { + let m: Maybe = Maybe::some(Droppable::new()); + m.into_option(); + } + + assert_eq!(Droppable::count(), 0); + + // Test clone semantics w.r.t. drop + + { + let m: Maybe = Maybe::some(Droppable::new()); + + let _m2 = m.clone(); + + core::mem::drop(m); + + assert_eq!(Droppable::count(), 1); + } + + assert_eq!(Droppable::count(), 0); + + // Test clear semantics w.r.t. drop + + { + let mut m: Maybe = Maybe::some(Droppable::new()); + + m.clear(); + } + + assert_eq!(Droppable::count(), 0); + } +} diff --git a/rs-matter/tests/common/e2e.rs b/rs-matter/tests/common/e2e.rs index e0936231..f7854e89 100644 --- a/rs-matter/tests/common/e2e.rs +++ b/rs-matter/tests/common/e2e.rs @@ -25,6 +25,7 @@ use embassy_sync::{ }; use rs_matter::acl::{AclEntry, AuthMode}; +use rs_matter::crypto::KeyPair; use rs_matter::data_model::cluster_basic_information::BasicInfoConfig; use rs_matter::data_model::core::{DataModel, IMBuffer}; use rs_matter::data_model::objects::{AsyncHandler, AsyncMetadata, Privilege}; @@ -132,10 +133,13 @@ impl E2eRunner { /// Add a default ACL entry to the remote (tested) Matter instance. pub fn add_default_acl(&self) { // Only allow the standard peer node id of the IM Engine - let mut default_acl = - AclEntry::new(NonZeroU8::new(1).unwrap(), Privilege::ADMIN, AuthMode::Case); + let mut default_acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); default_acl.add_subject(Self::PEER_ID).unwrap(); - self.matter.acl_mgr.borrow_mut().add(default_acl).unwrap(); + self.matter + .fabric_mgr + .borrow_mut() + .acl_add(NonZeroU8::new(1).unwrap(), default_acl) + .unwrap(); } /// Initiates a new exchange on the local Matter instance @@ -216,6 +220,12 @@ impl E2eRunner { MATTER_PORT, ); + matter + .fabric_mgr + .borrow_mut() + .add_with_post_init(KeyPair::new(matter.rand()).unwrap(), |_| Ok(())) + .unwrap(); + matter.initialize_transport_buffers().unwrap(); matter diff --git a/rs-matter/tests/data_model/acl_and_dataver.rs b/rs-matter/tests/data_model/acl_and_dataver.rs index 48a23b6f..6e0773ec 100644 --- a/rs-matter/tests/data_model/acl_and_dataver.rs +++ b/rs-matter/tests/data_model/acl_and_dataver.rs @@ -67,10 +67,14 @@ fn wc_read_attribute() { im.handle_read_reqs(&handler, &[AttrPath::new(&wc_att1)], &[]); // Add ACL to allow our peer to only access endpoint 0 - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); acl.add_target(Target::new(Some(0), None, None)).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test2: Only Single response as only single endpoint is allowed im.handle_read_reqs( @@ -80,10 +84,14 @@ fn wc_read_attribute() { ); // Add ACL to allow our peer to also access endpoint 1 - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); acl.add_target(Target::new(Some(1), None, None)).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test3: Both responses are valid im.handle_read_reqs( @@ -117,9 +125,13 @@ fn exact_read_attribute() { im.handle_read_reqs(&handler, input, expected); // Add ACL to allow our peer to access any endpoint - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test2: Only Single response as only single endpoint is allowed let input = &[AttrPath::new(&wc_att1)]; @@ -166,10 +178,14 @@ fn wc_write_attribute() { ); // Add ACL to allow our peer to access one endpoint - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); acl.add_target(Target::new(Some(0), None, None)).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test 2: Wildcard write to attributes will only return attributes // where the writes were successful @@ -185,10 +201,14 @@ fn wc_write_attribute() { ); // Add ACL to allow our peer to access another endpoint - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); acl.add_target(Target::new(Some(1), None, None)).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test 3: Wildcard write to attributes will return multiple attributes // where the writes were successful @@ -237,9 +257,13 @@ fn exact_write_attribute() { ); // Add ACL to allow our peer to access any endpoint - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test 1: Exact write to an attribute with permission should grant // access @@ -285,9 +309,13 @@ fn exact_write_attribute_noc_cat() { ); // Add ACL to allow our peer to access any endpoint - let mut acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); acl.add_subject_catid(cat_in_acl).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test 1: Exact write to an attribute with permission should grant // access @@ -311,10 +339,14 @@ fn insufficient_perms_write() { let handler = im.handler(); // Add ACL to allow our peer with only OPERATE permission - let mut acl = AclEntry::new(FAB_1, Privilege::OPERATE, AuthMode::Case); + let mut acl = AclEntry::new(Privilege::OPERATE, AuthMode::Case); acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); acl.add_target(Target::new(Some(0), None, None)).unwrap(); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); // Test: Not enough permission should return error im.handle_write_reqs( @@ -366,7 +398,7 @@ fn write_with_runtime_acl_add() { let input0 = TestAttrData::new(None, AttrPath::new(&ep0_att), &val0 as _); // Create ACL to allow our peer ADMIN on everything - let mut allow_acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut allow_acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); allow_acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); let acl_att = GenericPath::new( @@ -377,12 +409,16 @@ fn write_with_runtime_acl_add() { let acl_input = TestAttrData::new(None, AttrPath::new(&acl_att), &allow_acl); // Create ACL that only allows write to the ACL Cluster - let mut basic_acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); + let mut basic_acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); basic_acl.add_subject(IM_ENGINE_PEER_ID).unwrap(); basic_acl .add_target(Target::new(Some(0), Some(access_control::ID), None)) .unwrap(); - im.matter.acl_mgr.borrow_mut().add(basic_acl).unwrap(); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, basic_acl) + .unwrap(); // Test: deny write (with error), then ACL is added, then allow write im.handle_write_reqs( @@ -412,8 +448,12 @@ fn test_read_data_ver() { let handler = im.handler(); // Add ACL to allow our peer with only OPERATE permission - let acl = AclEntry::new(FAB_1, Privilege::OPERATE, AuthMode::Case); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + let acl = AclEntry::new(Privilege::OPERATE, AuthMode::Case); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); let wc_ep_att1 = GenericPath::new( None, @@ -505,8 +545,12 @@ fn test_write_data_ver() { let handler = im.handler(); // Add ACL to allow our peer with only OPERATE permission - let acl = AclEntry::new(FAB_1, Privilege::ADMIN, AuthMode::Case); - im.matter.acl_mgr.borrow_mut().add(acl).unwrap(); + let acl = AclEntry::new(Privilege::ADMIN, AuthMode::Case); + im.matter + .fabric_mgr + .borrow_mut() + .acl_add(FAB_1, acl) + .unwrap(); let wc_ep_attwrite = GenericPath::new( None,