1use core::num::NonZeroU8;
21
22use crate::acl::{self, AclEntry, AuthMode, MAX_ACL_ENTRIES_PER_FABRIC};
23use crate::dm::endpoints::ROOT_ENDPOINT_ID;
24use crate::dm::{
25 ArrayAttributeRead, ArrayAttributeWrite, AttrDetails, Cluster, Dataver, HandlerContext,
26 InvokeContext, NodeId, ReadContext, WriteContext,
27};
28use crate::error::{Error, ErrorCode};
29use crate::fabric::{Fabric, FabricPersist, Fabrics};
30use crate::tlv::{Nullable, TLVArray, TLVBuilderParent};
31use crate::utils::init::stack_try_pin_init;
32use crate::with;
33
34pub use crate::dm::clusters::decl::access_control::*;
35
36#[derive(Debug, Clone)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39pub struct AclHandler {
40 dataver: Dataver,
41}
42
43impl AclHandler {
44 pub const fn new(dataver: Dataver) -> Self {
46 Self { dataver }
47 }
48
49 pub const fn adapt(self) -> HandlerAdaptor<Self> {
51 HandlerAdaptor(self)
52 }
53
54 fn acl<P: TLVBuilderParent>(
57 &self,
58 fabrics: &Fabrics,
59 attr: &AttrDetails,
60 builder: ArrayAttributeRead<
61 AccessControlEntryStructArrayBuilder<P>,
62 AccessControlEntryStructBuilder<P>,
63 >,
64 ) -> Result<P, Error> {
65 let mut acls = fabrics
66 .iter()
67 .filter(|fabric| !attr.fab_filter || fabric.fab_idx().get() == attr.fab_idx)
68 .flat_map(|fabric| fabric.acl_iter().map(|entry| (fabric.fab_idx(), entry)));
69
70 match builder {
71 ArrayAttributeRead::ReadAll(mut builder) => {
72 for (fab_idx, entry) in acls {
73 builder =
74 entry.read_into(attr.fab_idx, Some(fab_idx.get()), builder.push()?)?;
75 }
76
77 builder.end()
78 }
79 ArrayAttributeRead::ReadOne(index, builder) => {
80 let Some((fab_idx, entry)) = acls.nth(index as usize) else {
81 return Err(ErrorCode::ConstraintError.into());
82 };
83
84 entry.read_into(attr.fab_idx, Some(fab_idx.get()), builder)
85 }
86 ArrayAttributeRead::ReadNone(builder) => builder.end(),
87 }
88 }
89
90 fn set_acl(
93 &self,
94 fabric: &mut Fabric,
95 value: ArrayAttributeWrite<
96 TLVArray<'_, AccessControlEntryStruct<'_>>,
97 AccessControlEntryStruct<'_>,
98 >,
99 ) -> Result<(), Error> {
100 match value {
101 ArrayAttributeWrite::Replace(list) => {
102 let mut count: usize = 0;
104 for entry in &list {
105 count += 1;
106 if count > MAX_ACL_ENTRIES_PER_FABRIC {
107 return Err(ErrorCode::ResourceExhausted.into());
108 }
109 let entry = entry?;
110 stack_try_pin_init!(let _processed =? AclEntry::init_with(fabric.fab_idx(), &entry));
112 }
113
114 fabric.acl_remove_all();
116 for entry in &list {
117 let entry = unwrap!(entry);
120 unwrap!(fabric.acl_add_init(AclEntry::init_with(fabric.fab_idx(), &entry)));
121 }
122 }
123 ArrayAttributeWrite::Add(entry) => {
124 fabric.acl_add_init(AclEntry::init_with(fabric.fab_idx(), &entry))?;
125 }
126 ArrayAttributeWrite::Update(index, entry) => {
127 fabric
128 .acl_update_init(index as _, AclEntry::init_with(fabric.fab_idx(), &entry))?;
129 }
130 ArrayAttributeWrite::Remove(index) => {
131 fabric.acl_remove(index as _)?;
132 }
133 }
134
135 Ok(())
136 }
137
138 #[cfg(feature = "groups")]
142 fn build_auxiliary_entry<P: TLVBuilderParent>(
143 accessing_fab_idx: u8,
144 fab_idx: NonZeroU8,
145 group_id: u16,
146 endpoints: &[crate::dm::EndptId],
147 builder: AccessControlEntryStructBuilder<P>,
148 ) -> Result<P, Error> {
149 use crate::tlv::Nullable;
150
151 let same_fab_idx = accessing_fab_idx == fab_idx.get();
152
153 builder
154 .privilege(same_fab_idx.then_some(AccessControlEntryPrivilegeEnum::Operate))?
155 .auth_mode(same_fab_idx.then_some(AccessControlEntryAuthModeEnum::Group))?
156 .subjects()?
157 .with_some_if(same_fab_idx, |builder| {
158 builder.with_non_null(
159 Nullable::some([group_id as u64]),
160 |subjects, mut builder| {
161 for subject in subjects {
162 builder = builder.push(subject)?;
163 }
164
165 builder.end()
166 },
167 )
168 })?
169 .targets()?
170 .with_some_if(same_fab_idx, |builder| {
171 builder.with_non_null(Nullable::some(endpoints), |endpoints, mut builder| {
172 for endpoint in *endpoints {
173 builder = builder
174 .push()?
175 .cluster(Nullable::none())?
176 .endpoint(Nullable::some(*endpoint))?
177 .device_type(Nullable::none())?
178 .end()?;
179 }
180
181 builder.end()
182 })
183 })?
184 .auxiliary_type(same_fab_idx.then_some(AccessControlAuxiliaryTypeEnum::Groupcast))?
185 .fabric_index(Some(fab_idx.get()))?
186 .end()
187 }
188}
189
190impl ClusterHandler for AclHandler {
191 const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required)).with_cmds(with!());
192
193 fn dataver(&self) -> u32 {
194 self.dataver.get()
195 }
196
197 fn dataver_changed(&self) {
198 self.dataver.changed();
199 }
200
201 fn acl<P: TLVBuilderParent>(
202 &self,
203 ctx: impl ReadContext,
204 builder: ArrayAttributeRead<
205 AccessControlEntryStructArrayBuilder<P>,
206 AccessControlEntryStructBuilder<P>,
207 >,
208 ) -> Result<P, Error> {
209 ctx.exchange()
210 .with_state(|state| self.acl(&state.fabrics, ctx.attr(), builder))
211 }
212
213 fn auxiliary_acl<P: TLVBuilderParent>(
214 &self,
215 ctx: impl ReadContext,
216 builder: ArrayAttributeRead<
217 AccessControlEntryStructArrayBuilder<P>,
218 AccessControlEntryStructBuilder<P>,
219 >,
220 ) -> Result<P, Error> {
221 #[cfg(feature = "groups")]
226 {
227 ctx.exchange().with_state(|state| {
228 let attr = ctx.attr();
229
230 let mut entries = state
233 .fabrics
234 .iter()
235 .filter(|fabric| !attr.fab_filter || fabric.fab_idx().get() == attr.fab_idx)
236 .flat_map(|fabric| {
237 fabric
238 .groups()
239 .iter()
240 .filter(|entry| entry.has_aux_acl() && !entry.endpoints.is_empty())
241 .flat_map(move |entry| {
242 entry
243 .endpoints
244 .chunks(acl::MAX_TARGETS_PER_ACL_ENTRY)
245 .map(move |endpoints| (fabric, entry.group_id, endpoints))
246 })
247 });
248
249 match builder {
250 ArrayAttributeRead::ReadAll(mut builder) => {
251 for (fabric, group_id, endpoints) in entries {
252 builder = Self::build_auxiliary_entry(
253 attr.fab_idx,
254 fabric.fab_idx(),
255 group_id,
256 endpoints,
257 builder.push()?,
258 )?;
259 }
260
261 builder.end()
262 }
263 ArrayAttributeRead::ReadOne(index, builder) => {
264 let Some((fabric, group_id, endpoints)) = entries.nth(index as usize)
265 else {
266 return Err(ErrorCode::ConstraintError.into());
267 };
268
269 Self::build_auxiliary_entry(
270 attr.fab_idx,
271 fabric.fab_idx(),
272 group_id,
273 endpoints,
274 builder,
275 )
276 }
277 ArrayAttributeRead::ReadNone(builder) => builder.end(),
278 }
279 })
280 }
281
282 #[cfg(not(feature = "groups"))]
283 {
284 let _ = ctx;
285
286 match builder {
287 ArrayAttributeRead::ReadAll(builder) => builder.end(),
288 ArrayAttributeRead::ReadOne(_, _) => Err(ErrorCode::ConstraintError.into()),
289 ArrayAttributeRead::ReadNone(builder) => builder.end(),
290 }
291 }
292 }
293
294 fn subjects_per_access_control_entry(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
295 Ok(acl::MAX_SUBJECTS_PER_ACL_ENTRY as _)
296 }
297
298 fn targets_per_access_control_entry(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
299 Ok(acl::MAX_TARGETS_PER_ACL_ENTRY as _)
300 }
301
302 fn access_control_entries_per_fabric(&self, _ctx: impl ReadContext) -> Result<u16, Error> {
303 Ok(acl::MAX_ACL_ENTRIES_PER_FABRIC as _)
304 }
305
306 fn set_acl(
307 &self,
308 ctx: impl WriteContext,
309 value: ArrayAttributeWrite<
310 TLVArray<'_, AccessControlEntryStruct<'_>>,
311 AccessControlEntryStruct<'_>,
312 >,
313 ) -> Result<(), Error> {
314 let mut persist = FabricPersist::new(ctx.kv());
315
316 let accessor = ctx.accessor()?;
317 let admin_node_id: Nullable<u64> = match accessor.peer_node_id() {
318 Some(id) => Nullable::some(id),
319 None => Nullable::none(),
320 };
321 let admin_passcode_id: Nullable<u16> =
322 if matches!(accessor.auth_mode(), Some(AuthMode::Pase)) {
323 Nullable::some(0u16)
324 } else {
325 Nullable::none()
326 };
327
328 let fab_idx = NonZeroU8::new(ctx.attr().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
329
330 ctx.exchange().with_state(|state| {
335 let fabric = state.fabrics.fabric_mut(fab_idx)?;
336
337 match value {
338 ArrayAttributeWrite::Replace(list) => {
339 let mut old_entries: heapless::Vec<AclEntry, MAX_ACL_ENTRIES_PER_FABRIC> =
344 heapless::Vec::new();
345 for e in fabric.acl_iter() {
346 let _ = old_entries.push(e.clone());
347 }
348
349 self.set_acl(fabric, ArrayAttributeWrite::Replace(list))?;
350
351 let new_count = fabric.acl_iter().count();
352 let old_count = old_entries.len();
353
354 for (i, entry) in fabric.acl_iter().enumerate() {
356 let change = if i < old_count {
357 ChangeTypeEnum::Changed
358 } else {
359 ChangeTypeEnum::Added
360 };
361 emit_acl_entry_changed(
362 &ctx,
363 admin_node_id.clone(),
364 admin_passcode_id.clone(),
365 change,
366 entry,
367 fab_idx.get(),
368 )?;
369 }
370
371 for old_entry in old_entries.iter().skip(new_count) {
374 emit_acl_entry_changed(
375 &ctx,
376 admin_node_id.clone(),
377 admin_passcode_id.clone(),
378 ChangeTypeEnum::Removed,
379 old_entry,
380 fab_idx.get(),
381 )?;
382 }
383 }
384 ArrayAttributeWrite::Add(entry) => {
385 let old_count = fabric.acl_iter().count();
386 self.set_acl(fabric, ArrayAttributeWrite::Add(entry))?;
387 if let Some(new_entry) = fabric.acl_iter().nth(old_count) {
388 emit_acl_entry_changed(
389 &ctx,
390 admin_node_id.clone(),
391 admin_passcode_id.clone(),
392 ChangeTypeEnum::Added,
393 new_entry,
394 fab_idx.get(),
395 )?;
396 }
397 }
398 ArrayAttributeWrite::Update(index, entry) => {
399 let idx = index as usize;
400 self.set_acl(fabric, ArrayAttributeWrite::Update(index, entry))?;
401 if let Some(new_entry) = fabric.acl_iter().nth(idx) {
402 emit_acl_entry_changed(
403 &ctx,
404 admin_node_id.clone(),
405 admin_passcode_id.clone(),
406 ChangeTypeEnum::Changed,
407 new_entry,
408 fab_idx.get(),
409 )?;
410 }
411 }
412 ArrayAttributeWrite::Remove(index) => {
413 let idx = index as usize;
414 let removed = fabric.acl_iter().nth(idx).cloned();
415 self.set_acl(fabric, ArrayAttributeWrite::Remove(index))?;
416 if let Some(old_entry) = removed {
417 emit_acl_entry_changed(
418 &ctx,
419 admin_node_id.clone(),
420 admin_passcode_id.clone(),
421 ChangeTypeEnum::Removed,
422 &old_entry,
423 fab_idx.get(),
424 )?;
425 }
426 }
427 }
428
429 if !state.failsafe.is_armed_for(fab_idx.get()) {
433 persist.store(fabric)?;
434 }
435
436 Ok(())
437 })?;
438
439 persist.run()
440 }
441
442 fn handle_review_fabric_restrictions<P: TLVBuilderParent>(
443 &self,
444 _ctx: impl InvokeContext,
445 _request: ReviewFabricRestrictionsRequest<'_>,
446 _response: ReviewFabricRestrictionsResponseBuilder<P>,
447 ) -> Result<P, Error> {
448 unimplemented!()
450 }
451}
452
453pub const CLUSTER_AUX: Cluster<'static> = FULL_CLUSTER
464 .with_attrs(with!(required; AttributeId::AuxiliaryACL))
465 .with_cmds(with!())
466 .with_features(Feature::AUXILIARY.bits());
467
468pub fn notify_auxiliary_access_updated(
471 ctx: &impl HandlerContext,
472 admin_node_id: Option<NodeId>,
473 fab_idx: NonZeroU8,
474) -> Result<(), Error> {
475 ctx.notify_attr_changed(
476 ROOT_ENDPOINT_ID,
477 FULL_CLUSTER.id,
478 AttributeId::AuxiliaryACL as _,
479 );
480
481 AuxiliaryAccessUpdated::emit_for(ctx, ROOT_ENDPOINT_ID, |event| {
482 event
483 .admin_node_id(Nullable::new(admin_node_id))?
484 .fabric_index(Some(fab_idx.get()))?
485 .end()
486 })
487 .map(|_event_number| ())
488}
489
490pub(crate) fn emit_acl_entry_changed<E>(
493 emitter: E,
494 admin_node_id: Nullable<u64>,
495 admin_passcode_id: Nullable<u16>,
496 change_type: ChangeTypeEnum,
497 entry: &AclEntry,
498 fab_idx: u8,
499) -> Result<(), Error>
500where
501 E: crate::dm::EventEmitter,
502{
503 AccessControlEntryChanged::emit_for(emitter, 0, |tw| {
504 let inner = tw
505 .admin_node_id(admin_node_id)?
506 .admin_passcode_id(admin_passcode_id)?
507 .change_type(change_type)?
508 .latest_value()?
509 .non_null()?;
510
511 let parent = entry.read_into(fab_idx, Some(fab_idx), inner)?;
515
516 parent.fabric_index(Some(fab_idx))?.end()
517 })?;
518
519 Ok(())
520}
521
522#[cfg(test)]
523mod tests {
524 use core::cell::Cell;
525 use core::num::NonZeroU8;
526
527 use crate::acl::{AclEntry, AuthMode};
528 use crate::dm::clusters::acl::{
529 AccessControlEntryStruct, AccessControlEntryStructArrayBuilder, Dataver,
530 };
531 use crate::dm::{
532 ArrayAttributeRead, ArrayAttributeWrite, AttrDetails, AttrReadReplyInstance, Privilege,
533 ReadReply, ReadReplyInstance, Reply,
534 };
535 use crate::fabric::Fabrics;
536 use crate::tlv::{get_root_node_struct, TLVElement, TLVTag, TLVWriteParent, ToTLV};
537 use crate::utils::storage::WriteBuf;
538
539 use super::AclHandler;
540
541 use crate::acl::tests::{FAB_1, FAB_2};
542
543 #[test]
544 fn acl_cluster_add() {
546 let mut buf: [u8; 100] = [0; 100];
547 let mut tw = WriteBuf::new(&mut buf);
548
549 let mut fabrics = Fabrics::new();
550
551 unwrap!(fabrics.add_with_post_init(|_| Ok(())));
553
554 let acl = AclHandler::new(Dataver::new(0));
555
556 let new = AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case);
557
558 unwrap!(new.to_tlv(&TLVTag::Anonymous, &mut tw));
559 let data = unwrap!(get_root_node_struct(tw.as_slice()));
560
561 acl_add(&acl, &mut fabrics, &data, FAB_1);
564
565 let verifier = AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case);
566 for fabric in fabrics.iter() {
567 for a in fabric.acl_iter() {
568 assert_eq!(*a, verifier);
569 }
570 }
571 }
572
573 #[test]
574 fn acl_cluster_edit() {
576 let mut buf: [u8; 100] = [0; 100];
577 let mut tw = WriteBuf::new(&mut buf);
578
579 let mut fabrics = Fabrics::new();
580
581 fabrics.add_with_post_init(|_| Ok(())).unwrap();
583
584 fabrics.add_with_post_init(|_| Ok(())).unwrap();
586
587 let mut verifier = [
589 AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case),
590 AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case),
591 AclEntry::new(Some(FAB_2), Privilege::ADMIN, AuthMode::Case),
592 ];
593 for i in &verifier {
594 fabrics
595 .fabric_mut(i.fab_idx.unwrap())
596 .unwrap()
597 .acl_add(i.clone())
598 .unwrap();
599 }
600 let acl = AclHandler::new(Dataver::new(0));
601
602 let new = AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case);
603 new.to_tlv(&TLVTag::Anonymous, &mut tw).unwrap();
604 let data = get_root_node_struct(tw.as_slice()).unwrap();
605
606 acl_edit(&acl, &mut fabrics, 1, &data, FAB_2);
608 verifier[2] = new;
610
611 assert_eq!(fabrics.get(FAB_1).unwrap().acl_iter().count(), 1);
613 assert_eq!(
614 fabrics.get(FAB_1).unwrap().acl_iter().next().unwrap(),
615 &verifier[1]
616 );
617 assert_eq!(fabrics.get(FAB_2).unwrap().acl_iter().count(), 2);
618 assert_eq!(
619 fabrics.get(FAB_2).unwrap().acl_iter().next().unwrap(),
620 &verifier[0]
621 );
622 assert_eq!(
623 fabrics.get(FAB_2).unwrap().acl_iter().nth(1).unwrap(),
624 &verifier[2]
625 );
626 }
627
628 #[test]
629 fn acl_cluster_delete() {
631 let mut fabrics = Fabrics::new();
632
633 fabrics.add_with_post_init(|_| Ok(())).unwrap();
635
636 fabrics.add_with_post_init(|_| Ok(())).unwrap();
638
639 let input = [
641 AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case),
642 AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case),
643 AclEntry::new(Some(FAB_2), Privilege::ADMIN, AuthMode::Case),
644 ];
645 for i in &input {
646 fabrics
647 .fabric_mut(i.fab_idx.unwrap())
648 .unwrap()
649 .acl_add(i.clone())
650 .unwrap();
651 }
652 let acl = AclHandler::new(Dataver::new(0));
653
654 acl_remove(&acl, &mut fabrics, 0, FAB_1);
656
657 let verifier = [input[0].clone(), input[2].clone()];
658 let mut index = 0;
660 for fabric in fabrics.iter() {
661 for a in fabric.acl_iter() {
662 assert_eq!(*a, verifier[index]);
663 index += 1;
664 }
665 }
666 }
667
668 #[test]
669 fn acl_cluster_read() {
671 let mut buf: [u8; 100] = [0; 100];
672 let mut writebuf = WriteBuf::new(&mut buf);
673
674 let mut fabrics = Fabrics::new();
675
676 fabrics.add_with_post_init(|_| Ok(())).unwrap();
678
679 fabrics.add_with_post_init(|_| Ok(())).unwrap();
681
682 let input = [
684 AclEntry::new(Some(FAB_2), Privilege::VIEW, AuthMode::Case),
685 AclEntry::new(Some(FAB_1), Privilege::VIEW, AuthMode::Case),
686 AclEntry::new(Some(FAB_2), Privilege::ADMIN, AuthMode::Case),
687 ];
688 for i in input {
689 fabrics
690 .fabric_mut(i.fab_idx.unwrap())
691 .unwrap()
692 .acl_add(i)
693 .unwrap();
694 }
695 let acl = AclHandler::new(Dataver::new(0));
696
697 {
699 let attr = AttrDetails {
700 endpoint_id: 0,
701 cluster_id: 0,
702 attr_id: 0,
703 list_index: None,
704 list_chunked: false,
705 fab_idx: 1,
706 fab_filter: false,
707 dataver: None,
708 wildcard: false,
709 array: false,
710 cluster_status: Cell::new(0),
711 };
712
713 acl_read(&acl, &fabrics, &attr, &mut writebuf);
714 assert_eq!(
715 &[
716 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1,
717 1, 36, 2, 2, 52, 3, 52, 4, 36, 254, 1, 24, 21, 36, 254, 2, 24, 21, 36, 254, 2,
718 24, 24, 24, 24
719 ],
720 writebuf.as_slice()
721 );
722 }
723 writebuf.reset();
724
725 {
727 let attr = AttrDetails {
728 endpoint_id: 0,
729 cluster_id: 0,
730 attr_id: 0,
731 list_index: None,
732 list_chunked: false,
733 fab_idx: 1,
734 fab_filter: true,
735 dataver: None,
736 wildcard: false,
737 array: false,
738 cluster_status: Cell::new(0),
739 };
740
741 acl_read(&acl, &fabrics, &attr, &mut writebuf);
742 assert_eq!(
743 &[
744 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1,
745 1, 36, 2, 2, 52, 3, 52, 4, 36, 254, 1, 24, 24, 24, 24
746 ],
747 writebuf.as_slice()
748 );
749 }
750 writebuf.reset();
751
752 {
754 let attr = AttrDetails {
755 endpoint_id: 0,
756 cluster_id: 0,
757 attr_id: 0,
758 list_index: None,
759 list_chunked: false,
760 fab_idx: 2,
761 fab_filter: true,
762 dataver: None,
763 wildcard: false,
764 array: false,
765 cluster_status: Cell::new(0),
766 };
767
768 acl_read(&acl, &fabrics, &attr, &mut writebuf);
769 assert_eq!(
770 &[
771 21, 53, 1, 36, 0, 0, 55, 1, 36, 2, 0, 36, 3, 0, 36, 4, 0, 24, 54, 2, 21, 36, 1,
772 1, 36, 2, 2, 52, 3, 52, 4, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 52, 3, 52,
773 4, 36, 254, 2, 24, 24, 24, 24
774 ],
775 writebuf.as_slice()
776 );
777 }
778 }
779
780 fn acl_read(acl: &AclHandler, fabrics: &Fabrics, attr: &AttrDetails, tw: &mut WriteBuf<'_>) {
781 let encoder = ReadReplyInstance::new(attr, &mut *tw);
782 let mut writer = unwrap!(unwrap!(encoder.with_dataver(acl.dataver.get())));
783 let build_root = TLVWriteParent::new((), writer.writer());
784 unwrap!(acl.acl(
785 fabrics,
786 attr,
787 ArrayAttributeRead::ReadAll(unwrap!(AccessControlEntryStructArrayBuilder::new(
788 build_root,
789 &AttrReadReplyInstance::<WriteBuf>::TAG
790 )))
791 ));
792
793 unwrap!(writer.complete());
794 }
795
796 fn acl_add(acl: &AclHandler, fabrics: &mut Fabrics, data: &TLVElement<'_>, fab_idx: NonZeroU8) {
797 unwrap!(acl.set_acl(
798 fabrics.fabric_mut(fab_idx).unwrap(),
799 ArrayAttributeWrite::Add(AccessControlEntryStruct::new(data.clone())),
800 ));
801 }
802
803 fn acl_edit(
804 acl: &AclHandler,
805 fabrics: &mut Fabrics,
806 index: u16,
807 data: &TLVElement<'_>,
808 fab_idx: NonZeroU8,
809 ) {
810 unwrap!(acl.set_acl(
811 fabrics.fabric_mut(fab_idx).unwrap(),
812 ArrayAttributeWrite::Update(index, AccessControlEntryStruct::new(data.clone())),
813 ));
814 }
815
816 fn acl_remove(acl: &AclHandler, fabrics: &mut Fabrics, index: u16, fab_idx: NonZeroU8) {
817 unwrap!(acl.set_acl(
818 fabrics.fabric_mut(fab_idx).unwrap(),
819 ArrayAttributeWrite::Remove(index)
820 ));
821 }
822}