/* * * Copyright (c) 2021 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ExampleAccessControlDelegate.h" #include #include #include #include #include namespace { using chip::ClusterId; using chip::DeviceTypeId; using chip::EndpointId; using chip::FabricIndex; using chip::NodeId; using chip::kUndefinedNodeId; using chip::Access::AccessControl; using chip::Access::AuthMode; using chip::Access::Privilege; using chip::Access::RequestPath; using chip::Access::SubjectDescriptor; using Entry = chip::Access::AccessControl::Entry; using EntryIterator = chip::Access::AccessControl::EntryIterator; using Target = Entry::Target; // Pool sizes constexpr int kEntryStoragePoolSize = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_STORAGE_POOL_SIZE; constexpr int kEntryDelegatePoolSize = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_DELEGATE_POOL_SIZE; constexpr int kEntryIteratorDelegatePoolSize = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_ITERATOR_DELEGATE_POOL_SIZE; /* +---+ +---+ +---+ +---+ | 1 | | 2 | | A | | C | ENTRIES +---+ +---+ +---+ +---+ | | | | | +-+ +-+ | +----+ | | +-----+ | | | | v v v v +---+---+---+---+ | 1 | 2 | A | C | ENTRY DELEGATE POOL +---+---+---+---+ | | | | +-----------+ | | +-----------+ | +-----------+ +-------+ | | | | | v v v v +---+---+---+---+ +---+---+---+---+ | 0 | 1 | 2 | X | | A | X | C | X | ACL ENTRY STORAGE & POOL +---+---+---+---+ +---+---+---+---+ ^ ^ | | | +-----------+ +---------------+ | | | +---+---+---+---+ | 0 | 2 | X | X | ENTRY ITERATOR DELEGATE POOL +---+---+---+---+ ^ ^ | | | +-------+ | | +---+ +---+ | 0 | | 2 | ENTRY ITERATORS +---+ +---+ */ class SubjectStorage { public: bool IsEmpty() const { return mNode == kUndefinedNodeId; } void Clear() { mNode = kUndefinedNodeId; } CHIP_ERROR Get(NodeId & node) const { if (!IsEmpty()) { node = mNode; return CHIP_NO_ERROR; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR Set(NodeId node) { if (!IsEmpty()) { if (IsValid(node)) { mNode = node; return CHIP_NO_ERROR; } return CHIP_ERROR_INVALID_ARGUMENT; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR Add(NodeId node) { if (IsValid(node)) { mNode = node; return CHIP_NO_ERROR; } return CHIP_ERROR_INVALID_ARGUMENT; } private: static bool IsValid(NodeId node) { return node != kUndefinedNodeId; } static_assert(sizeof(NodeId) == 8, "Expecting 8 byte node ID"); NodeId mNode; }; class TargetStorage { public: bool IsEmpty() const { return mCluster == kClusterEmpty && mDeviceType == kDeviceTypeEmpty; } void Clear() { mCluster = kClusterEmpty; mDeviceType = kDeviceTypeEmpty; } CHIP_ERROR Get(Target & target) const { if (!IsEmpty()) { Decode(target); return CHIP_NO_ERROR; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR Set(const Target & target) { if (!IsEmpty()) { if (IsValid(target)) { Encode(target); return CHIP_NO_ERROR; } return CHIP_ERROR_INVALID_ARGUMENT; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR Add(const Target & target) { if (IsValid(target)) { Encode(target); return CHIP_NO_ERROR; } return CHIP_ERROR_INVALID_ARGUMENT; } private: // TODO: eventually this functionality should live where the type itself is defined static bool IsValidCluster(ClusterId cluster) { const auto id = cluster & kClusterIdMask; const auto vendor = cluster & kClusterVendorMask; return ((id <= kClusterIdMaxStd) || (kClusterIdMinMs <= id && id <= kClusterIdMaxMs)) && (vendor <= kClusterVendorMax); } // TODO: eventually this functionality should live where the type itself is defined static constexpr bool IsValidEndpoint(EndpointId endpoint) { return true; } // TODO: eventually this functionality should live where the type itself is defined static bool IsValidDeviceType(DeviceTypeId deviceType) { const auto id = deviceType & kDeviceTypeIdMask; const auto vendor = deviceType & kDeviceTypeVendorMask; return (id <= kDeviceTypeIdMax) && (vendor <= kDeviceTypeVendorMax); } // TODO: eventually this functionality should live where the type itself is defined static bool IsValid(const Target & target) { constexpr Target::Flags kNotAll = Target::kEndpoint | Target::kDeviceType; constexpr Target::Flags kAtLeastOne = kNotAll | Target::kCluster; constexpr Target::Flags kNone = ~kAtLeastOne; return ((target.flags & kNone) == 0) && ((target.flags & kAtLeastOne) != 0) && ((target.flags & kNotAll) != kNotAll) && !((target.flags & Target::kCluster) && !IsValidCluster(target.cluster)) && !((target.flags & Target::kEndpoint) && !IsValidEndpoint(target.endpoint)) && !((target.flags & Target::kDeviceType) && !IsValidDeviceType(target.deviceType)); } void Decode(Target & target) const { auto & flags = target.flags; auto & cluster = target.cluster; auto & endpoint = target.endpoint; auto & deviceType = target.deviceType; flags = 0; if (mCluster != kClusterEmpty) { cluster = mCluster; flags |= Target::kCluster; } if (mDeviceType != kDeviceTypeEmpty) { if ((mDeviceType & kDeviceTypeIdMask) == kEndpointMagic) { endpoint = static_cast(mDeviceType >> kEndpointShift); flags |= Target::kEndpoint; } else { deviceType = mDeviceType; flags |= Target::kDeviceType; } } } void Encode(const Target & target) { const auto flags = target.flags; const auto cluster = target.cluster; const auto endpoint = target.endpoint; const auto deviceType = target.deviceType; if (flags & Target::kCluster) { mCluster = cluster; } else { mCluster = kClusterEmpty; } if (flags & Target::kEndpoint) { mDeviceType = (static_cast(endpoint) << kEndpointShift) | kEndpointMagic; } else if (flags & Target::kDeviceType) { mDeviceType = deviceType; } else { mDeviceType = kDeviceTypeEmpty; } } static_assert(sizeof(ClusterId) == 4, "Expecting 4 byte cluster ID"); static_assert(sizeof(EndpointId) == 2, "Expecting 2 byte endpoint ID"); static_assert(sizeof(DeviceTypeId) == 4, "Expecting 4 byte device type ID"); // TODO: some (not all) of these values should live where the type itself is defined // (mCluster == kClusterEmpty) --> mCluster contains no cluster static constexpr ClusterId kClusterEmpty = 0xFFFFFFFF; // (mCluster & kClusterIdMask) --> cluster id portion static constexpr ClusterId kClusterIdMask = 0x0000FFFF; // ((mCluster & kClusterIdMask) < kClusterIdMaxStd) --> invalid static constexpr ClusterId kClusterIdMaxStd = 0x00007FFF; // ((mCluster & kClusterIdMask) < kClusterIdMinMs) --> invalid static constexpr ClusterId kClusterIdMinMs = 0x0000FC00; // ((mCluster & kClusterIdMask) < kClusterIdMaxMs) --> invalid static constexpr ClusterId kClusterIdMaxMs = 0x0000FFFE; // (mCluster & kClusterVendorMask) --> cluster vendor portion static constexpr ClusterId kClusterVendorMask = 0xFFFF0000; // ((mCluster & kClusterVendorMask) > kClusterVendorMax) --> invalid static constexpr ClusterId kClusterVendorMax = 0xFFFE0000; // (mDeviceType == kDeviceTypeEmpty) --> mDeviceType contains neither endpoint nor device type static constexpr DeviceTypeId kDeviceTypeEmpty = 0xFFFFFFFF; // (mDeviceType & kDeviceTypeIdMask) --> device type id portion static constexpr DeviceTypeId kDeviceTypeIdMask = 0x0000FFFF; // ((mDeviceType & kDeviceTypeIdMask) < kDeviceTypeIdMax) --> invalid static constexpr DeviceTypeId kDeviceTypeIdMax = 0x0000BFFF; // (mDeviceType & kDeviceTypeVendorMask) --> device type vendor portion static constexpr DeviceTypeId kDeviceTypeVendorMask = 0xFFFF0000; // ((mDeviceType & kDeviceTypeVendorMask) > kDeviceTypeVendorMax) --> invalid static constexpr DeviceTypeId kDeviceTypeVendorMax = 0xFFFE0000; // ((mDeviceType & kDeviceTypeIdMask) == kEndpointMagic) --> mDeviceType contains endpoint static constexpr DeviceTypeId kEndpointMagic = 0x0000EEEE; // (mDeviceType >> kEndpointShift) --> extract endpoint from mDeviceType static constexpr int kEndpointShift = 16; ClusterId mCluster; DeviceTypeId mDeviceType; }; class EntryStorage { public: // ACL support static constexpr size_t kNumberOfFabrics = CHIP_CONFIG_MAX_FABRICS; static constexpr size_t kEntriesPerFabric = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_ENTRIES_PER_FABRIC; static EntryStorage acl[kNumberOfFabrics * kEntriesPerFabric]; // Find the next unused entry storage in the access control list, if one exists. static EntryStorage * FindUnusedInAcl() { for (auto & storage : acl) { if (!storage.InUse()) { return &storage; } } return nullptr; } // Find the specified used entry storage in the access control list, if it exists. static EntryStorage * FindUsedInAcl(size_t index, const FabricIndex * fabricIndex) { if (fabricIndex != nullptr) { ConvertIndex(index, *fabricIndex, ConvertDirection::kRelativeToAbsolute); } if (index < ArraySize(acl)) { auto * storage = acl + index; if (storage->InUse()) { return storage; } } return nullptr; } // Pool support static EntryStorage pool[kEntryStoragePoolSize]; // Find an unused entry storage in the pool, if one is available. // The candidate is preferred if provided and it is in the pool, // regardless of whether it is already in use. static EntryStorage * Find(EntryStorage * candidate) { if (candidate && candidate->InPool()) { return candidate; } for (auto & storage : pool) { if (!storage.InUse()) { return &storage; } } return nullptr; } bool InPool() const { constexpr auto * end = pool + ArraySize(pool); return pool <= this && this < end; } EntryStorage() = default; void Init() { if (!mInUse) { Clear(); mInUse = true; } } bool InUse() const { return mInUse; } void Release() { if (InPool()) { mInUse = false; } } void Clear() { mInUse = false; mFabricIndex = chip::kUndefinedFabricIndex; mAuthMode = AuthMode::kPase; mPrivilege = Privilege::kView; for (auto & subject : mSubjects) { subject.Clear(); } for (auto & target : mTargets) { target.Clear(); } } enum class ConvertDirection { kAbsoluteToRelative, kRelativeToAbsolute }; // Entries have a position in the access control list, denoted by an "absolute" index. // Because entries are scoped to a fabric, a "fabric relative" index can be inferred. // // For example: suppose there are 8 entries for fabrics A, B, and C (as fabric indexes 1, 2, 3). // // 0 1 2 3 4 5 6 7 ABSOLUTE INDEX // +---+---+---+---+---+---+---+---+ // | A0| A1| B0| A2| B1| B2| C0| C1| FABRIC RELATIVE INDEX // +---+---+---+---+---+---+---+---+ // // While the entry at (absolute) index 2 is the third entry, it is the first entry scoped to // fabric B. So relative to fabric index 2, the entry is at (relative) index 0. // // The opposite is true: the second entry scoped to fabric B, at (relative) index 1, is the // fifth entry overall, at (absolute) index 4. // // Not all conversions are possible. For example, absolute index 3 is not scoped to fabric B, so // attempting to convert it to be relative to fabric index 2 will fail. Likewise, fabric B does // not contain a fourth entry, so attempting to convert index 3 (relative to fabric index 2) to // an absolute index will also fail. Such failures are denoted by use of an index that is one // past the end of the access control list. (So in this example, failure produces index 8.) static void ConvertIndex(size_t & index, const FabricIndex fabricIndex, ConvertDirection direction) { size_t absoluteIndex = 0; size_t relativeIndex = 0; size_t & fromIndex = (direction == ConvertDirection::kAbsoluteToRelative) ? absoluteIndex : relativeIndex; size_t & toIndex = (direction == ConvertDirection::kAbsoluteToRelative) ? relativeIndex : absoluteIndex; bool found = false; for (const auto & storage : acl) { if (!storage.InUse()) { break; } if (storage.mFabricIndex == fabricIndex) { if (index == fromIndex) { found = true; break; } relativeIndex++; } absoluteIndex++; } index = found ? toIndex : ArraySize(acl); } static constexpr size_t kMaxSubjects = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_SUBJECTS_PER_ENTRY; static constexpr size_t kMaxTargets = CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_TARGETS_PER_ENTRY; bool mInUse; FabricIndex mFabricIndex; AuthMode mAuthMode; Privilege mPrivilege; SubjectStorage mSubjects[kMaxSubjects]; TargetStorage mTargets[kMaxTargets]; }; class EntryDelegate : public Entry::Delegate { public: // Pool support static EntryDelegate pool[kEntryDelegatePoolSize]; // Find an unused entry delegate in the pool, if one is available. // The candidate is preferred if it is in the pool, regardless of whether // it is already in use. static EntryDelegate * Find(Entry::Delegate & candidate) { if (InPool(candidate)) { return &static_cast(candidate); } for (auto & delegate : pool) { if (!delegate.InUse()) { return &delegate; } } return nullptr; } static bool InPool(const Entry::Delegate & delegate) { constexpr auto * end = pool + ArraySize(pool); return pool <= &delegate && &delegate < end; } void Release() override { mStorage->Release(); mStorage = nullptr; } CHIP_ERROR GetAuthMode(AuthMode & authMode) const override { authMode = mStorage->mAuthMode; return CHIP_NO_ERROR; } CHIP_ERROR GetFabricIndex(FabricIndex & fabricIndex) const override { fabricIndex = mStorage->mFabricIndex; return CHIP_NO_ERROR; } CHIP_ERROR GetPrivilege(Privilege & privilege) const override { privilege = mStorage->mPrivilege; return CHIP_NO_ERROR; } CHIP_ERROR SetAuthMode(AuthMode authMode) override { ReturnErrorOnFailure(EnsureStorageInPool()); mStorage->mAuthMode = authMode; return CHIP_NO_ERROR; } CHIP_ERROR SetFabricIndex(FabricIndex fabricIndex) override { ReturnErrorOnFailure(EnsureStorageInPool()); mStorage->mFabricIndex = fabricIndex; return CHIP_NO_ERROR; } CHIP_ERROR SetPrivilege(Privilege privilege) override { ReturnErrorOnFailure(EnsureStorageInPool()); mStorage->mPrivilege = privilege; return CHIP_NO_ERROR; } CHIP_ERROR GetSubjectCount(size_t & count) const override { count = 0; for (const auto & subject : mStorage->mSubjects) { if (subject.IsEmpty()) { break; } count++; } return CHIP_NO_ERROR; } CHIP_ERROR GetSubject(size_t index, NodeId & subject) const override { if (index < EntryStorage::kMaxSubjects) { return mStorage->mSubjects[index].Get(subject); } return CHIP_ERROR_SENTINEL; } CHIP_ERROR SetSubject(size_t index, NodeId subject) override { ReturnErrorOnFailure(EnsureStorageInPool()); if (index < EntryStorage::kMaxSubjects) { return mStorage->mSubjects[index].Set(subject); } return CHIP_ERROR_SENTINEL; } CHIP_ERROR AddSubject(size_t * index, NodeId subject) override { ReturnErrorOnFailure(EnsureStorageInPool()); size_t count = 0; GetSubjectCount(count); if (count < EntryStorage::kMaxSubjects) { CHIP_ERROR err = mStorage->mSubjects[count].Add(subject); if (err == CHIP_NO_ERROR && index != nullptr) { *index = count; } return err; } return CHIP_ERROR_BUFFER_TOO_SMALL; } CHIP_ERROR RemoveSubject(size_t index) override { ReturnErrorOnFailure(EnsureStorageInPool()); size_t count = 0; GetSubjectCount(count); if (index < count) { // The storage at the specified index will be deleted by copying any subsequent storage // over it, ideally also including one unused storage past the ones in use. If all // storage was in use, this isn't possible, so the final storage is manually cleared. auto * dest = mStorage->mSubjects + index; const auto * src = dest + 1; const auto n = std::min(count, EntryStorage::kMaxSubjects - 1) - index; memmove(dest, src, n * sizeof(*dest)); if (count == EntryStorage::kMaxSubjects) { mStorage->mSubjects[EntryStorage::kMaxSubjects - 1].Clear(); } return CHIP_NO_ERROR; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR GetTargetCount(size_t & count) const override { count = 0; for (const auto & target : mStorage->mTargets) { if (target.IsEmpty()) { break; } count++; } return CHIP_NO_ERROR; } CHIP_ERROR GetTarget(size_t index, Target & target) const override { if (index < EntryStorage::kMaxTargets) { return mStorage->mTargets[index].Get(target); } return CHIP_ERROR_SENTINEL; } CHIP_ERROR SetTarget(size_t index, const Target & target) override { ReturnErrorOnFailure(EnsureStorageInPool()); if (index < EntryStorage::kMaxTargets) { return mStorage->mTargets[index].Set(target); } return CHIP_ERROR_SENTINEL; } CHIP_ERROR AddTarget(size_t * index, const Target & target) override { ReturnErrorOnFailure(EnsureStorageInPool()); size_t count = 0; GetTargetCount(count); if (count < EntryStorage::kMaxTargets) { CHIP_ERROR err = mStorage->mTargets[count].Add(target); if (err == CHIP_NO_ERROR && index != nullptr) { *index = count; } return err; } return CHIP_ERROR_BUFFER_TOO_SMALL; } CHIP_ERROR RemoveTarget(size_t index) override { ReturnErrorOnFailure(EnsureStorageInPool()); size_t count = 0; GetTargetCount(count); if (index < count) { // The storage at the specified index will be deleted by copying any subsequent storage // over it, ideally also including one unused storage past the ones in use. If all // storage was in use, this isn't possible, so the final storage is manually cleared. auto * dest = mStorage->mTargets + index; const auto * src = dest + 1; const auto n = std::min(count, EntryStorage::kMaxTargets - 1) - index; memmove(dest, src, n * sizeof(*dest)); if (count == EntryStorage::kMaxTargets) { mStorage->mTargets[EntryStorage::kMaxTargets - 1].Clear(); } return CHIP_NO_ERROR; } return CHIP_ERROR_SENTINEL; } void Init(Entry & entry, EntryStorage & storage) { entry.SetDelegate(*this); storage.Init(); mEntry = &entry; mStorage = &storage; } bool InUse() const { return mStorage != nullptr; } const EntryStorage * GetStorage() const { return mStorage; } EntryStorage * GetStorage() { return mStorage; } // A storage is about to be deleted. If this delegate was // using it, make a best effort to copy it to the pool. void FixBeforeDelete(EntryStorage & storage) { if (mStorage == &storage) { // Best effort, OK if it fails. EnsureStorageInPool(); } } // A storage was deleted, and others shuffled into its place. // Fix this delegate (if necessary) to ensure it's using the // correct storage. void FixAfterDelete(EntryStorage & storage) { constexpr auto & acl = EntryStorage::acl; constexpr auto * end = acl + ArraySize(acl); if (mStorage == &storage) { mEntry->ResetDelegate(); } else if (&storage < mStorage && mStorage < end) { mStorage--; } } // Ensure the delegate is using storage from the pool (not the access control list), // by copying (from the access control list to the pool) if necessary. CHIP_ERROR EnsureStorageInPool() { if (mStorage->InPool()) { return CHIP_NO_ERROR; } if (auto * storage = EntryStorage::Find(nullptr)) { *storage = *mStorage; mStorage = storage; return CHIP_NO_ERROR; } return CHIP_ERROR_BUFFER_TOO_SMALL; } private: Entry * mEntry = nullptr; EntryStorage * mStorage = nullptr; }; class EntryIteratorDelegate : public EntryIterator::Delegate { public: // Pool support static EntryIteratorDelegate pool[kEntryIteratorDelegatePoolSize]; // Find an unused entry iterator delegate in the pool, if one is available. // The candidate is preferred if it is in the pool, regardless of whether // it is already in use. static EntryIteratorDelegate * Find(EntryIterator::Delegate & candidate) { if (InPool(candidate)) { return static_cast(&candidate); } for (auto & delegate : pool) { if (!delegate.InUse()) { return &delegate; } } return nullptr; } static bool InPool(const EntryIterator::Delegate & delegate) { constexpr auto * end = pool + ArraySize(pool); return pool <= &delegate && &delegate < end; } void Release() override { mInUse = false; } CHIP_ERROR Next(Entry & entry) override { constexpr auto & acl = EntryStorage::acl; constexpr auto * end = acl + ArraySize(acl); while (true) { if (mStorage == nullptr) { // Start at beginning of access control list... mStorage = acl; } else if (mStorage < end) { // ...and continue iterating entries... mStorage++; } if (mStorage == end || !mStorage->InUse()) { // ...but only used ones... mStorage = end; break; } if (mFabricFiltered && mStorage->mFabricIndex != mFabricIndex) { // ...skipping those that aren't scoped to a specified fabric... continue; } if (auto * delegate = EntryDelegate::Find(entry.GetDelegate())) { // ...returning any next entry via a delegate. delegate->Init(entry, *mStorage); return CHIP_NO_ERROR; } return CHIP_ERROR_BUFFER_TOO_SMALL; } return CHIP_ERROR_SENTINEL; } void Init(EntryIterator & iterator, const FabricIndex * fabricIndex) { iterator.SetDelegate(*this); mInUse = true; mFabricFiltered = fabricIndex != nullptr; if (mFabricFiltered) { mFabricIndex = *fabricIndex; } mStorage = nullptr; } bool InUse() const { return mInUse; } // A storage was deleted, and others shuffled into its place. // Fix this delegate (if necessary) to ensure it's using the // correct storage. void FixAfterDelete(EntryStorage & storage) { constexpr auto & acl = EntryStorage::acl; constexpr auto * end = acl + ArraySize(acl); if (&storage <= mStorage && mStorage < end) { if (mStorage == acl) { mStorage = nullptr; } else { mStorage--; } } } private: bool mInUse = false; bool mFabricFiltered; FabricIndex mFabricIndex; EntryStorage * mStorage; }; CHIP_ERROR CopyViaInterface(const Entry & entry, EntryStorage & storage) { #if CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT // NOTE: uses sizeof(EntryStorage) on stack as a temporary and is only necessary if using this // file with other Entry::Delegate implementations that are not EntryDelegate in this file EntryStorage temp; temp.Clear(); FabricIndex fabricIndex = kUndefinedFabricIndex; ReturnErrorOnFailure(entry.GetFabricIndex(fabricIndex)); temp.mFabricIndex = fabricIndex; AuthMode authMode = AuthMode::kNone; ReturnErrorOnFailure(entry.GetAuthMode(authMode)); temp.mAuthMode = authMode; Privilege privilege = Privilege::kView; ReturnErrorOnFailure(entry.GetPrivilege(privilege)); temp.mPrivilege = privilege; size_t subjectCount = 0; ReturnErrorOnFailure(entry.GetSubjectCount(subjectCount)); VerifyOrReturnError(subjectCount <= EntryStorage::kMaxSubjects, CHIP_ERROR_BUFFER_TOO_SMALL); for (size_t i = 0; i < subjectCount; ++i) { NodeId subject = kUndefinedNodeId; ReturnErrorOnFailure(entry.GetSubject(i, subject)); temp.mSubjects[i].Add(subject); } size_t targetCount = 0; ReturnErrorOnFailure(entry.GetTargetCount(targetCount)); VerifyOrReturnError(targetCount <= EntryStorage::kMaxTargets, CHIP_ERROR_BUFFER_TOO_SMALL); for (size_t i = 0; i < targetCount; ++i) { Target target; ReturnErrorOnFailure(entry.GetTarget(i, target)); temp.mTargets[i].Add(target); } temp.mInUse = true; storage = temp; return CHIP_NO_ERROR; #else // NOTE: save space by not implementing function VerifyOrDie(false); return CHIP_ERROR_NOT_IMPLEMENTED; #endif } CHIP_ERROR Copy(const Entry & entry, EntryStorage & storage) { #if CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT auto & delegate = entry.GetDelegate(); if (EntryDelegate::InPool(delegate)) { // NOTE: if an entry's delegate is in the pool, it must be an EntryDelegate, // which must have a storage that is in use, which can be copied as POD storage = *static_cast(delegate).GetStorage(); return CHIP_NO_ERROR; } #endif return CopyViaInterface(entry, storage); } class AccessControlDelegate : public AccessControl::Delegate { public: CHIP_ERROR Init() override { ChipLogProgress(DataManagement, "Examples::AccessControlDelegate::Init"); for (auto & storage : EntryStorage::acl) { storage.Clear(); } return CHIP_NO_ERROR; } void Finish() override { ChipLogProgress(DataManagement, "Examples::AccessControlDelegate::Finish"); } CHIP_ERROR GetMaxEntriesPerFabric(size_t & value) const override { value = EntryStorage::kEntriesPerFabric; return CHIP_NO_ERROR; } CHIP_ERROR GetMaxSubjectsPerEntry(size_t & value) const override { value = EntryStorage::kMaxSubjects; return CHIP_NO_ERROR; } CHIP_ERROR GetMaxTargetsPerEntry(size_t & value) const override { value = EntryStorage::kMaxTargets; return CHIP_NO_ERROR; } CHIP_ERROR GetMaxEntryCount(size_t & value) const override { value = ArraySize(EntryStorage::acl); return CHIP_NO_ERROR; } CHIP_ERROR GetEntryCount(FabricIndex fabric, size_t & value) const override { value = 0; for (const auto & storage : EntryStorage::acl) { if (!storage.InUse()) { break; } if (storage.mFabricIndex == fabric) { value++; } } return CHIP_NO_ERROR; } CHIP_ERROR GetEntryCount(size_t & value) const override { value = 0; for (const auto & storage : EntryStorage::acl) { if (!storage.InUse()) { break; } value++; } return CHIP_NO_ERROR; } CHIP_ERROR PrepareEntry(Entry & entry) override { if (auto * delegate = EntryDelegate::Find(entry.GetDelegate())) { if (auto * storage = EntryStorage::Find(delegate->GetStorage())) { delegate->Init(entry, *storage); return CHIP_NO_ERROR; } } return CHIP_ERROR_BUFFER_TOO_SMALL; } CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, FabricIndex * fabricIndex) override { if (auto * storage = EntryStorage::FindUnusedInAcl()) { CHIP_ERROR err = Copy(entry, *storage); if (err == CHIP_NO_ERROR) { if (fabricIndex != nullptr) { *fabricIndex = storage->mFabricIndex; } if (index != nullptr) { *index = size_t(storage - EntryStorage::acl); if (fabricIndex != nullptr) { EntryStorage::ConvertIndex(*index, *fabricIndex, EntryStorage::ConvertDirection::kAbsoluteToRelative); } } } return err; } return CHIP_ERROR_BUFFER_TOO_SMALL; } CHIP_ERROR ReadEntry(size_t index, Entry & entry, const FabricIndex * fabricIndex) const override { if (auto * storage = EntryStorage::FindUsedInAcl(index, fabricIndex)) { if (auto * delegate = EntryDelegate::Find(entry.GetDelegate())) { delegate->Init(entry, *storage); return CHIP_NO_ERROR; } return CHIP_ERROR_BUFFER_TOO_SMALL; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex * fabricIndex) override { if (auto * storage = EntryStorage::FindUsedInAcl(index, fabricIndex)) { return Copy(entry, *storage); } return CHIP_ERROR_SENTINEL; } CHIP_ERROR DeleteEntry(size_t index, const FabricIndex * fabricIndex) override { if (auto * storage = EntryStorage::FindUsedInAcl(index, fabricIndex)) { // Best effort attempt to preserve any outstanding delegates... for (auto & delegate : EntryDelegate::pool) { delegate.FixBeforeDelete(*storage); } // ...then go through the access control list starting at the deleted storage... constexpr auto & acl = EntryStorage::acl; constexpr auto * end = acl + ArraySize(acl); for (auto * next = storage + 1; storage < end; ++storage, ++next) { // ...copying over each storage with its next one... if (next < end && next->InUse()) { *storage = *next; } else { // ...clearing the last previously used one... storage->Clear(); break; } } // ...then fix up all the delegates so they still use the proper storage. storage = acl + index; for (auto & delegate : EntryDelegate::pool) { delegate.FixAfterDelete(*storage); } for (auto & delegate : EntryIteratorDelegate::pool) { delegate.FixAfterDelete(*storage); } return CHIP_NO_ERROR; } return CHIP_ERROR_SENTINEL; } CHIP_ERROR Entries(EntryIterator & iterator, const FabricIndex * fabricIndex) const override { if (auto * delegate = EntryIteratorDelegate::Find(iterator.GetDelegate())) { delegate->Init(iterator, fabricIndex); return CHIP_NO_ERROR; } return CHIP_ERROR_BUFFER_TOO_SMALL; } CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege) override { return CHIP_ERROR_NOT_IMPLEMENTED; } }; static_assert(std::is_pod(), "Storage type must be POD"); static_assert(std::is_pod(), "Storage type must be POD"); static_assert(std::is_pod(), "Storage type must be POD"); EntryStorage EntryStorage::acl[]; EntryStorage EntryStorage::pool[]; EntryDelegate EntryDelegate::pool[]; EntryIteratorDelegate EntryIteratorDelegate::pool[]; } // namespace namespace chip { namespace Access { namespace Examples { AccessControl::Delegate * GetAccessControlDelegate() { static AccessControlDelegate accessControlDelegate; return &accessControlDelegate; } } // namespace Examples } // namespace Access } // namespace chip