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 + /** * @} */