From 760bc7fde43b682c3a1de3dfbe5699a85811f22d Mon Sep 17 00:00:00 2001 From: Marc Lepage <67919234+mlepage-google@users.noreply.github.com> Date: Tue, 16 Nov 2021 11:15:00 -0500 Subject: [PATCH] Add example implementation of access control (#11550) * Add full implementation of access control New interface (issue #10249) and new implementation (issue #10250). Implementation is all in-memory and uses only static storage and stack (no heap). Some details missing (e.g. CAT support) but most is here, though not yet hooked up to other code. Comes with unit tests. * Fix configuration Was added at last minute, missed this spot. * Address code review comments - rewrite portions of the check loop to use boolean instead of goto - refactor privilege checking to make it clearer - ensure storage classes are POD types (with static_assert) - remove memsets on storage classes (for now, can re-add later) - clarify some comments - remove debug log statement * Restyled by clang-format * Address code review comments Also fix some compiler warnings/errors on other builds * Add tests for fabric filtered indexing Refactor the index conversion (to/from fabric filtered) to be clearer. * Restyled by clang-format * Address code review comments - change Target::Flags from int to unsigned - use unsigned in static_asserts for flags - tweak auto variables * Add docs and comments - Add API documentation (mainly to clarify in/out parameters). - Add implementation comments (where warranted). - Add more unit tests (mainly for removing subjects/targets). - A few fixes to get aforementioned tests passing. - A bit of refactoring/renaming to clarify the code. * Restyled by whitespace * Restyled by clang-format * Some code review suggestions * Restyled by clang-format * Fix errors on other compilers * Fix more build errors on other compilers * More code review suggestions * Restyled by clang-format * Fix typo in config flag Co-authored-by: Restyled.io --- src/access/AccessControl.cpp | 177 ++- src/access/AccessControl.h | 404 +++++- src/access/AccessControlDataProvider.h | 134 -- src/access/AuthMode.h | 4 +- src/access/BUILD.gn | 5 +- src/access/Privilege.h | 4 +- src/access/RequestPath.h | 2 +- src/access/SubjectDescriptor.h | 13 +- .../ExampleAccessControlDataProvider.cpp | 51 - .../examples/ExampleAccessControlDelegate.cpp | 1113 ++++++++++++++ ...vider.h => ExampleAccessControlDelegate.h} | 4 +- src/access/tests/TestAccessControl.cpp | 1288 ++++++++++++----- src/lib/core/CHIPConfig.h | 90 ++ 13 files changed, 2654 insertions(+), 635 deletions(-) delete mode 100644 src/access/AccessControlDataProvider.h delete mode 100644 src/access/examples/ExampleAccessControlDataProvider.cpp create mode 100644 src/access/examples/ExampleAccessControlDelegate.cpp rename src/access/examples/{ExampleAccessControlDataProvider.h => ExampleAccessControlDelegate.h} (87%) diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index ca062f28e2927e..9b3278d5b1a2cd 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -23,92 +23,151 @@ namespace { using chip::FabricIndex; using namespace chip::Access; -// Avoid GetAccessControl returning nullptr before SetAccessControl is called. -class UnimplementedDataProvider : public AccessControlDataProvider +AccessControl defaultAccessControl; +AccessControl * globalAccessControl = &defaultAccessControl; + +static_assert(((unsigned(Privilege::kAdminister) & unsigned(Privilege::kManage)) == 0) && + ((unsigned(Privilege::kAdminister) & unsigned(Privilege::kOperate)) == 0) && + ((unsigned(Privilege::kAdminister) & unsigned(Privilege::kView)) == 0) && + ((unsigned(Privilege::kAdminister) & unsigned(Privilege::kProxyView)) == 0) && + ((unsigned(Privilege::kManage) & unsigned(Privilege::kOperate)) == 0) && + ((unsigned(Privilege::kManage) & unsigned(Privilege::kView)) == 0) && + ((unsigned(Privilege::kManage) & unsigned(Privilege::kProxyView)) == 0) && + ((unsigned(Privilege::kOperate) & unsigned(Privilege::kView)) == 0) && + ((unsigned(Privilege::kOperate) & unsigned(Privilege::kProxyView)) == 0) && + ((unsigned(Privilege::kView) & unsigned(Privilege::kProxyView)) == 0), + "Privilege bits must be unique"); + +bool CheckRequestPrivilegeAgainstEntryPrivilege(Privilege requestPrivilege, Privilege entryPrivilege) { - CHIP_ERROR Init() override { return CHIP_NO_ERROR; } - - void Finish() override {} - - EntryIterator * Entries() const override { return nullptr; } - - EntryIterator * Entries(FabricIndex fabricIndex) const override { return nullptr; } -}; - -// Avoid GetAccessControl returning nullptr before SetAccessControl is called. -UnimplementedDataProvider gUnimplementedDataProvider; -AccessControl gUnimplementedAccessControl(gUnimplementedDataProvider); - -AccessControl * gAccessControl = &gUnimplementedAccessControl; + switch (entryPrivilege) + { + case Privilege::kView: + return requestPrivilege == Privilege::kView; + case Privilege::kProxyView: + return requestPrivilege == Privilege::kProxyView || requestPrivilege == Privilege::kView; + case Privilege::kOperate: + return requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kView; + case Privilege::kManage: + return requestPrivilege == Privilege::kManage || requestPrivilege == Privilege::kOperate || + requestPrivilege == Privilege::kView; + case Privilege::kAdminister: + return requestPrivilege == Privilege::kAdminister || requestPrivilege == Privilege::kManage || + requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kView || + requestPrivilege == Privilege::kProxyView; + } + return false; +} } // namespace namespace chip { namespace Access { +AccessControl::Entry::Delegate AccessControl::Entry::mDefaultDelegate; +AccessControl::EntryIterator::Delegate AccessControl::EntryIterator::mDefaultDelegate; +AccessControl::Delegate AccessControl::mDefaultDelegate; + CHIP_ERROR AccessControl::Init() { - ChipLogDetail(DataManagement, "access control: initializing"); - // ... - return CHIP_NO_ERROR; + ChipLogDetail(DataManagement, "AccessControl::Init"); + return mDelegate.Init(); } -void AccessControl::Finish() +CHIP_ERROR AccessControl::Finish() { - ChipLogDetail(DataManagement, "access control: finishing"); - // ... + ChipLogDetail(DataManagement, "AccessControl::Finish"); + return mDelegate.Finish(); } -CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege privilege) +CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, + Privilege requestPrivilege) { - CHIP_ERROR err = CHIP_ERROR_ACCESS_DENIED; + EntryIterator iterator; + ReturnErrorOnFailure(Entries(iterator, &subjectDescriptor.fabricIndex)); - EntryIterator * iterator = mDataProvider.Entries(subjectDescriptor.fabricIndex); - // TODO: check error (but can't until we have an implementation) -#if 0 - ReturnErrorCodeIf(iterator == nullptr, CHIP_ERROR_INTERNAL); -#else - ReturnErrorCodeIf(iterator == nullptr, CHIP_NO_ERROR); -#endif - - // TODO: a few more cases (PASE commissioning, CASE Authenticated Tags, etc.) - - while (auto entry = iterator->Next()) + Entry entry; + while (iterator.Next(entry) == CHIP_NO_ERROR) { - ChipLogDetail(DataManagement, "Checking entry"); - - if (!entry->MatchesPrivilege(privilege)) - continue; - ChipLogDetail(DataManagement, " --> matched privilege"); - if (!entry->MatchesAuthMode(subjectDescriptor.authMode)) - continue; - ChipLogDetail(DataManagement, " --> matched authmode"); - if (!entry->MatchesSubject(subjectDescriptor.subjects[0])) + AuthMode authMode = AuthMode::kNone; + ReturnErrorOnFailure(entry.GetAuthMode(authMode)); + if (authMode != subjectDescriptor.authMode) + { continue; - ChipLogDetail(DataManagement, " --> matched subject"); - if (!entry->MatchesTarget(requestPath.endpoint, requestPath.cluster)) - continue; - ChipLogDetail(DataManagement, " --> matched target"); + } - err = CHIP_NO_ERROR; - break; + Privilege privilege = Privilege::kView; + ReturnErrorOnFailure(entry.GetPrivilege(privilege)); + if (!CheckRequestPrivilegeAgainstEntryPrivilege(requestPrivilege, privilege)) + { + continue; + } + + size_t subjectCount = 0; + ReturnErrorOnFailure(entry.GetSubjectCount(subjectCount)); + if (subjectCount > 0) + { + bool subjectMatched = false; + for (size_t i = 0; i < subjectCount; ++i) + { + NodeId subject = kUndefinedNodeId; + ReturnErrorOnFailure(entry.GetSubject(i, subject)); + if (subject == subjectDescriptor.subjects[0]) + { + subjectMatched = true; + break; + } + // TODO: check against CATs in subject descriptor + } + if (!subjectMatched) + { + continue; + } + } + + size_t targetCount = 0; + ReturnErrorOnFailure(entry.GetTargetCount(targetCount)); + if (targetCount > 0) + { + bool targetMatched = false; + for (size_t i = 0; i < targetCount; ++i) + { + Entry::Target target; + ReturnErrorOnFailure(entry.GetTarget(i, target)); + if ((target.flags & Entry::Target::kCluster) && target.cluster != requestPath.cluster) + { + continue; + } + if ((target.flags & Entry::Target::kEndpoint) && target.endpoint != requestPath.endpoint) + { + continue; + } + // TODO: check against target.deviceType (requires lookup) + targetMatched = true; + break; + } + if (!targetMatched) + { + continue; + } + } + + // Entry passed all checks: access is allowed. + return CHIP_NO_ERROR; } - iterator->Release(); - return err; + // No entry was found which passed all checks: access is denied. + return CHIP_ERROR_ACCESS_DENIED; } -AccessControl * GetAccessControl() +AccessControl & GetAccessControl() { - return gAccessControl; + return *globalAccessControl; } -void SetAccessControl(AccessControl * accessControl) +void SetAccessControl(AccessControl & accessControl) { - if (accessControl != nullptr) - { - gAccessControl = accessControl; - } + globalAccessControl = &accessControl; } } // namespace Access diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h index 554937e43356dd..c11bb44c2d09ca 100644 --- a/src/access/AccessControl.h +++ b/src/access/AccessControl.h @@ -18,7 +18,6 @@ #pragma once -#include "AccessControlDataProvider.h" #include "Privilege.h" #include "RequestPath.h" #include "SubjectDescriptor.h" @@ -32,15 +31,311 @@ class AccessControl { public: /** - * Create an access control module. Must be initialized before use, and - * deinitialized when finished. Must be configured with an - * AccessControlDataProvider, which must outlive this module. + * Handle to an entry in the access control list. + * + * Must be prepared (`AccessControl::PrepareEntry`) or read (`AccessControl::ReadEntry`) before first use. */ - AccessControl(AccessControlDataProvider & dataProvider) : mDataProvider(dataProvider) {} + class Entry + { + public: + struct Target + { + using Flags = unsigned; + static constexpr Flags kCluster = 1 << 0; + static constexpr Flags kEndpoint = 1 << 1; + static constexpr Flags kDeviceType = 1 << 2; + Flags flags; + ClusterId cluster; + EndpointId endpoint; + DeviceTypeId deviceType; + }; + + class Delegate + { + public: + Delegate() = default; + + Delegate(const Delegate &) = delete; + Delegate & operator=(const Delegate &) = delete; + + virtual ~Delegate() = default; + + virtual void Release() {} + + // Simple getters + virtual CHIP_ERROR GetAuthMode(AuthMode & authMode) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR GetFabricIndex(FabricIndex & fabricIndex) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR GetPrivilege(Privilege & privilege) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // Simple setters + virtual CHIP_ERROR SetAuthMode(AuthMode authMode) { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR SetFabricIndex(FabricIndex fabricIndex) { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR SetPrivilege(Privilege privilege) { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // Subjects + virtual CHIP_ERROR GetSubjectCount(size_t & count) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR GetSubject(size_t index, NodeId & subject) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR SetSubject(size_t index, NodeId subject) { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR AddSubject(size_t * index, NodeId subject) { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR RemoveSubject(size_t index) { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // Targets + virtual CHIP_ERROR GetTargetCount(size_t & count) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR GetTarget(size_t index, Target & target) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR SetTarget(size_t index, const Target & target) { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR AddTarget(size_t * index, const Target & target) { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR RemoveTarget(size_t index) { return CHIP_ERROR_NOT_IMPLEMENTED; } + }; + + Entry() = default; + + Entry(const Entry &) = delete; + Entry & operator=(const Entry &) = delete; + + ~Entry() { mDelegate->Release(); } + + // Simple getters + CHIP_ERROR GetAuthMode(AuthMode & authMode) const { return mDelegate->GetAuthMode(authMode); } + CHIP_ERROR GetFabricIndex(FabricIndex & fabricIndex) const { return mDelegate->GetFabricIndex(fabricIndex); } + CHIP_ERROR GetPrivilege(Privilege & privilege) const { return mDelegate->GetPrivilege(privilege); } + + // Simple setters + CHIP_ERROR SetAuthMode(AuthMode authMode) { return mDelegate->SetAuthMode(authMode); } + CHIP_ERROR SetFabricIndex(FabricIndex fabricIndex) { return mDelegate->SetFabricIndex(fabricIndex); } + CHIP_ERROR SetPrivilege(Privilege privilege) { return mDelegate->SetPrivilege(privilege); } + + /** + * Gets the number of subjects. + * + * @param [out] count The number of subjects. + */ + CHIP_ERROR GetSubjectCount(size_t & count) const { return mDelegate->GetSubjectCount(count); } + + /** + * Gets the specified subject. + * + * @param [in] index The index of the subject to get. + * @param [out] subject The subject into which to get. + */ + CHIP_ERROR GetSubject(size_t index, NodeId & subject) const { return mDelegate->GetSubject(index, subject); } + + /** + * Sets the specified subject. + * + * @param [in] index The index of the subject to set. + * @param [in] subject The subject from which to set. + */ + CHIP_ERROR SetSubject(size_t index, NodeId subject) { return mDelegate->SetSubject(index, subject); } + + /** + * Adds the specified subject. + * + * @param [out] index The index of the added subject, if not null. + * @param [in] subject The subject to add. + */ + CHIP_ERROR AddSubject(size_t * index, NodeId subject) { return mDelegate->AddSubject(index, subject); } + + /** + * Removes the specified subject. + * + * @param [in] index The index of the subject to delete. + */ + CHIP_ERROR RemoveSubject(size_t index) { return mDelegate->RemoveSubject(index); } + + /** + * Gets the number of targets. + * + * @param [out] count The number of targets. + */ + CHIP_ERROR GetTargetCount(size_t & count) const { return mDelegate->GetTargetCount(count); } + + /** + * Gets the specified target. + * + * @param [in] index The index of the target to get. + * @param [out] target The target into which to get. + */ + CHIP_ERROR GetTarget(size_t index, Target & target) const { return mDelegate->GetTarget(index, target); } + + /** + * Sets the specified target. + * + * @param [in] index The index of the target to set. + * @param [in] target The target from which to set. + */ + CHIP_ERROR SetTarget(size_t index, const Target & target) { return mDelegate->SetTarget(index, target); } + + /** + * Adds the specified target. + * + * @param [out] index The index of the added target, if not null. + * @param [in] target The target to add. + */ + CHIP_ERROR AddTarget(size_t * index, const Target & target) { return mDelegate->AddTarget(index, target); } + + /** + * Removes the specified target. + * + * @param [in] index The index of the target to delete. + */ + CHIP_ERROR RemoveTarget(size_t index) { return mDelegate->RemoveTarget(index); } + + public: + const Delegate & GetDelegate() const { return *mDelegate; } + + Delegate & GetDelegate() { return *mDelegate; } + + void SetDelegate(Delegate & delegate) + { + mDelegate->Release(); + mDelegate = &delegate; + } + + void ResetDelegate() + { + mDelegate->Release(); + mDelegate = &mDefaultDelegate; + } + + private: + static Delegate mDefaultDelegate; + Delegate * mDelegate = &mDefaultDelegate; + }; + + /** + * Handle to an entry iterator in the access control list. + * + * Must be initialized (`AccessControl::Entries`) before first use. + */ + class EntryIterator + { + public: + class Delegate + { + public: + Delegate() = default; + + Delegate(const Delegate &) = delete; + Delegate & operator=(const Delegate &) = delete; + + virtual ~Delegate() = default; + + virtual void Release() {} + + virtual CHIP_ERROR Next(Entry & entry) { return CHIP_ERROR_SENTINEL; } + }; + + EntryIterator() = default; + + EntryIterator(const EntryIterator &) = delete; + EntryIterator & operator=(const EntryIterator &) = delete; + + ~EntryIterator() { mDelegate->Release(); } + + CHIP_ERROR Next(Entry & entry) { return mDelegate->Next(entry); } + + public: + const Delegate & GetDelegate() const { return *mDelegate; } + + Delegate & GetDelegate() { return *mDelegate; } + + void SetDelegate(Delegate & delegate) + { + mDelegate->Release(); + mDelegate = &delegate; + } + + void ResetDelegate() + { + mDelegate->Release(); + mDelegate = &mDefaultDelegate; + } + + private: + static Delegate mDefaultDelegate; + Delegate * mDelegate = &mDefaultDelegate; + }; + + class Extension + { + // TODO: implement extension + }; + + class ExtensionIterator + { + // TODO: implement extension iterator + }; + + class Listener + { + public: + virtual ~Listener() = default; + + // TODO: add entry/extension to listener interface + virtual void OnEntryChanged() = 0; + virtual void OnExtensionChanged() = 0; + }; + + class Delegate + { + public: + Delegate() = default; + + Delegate(const Delegate &) = delete; + Delegate & operator=(const Delegate &) = delete; + + virtual ~Delegate() = default; + + virtual void Release() {} + + virtual CHIP_ERROR Init() { return CHIP_ERROR_NOT_IMPLEMENTED; } + virtual CHIP_ERROR Finish() { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // Capabilities + virtual CHIP_ERROR GetMaxEntries(int & value) const { return CHIP_ERROR_NOT_IMPLEMENTED; } + // TODO: more capabilities + + // Preparation + virtual CHIP_ERROR PrepareEntry(Entry & entry) { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // CRUD + virtual CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, FabricIndex * fabricIndex) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + virtual CHIP_ERROR ReadEntry(size_t index, Entry & entry, const FabricIndex * fabricIndex) const + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + virtual CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex * fabricIndex) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + virtual CHIP_ERROR DeleteEntry(size_t index, const FabricIndex * fabricIndex) { return CHIP_ERROR_NOT_IMPLEMENTED; } + + // Iteration + virtual CHIP_ERROR Entries(EntryIterator & iterator, const FabricIndex * fabricIndex) const + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + // Listening + virtual void SetListener(Listener & listener) { mListener = &listener; } + virtual void ClearListener() { mListener = nullptr; } + + private: + Listener * mListener = nullptr; + }; + + AccessControl() = default; + + AccessControl(Delegate & delegate) : mDelegate(delegate) {} AccessControl(const AccessControl &) = delete; AccessControl & operator=(const AccessControl &) = delete; + ~AccessControl() { mDelegate.Release(); } + /** * Initialize the access control module. Must be called before first use. * @@ -51,7 +346,77 @@ class AccessControl /** * Deinitialize the access control module. Must be called when finished. */ - void Finish(); + CHIP_ERROR Finish(); + + // Capabilities + CHIP_ERROR GetMaxEntries(int & value) const { return mDelegate.GetMaxEntries(value); } + + /** + * Prepares an entry. + * + * An entry must be prepared or read (`ReadEntry`) before first use. + * + * @param [in] entry Entry to prepare. + */ + CHIP_ERROR PrepareEntry(Entry & entry) { return mDelegate.PrepareEntry(entry); } + + /** + * Creates an entry in the access control list. + * + * @param [out] index Entry index of created entry, if not null. May be relative to `fabricIndex`. + * @param [in] entry Entry from which to copy. + * @param [out] fabricIndex Fabric index of created entry, if not null, in which case entry `index` will be relative to fabric. + */ + CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, FabricIndex * fabricIndex = nullptr) + { + return mDelegate.CreateEntry(index, entry, fabricIndex); + } + + /** + * Reads an entry from the access control list. + * + * @param [in] index Entry index of entry to read. May be relative to `fabricIndex`. + * @param [out] entry Entry into which to copy. + * @param [in] fabricIndex Fabric to which entry `index` is relative, if not null. + */ + CHIP_ERROR ReadEntry(size_t index, Entry & entry, const FabricIndex * fabricIndex = nullptr) const + { + return mDelegate.ReadEntry(index, entry, fabricIndex); + } + + /** + * Updates an entry in the access control list. + * + * @param [in] index Entry index of entry to update, if not null. May be relative to `fabricIndex`. + * @param [in] entry Entry from which to copy. + * @param [in] fabricIndex Fabric to which entry `index` is relative, if not null. + */ + CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex * fabricIndex = nullptr) + { + return mDelegate.UpdateEntry(index, entry, fabricIndex); + } + + /** + * Deletes an entry from the access control list. + * + * @param [in] index Entry index of entry to delete. May be relative to `fabricIndex`. + * @param [in] fabricIndex Fabric to which entry `index` is relative, if not null. + */ + CHIP_ERROR DeleteEntry(size_t index, const FabricIndex * fabricIndex = nullptr) + { + return mDelegate.DeleteEntry(index, fabricIndex); + } + + /** + * Iterates over entries in the access control list. + * + * @param [out] iterator Iterator controlling the iteration. + * @param [in] fabricIndex Iteration is confined to fabric, if not null. + */ + CHIP_ERROR Entries(EntryIterator & iterator, const FabricIndex * fabricIndex = nullptr) const + { + return mDelegate.Entries(iterator, fabricIndex); + } /** * Check whether access (by a subject descriptor, to a request path, @@ -61,30 +426,33 @@ class AccessControl * @retval other errors should also be treated as denied. * @retval #CHIP_NO_ERROR if allowed. */ - CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege privilege); + CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege); private: - AccessControlDataProvider & mDataProvider; + static Delegate mDefaultDelegate; + Delegate & mDelegate = mDefaultDelegate; }; /** - * Instance getter for the global AccessControl. - * - * Callers have to externally synchronize usage of this function. + * Get the global instance set by SetAccessControl, or the default. * - * @return The global AccessControl instance. Assume never null. + * Calls to this function must be synchronized externally. */ -AccessControl * GetAccessControl(); +AccessControl & GetAccessControl(); /** - * Instance setter for the global AccessControl. + * Set the global instance returned by GetAccessControl. * - * Callers have to externally synchronize usage of this function. + * Calls to this function must be synchronized externally. + */ +void SetAccessControl(AccessControl & accessControl); + +/** + * Reset the global instance to the default. * - * @param[in] accessControl the instance to start returning with the getter; - * if nullptr, no change occurs. + * Calls to this function must be synchronized externally. */ -void SetAccessControl(AccessControl * accessControl); +void ResetAccessControl(); } // namespace Access } // namespace chip diff --git a/src/access/AccessControlDataProvider.h b/src/access/AccessControlDataProvider.h deleted file mode 100644 index eb693f80e3b1d6..00000000000000 --- a/src/access/AccessControlDataProvider.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * - * 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. - */ - -#pragma once - -#include "AuthMode.h" -#include "Privilege.h" -#include "SubjectDescriptor.h" - -#include -#include - -namespace chip { -namespace Access { - -class Entry -{ -public: - virtual ~Entry() = default; - - /** - * Whether the auth mode matches the entry. Must be called before calling - * MatchesSubject. - */ - virtual bool MatchesAuthMode(AuthMode authMode) const = 0; - - /** - * Whether the fabric matches the entry. Entries with fabric index 0 will - * match all fabrics. - */ - virtual bool MatchesFabric(FabricIndex fabricIndex) const = 0; - - /** - * Whether the privilege matches the entry, including subsumed privileges. - * E.g. both Privilege::kOperate and Privilege::kView will match an entry - * with Privilege::kOperate, but Privilege::kManage will not match such an - * entry. - */ - virtual bool MatchesPrivilege(Privilege privilege) const = 0; - - /** - * Whether the subject matches the entry. Must only be called if auth mode - * matches. - */ - virtual bool MatchesSubject(SubjectId subject) const = 0; - - /** - * Whether the target matches the entry. Some entries may match all - * endpoints or all clusters. - */ - virtual bool MatchesTarget(EndpointId endpoint, ClusterId cluster) const = 0; -}; - -class EntryIterator -{ -public: - /** - * Create an entry iterator. Must call release when finished. - */ - EntryIterator() = default; - - virtual ~EntryIterator() = default; - - /** - * Returns the next entry, or nullptr if there is no next entry. - */ - virtual Entry * Next() = 0; - - /** - * Release the iterator. Must be called when finished. - */ - virtual void Release() = 0; -}; - -class AccessControlDataProvider -{ -public: - /** - * Create a data provider. Must be initialized before use, and deinitialized - * when finished. - */ - AccessControlDataProvider() = default; - - virtual ~AccessControlDataProvider() = default; - - AccessControlDataProvider(const AccessControlDataProvider &) = delete; - AccessControlDataProvider & operator=(const AccessControlDataProvider &) = delete; - - /** - * Initialize the data provider. - * - * @retval various errors, probably fatal. - */ - virtual CHIP_ERROR Init() = 0; - - /** - * Deinitialize the data provider. - */ - virtual void Finish() = 0; - - /** - * Get an iterator over all entries. - * - * @retval iterator, release when finished. - * @retval nullptr if error, probably fatal, generally should not happen. - */ - virtual EntryIterator * Entries() const = 0; - - /** - * Get an iterator over all entries for a particular fabric. - * - * @retval iterator, release when finished. - * @retval nullptr if error, probably fatal, generally should not happen. - */ - virtual EntryIterator * Entries(FabricIndex fabricIndex) const = 0; -}; - -} // namespace Access -} // namespace chip diff --git a/src/access/AuthMode.h b/src/access/AuthMode.h index 75a667afd07f5e..c5a2d76820855a 100644 --- a/src/access/AuthMode.h +++ b/src/access/AuthMode.h @@ -18,12 +18,14 @@ #pragma once +#include + namespace chip { namespace Access { // Using bitfield values so auth mode and privilege set can be stored together. // Auth mode should have only one value expressed, which should not be None. -enum class AuthMode +enum class AuthMode : uint8_t { kNone = 0, kPase = 1 << 5, diff --git a/src/access/BUILD.gn b/src/access/BUILD.gn index 25836f2e472c8f..bca34a9f1a9bd3 100644 --- a/src/access/BUILD.gn +++ b/src/access/BUILD.gn @@ -20,13 +20,12 @@ static_library("access") { sources = [ "AccessControl.cpp", "AccessControl.h", - "AccessControlDataProvider.h", "AuthMode.h", "Privilege.h", "RequestPath.h", "SubjectDescriptor.h", - "examples/ExampleAccessControlDataProvider.cpp", - "examples/ExampleAccessControlDataProvider.h", + "examples/ExampleAccessControlDelegate.cpp", + "examples/ExampleAccessControlDelegate.h", ] cflags = [ "-Wconversion" ] diff --git a/src/access/Privilege.h b/src/access/Privilege.h index 9aacecad99e911..315c54fbe07043 100644 --- a/src/access/Privilege.h +++ b/src/access/Privilege.h @@ -18,13 +18,15 @@ #pragma once +#include + namespace chip { namespace Access { // Using bitfield values so privilege set and auth mode can be stored together. // Privilege set can have more than one value expressed (e.g. View, // ProxyView, and Operate). -enum class Privilege +enum class Privilege : uint8_t { kView = 1 << 0, kProxyView = 1 << 1, diff --git a/src/access/RequestPath.h b/src/access/RequestPath.h index 09a180bff957ae..966f27afe8f716 100644 --- a/src/access/RequestPath.h +++ b/src/access/RequestPath.h @@ -26,8 +26,8 @@ namespace Access { struct RequestPath { // NOTE: eventually this will likely also contain node, for proxying - EndpointId endpoint = 0; ClusterId cluster = 0; + EndpointId endpoint = 0; }; } // namespace Access diff --git a/src/access/SubjectDescriptor.h b/src/access/SubjectDescriptor.h index 24502ed065c4bb..d9f7664c94aafe 100644 --- a/src/access/SubjectDescriptor.h +++ b/src/access/SubjectDescriptor.h @@ -21,24 +21,15 @@ #include "AuthMode.h" #include -#include #include -#include namespace chip { namespace Access { -union SubjectId -{ - PasscodeId passcode; - NodeId node; - GroupId group; -}; - struct SubjectDescriptor { // Holds FabricIndex of fabric, 0 if no fabric. - FabricIndex fabricIndex = 0; + FabricIndex fabricIndex = kUndefinedFabricIndex; // Holds AuthMode of subject(s), kNone if no access. AuthMode authMode = AuthMode::kNone; @@ -47,7 +38,7 @@ struct SubjectDescriptor // Holds subjects according to auth mode, and the latter two are only valid // if auth mode is CASE. - SubjectId subjects[3] = {}; + NodeId subjects[3] = { kUndefinedNodeId, kUndefinedNodeId, kUndefinedNodeId }; }; } // namespace Access diff --git a/src/access/examples/ExampleAccessControlDataProvider.cpp b/src/access/examples/ExampleAccessControlDataProvider.cpp deleted file mode 100644 index c5c2a27f9a20aa..00000000000000 --- a/src/access/examples/ExampleAccessControlDataProvider.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * - * 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 "ExampleAccessControlDataProvider.h" - -namespace { - -using chip::FabricIndex; -using namespace chip::Access; - -class ExampleDataProvider : public AccessControlDataProvider -{ - CHIP_ERROR Init() override { return CHIP_NO_ERROR; } - - void Finish() override {} - - EntryIterator * Entries() const override { return nullptr; } - - EntryIterator * Entries(FabricIndex fabricIndex) const override { return nullptr; } -}; - -} // namespace - -namespace chip { -namespace Access { -namespace Examples { - -AccessControlDataProvider * GetExampleAccessControlDataProvider() -{ - static ExampleDataProvider exampleProvider; - return &exampleProvider; -} - -} // namespace Examples -} // namespace Access -} // namespace chip diff --git a/src/access/examples/ExampleAccessControlDelegate.cpp b/src/access/examples/ExampleAccessControlDelegate.cpp new file mode 100644 index 00000000000000..72d1518febc485 --- /dev/null +++ b/src/access/examples/ExampleAccessControlDelegate.cpp @@ -0,0 +1,1113 @@ +/* + * + * 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 + +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 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; } + +private: + static_assert(sizeof(NodeId) == 8, "Expecting 8 byte node ID"); + +private: + 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)); + } + +private: + 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; + } + } + +private: + 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; + +private: + ClusterId mCluster; + DeviceTypeId mDeviceType; +}; + +class EntryStorage +{ +public: + // ACL support + static constexpr size_t kNumberOfFabrics = CHIP_CONFIG_MAX_DEVICE_ADMINS; + 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; + } + +public: + // 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; + } + +public: + 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(); + } + } + +public: + 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); + } + +public: + 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; + } + +public: + 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; + } + +public: + 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; } + + 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; + } + else 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; + } + +public: + 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; + } + +public: + 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; } + + 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(delegate).GetStorage(); + return CHIP_NO_ERROR; + } +#endif + return CopyViaInterface(entry, storage); +} + +class AccessControlDelegate : public AccessControl::Delegate +{ +public: + CHIP_ERROR Init() override + { + ChipLogDetail(DataManagement, "Examples::AccessControlDelegate::Init"); + CHIP_ERROR err = LoadFromFlash(); + if (err != CHIP_NO_ERROR) + { + for (auto & storage : EntryStorage::acl) + { + storage.Clear(); + } + } + return err; + } + + CHIP_ERROR Finish() override + { + ChipLogDetail(DataManagement, "Examples::AccessControlDelegate::Finish"); + return SaveToFlash(); + } + + CHIP_ERROR GetMaxEntries(int & value) const override + { + value = ArraySize(EntryStorage::acl); + 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)) + { + constexpr auto & acl = EntryStorage::acl; + constexpr auto * end = acl + ArraySize(acl); + // Go through the access control list starting at the deleted storage... + 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; + } + +private: + CHIP_ERROR LoadFromFlash() { return CHIP_NO_ERROR; } + + CHIP_ERROR SaveToFlash() { return CHIP_NO_ERROR; } +}; + +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 diff --git a/src/access/examples/ExampleAccessControlDataProvider.h b/src/access/examples/ExampleAccessControlDelegate.h similarity index 87% rename from src/access/examples/ExampleAccessControlDataProvider.h rename to src/access/examples/ExampleAccessControlDelegate.h index f679ef513ef204..f411de1339df3c 100644 --- a/src/access/examples/ExampleAccessControlDataProvider.h +++ b/src/access/examples/ExampleAccessControlDelegate.h @@ -16,13 +16,13 @@ */ #pragma once -#include "access/AccessControlDataProvider.h" +#include "access/AccessControl.h" namespace chip { namespace Access { namespace Examples { -AccessControlDataProvider * GetExampleAccessControlDataProvider(); +AccessControl::Delegate & GetAccessControlDelegate(); } // namespace Examples } // namespace Access diff --git a/src/access/tests/TestAccessControl.cpp b/src/access/tests/TestAccessControl.cpp index aea90be12e488c..98dfec1ece65d2 100644 --- a/src/access/tests/TestAccessControl.cpp +++ b/src/access/tests/TestAccessControl.cpp @@ -17,6 +17,7 @@ */ #include "access/AccessControl.h" +#include "access/examples/ExampleAccessControlDelegate.h" #include #include @@ -28,497 +29,1069 @@ namespace { using namespace chip; using namespace chip::Access; -constexpr EndpointId kEndpoint0 = 0; -constexpr EndpointId kEndpoint1 = 1; -constexpr EndpointId kEndpoint2 = 2; - -constexpr ClusterId kOnOffCluster = 0x00000006; -constexpr ClusterId kLevelControlCluster = 0x00000008; -constexpr ClusterId kColorControlCluster = 0x00000300; -constexpr ClusterId kAccessControlCluster = 0x0000001F; - -constexpr size_t kSubjectsPerEntry = 4; -constexpr size_t kTargetsPerEntry = 3; - -// Used to detect empty subjects, targets, etc. -constexpr int kEmptyFlags = 0; - -// For test purposes, store all subjects as node, use tags to discriminate -// passcode/group, and don't allow 0. -constexpr NodeId kTestSubjectMask = 0xFFFFFFFFFFFF0000; -constexpr NodeId kTestSubjectPasscode = 0xDDDDDDDDDDDD0000; -constexpr NodeId kTestSubjectGroup = 0xEEEEEEEEEEEE0000; -constexpr NodeId kTestSubjectEmpty = 0x0000000000000000; +using Entry = AccessControl::Entry; +using EntryIterator = AccessControl::EntryIterator; +using Target = Entry::Target; + +AccessControl accessControl(Examples::GetAccessControlDelegate()); + +constexpr ClusterId kOnOffCluster = 0x0006; +constexpr ClusterId kLevelControlCluster = 0x0008; +constexpr ClusterId kAccessControlCluster = 0x001F; +constexpr ClusterId kColorControlCluster = 0x0300; + +constexpr DeviceTypeId kColorLightDeviceType = 0x0102; + +constexpr NodeId kPaseVerifier0 = 0xFFFFFFFB0000'0000; +constexpr NodeId kPaseVerifier1 = 0xFFFFFFFB0000'0001; +constexpr NodeId kPaseVerifier3 = 0xFFFFFFFB0000'0003; +constexpr NodeId kPaseVerifier5 = 0xFFFFFFFB0000'0005; + +constexpr NodeId kGroup2 = 0xFFFFFFFFFFFF'0002; +constexpr NodeId kGroup4 = 0xFFFFFFFFFFFF'0004; +constexpr NodeId kGroup6 = 0xFFFFFFFFFFFF'0006; +constexpr NodeId kGroup8 = 0xFFFFFFFFFFFF'0008; + +constexpr AuthMode authModes[] = { AuthMode::kPase, AuthMode::kCase, AuthMode::kGroup }; + +constexpr FabricIndex fabricIndexes[] = { 1, 2, 3 }; + +constexpr Privilege privileges[] = { Privilege::kView, Privilege::kProxyView, Privilege::kOperate, Privilege::kManage, + Privilege::kAdminister }; + +constexpr NodeId subjects[][3] = { { + kPaseVerifier0, + kPaseVerifier3, + kPaseVerifier5, + }, + { + 0x0123456789ABCDEF, // CASE node + 0xFFFFFFFD'00000001, // CAT1 + 0xFFFFFFFC'00000002, // CAT2 + }, + { + kGroup4, + kGroup6, + kGroup8, + } }; + +constexpr Target targets[] = { + { .flags = Target::kCluster, .cluster = kOnOffCluster }, + { .flags = Target::kEndpoint, .endpoint = 3 }, + { .flags = Target::kDeviceType, .deviceType = kColorLightDeviceType }, +}; -constexpr SubjectId Passcode(PasscodeId passcode) +bool operator==(const Target & a, const Target & b) { - // For test purposes, stuff passcode into node with tag - return { .node = kTestSubjectPasscode | passcode }; + if (a.flags != b.flags) + return false; + if ((a.flags & Target::kCluster) && a.cluster != b.cluster) + return false; + if ((a.flags & Target::kEndpoint) && a.endpoint != b.endpoint) + return false; + if ((a.flags & Target::kDeviceType) && a.deviceType != b.deviceType) + return false; + return true; } -constexpr SubjectId Node(NodeId node) +bool operator!=(const Target & a, const Target & b) { - return { .node = node }; + return !(a == b); } -constexpr SubjectId CAT1(NodeId node) +struct EntryData { - return { .node = node }; -} + static constexpr int kMaxSubjects = 3; + static constexpr int kMaxTargets = 3; -constexpr SubjectId CAT2(NodeId node) -{ - return { .node = node }; -} + FabricIndex fabricIndex = kUndefinedFabricIndex; + Privilege privilege = Privilege::kView; + AuthMode authMode = AuthMode::kNone; + NodeId subjects[kMaxSubjects] = { 0 }; + Target targets[kMaxTargets] = { { 0 } }; -constexpr SubjectId Group(GroupId group) -{ - // For test purposes, stuff group into node with tag - return { .node = kTestSubjectGroup | group }; -} + void Clear() { memset(this, 0, sizeof(*this)); } -struct TestTarget -{ - enum Flag + bool IsEmpty() const { return authMode == AuthMode::kNone; } + + size_t GetSubjectCount() const { - kDeviceType = 1 << 0, - kEndpoint = 1 << 1, - kCluster = 1 << 2, - }; + size_t count = 0; + for (auto & subject : subjects) + { + if (subject == kUndefinedNodeId) + { + break; + } + count++; + } + return count; + } + + void AddSubject(size_t * index, NodeId subject) + { + size_t count = GetSubjectCount(); + if (count < kMaxSubjects) + { + subjects[count] = subject; + if (index) + { + *index = count; + } + } + } - int flags = kEmptyFlags; - DeviceTypeId deviceType; - EndpointId endpoint; - ClusterId cluster; + void RemoveSubject(size_t index) + { + size_t count = GetSubjectCount(); + if (index < count) + { + while (++index < kMaxSubjects) + { + subjects[index - 1] = subjects[index]; + } + subjects[kMaxSubjects - 1] = { 0 }; + } + } + + size_t GetTargetCount() const + { + size_t count = 0; + for (auto & target : targets) + { + if (target.flags == 0) + { + break; + } + count++; + } + return count; + } + + void AddTarget(size_t * index, const Target & target) + { + size_t count = GetTargetCount(); + if (count < kMaxTargets) + { + targets[count] = target; + if (index) + { + *index = count; + } + } + } + + void RemoveTarget(size_t index) + { + size_t count = GetTargetCount(); + if (index < count) + { + while (++index < kMaxTargets) + { + targets[index - 1] = targets[index]; + } + targets[kMaxTargets - 1] = { 0 }; + } + } }; -TestTarget Target(EndpointId endpoint, ClusterId cluster) +CHIP_ERROR CompareEntry(const Entry & entry, const EntryData & entryData) +{ + AuthMode authMode = AuthMode::kNone; + ReturnErrorOnFailure(entry.GetAuthMode(authMode)); + ReturnErrorCodeIf(authMode != entryData.authMode, CHIP_ERROR_INCORRECT_STATE); + FabricIndex fabricIndex = kUndefinedFabricIndex; + ReturnErrorOnFailure(entry.GetFabricIndex(fabricIndex)); + ReturnErrorCodeIf(fabricIndex != entryData.fabricIndex, CHIP_ERROR_INCORRECT_STATE); + Privilege privilege = Privilege::kView; + ReturnErrorOnFailure(entry.GetPrivilege(privilege)); + ReturnErrorCodeIf(privilege != entryData.privilege, CHIP_ERROR_INCORRECT_STATE); + size_t subjectCount = 0; + ReturnErrorOnFailure(entry.GetSubjectCount(subjectCount)); + ReturnErrorCodeIf(subjectCount != entryData.GetSubjectCount(), CHIP_ERROR_INCORRECT_STATE); + for (size_t i = 0; i < subjectCount; ++i) + { + NodeId subject = kUndefinedNodeId; + ReturnErrorOnFailure(entry.GetSubject(i, subject)); + ReturnErrorCodeIf(subject != entryData.subjects[i], CHIP_ERROR_INCORRECT_STATE); + } + size_t targetCount = 0; + ReturnErrorOnFailure(entry.GetTargetCount(targetCount)); + ReturnErrorCodeIf(targetCount != entryData.GetTargetCount(), CHIP_ERROR_INCORRECT_STATE); + for (size_t i = 0; i < targetCount; ++i) + { + Target target; + ReturnErrorOnFailure(entry.GetTarget(i, target)); + ReturnErrorCodeIf(target != entryData.targets[i], CHIP_ERROR_INCORRECT_STATE); + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR LoadEntry(Entry & entry, const EntryData & entryData) { - return { .flags = TestTarget::kEndpoint | TestTarget::kCluster, .endpoint = endpoint, .cluster = cluster }; + ReturnErrorOnFailure(entry.SetAuthMode(entryData.authMode)); + ReturnErrorOnFailure(entry.SetFabricIndex(entryData.fabricIndex)); + ReturnErrorOnFailure(entry.SetPrivilege(entryData.privilege)); + for (size_t i = 0; i < entryData.GetSubjectCount(); ++i) + { + ReturnErrorOnFailure(entry.AddSubject(nullptr, entryData.subjects[i])); + } + for (size_t i = 0; i < entryData.GetTargetCount(); ++i) + { + ReturnErrorOnFailure(entry.AddTarget(nullptr, entryData.targets[i])); + } + return CHIP_NO_ERROR; } -TestTarget Target(EndpointId endpoint) +CHIP_ERROR ClearAccessControl(AccessControl & ac) { - return { .flags = TestTarget::kEndpoint, .endpoint = endpoint }; + CHIP_ERROR err; + do + { + err = accessControl.DeleteEntry(0); + } while (err == CHIP_NO_ERROR); + return CHIP_NO_ERROR; } -TestTarget Target(ClusterId cluster) +CHIP_ERROR CompareAccessControl(AccessControl & ac, const EntryData * entryData, size_t count) { - return { .flags = TestTarget::kCluster, .cluster = cluster }; + Entry entry; + for (size_t i = 0; i < count; ++i, ++entryData) + { + ReturnErrorOnFailure(ac.ReadEntry(i, entry)); + ReturnErrorOnFailure(CompareEntry(entry, *entryData)); + } + ReturnErrorCodeIf(ac.ReadEntry(count, entry) == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE); + return CHIP_NO_ERROR; } -struct TestEntryDelegate +CHIP_ERROR LoadAccessControl(AccessControl & ac, const EntryData * entryData, size_t count) { - FabricIndex fabricIndex = 0; - AuthMode authMode = AuthMode::kNone; - Privilege privilege = Privilege::kView; - SubjectId subjects[kSubjectsPerEntry + 1]; - TestTarget targets[kTargetsPerEntry + 1]; - const char * tag = ""; - bool touched = false; -}; + Entry entry; + for (size_t i = 0; i < count; ++i, ++entryData) + { + ReturnErrorOnFailure(ac.PrepareEntry(entry)); + ReturnErrorOnFailure(LoadEntry(entry, *entryData)); + ReturnErrorOnFailure(ac.CreateEntry(nullptr, entry)); + } + return CHIP_NO_ERROR; +} -TestEntryDelegate entries[] = { +constexpr EntryData entryData1[] = { { .fabricIndex = 1, - .authMode = AuthMode::kCase, .privilege = Privilege::kAdminister, - .subjects = { Node(0x1122334455667788) }, - .tag = "1-admin", + .authMode = AuthMode::kCase, + .subjects = { 0x1111111111111111 }, }, { - .fabricIndex = 2, + .fabricIndex = 1, + .privilege = Privilege::kView, .authMode = AuthMode::kCase, - .privilege = Privilege::kAdminister, - .subjects = { Node(0x8877665544332211) }, - .tag = "2-admin", }, { .fabricIndex = 2, + .privilege = Privilege::kAdminister, .authMode = AuthMode::kCase, - .privilege = Privilege::kView, + .subjects = { 0x2222222222222222 }, }, { .fabricIndex = 1, + .privilege = Privilege::kOperate, + .authMode = AuthMode::kCase, + .targets = { { .flags = Target::kCluster, .cluster = kOnOffCluster } }, + }, + { + .fabricIndex = 2, + .privilege = Privilege::kManage, .authMode = AuthMode::kPase, - .privilege = Privilege::kView, - .subjects = { Passcode(0) }, - .targets = { Target(kEndpoint2, kOnOffCluster) }, - .tag = "1-pase-view-onoff-2", + .subjects = { kPaseVerifier1 }, + .targets = { { .flags = Target::kCluster | Target::kEndpoint, .cluster = kOnOffCluster, .endpoint = 2 } }, }, { - .fabricIndex = 1, - .authMode = AuthMode::kCase, - .privilege = Privilege::kView, - .targets = { Target(kEndpoint1, kLevelControlCluster) }, + .fabricIndex = 2, + .privilege = Privilege::kProxyView, + .authMode = AuthMode::kGroup, + .subjects = { kGroup2 }, + .targets = { { .flags = Target::kCluster | Target::kEndpoint, .cluster = kLevelControlCluster, .endpoint = 1 }, + { .flags = Target::kCluster, .cluster = kOnOffCluster }, + { .flags = Target::kEndpoint, .endpoint = 2 } }, }, +}; + +struct CheckData +{ + SubjectDescriptor subjectDescriptor; + RequestPath requestPath; + Privilege privilege; + bool allow; +}; + +constexpr CheckData checkData1[] = +{ + // Checks for entry 0 { - .fabricIndex = 1, - .authMode = AuthMode::kCase, - .privilege = Privilege::kOperate, - .targets = { Target(kEndpoint1, kColorControlCluster) }, + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = kAccessControlCluster, .endpoint = 0 }, + .privilege = Privilege::kAdminister, + .allow = true }, { - .fabricIndex = 1, - .authMode = AuthMode::kCase, - .privilege = Privilege::kView, - .targets = { Target(kEndpoint2, kOnOffCluster) }, + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = true }, { - .fabricIndex = 1, - .authMode = AuthMode::kCase, - .privilege = Privilege::kView, - .subjects = { CAT1(86), CAT2(99) }, - .targets = { Target(kEndpoint0), Target(kAccessControlCluster) }, + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 3, .endpoint = 4 }, + .privilege = Privilege::kOperate, + .allow = true }, { - .fabricIndex = 1, - .authMode = AuthMode::kGroup, - .privilege = Privilege::kView, - .subjects = { Group(7) }, + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 5, .endpoint = 6 }, + .privilege = Privilege::kView, + .allow = true }, { - .fabricIndex = 3, - .authMode = AuthMode::kCase, - .privilege = Privilege::kAdminister, + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 7, .endpoint = 8 }, + .privilege = Privilege::kProxyView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kGroup, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + // Checks for entry 1 + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = 11, .endpoint = 13 }, + .privilege = Privilege::kView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = 11, .endpoint = 13 }, + .privilege = Privilege::kOperate, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = 11, .endpoint = 13 }, + .privilege = Privilege::kView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = 11, .endpoint = 13 }, + .privilege = Privilege::kView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kGroup, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = 11, .endpoint = 13 }, + .privilege = Privilege::kView, + .allow = false + }, + // Checks for entry 2 + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = kAccessControlCluster, .endpoint = 0 }, + .privilege = Privilege::kAdminister, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 3, .endpoint = 4 }, + .privilege = Privilege::kOperate, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 5, .endpoint = 6 }, + .privilege = Privilege::kView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 7, .endpoint = 8 }, + .privilege = Privilege::kProxyView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { 0x2222222222222222 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x1111111111111111 }, }, + .requestPath = { .cluster = 1, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + // Checks for entry 3 + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 11 }, + .privilege = Privilege::kOperate, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1122334455667788 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 13 }, + .privilege = Privilege::kOperate, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 11 }, + .privilege = Privilege::kOperate, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 11 }, + .privilege = Privilege::kOperate, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = 123, .endpoint = 11 }, + .privilege = Privilege::kOperate, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { 0x1234567812345678 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 11 }, + .privilege = Privilege::kManage, + .allow = false + }, + // Checks for entry 4 + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier0 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier3 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 2 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1 }, + .privilege = Privilege::kManage, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kPaseVerifier1 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 2 }, + .privilege = Privilege::kAdminister, + .allow = false + }, + // Checks for entry 5 + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 3 }, + .privilege = Privilege::kProxyView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kColorControlCluster, .endpoint = 2 }, + .privilege = Privilege::kProxyView, + .allow = true + }, + { + .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup4 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kColorControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kColorControlCluster, .endpoint = 1 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 3 }, + .privilege = Privilege::kProxyView, + .allow = false + }, + { + .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kGroup, .subjects = { kGroup2 }, }, + .requestPath = { .cluster = kLevelControlCluster, .endpoint = 1 }, + .privilege = Privilege::kOperate, + .allow = false }, }; -constexpr int kNumEntries = sizeof(entries) / sizeof(entries[0]); -void ResetTouchedEntries() +void MetaTest(nlTestSuite * inSuite, void * inContext) { - for (int i = 0; i < kNumEntries; ++i) + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, entryData1, ArraySize(entryData1)) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CompareAccessControl(accessControl, entryData1, ArraySize(entryData1)) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, accessControl.DeleteEntry(3) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CompareAccessControl(accessControl, entryData1, ArraySize(entryData1)) != CHIP_NO_ERROR); +} + +void TestCheck(nlTestSuite * inSuite, void * inContext) +{ + LoadAccessControl(accessControl, entryData1, ArraySize(entryData1)); + for (const auto & checkData : checkData1) { - entries[i].touched = false; + CHIP_ERROR expectedResult = checkData.allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED; + NL_TEST_ASSERT(inSuite, + accessControl.Check(checkData.subjectDescriptor, checkData.requestPath, checkData.privilege) == + expectedResult); } } -bool EntriesTouchedToTag(const char * tag, FabricIndex fabricIndex) +void TestCreateReadEntry(nlTestSuite * inSuite, void * inContext) { - bool expectedTouched = true; + for (size_t i = 0; i < ArraySize(entryData1); ++i) + { + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, entryData1 + i, 1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CompareAccessControl(accessControl, entryData1, i + 1) == CHIP_NO_ERROR); + } +} - for (int i = 0; i < kNumEntries; ++i) +void TestDeleteEntry(nlTestSuite * inSuite, void * inContext) +{ + EntryData data[ArraySize(entryData1)]; + for (size_t pos = 0; pos < ArraySize(data); ++pos) { - auto & entry = entries[i]; - if (entry.fabricIndex == fabricIndex) + for (size_t count = ArraySize(data) - pos; count > 0; --count) { - if (entry.touched != expectedTouched) - { - return false; - } - if (strcmp(entry.tag, tag) == 0) + memcpy(data, entryData1, sizeof(data)); + NL_TEST_ASSERT(inSuite, ClearAccessControl(accessControl) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, data, ArraySize(data)) == CHIP_NO_ERROR); + + memcpy(&data[pos], &data[pos + count], (ArraySize(data) - count - pos) * sizeof(data[0])); + + for (size_t i = 0; i < count; ++i) { - expectedTouched = false; + NL_TEST_ASSERT(inSuite, accessControl.DeleteEntry(pos) == CHIP_NO_ERROR); } - } - else if (entry.touched) - { - return false; + + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, data, ArraySize(data) - count) == CHIP_NO_ERROR); } } - - return true; } -class TestEntry : public Entry +void TestFabricFilteredCreateEntry(nlTestSuite * inSuite, void * inContext) { -public: - virtual ~TestEntry() = default; - - bool MatchesAuthMode(AuthMode authMode) const override { return delegate->authMode == authMode; } - - bool MatchesFabric(FabricIndex fabricIndex) const override + for (auto & fabricIndex : fabricIndexes) { - return (delegate->fabricIndex == 0) || (delegate->fabricIndex == fabricIndex); - } - - bool MatchesPrivilege(Privilege privilege) const override - { - switch (privilege) + for (size_t count = 0; count < ArraySize(entryData1); ++count) { - case Privilege::kProxyView: - return (delegate->privilege == Privilege::kProxyView) || (delegate->privilege == Privilege::kAdminister); - case Privilege::kView: - if (delegate->privilege == Privilege::kView) - return true; - FALLTHROUGH; // fall through - case Privilege::kOperate: - if (delegate->privilege == Privilege::kOperate) - return true; - FALLTHROUGH; // fall through - case Privilege::kManage: - if (delegate->privilege == Privilege::kManage) - return true; - FALLTHROUGH; // fall through - case Privilege::kAdminister: - return delegate->privilege == Privilege::kAdminister; + NL_TEST_ASSERT(inSuite, ClearAccessControl(accessControl) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, entryData1, count) == CHIP_NO_ERROR); + + constexpr size_t expectedIndexes[][ArraySize(entryData1)] = { + { 0, 1, 2, 2, 3, 3 }, + { 0, 0, 0, 1, 1, 2 }, + { 0, 0, 0, 0, 0, 0 }, + }; + const size_t expectedIndex = expectedIndexes[&fabricIndex - fabricIndexes][count]; + + Entry entry; + NL_TEST_ASSERT(inSuite, accessControl.PrepareEntry(entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetFabricIndex(fabricIndex) == CHIP_NO_ERROR); + + size_t outIndex = 999; + FabricIndex outFabricIndex = 123; + NL_TEST_ASSERT(inSuite, accessControl.CreateEntry(&outIndex, entry, &outFabricIndex) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, outIndex == expectedIndex); + NL_TEST_ASSERT(inSuite, outFabricIndex == fabricIndex); } - return false; } +} - bool MatchesSubject(SubjectId subject) const override +void TestFabricFilteredReadEntry(nlTestSuite * inSuite, void * inContext) +{ + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, entryData1, ArraySize(entryData1)) == CHIP_NO_ERROR); + + for (auto & fabricIndex : fabricIndexes) { - SubjectId * p = delegate->subjects; - if (p->node == kTestSubjectEmpty) - return true; - for (; p->node != kTestSubjectEmpty; ++p) + constexpr size_t indexes[] = { 0, 1, 2, 3 }; + for (auto & index : indexes) { - // Don't call ::MatchesSubject because of special storage/tags - if ((p->node & kTestSubjectMask) == kTestSubjectPasscode) - { - if ((p->node & ~kTestSubjectMask) == subject.passcode) - { - return true; - } - } - else if ((p->node & kTestSubjectMask) == kTestSubjectGroup) + constexpr size_t illegalIndex = ArraySize(entryData1); + constexpr size_t expectedIndexes[][ArraySize(indexes)] = { + { 0, 1, 3, illegalIndex }, + { 2, 4, 5, illegalIndex }, + { illegalIndex, illegalIndex, illegalIndex, illegalIndex }, + }; + const size_t expectedIndex = expectedIndexes[&fabricIndex - fabricIndexes][&index - indexes]; + + Entry entry; + CHIP_ERROR err = accessControl.ReadEntry(index, entry, &fabricIndex); + + if (expectedIndex != illegalIndex) { - if ((p->node & ~kTestSubjectMask) == subject.group) - { - return true; - } + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, CompareEntry(entry, entryData1[expectedIndex]) == CHIP_NO_ERROR); } else { - // TODO: handle CASE Authenticated Tags (CAT1/CAT2) - if (p->node == subject.node) - { - return true; - } + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); } } - return false; } +} + +void TestIterator(nlTestSuite * inSuite, void * inContext) +{ + LoadAccessControl(accessControl, entryData1, ArraySize(entryData1)); + + FabricIndex fabricIndex; + EntryIterator iterator; + Entry entry; + size_t count; - bool MatchesTarget(EndpointId endpoint, ClusterId cluster) const override + NL_TEST_ASSERT(inSuite, accessControl.Entries(iterator) == CHIP_NO_ERROR); + count = 0; + while (iterator.Next(entry) == CHIP_NO_ERROR) { - TestTarget * p = delegate->targets; - if (p->flags == kEmptyFlags) - return true; - for (; p->flags != kEmptyFlags; ++p) - { - if (((p->flags & TestTarget::kEndpoint) == 0 || p->endpoint == endpoint) && - ((p->flags & TestTarget::kCluster) == 0 || p->cluster == cluster)) - return true; - } - return false; + NL_TEST_ASSERT(inSuite, CompareEntry(entry, entryData1[count]) == CHIP_NO_ERROR); + count++; } + NL_TEST_ASSERT(inSuite, count == ArraySize(entryData1)); - TestEntryDelegate * delegate; -}; - -class TestEntryIterator : public EntryIterator -{ -public: - virtual ~TestEntryIterator() = default; + fabricIndex = kUndefinedFabricIndex; + NL_TEST_ASSERT(inSuite, accessControl.Entries(iterator, &fabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, iterator.Next(entry) != CHIP_NO_ERROR); - void Initialize() + fabricIndex = 1; + NL_TEST_ASSERT(inSuite, accessControl.Entries(iterator, &fabricIndex) == CHIP_NO_ERROR); + size_t fabric1[] = { 0, 1, 3 }; + count = 0; + while (iterator.Next(entry) == CHIP_NO_ERROR) { - fabricFiltered = false; - entry.delegate = nullptr; + NL_TEST_ASSERT(inSuite, CompareEntry(entry, entryData1[fabric1[count]]) == CHIP_NO_ERROR); + count++; } + NL_TEST_ASSERT(inSuite, count == ArraySize(fabric1)); - void Initialize(FabricIndex fabricIndex_) + fabricIndex = 2; + NL_TEST_ASSERT(inSuite, accessControl.Entries(iterator, &fabricIndex) == CHIP_NO_ERROR); + size_t fabric2[] = { 2, 4, 5 }; + count = 0; + while (iterator.Next(entry) == CHIP_NO_ERROR) { - fabricFiltered = true; - this->fabricIndex = fabricIndex_; - entry.delegate = nullptr; + NL_TEST_ASSERT(inSuite, CompareEntry(entry, entryData1[fabric2[count]]) == CHIP_NO_ERROR); + count++; } + NL_TEST_ASSERT(inSuite, count == ArraySize(fabric1)); +} - Entry * Next() override +void TestPrepareEntry(nlTestSuite * inSuite, void * inContext) +{ + Entry entry; + for (auto authMode : authModes) { - do + for (auto fabricIndex : fabricIndexes) { - if (entry.delegate == nullptr) + for (auto privilege : privileges) { - entry.delegate = entries; - } - else if ((entry.delegate - entries) < kNumEntries) - { - entry.delegate++; - } - } while ((entry.delegate - entries) < kNumEntries && fabricFiltered && entry.delegate->fabricIndex != fabricIndex); + NL_TEST_ASSERT(inSuite, accessControl.PrepareEntry(entry) == CHIP_NO_ERROR); - if ((entry.delegate - entries) < kNumEntries) - { - entry.delegate->touched = true; - return &entry; - } + size_t subjectCount; + size_t targetCount; - return nullptr; - } + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(subjectCount) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(targetCount) == CHIP_NO_ERROR); - void Release() override {} + NL_TEST_ASSERT(inSuite, subjectCount == 0); + NL_TEST_ASSERT(inSuite, targetCount == 0); - bool fabricFiltered; - FabricIndex fabricIndex; + NL_TEST_ASSERT(inSuite, entry.SetAuthMode(authMode) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetFabricIndex(fabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetPrivilege(privilege) == CHIP_NO_ERROR); - TestEntry entry; - TestEntryDelegate * next; -}; + int subjectIndex; + switch (authMode) + { + default: + case AuthMode::kPase: + subjectIndex = 0; + break; + case AuthMode::kCase: + subjectIndex = 1; + break; + case AuthMode::kGroup: + subjectIndex = 2; + break; + } -class TestDataProvider : public AccessControlDataProvider -{ -public: - TestDataProvider() = default; - virtual ~TestDataProvider() = default; + for (auto subject : subjects[subjectIndex]) + { + NL_TEST_ASSERT(inSuite, entry.AddSubject(nullptr, subject) == CHIP_NO_ERROR); + } - CHIP_ERROR Init() override { return CHIP_NO_ERROR; } + for (auto & target : targets) + { + NL_TEST_ASSERT(inSuite, entry.AddTarget(nullptr, target) == CHIP_NO_ERROR); + } - void Finish() override {} + AuthMode a; + FabricIndex f; + Privilege p; - EntryIterator * Entries() const override - { - iterator.Initialize(); - return &iterator; - } + NL_TEST_ASSERT(inSuite, entry.GetAuthMode(a) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetFabricIndex(f) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetPrivilege(p) == CHIP_NO_ERROR); - EntryIterator * Entries(FabricIndex fabricIndex) const override - { - iterator.Initialize(fabricIndex); - return &iterator; - } + NL_TEST_ASSERT(inSuite, a == authMode); + NL_TEST_ASSERT(inSuite, f == fabricIndex); + NL_TEST_ASSERT(inSuite, p == privilege); - mutable TestEntryIterator iterator; -}; + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(subjectCount) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(targetCount) == CHIP_NO_ERROR); -TestDataProvider testDataProvider; -AccessControl testAccessControl(testDataProvider); + NL_TEST_ASSERT(inSuite, subjectCount == 3); + NL_TEST_ASSERT(inSuite, targetCount == 3); -void MetaTestIterator(nlTestSuite * inSuite, void * inContext) -{ - EntryIterator * iterator = testDataProvider.Entries(); - NL_TEST_ASSERT(inSuite, iterator != nullptr); + for (size_t i = 0; i < ArraySize(subjects[subjectIndex]); ++i) + { + NodeId n; + NL_TEST_ASSERT(inSuite, entry.GetSubject(i, n) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, n == subjects[subjectIndex][i]); + } - TestEntryDelegate * p = entries; - while (auto entry = iterator->Next()) - { - NL_TEST_ASSERT_LOOP(inSuite, int(p - entries), static_cast(entry)->delegate == p); - ++p; + for (size_t i = 0; i < ArraySize(targets); ++i) + { + Target t; + NL_TEST_ASSERT(inSuite, entry.GetTarget(i, t) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, t == targets[i]); + } + } + } } +} - NL_TEST_ASSERT(inSuite, p == entries + kNumEntries); +void TestSubjectsTargets(nlTestSuite * inSuite, void * inContext) +{ + Entry entry; + NL_TEST_ASSERT(inSuite, accessControl.PrepareEntry(entry) == CHIP_NO_ERROR); - iterator->Release(); + NL_TEST_ASSERT(inSuite, entry.SetFabricIndex(1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetPrivilege(Privilege::kAdminister) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetAuthMode(AuthMode::kCase) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, entry.AddTarget(nullptr, { Target::kCluster, 1, 0, 0 }) == CHIP_NO_ERROR); + + size_t index = 999; + NL_TEST_ASSERT(inSuite, accessControl.CreateEntry(&index, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, int(index) == 0); + + NL_TEST_ASSERT(inSuite, entry.SetFabricIndex(2) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetPrivilege(Privilege::kManage) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetAuthMode(AuthMode::kPase) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddSubject(nullptr, 0x0000000011111111) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddTarget(nullptr, { Target::kEndpoint, 0, 2, 0 }) == CHIP_NO_ERROR); + + index = 999; + NL_TEST_ASSERT(inSuite, accessControl.CreateEntry(&index, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, int(index) == 1); + + NL_TEST_ASSERT(inSuite, entry.SetFabricIndex(3) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetPrivilege(Privilege::kOperate) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetAuthMode(AuthMode::kGroup) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddSubject(nullptr, 0x0000000022222222) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddTarget(nullptr, { Target::kDeviceType, 0, 0, 3 }) == CHIP_NO_ERROR); + + index = 999; + NL_TEST_ASSERT(inSuite, accessControl.CreateEntry(&index, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, int(index) == 2); + + FabricIndex fabricIndex; + Privilege privilege; + AuthMode authMode; + size_t count; + NodeId subject; + Target target; + + NL_TEST_ASSERT(inSuite, accessControl.ReadEntry(0, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetFabricIndex(fabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricIndex == 1); + NL_TEST_ASSERT(inSuite, entry.GetPrivilege(privilege) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, privilege == Privilege::kAdminister); + NL_TEST_ASSERT(inSuite, entry.GetAuthMode(authMode) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, authMode == AuthMode::kCase); + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 0); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 1); + NL_TEST_ASSERT(inSuite, entry.GetTarget(0, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, target.flags == Target::kCluster && target.cluster == 1); + + NL_TEST_ASSERT(inSuite, accessControl.ReadEntry(1, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetFabricIndex(fabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricIndex == 2); + NL_TEST_ASSERT(inSuite, entry.GetPrivilege(privilege) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, privilege == Privilege::kManage); + NL_TEST_ASSERT(inSuite, entry.GetAuthMode(authMode) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, authMode == AuthMode::kPase); + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 1); + NL_TEST_ASSERT(inSuite, entry.GetSubject(0, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x0000000011111111); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 2); + NL_TEST_ASSERT(inSuite, entry.GetTarget(0, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, target.flags == Target::kCluster && target.cluster == 1); + NL_TEST_ASSERT(inSuite, entry.GetTarget(1, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, target.flags == Target::kEndpoint && target.endpoint == 2); + + NL_TEST_ASSERT(inSuite, accessControl.ReadEntry(2, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetFabricIndex(fabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricIndex == 3); + NL_TEST_ASSERT(inSuite, entry.GetPrivilege(privilege) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, privilege == Privilege::kOperate); + NL_TEST_ASSERT(inSuite, entry.GetAuthMode(authMode) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, authMode == AuthMode::kGroup); + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 2); + NL_TEST_ASSERT(inSuite, entry.GetSubject(0, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x0000000011111111); + NL_TEST_ASSERT(inSuite, entry.GetSubject(1, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x0000000022222222); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 3); + NL_TEST_ASSERT(inSuite, entry.GetTarget(0, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, target.flags == Target::kCluster && target.cluster == 1); + NL_TEST_ASSERT(inSuite, entry.GetTarget(1, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, target.flags == Target::kEndpoint && target.endpoint == 2); + NL_TEST_ASSERT(inSuite, entry.GetTarget(2, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, target.flags == Target::kDeviceType && target.deviceType == 3); + + NL_TEST_ASSERT(inSuite, accessControl.PrepareEntry(entry) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, entry.SetFabricIndex(11) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetPrivilege(Privilege::kProxyView) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.SetAuthMode(AuthMode::kCase) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, entry.AddSubject(nullptr, 0x11111111AAAAAAAA) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddSubject(nullptr, 0x22222222BBBBBBBB) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddSubject(nullptr, 0x33333333CCCCCCCC) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, entry.AddTarget(nullptr, { Target::kCluster | Target::kEndpoint, 11, 22, 0 }) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.AddTarget(nullptr, { Target::kCluster | Target::kDeviceType, 33, 0, 44 }) == CHIP_NO_ERROR); + NL_TEST_ASSERT( + inSuite, entry.AddTarget(nullptr, { Target::kCluster | Target::kDeviceType, 0xAAAA5555, 0, 0xBBBB6666 }) == CHIP_NO_ERROR); + + index = 999; + NL_TEST_ASSERT(inSuite, accessControl.CreateEntry(&index, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, int(index) == 3); + + NL_TEST_ASSERT(inSuite, accessControl.ReadEntry(3, entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetFabricIndex(fabricIndex) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricIndex == 11); + NL_TEST_ASSERT(inSuite, entry.GetPrivilege(privilege) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, privilege == Privilege::kProxyView); + NL_TEST_ASSERT(inSuite, entry.GetAuthMode(authMode) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, authMode == AuthMode::kCase); + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 3); + NL_TEST_ASSERT(inSuite, entry.GetSubject(0, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x11111111AAAAAAAA); + NL_TEST_ASSERT(inSuite, entry.GetSubject(1, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x22222222BBBBBBBB); + NL_TEST_ASSERT(inSuite, entry.GetSubject(2, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x33333333CCCCCCCC); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 3); + NL_TEST_ASSERT(inSuite, entry.GetTarget(0, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + target.flags == (Target::kCluster | Target::kEndpoint) && target.cluster == 11 && target.endpoint == 22); + NL_TEST_ASSERT(inSuite, entry.GetTarget(1, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + target.flags == (Target::kCluster | Target::kDeviceType) && target.cluster == 33 && target.deviceType == 44); + NL_TEST_ASSERT(inSuite, entry.GetTarget(2, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + target.flags == (Target::kCluster | Target::kDeviceType) && target.cluster == 0xAAAA5555 && + target.deviceType == 0xBBBB6666); + + NL_TEST_ASSERT(inSuite, entry.RemoveSubject(1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetSubjectCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 2); + NL_TEST_ASSERT(inSuite, entry.GetSubject(0, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x11111111AAAAAAAA); + NL_TEST_ASSERT(inSuite, entry.GetSubject(1, subject) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subject == 0x33333333CCCCCCCC); + NL_TEST_ASSERT(inSuite, entry.GetSubject(2, subject) != CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, entry.RemoveTarget(1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, entry.GetTargetCount(count) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, count == 2); + NL_TEST_ASSERT(inSuite, entry.GetTarget(0, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + target.flags == (Target::kCluster | Target::kEndpoint) && target.cluster == 11 && target.endpoint == 22); + NL_TEST_ASSERT(inSuite, entry.GetTarget(1, target) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + target.flags == (Target::kCluster | Target::kDeviceType) && target.cluster == 0xAAAA5555 && + target.deviceType == 0xBBBB6666); + NL_TEST_ASSERT(inSuite, entry.GetTarget(2, target) != CHIP_NO_ERROR); } -// Given the entries, test many cases to ensure AccessControl::Check both -// returns the correct answer and has used the expected entries to do so -void TestCheck(nlTestSuite * inSuite, void * inContext) +void TestUpdateEntry(nlTestSuite * inSuite, void * inContext) { - AccessControl & context = *reinterpret_cast(inContext); + EntryData data[6]; + memcpy(data, entryData1, sizeof(data)); + NL_TEST_ASSERT(inSuite, LoadAccessControl(accessControl, data, 6) == CHIP_NO_ERROR); - constexpr struct + EntryData updateData; + for (size_t i = 0; i < 6; ++i) { - SubjectDescriptor subjectDescriptor; - RequestPath requestPath; - Privilege privilege; - CHIP_ERROR expectedResult; - const char * expectedTag; - } checks[] = { - // clang-format off - { - { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { Node(0x1122334455667788) } }, - { .endpoint = kEndpoint1, .cluster = kOnOffCluster }, - Privilege::kAdminister, - CHIP_NO_ERROR, - "1-admin", - }, - { - { .fabricIndex = 1, .authMode = AuthMode::kCase, .subjects = { Node(0x1111222233334444) } }, - { .endpoint = kEndpoint1, .cluster = kOnOffCluster }, - Privilege::kAdminister, - CHIP_ERROR_ACCESS_DENIED, - "(end)", - }, - { - { .fabricIndex = 1, .authMode = AuthMode::kPase, .subjects = { Passcode(kDefaultCommissioningPasscodeId) } }, - { .endpoint = kEndpoint1, .cluster = kOnOffCluster }, - Privilege::kAdminister, - CHIP_ERROR_ACCESS_DENIED, - "(end)", - }, - { - { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { Node(0x8877665544332211) } }, - { .endpoint = kEndpoint1, .cluster = kOnOffCluster }, - Privilege::kAdminister, - CHIP_NO_ERROR, - "2-admin", - }, - { - { .fabricIndex = 2, .authMode = AuthMode::kCase, .subjects = { Node(0x1122334455667788) } }, - { .endpoint = kEndpoint1, .cluster = kOnOffCluster }, - Privilege::kAdminister, - CHIP_ERROR_ACCESS_DENIED, - "(end)", - }, + updateData.authMode = authModes[i % 3]; + updateData.fabricIndex = fabricIndexes[i % 3]; + updateData.privilege = privileges[i % 3]; + + if (i < 3) { - { .fabricIndex = 2, .authMode = AuthMode::kPase, .subjects = { Passcode(kDefaultCommissioningPasscodeId) } }, - { .endpoint = kEndpoint1, .cluster = kOnOffCluster }, - Privilege::kAdminister, - CHIP_ERROR_ACCESS_DENIED, - "(end)", - }, + updateData.AddSubject(nullptr, subjects[i][i]); + } + else { - { .fabricIndex = 1, .authMode = AuthMode::kPase, .subjects = { Passcode(kDefaultCommissioningPasscodeId) } }, - { .endpoint = kEndpoint2, .cluster = kOnOffCluster }, - Privilege::kView, - CHIP_NO_ERROR, - "1-pase-view-onoff-2", - }, - // clang-format on - }; - - for (int i = 0; i < int(sizeof(checks) / sizeof(checks[0])); ++i) - { - auto & check = checks[i]; - ResetTouchedEntries(); - CHIP_ERROR err = context.Check(check.subjectDescriptor, check.requestPath, check.privilege); - NL_TEST_ASSERT_LOOP(inSuite, i, err == check.expectedResult); - NL_TEST_ASSERT_LOOP(inSuite, i, EntriesTouchedToTag(check.expectedTag, check.subjectDescriptor.fabricIndex)); - } -} - -void TestGlobalInstance(nlTestSuite * inSuite, void * inContext) -{ - // Initial instance should not be nullptr - AccessControl * instance = GetAccessControl(); - NL_TEST_ASSERT(inSuite, instance != nullptr); + updateData.AddTarget(nullptr, targets[i - 3]); + } - // Attempting to set nullptr should have no effect - SetAccessControl(nullptr); - NL_TEST_ASSERT(inSuite, GetAccessControl() == instance); + data[i] = updateData; - // Setting another instance should have immediate effect - SetAccessControl(&testAccessControl); - NL_TEST_ASSERT(inSuite, GetAccessControl() == &testAccessControl); + { + Entry entry; + NL_TEST_ASSERT(inSuite, accessControl.PrepareEntry(entry) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, LoadEntry(entry, updateData) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, accessControl.UpdateEntry(i, entry) == CHIP_NO_ERROR); + } - // Restoring initial instance should also work - SetAccessControl(instance); - NL_TEST_ASSERT(inSuite, GetAccessControl() == instance); + NL_TEST_ASSERT(inSuite, CompareAccessControl(accessControl, data, 6) == CHIP_NO_ERROR); + } } int Setup(void * inContext) { - CHIP_ERROR err = testDataProvider.Init(); - if (err != CHIP_NO_ERROR) - return FAILURE; - err = testAccessControl.Init(); - if (err != CHIP_NO_ERROR) - return FAILURE; + SetAccessControl(accessControl); + GetAccessControl().Init(); return SUCCESS; } int Teardown(void * inContext) { - testDataProvider.Finish(); - testAccessControl.Finish(); + GetAccessControl().Finish(); return SUCCESS; } int Initialize(void * inContext) { - return SUCCESS; + return ClearAccessControl(accessControl) == CHIP_NO_ERROR ? SUCCESS : FAILURE; } int Terminate(void * inContext) @@ -532,8 +1105,15 @@ int TestAccessControl() { // clang-format off constexpr nlTest tests[] = { - NL_TEST_DEF("MetaTestIterator", MetaTestIterator), - NL_TEST_DEF("TestGlobalInstance", TestGlobalInstance), + NL_TEST_DEF("MetaTest", MetaTest), + NL_TEST_DEF("TestPrepareEntry", TestPrepareEntry), + NL_TEST_DEF("TestCreateReadEntry", TestCreateReadEntry), + NL_TEST_DEF("TestUpdateEntry", TestUpdateEntry), + NL_TEST_DEF("TestDeleteEntry", TestDeleteEntry), + NL_TEST_DEF("TestSubjectsTargets", TestSubjectsTargets), + NL_TEST_DEF("TestIterator", TestIterator), + NL_TEST_DEF("TestFabricFilteredReadEntry", TestFabricFilteredReadEntry), + NL_TEST_DEF("TestFabricFilteredCreateEntry", TestFabricFilteredCreateEntry), NL_TEST_DEF("TestCheck", TestCheck), NL_TEST_SENTINEL() }; @@ -548,7 +1128,7 @@ int TestAccessControl() .terminate = Terminate, }; - nlTestRunner(&suite, &testAccessControl); + nlTestRunner(&suite, nullptr); return nlTestRunnerStats(&suite); } diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 25a8407e6a18eb..fb9a34afea16c7 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -2583,6 +2583,96 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_MAX_GROUP_CONCURRENT_ITERATORS 2 #endif +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_ENTRIES_PER_FABRIC + * + * Defines the number of access control entries supported per fabric in the + * example access control code. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_ENTRIES_PER_FABRIC +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_ENTRIES_PER_FABRIC 3 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_SUBJECTS_PER_ENTRY + * + * Defines the number of access control subjects supported per entry in the + * example access control code. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_SUBJECTS_PER_ENTRY +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_SUBJECTS_PER_ENTRY 4 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_TARGETS_PER_ENTRY + * + * Defines the number of access control targets supported per entry in the + * example access control code. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_TARGETS_PER_ENTRY +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_MAX_TARGETS_PER_ENTRY 3 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_STORAGE_POOL_SIZE + * + * Defines the entry storage pool size in the example access control code. + * It's possible to get by with only one. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_STORAGE_POOL_SIZE +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_STORAGE_POOL_SIZE 1 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_DELEGATE_POOL_SIZE + * + * Defines the entry delegate pool size in the example access control code. + * It's possible to get by with only one. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_DELEGATE_POOL_SIZE +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_DELEGATE_POOL_SIZE 1 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_ITERATOR_DELEGATE_POOL_SIZE + * + * Defines the entry iterator delegate pool size in the example access control code. + * It's possible to get by with only one. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_ITERATOR_DELEGATE_POOL_SIZE +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_ENTRY_ITERATOR_DELEGATE_POOL_SIZE 1 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT + * + * Support fast copy in the example access control implementation. + * + * At least one of "fast" or "flexible" copy support must be enabled. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT 1 +#endif + +/** + * @def CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT + * + * Support flexible copy in the example access control implementation. + * + * Only needed if mixing the example access control implementation with other + * non-example access control delegate implementations; omitting it saves space. + * + * At least one of "fast" or "flexible" copy support must be enabled. + */ +#ifndef CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT +#define CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT 0 +#endif + +#if !CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT && !CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT +#error \ + "Please enable at least one of CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT or CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT" +#endif + /** * @} */