| /* |
| * |
| * 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 <lib/core/CHIPConfig.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <string> |
| #include <type_traits> |
| |
| 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<EndpointId>(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<DeviceTypeId>(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<EntryDelegate &>(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<EntryIteratorDelegate *>(&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)); |
| ReturnErrorCodeIf(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)); |
| ReturnErrorCodeIf(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<const EntryDelegate &>(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<SubjectStorage>(), "Storage type must be POD"); |
| static_assert(std::is_pod<TargetStorage>(), "Storage type must be POD"); |
| static_assert(std::is_pod<EntryStorage>(), "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 |