1use core::num::NonZeroU8;
21
22use crate::acl::{self, AclEntry, AuthMode, MAX_ACL_ENTRIES_PER_FABRIC};
23use crate::dm::{
24 ArrayAttributeRead, ArrayAttributeWrite, AttrDetails, Cluster, Dataver, InvokeContext,
25 ReadContext, WriteContext,
26};
27use crate::error::{Error, ErrorCode};
28use crate::fabric::{Fabric, FabricPersist, Fabrics};
29use crate::tlv::{Nullable, TLVArray, TLVBuilderParent};
30use crate::utils::init::stack_try_pin_init;
31use crate::with;
32
33pub use crate::dm::clusters::decl::access_control::*;
34
35#[derive(Debug, Clone)]
37#[cfg_attr(feature = "defmt", derive(defmt::Format))]
38pub struct AclHandler {
39 dataver: Dataver,
40}
41
42impl AclHandler {
43 pub const fn new(dataver: Dataver) -> Self {
45 Self { dataver }
46 }
47
48 pub const fn adapt(self) -> HandlerAdaptor<Self> {
50 HandlerAdaptor(self)
51 }
52
53 fn acl<P: TLVBuilderParent>(
56 &self,
57 fabrics: &Fabrics,
58 attr: &AttrDetails,
59 builder: ArrayAttributeRead<
60 AccessControlEntryStructArrayBuilder<P>,
61 AccessControlEntryStructBuilder<P>,
62 >,
63 ) -> Result<P, Error> {
64 let mut acls = fabrics
65 .iter()
66 .filter(|fabric| !attr.fab_filter || fabric.fab_idx().get() == attr.fab_idx)
67 .flat_map(|fabric| fabric.acl_iter().map(|entry| (fabric.fab_idx(), entry)));
68
69 match builder {
70 ArrayAttributeRead::ReadAll(mut builder) => {
71 for (fab_idx, entry) in acls {
72 builder =
73 entry.read_into(attr.fab_idx, Some(fab_idx.get()), builder.push()?)?;
74 }
75
76 builder.end()
77 }
78 ArrayAttributeRead::ReadOne(index, builder) => {
79 let Some((fab_idx, entry)) = acls.nth(index as usize) else {
80 return Err(ErrorCode::ConstraintError.into());
81 };
82
83 entry.read_into(attr.fab_idx, Some(fab_idx.get()), builder)
84 }
85 ArrayAttributeRead::ReadNone(builder) => builder.end(),
86 }
87 }
88
89 fn set_acl(
92 &self,
93 fabric: &mut Fabric,
94 value: ArrayAttributeWrite<
95 TLVArray<'_, AccessControlEntryStruct<'_>>,
96 AccessControlEntryStruct<'_>,
97 >,
98 ) -> Result<(), Error> {
99 match value {
100 ArrayAttributeWrite::Replace(list) => {
101 let mut count: usize = 0;
103 for entry in &list {
104 count += 1;
105 if count > MAX_ACL_ENTRIES_PER_FABRIC {
106 return Err(ErrorCode::ResourceExhausted)?;
107 }
108 let entry = entry?;
109 stack_try_pin_init!(let _processed =? AclEntry::init_with(fabric.fab_idx(), &entry));
111 }
112
113 fabric.acl_remove_all();
115 for entry in &list {
116 let entry = unwrap!(entry);
119 unwrap!(fabric.acl_add_init(AclEntry::init_with(fabric.fab_idx(), &entry)));
120 }
121 }
122 ArrayAttributeWrite::Add(entry) => {
123 fabric.acl_add_init(AclEntry::init_with(fabric.fab_idx(), &entry))?;
124 }
125 ArrayAttributeWrite::Update(index, entry) => {
126 fabric
127 .acl_update_init(index as _, AclEntry::init_with(fabric.fab_idx(), &entry))?;
128 }
129 ArrayAttributeWrite::Remove(index) => {
130 fabric.acl_remove(index as _)?;
131 }
132 }
133
134 Ok(())
135 }
136}
137
138impl ClusterHandler for AclHandler {
139 const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required)).with_cmds(with!());
140
141 fn dataver(&self) -> u32 {
142 self.dataver.get()
143 }
144
145 fn dataver_changed(&self) {
146 self.dataver.changed();
147 }
148
149 fn acl<P: TLVBuilderParent>(
150 &self,
151 ctx: impl ReadContext,
152 builder: ArrayAttributeRead<
153 AccessControlEntryStructArrayBuilder<P>,
154 AccessControlEntryStructBuilder<P>,
155 >,
156 ) -> Result<P, Error> {
157 ctx.exchange()
158 .with_state(|state| self.acl(&state.fabrics, ctx.attr(), builder))
159 }
160
161 fn subjects_per_access_control_entry(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
162 Ok(acl::MAX_SUBJECTS_PER_ACL_ENTRY as _)
163 }
164
165 fn targets_per_access_control_entry(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
166 Ok(acl::MAX_TARGETS_PER_ACL_ENTRY as _)
167 }
168
169 fn access_control_entries_per_fabric(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
170 Ok(acl::MAX_ACL_ENTRIES_PER_FABRIC as _)
171 }
172
173 fn set_acl(
174 &self,
175 ctx: impl WriteContext,
176 value: ArrayAttributeWrite<
177 TLVArray<'_, AccessControlEntryStruct<'_>>,
178 AccessControlEntryStruct<'_>,
179 >,
180 ) -> Result<(), Error> {
181 let mut persist = FabricPersist::new(ctx.kv());
182
183 let accessor = ctx.exchange().accessor()?;
184 let admin_node_id: Nullable<u64> = match accessor.peer_node_id() {
185 Some(id) => Nullable::some(id),
186 None => Nullable::none(),
187 };
188 let admin_passcode_id: Nullable<u16> =
189 if matches!(accessor.auth_mode(), Some(AuthMode::Pase)) {
190 Nullable::some(0u16)
191 } else {
192 Nullable::none()
193 };
194
195 let fab_idx = NonZeroU8::new(ctx.attr().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
196
197 ctx.exchange().with_state(|state| {
202 let fabric = state.fabrics.fabric_mut(fab_idx)?;
203
204 match value {
205 ArrayAttributeWrite::Replace(list) => {
206 let mut old_entries: heapless::Vec<AclEntry, MAX_ACL_ENTRIES_PER_FABRIC> =
211 heapless::Vec::new();
212 for e in fabric.acl_iter() {
213 let _ = old_entries.push(e.clone());
214 }
215
216 self.set_acl(fabric, ArrayAttributeWrite::Replace(list))?;
217
218 let new_count = fabric.acl_iter().count();
219 let old_count = old_entries.len();
220
221 for (i, entry) in fabric.acl_iter().enumerate() {
223 let change = if i < old_count {
224 ChangeTypeEnum::Changed
225 } else {
226 ChangeTypeEnum::Added
227 };
228 emit_acl_entry_changed(
229 &ctx,
230 admin_node_id.clone(),
231 admin_passcode_id.clone(),
232 change,
233 entry,
234 fab_idx.get(),
235 )?;
236 }
237
238 for old_entry in old_entries.iter().skip(new_count) {
241 emit_acl_entry_changed(
242 &ctx,
243 admin_node_id.clone(),
244 admin_passcode_id.clone(),
245 ChangeTypeEnum::Removed,
246 old_entry,
247 fab_idx.get(),
248 )?;
249 }
250 }
251 ArrayAttributeWrite::Add(entry) => {
252 let old_count = fabric.acl_iter().count();
253 self.set_acl(fabric, ArrayAttributeWrite::Add(entry))?;
254 if let Some(new_entry) = fabric.acl_iter().nth(old_count) {
255 emit_acl_entry_changed(
256 &ctx,
257 admin_node_id.clone(),
258 admin_passcode_id.clone(),
259 ChangeTypeEnum::Added,
260 new_entry,
261 fab_idx.get(),
262 )?;
263 }
264 }
265 ArrayAttributeWrite::Update(index, entry) => {
266 let idx = index as usize;
267 self.set_acl(fabric, ArrayAttributeWrite::Update(index, entry))?;
268 if let Some(new_entry) = fabric.acl_iter().nth(idx) {
269 emit_acl_entry_changed(
270 &ctx,
271 admin_node_id.clone(),
272 admin_passcode_id.clone(),
273 ChangeTypeEnum::Changed,
274 new_entry,
275 fab_idx.get(),
276 )?;
277 }
278 }
279 ArrayAttributeWrite::Remove(index) => {
280 let idx = index as usize;
281 let removed = fabric.acl_iter().nth(idx).cloned();
282 self.set_acl(fabric, ArrayAttributeWrite::Remove(index))?;
283 if let Some(old_entry) = removed {
284 emit_acl_entry_changed(
285 &ctx,
286 admin_node_id.clone(),
287 admin_passcode_id.clone(),
288 ChangeTypeEnum::Removed,
289 &old_entry,
290 fab_idx.get(),
291 )?;
292 }
293 }
294 }
295
296 if !state.failsafe.is_armed_for(fab_idx.get()) {
300 persist.store(fabric)?;
301 }
302
303 Ok(())
304 })?;
305
306 persist.run()
307 }
308
309 fn handle_review_fabric_restrictions<P: TLVBuilderParent>(
310 &self,
311 _ctx: impl InvokeContext,
312 _request: ReviewFabricRestrictionsRequest<'_>,
313 _response: ReviewFabricRestrictionsResponseBuilder<P>,
314 ) -> Result<P, Error> {
315 unimplemented!()
317 }
318}
319
320pub(crate) fn emit_acl_entry_changed<E>(
333 emitter: E,
334 admin_node_id: Nullable<u64>,
335 admin_passcode_id: Nullable<u16>,
336 change_type: ChangeTypeEnum,
337 entry: &AclEntry,
338 fab_idx: u8,
339) -> Result<(), Error>
340where
341 E: crate::dm::EventEmitter,
342{
343 AccessControlEntryChanged::emit_for(emitter, 0, |tw| {
344 let inner = tw
345 .admin_node_id(admin_node_id)?
346 .admin_passcode_id(admin_passcode_id)?
347 .change_type(change_type)?
348 .latest_value()?
349 .non_null()?;
350
351 let parent = entry.read_into(fab_idx, Some(fab_idx), inner)?;
355
356 parent.fabric_index(Some(fab_idx))?.end()
357 })?;
358
359 Ok(())
360}
361
362#[cfg(test)]
363mod tests {
364 use core::cell::Cell;
365 use core::num::NonZeroU8;
366
367 use crate::acl::{AclEntry, AuthMode};
368 use crate::dm::clusters::acl::{
369 AccessControlEntryStruct, AccessControlEntryStructArrayBuilder, Dataver,
370 };
371 use crate::dm::{
372 ArrayAttributeRead, ArrayAttributeWrite, AttrDetails, AttrReadReplyInstance, Privilege,
373 ReadReply, ReadReplyInstance, Reply,
374 };
375 use crate::fabric::Fabrics;
376 use crate::tlv::{get_root_node_struct, TLVElement, TLVTag, TLVWriteParent, ToTLV};
377 use crate::utils::storage::WriteBuf;
378
379 use super::AclHandler;
380
381 use crate::acl::tests::{FAB_1, FAB_2};
382
383 #[test]
384 fn acl_cluster_add() {
386 let mut buf: [u8; 100] = [0; 100];
387 let mut tw = WriteBuf::new(&mut buf);
388
389 let mut fabrics = Fabrics::new();
390
391 unwrap!(fabrics.add_with_post_init(|_| Ok(())));
393
394 let acl = AclHandler::new(Dataver::new(0));
395
396 let new = AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case);
397
398 unwrap!(new.to_tlv(&TLVTag::Anonymous, &mut tw));
399 let data = unwrap!(get_root_node_struct(tw.as_slice()));
400
401 acl_add(&acl, &mut fabrics, &data, FAB_1);
404
405 let verifier = AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case);
406 for fabric in fabrics.iter() {
407 for a in fabric.acl_iter() {
408 assert_eq!(*a, verifier);
409 }
410 }
411 }
412
413 #[test]
414 fn acl_cluster_edit() {
416 let mut buf: [u8; 100] = [0; 100];
417 let mut tw = WriteBuf::new(&mut buf);
418
419 let mut fabrics = Fabrics::new();
420
421 fabrics.add_with_post_init(|_| Ok(())).unwrap();
423
424 fabrics.add_with_post_init(|_| Ok(())).unwrap();
426
427 let mut verifier = [
429 AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case),
430 AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case),
431 AclEntry::new(Some(FAB_2), Privilege::ADMIN, AuthMode::Case),
432 ];
433 for i in &verifier {
434 fabrics
435 .fabric_mut(i.fab_idx.unwrap())
436 .unwrap()
437 .acl_add(i.clone())
438 .unwrap();
439 }
440 let acl = AclHandler::new(Dataver::new(0));
441
442 let new = AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case);
443 new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap();
444 let data = get_root_node_struct(tw.as_slice()).unwrap();
445
446 acl_edit(&acl, &mut fabrics, 1, &data, FAB_2);
448 verifier[2] = new;
450
451 assert_eq!(fabrics.get(FAB_1).unwrap().acl_iter().count(), 1);
453 assert_eq!(
454 fabrics.get(FAB_1).unwrap().acl_iter().next().unwrap(),
455 &verifier[1]
456 );
457 assert_eq!(fabrics.get(FAB_2).unwrap().acl_iter().count(), 2);
458 assert_eq!(
459 fabrics.get(FAB_2).unwrap().acl_iter().next().unwrap(),
460 &verifier[0]
461 );
462 assert_eq!(
463 fabrics.get(FAB_2).unwrap().acl_iter().nth(1).unwrap(),
464 &verifier[2]
465 );
466 }
467
468 #[test]
469 fn acl_cluster_delete() {
471 let mut fabrics = Fabrics::new();
472
473 fabrics.add_with_post_init(|_| Ok(())).unwrap();
475
476 fabrics.add_with_post_init(|_| Ok(())).unwrap();
478
479 let input = [
481 AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case),
482 AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case),
483 AclEntry::new(Some(FAB_2), Privilege::ADMIN, AuthMode::Case),
484 ];
485 for i in &input {
486 fabrics
487 .fabric_mut(i.fab_idx.unwrap())
488 .unwrap()
489 .acl_add(i.clone())
490 .unwrap();
491 }
492 let acl = AclHandler::new(Dataver::new(0));
493
494 acl_remove(&acl, &mut fabrics, 0, FAB_1);
496
497 let verifier = [input[0].clone(), input[2].clone()];
498 let mut index = 0;
500 for fabric in fabrics.iter() {
501 for a in fabric.acl_iter() {
502 assert_eq!(*a, verifier[index]);
503 index += 1;
504 }
505 }
506 }
507
508 #[test]
509 fn acl_cluster_read() {
511 let mut buf: [u8; 100] = [0; 100];
512 let mut writebuf = WriteBuf::new(&mut buf);
513
514 let mut fabrics = Fabrics::new();
515
516 fabrics.add_with_post_init(|_| Ok(())).unwrap();
518
519 fabrics.add_with_post_init(|_| Ok(())).unwrap();
521
522 let input = [
524 AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case),
525 AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case),
526 AclEntry::new(Some(FAB_2), Privilege::ADMIN, AuthMode::Case),
527 ];
528 for i in input {
529 fabrics
530 .fabric_mut(i.fab_idx.unwrap())
531 .unwrap()
532 .acl_add(i)
533 .unwrap();
534 }
535 let acl = AclHandler::new(Dataver::new(0));
536
537 {
539 let attr = AttrDetails {
540 endpoint_id: 0,
541 cluster_id: 0,
542 attr_id: 0,
543 list_index: None,
544 list_chunked: false,
545 fab_idx: 1,
546 fab_filter: false,
547 dataver: None,
548 wildcard: false,
549 array: false,
550 cluster_status: Cell::new(0),
551 };
552
553 acl_read(&acl, &fabrics, &attr, &mut writebuf);
554 assert_eq!(
555 &[
556 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1,
557 1, 36, 2, 2, 52, 3, 52, 4, 36, 254, 1, 24, 21, 36, 254, 2, 24, 21, 36, 254, 2,
558 24, 24, 24, 24
559 ],
560 writebuf.as_slice()
561 );
562 }
563 writebuf.reset();
564
565 {
567 let attr = AttrDetails {
568 endpoint_id: 0,
569 cluster_id: 0,
570 attr_id: 0,
571 list_index: None,
572 list_chunked: false,
573 fab_idx: 1,
574 fab_filter: true,
575 dataver: None,
576 wildcard: false,
577 array: false,
578 cluster_status: Cell::new(0),
579 };
580
581 acl_read(&acl, &fabrics, &attr, &mut writebuf);
582 assert_eq!(
583 &[
584 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1,
585 1, 36, 2, 2, 52, 3, 52, 4, 36, 254, 1, 24, 24, 24, 24
586 ],
587 writebuf.as_slice()
588 );
589 }
590 writebuf.reset();
591
592 {
594 let attr = AttrDetails {
595 endpoint_id: 0,
596 cluster_id: 0,
597 attr_id: 0,
598 list_index: None,
599 list_chunked: false,
600 fab_idx: 2,
601 fab_filter: true,
602 dataver: None,
603 wildcard: false,
604 array: false,
605 cluster_status: Cell::new(0),
606 };
607
608 acl_read(&acl, &fabrics, &attr, &mut writebuf);
609 assert_eq!(
610 &[
611 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1,
612 1, 36, 2, 2, 52, 3, 52, 4, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 52, 3, 52,
613 4, 36, 254, 2, 24, 24, 24, 24
614 ],
615 writebuf.as_slice()
616 );
617 }
618 }
619
620 fn acl_read(acl: &AclHandler, fabrics: &Fabrics, attr: &AttrDetails, tw: &mut WriteBuf<'_>) {
621 let encoder = ReadReplyInstance::new(attr, &mut *tw);
622 let mut writer = unwrap!(unwrap!(encoder.with_dataver(acl.dataver.get())));
623 let build_root = TLVWriteParent::new((), writer.writer());
624 unwrap!(acl.acl(
625 fabrics,
626 attr,
627 ArrayAttributeRead::ReadAll(unwrap!(AccessControlEntryStructArrayBuilder::new(
628 build_root,
629 &AttrReadReplyInstance::<WriteBuf>::TAG
630 )))
631 ));
632
633 unwrap!(writer.complete());
634 }
635
636 fn acl_add(acl: &AclHandler, fabrics: &mut Fabrics, data: &TLVElement<'_>, fab_idx: NonZeroU8) {
637 unwrap!(acl.set_acl(
638 fabrics.fabric_mut(fab_idx).unwrap(),
639 ArrayAttributeWrite::Add(AccessControlEntryStruct::new(data.clone())),
640 ));
641 }
642
643 fn acl_edit(
644 acl: &AclHandler,
645 fabrics: &mut Fabrics,
646 index: u16,
647 data: &TLVElement<'_>,
648 fab_idx: NonZeroU8,
649 ) {
650 unwrap!(acl.set_acl(
651 fabrics.fabric_mut(fab_idx).unwrap(),
652 ArrayAttributeWrite::Update(index, AccessControlEntryStruct::new(data.clone())),
653 ));
654 }
655
656 fn acl_remove(acl: &AclHandler, fabrics: &mut Fabrics, index: u16, fab_idx: NonZeroU8) {
657 unwrap!(acl.set_acl(
658 fabrics.fabric_mut(fab_idx).unwrap(),
659 ArrayAttributeWrite::Remove(index)
660 ));
661 }
662}