/*
 *
 *    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 <access/AccessConfig.h>

#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
#include "AccessRestrictionProvider.h"
#endif

#include "Privilege.h"
#include "RequestPath.h"
#include "SubjectDescriptor.h"

#include <lib/core/CHIPCore.h>
#include <lib/core/Global.h>
#include <lib/support/CodeUtils.h>

// Dump function for use during development only (0 for disabled, non-zero for enabled).
#define CHIP_ACCESS_CONTROL_DUMP_ENABLED 0

namespace chip {
namespace Access {

class AccessControl
{
public:
    /**
     * Used by access control to determine if a device type resolves to an endpoint.
     */
    struct DeviceTypeResolver
    {
    public:
        virtual ~DeviceTypeResolver() = default;

        virtual bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) = 0;
    };

    /**
     * Handle to an entry in the access control list.
     *
     * Must be prepared (`AccessControl::PrepareEntry`) or read (`AccessControl::ReadEntry`) before first use.
     */
    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                        = 0;
            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(Entry && other) : mDelegate(other.mDelegate) { other.mDelegate = &mDefaultDelegate.get(); }

        Entry & operator=(Entry && other)
        {
            if (this != &other)
            {
                mDelegate->Release();
                mDelegate       = other.mDelegate;
                other.mDelegate = &mDefaultDelegate.get();
            }
            return *this;
        }

        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); }

        bool HasDefaultDelegate() const { return mDelegate == &mDefaultDelegate.get(); }

        const Delegate & GetDelegate() const { return *mDelegate; }

        Delegate & GetDelegate() { return *mDelegate; }

        void SetDelegate(Delegate & delegate)
        {
            mDelegate->Release();
            mDelegate = &delegate;
        }

        void ResetDelegate()
        {
            mDelegate->Release();
            mDelegate = &mDefaultDelegate.get();
        }

        /**
         * Validate whether an ACL Entry is valid and complies with all defined constraints.
         *
         * @retval true if all the fields within the Entry are valid and compliant.
         */
        bool IsValid() const;

    private:
        static Global<Delegate> mDefaultDelegate;
        Delegate * mDelegate = &mDefaultDelegate.get();
    };

    /**
     * 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); }

        const Delegate & GetDelegate() const { return *mDelegate; }

        Delegate & GetDelegate() { return *mDelegate; }

        void SetDelegate(Delegate & delegate)
        {
            mDelegate->Release();
            mDelegate = &delegate;
        }

        void ResetDelegate()
        {
            mDelegate->Release();
            mDelegate = &mDefaultDelegate.get();
        }

    private:
        static Global<Delegate> mDefaultDelegate;
        Delegate * mDelegate = &mDefaultDelegate.get();
    };

    /**
     * Used by access control to notify of changes in access control list.
     */
    class EntryListener
    {
    public:
        enum class ChangeType
        {
            kAdded   = 1,
            kRemoved = 2,
            kUpdated = 3
        };

        virtual ~EntryListener() = default;

        /**
         * Notifies of a change in the access control list.
         *
         * The fabric is indicated by its own parameter. If available, a subject descriptor will
         * have more detail (and its fabric index will match). A best effort is made to provide
         * the latest value of the changed entry.
         *
         * @param [in] subjectDescriptor Optional (if available) subject descriptor for this operation.
         * @param [in] fabric            Index of fabric in which entry has changed.
         * @param [in] index             Index of entry to which has changed (relative to fabric).
         * @param [in] entry             Optional (best effort) latest value of entry which has changed.
         * @param [in] changeType        Type of change.
         */
        virtual void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index,
                                    const Entry * entry, ChangeType changeType) = 0;

    private:
        EntryListener * mNext = nullptr;

        friend class AccessControl;
    };

    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_NO_ERROR; }
        virtual void Finish() {}

        // Capabilities
        virtual CHIP_ERROR GetMaxEntriesPerFabric(size_t & value) const
        {
            value = 0;
            return CHIP_NO_ERROR;
        }

        virtual CHIP_ERROR GetMaxSubjectsPerEntry(size_t & value) const
        {
            value = 0;
            return CHIP_NO_ERROR;
        }

        virtual CHIP_ERROR GetMaxTargetsPerEntry(size_t & value) const
        {
            value = 0;
            return CHIP_NO_ERROR;
        }

        virtual CHIP_ERROR GetMaxEntryCount(size_t & value) const
        {
            value = 0;
            return CHIP_NO_ERROR;
        }

        // Actualities
        virtual CHIP_ERROR GetEntryCount(FabricIndex fabric, size_t & value) const
        {
            value = 0;
            return CHIP_NO_ERROR;
        }

        virtual CHIP_ERROR GetEntryCount(size_t & value) const
        {
            value = 0;
            return CHIP_NO_ERROR;
        }

        // Preparation
        virtual CHIP_ERROR PrepareEntry(Entry & entry) { return CHIP_NO_ERROR; }

        // CRUD
        virtual CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, FabricIndex * fabricIndex) { return CHIP_NO_ERROR; }
        virtual CHIP_ERROR ReadEntry(size_t index, Entry & entry, const FabricIndex * fabricIndex) const { return CHIP_NO_ERROR; }
        virtual CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex * fabricIndex) { return CHIP_NO_ERROR; }
        virtual CHIP_ERROR DeleteEntry(size_t index, const FabricIndex * fabricIndex) { return CHIP_NO_ERROR; }

        // Iteration
        virtual CHIP_ERROR Entries(EntryIterator & iterator, const FabricIndex * fabricIndex) const { return CHIP_NO_ERROR; }

        // Check
        // Return CHIP_NO_ERROR if allowed, CHIP_ERROR_ACCESS_DENIED if denied,
        // CHIP_ERROR_NOT_IMPLEMENTED to use the default check algorithm (against entries),
        // or any other CHIP_ERROR if another error occurred.
        virtual CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
                                 Privilege requestPrivilege)
        {
            return CHIP_ERROR_ACCESS_DENIED;
        }
    };

    AccessControl() = default;

    AccessControl(const AccessControl &)             = delete;
    AccessControl & operator=(const AccessControl &) = delete;

    ~AccessControl()
    {
        // Never-initialized AccessControl instances will not have the delegate set.
        if (IsInitialized())
        {
            mDelegate->Release();
        }
    }

    /**
     * Initialize the access control module. Must be called before first use.
     *
     * @return CHIP_NO_ERROR on success, CHIP_ERROR_INCORRECT_STATE if called more than once,
     *         CHIP_ERROR_INVALID_ARGUMENT if delegate is null, or other fatal error.
     */
    CHIP_ERROR Init(AccessControl::Delegate * delegate, DeviceTypeResolver & deviceTypeResolver);

    /**
     * Deinitialize the access control module. Must be called when finished.
     */
    void Finish();

    // Capabilities
    CHIP_ERROR GetMaxEntriesPerFabric(size_t & value) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->GetMaxEntriesPerFabric(value);
    }

    CHIP_ERROR GetMaxSubjectsPerEntry(size_t & value) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->GetMaxSubjectsPerEntry(value);
    }

    CHIP_ERROR GetMaxTargetsPerEntry(size_t & value) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->GetMaxTargetsPerEntry(value);
    }

    CHIP_ERROR GetMaxEntryCount(size_t & value) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->GetMaxEntryCount(value);
    }

    // Actualities
    CHIP_ERROR GetEntryCount(FabricIndex fabric, size_t & value) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->GetEntryCount(fabric, value);
    }

    CHIP_ERROR GetEntryCount(size_t & value) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->GetEntryCount(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)
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->PrepareEntry(entry);
    }

    /**
     * Creates an entry in the access control list.
     *
     * @param [in]  subjectDescriptor Optional subject descriptor for this operation.
     * @param [in]  fabric            Index of fabric in which to create entry.
     * @param [out] index             (If not nullptr) index of created entry (relative to fabric).
     * @param [in]  entry             Entry from which created entry is copied.
     */
    CHIP_ERROR CreateEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t * index, const Entry & 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)
    {
        VerifyOrReturnError(entry.IsValid(), CHIP_ERROR_INVALID_ARGUMENT);
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->CreateEntry(index, entry, fabricIndex);
    }

    /**
     * Reads an entry in the access control list.
     *
     * @param [in] fabric            Index of fabric in which to read entry.
     * @param [in] index             Index of entry to read (relative to fabric).
     * @param [in] entry             Entry into which read entry is copied.
     */
    CHIP_ERROR ReadEntry(FabricIndex fabric, size_t index, Entry & entry) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->ReadEntry(index, entry, &fabric);
    }

    /**
     * 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
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->ReadEntry(index, entry, fabricIndex);
    }

    /**
     * Updates an entry in the access control list.
     *
     * @param [in] subjectDescriptor Optional subject descriptor for this operation.
     * @param [in] fabric            Index of fabric in which to update entry.
     * @param [in] index             Index of entry to update (relative to fabric).
     * @param [in] entry             Entry from which updated entry is copied.
     */
    CHIP_ERROR UpdateEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry & entry);

    /**
     * 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)
    {
        VerifyOrReturnError(entry.IsValid(), CHIP_ERROR_INVALID_ARGUMENT);
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->UpdateEntry(index, entry, fabricIndex);
    }

    /**
     * Deletes an entry in the access control list.
     *
     * @param [in] subjectDescriptor Optional subject descriptor for this operation.
     * @param [in] fabric            Index of fabric in which to delete entry.
     * @param [in] index             Index of entry to delete (relative to fabric).
     */
    CHIP_ERROR DeleteEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index);

    /**
     * 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)
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->DeleteEntry(index, fabricIndex);
    }

    /**
     * @brief Remove all ACL entries for the given fabricIndex
     *
     * @param[in] fabricIndex fabric index for which to remove all entries
     */
    CHIP_ERROR DeleteAllEntriesForFabric(FabricIndex fabricIndex)
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);

        CHIP_ERROR stickyError = CHIP_NO_ERROR;

        // Remove access control entries in reverse order (it could be any order, but reverse order
        // will cause less churn in persistent storage).
        size_t aclCount = 0;
        if (GetEntryCount(fabricIndex, aclCount) == CHIP_NO_ERROR)
        {
            while (aclCount)
            {
                CHIP_ERROR err = DeleteEntry(nullptr, fabricIndex, --aclCount);
                stickyError    = (stickyError == CHIP_NO_ERROR) ? err : stickyError;
            }
        }

        return stickyError;
    }

    /**
     * Iterates over entries in the access control list.
     *
     * @param [in]  fabric   Fabric over which to iterate entries.
     * @param [out] iterator Iterator controlling the iteration.
     */
    CHIP_ERROR Entries(FabricIndex fabric, EntryIterator & iterator) const
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->Entries(iterator, &fabric);
    }

    /**
     * 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
    {
        VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
        return mDelegate->Entries(iterator, fabricIndex);
    }

    // Adds a listener to the end of the listener list, if not already in the list.
    void AddEntryListener(EntryListener & listener);

    // Removes a listener from the listener list, if in the list.
    void RemoveEntryListener(EntryListener & listener);

#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
    // Set an optional AcceessRestriction object for MNGD feature.
    void SetAccessRestrictionProvider(AccessRestrictionProvider * accessRestrictionProvider)
    {
        mAccessRestrictionProvider = accessRestrictionProvider;
    }

    AccessRestrictionProvider * GetAccessRestrictionProvider() { return mAccessRestrictionProvider; }
#endif

    /**
     * Check whether or not Access Restriction List is supported.
     *
     * @retval true if Access Restriction List is supported.
     */
    bool IsAccessRestrictionListSupported() const;

    /**
     * Check whether access (by a subject descriptor, to a request path,
     * requiring a privilege) should be allowed or denied.
     *
     * If an AccessRestrictionProvider object is set, it will be checked for additional access restrictions.
     *
     * @retval #CHIP_ERROR_ACCESS_DENIED if denied.
     * @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 requestPrivilege);

#if CHIP_ACCESS_CONTROL_DUMP_ENABLED
    CHIP_ERROR Dump(const Entry & entry);
#endif

private:
    bool IsInitialized() const { return (mDelegate != nullptr); }

    void NotifyEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry,
                            EntryListener::ChangeType changeType);

    /**
     * Check ACL for whether access (by a subject descriptor, to a request path,
     * requiring a privilege) should be allowed or denied.
     */
    CHIP_ERROR CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege);

    /**
     * Check CommissioningARL or ARL (as appropriate) for whether access (by a
     * subject descriptor, to a request path, requiring a privilege) should
     * be allowed or denied.
     */
    CHIP_ERROR CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege);

private:
    Delegate * mDelegate = nullptr;

    DeviceTypeResolver * mDeviceTypeResolver = nullptr;

    EntryListener * mEntryListener = nullptr;

#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
    AccessRestrictionProvider * mAccessRestrictionProvider;
#endif
};

/**
 * Get the global instance set by SetAccessControl, or the default.
 *
 * Calls to this function must be synchronized externally.
 */
AccessControl & GetAccessControl();

/**
 * Set the global instance returned by GetAccessControl.
 *
 * Calls to this function must be synchronized externally.
 */
void SetAccessControl(AccessControl & accessControl);

/**
 * Reset the global instance to the default.
 *
 * Calls to this function must be synchronized externally.
 */
void ResetAccessControlToDefault();

} // namespace Access
} // namespace chip
