From c2fdd060d34ebc14349aff3b0bfa0083c1e24f7c Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Tue, 30 Jul 2024 10:32:57 -0500
Subject: [PATCH 01/22] Add AccessRestrictionList support

---
 examples/network-manager-app/linux/args.gni   |   1 +
 .../network-manager-app.matter                |   9 +-
 .../network-manager-app.zap                   |  66 ++-
 examples/platform/linux/AppMain.cpp           |  18 +
 examples/platform/linux/BUILD.gn              |   2 +
 .../platform/linux/ExampleAccessRestriction.h |  55 +++
 examples/platform/linux/Options.cpp           |  63 +++
 examples/platform/linux/Options.h             |   8 +
 scripts/tools/check_includes_config.py        |   1 +
 src/access/AccessConfig.h                     |  22 +
 src/access/AccessControl.cpp                  |  19 +-
 src/access/AccessControl.h                    |  19 +
 src/access/AccessRestriction.cpp              | 280 +++++++++++
 src/access/AccessRestriction.h                | 288 +++++++++++
 src/access/BUILD.gn                           |  28 ++
 src/access/access.gni                         |  18 +
 src/access/tests/BUILD.gn                     |   5 +
 src/access/tests/TestAccessRestriction.cpp    | 463 ++++++++++++++++++
 .../access-control-server.cpp                 | 216 +++++++-
 src/app/server/ArlStorage.cpp                 | 158 ++++++
 src/app/server/ArlStorage.h                   | 148 ++++++
 src/app/server/BUILD.gn                       |  10 +
 src/app/server/DefaultArlStorage.cpp          | 203 ++++++++
 src/app/server/DefaultArlStorage.h            |  37 ++
 src/app/server/Server.cpp                     |  11 +
 src/app/server/Server.h                       |  18 +
 src/lib/core/CHIPConfig.h                     |  21 +
 src/lib/support/DefaultStorageKeyAllocator.h  |  10 +
 28 files changed, 2188 insertions(+), 9 deletions(-)
 create mode 100644 examples/platform/linux/ExampleAccessRestriction.h
 create mode 100644 src/access/AccessConfig.h
 create mode 100644 src/access/AccessRestriction.cpp
 create mode 100644 src/access/AccessRestriction.h
 create mode 100644 src/access/access.gni
 create mode 100644 src/access/tests/TestAccessRestriction.cpp
 create mode 100644 src/app/server/ArlStorage.cpp
 create mode 100644 src/app/server/ArlStorage.h
 create mode 100644 src/app/server/DefaultArlStorage.cpp
 create mode 100644 src/app/server/DefaultArlStorage.h

diff --git a/examples/network-manager-app/linux/args.gni b/examples/network-manager-app/linux/args.gni
index 53bb53d2b1b2a1..97661ce32a8bf7 100644
--- a/examples/network-manager-app/linux/args.gni
+++ b/examples/network-manager-app/linux/args.gni
@@ -22,3 +22,4 @@ chip_project_config_include_dirs = [
 ]
 
 chip_config_network_layer_ble = false
+chip_enable_access_restrictions = true
diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter
index 627838288db0d1..57118d365577f1 100644
--- a/examples/network-manager-app/network-manager-common/network-manager-app.matter
+++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter
@@ -1623,16 +1623,23 @@ endpoint 0 {
   server cluster AccessControl {
     emits event AccessControlEntryChanged;
     emits event AccessControlExtensionChanged;
+    emits event AccessRestrictionEntryChanged;
+    emits event FabricRestrictionReviewUpdate;
     callback attribute acl;
     callback attribute extension;
     callback attribute subjectsPerAccessControlEntry;
     callback attribute targetsPerAccessControlEntry;
     callback attribute accessControlEntriesPerFabric;
+    callback attribute commissioningARL;
+    callback attribute arl;
     callback attribute generatedCommandList;
     callback attribute acceptedCommandList;
     callback attribute attributeList;
-    ram      attribute featureMap default = 0;
+    ram      attribute featureMap default = 1;
     callback attribute clusterRevision;
+
+    handle command ReviewFabricRestrictions;
+    handle command ReviewFabricRestrictionsResponse;
   }
 
   server cluster BasicInformation {
diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap
index 1d27e3c346f320..240cd495a7fb4f 100644
--- a/examples/network-manager-app/network-manager-common/network-manager-app.zap
+++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap
@@ -314,6 +314,24 @@
           "define": "ACCESS_CONTROL_CLUSTER",
           "side": "server",
           "enabled": 1,
+          "commands": [
+            {
+              "name": "ReviewFabricRestrictions",
+              "code": 0,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 1,
+              "isEnabled": 1
+            },
+            {
+              "name": "ReviewFabricRestrictionsResponse",
+              "code": 1,
+              "mfgCode": null,
+              "source": "server",
+              "isIncoming": 0,
+              "isEnabled": 1
+            }
+          ],
           "attributes": [
             {
               "name": "ACL",
@@ -395,6 +413,38 @@
               "maxInterval": 65534,
               "reportableChange": 0
             },
+            {
+              "name": "CommissioningARL",
+              "code": 5,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "ARL",
+              "code": 6,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": "",
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
             {
               "name": "GeneratedCommandList",
               "code": 65528,
@@ -453,7 +503,7 @@
               "storageOption": "RAM",
               "singleton": 0,
               "bounded": 0,
-              "defaultValue": "0",
+              "defaultValue": "1",
               "reportable": 1,
               "minInterval": 1,
               "maxInterval": 65534,
@@ -490,6 +540,20 @@
               "mfgCode": null,
               "side": "server",
               "included": 1
+            },
+            {
+              "name": "AccessRestrictionEntryChanged",
+              "code": 2,
+              "mfgCode": null,
+              "side": "server",
+              "included": 1
+            },
+            {
+              "name": "FabricRestrictionReviewUpdate",
+              "code": 3,
+              "mfgCode": null,
+              "side": "server",
+              "included": 1
             }
           ]
         },
diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index 307b3428126db2..147a970124c0f2 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -103,6 +103,11 @@
 #include "AppMain.h"
 #include "CommissionableInit.h"
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+#include "ExampleAccessRestriction.h"
+#include <app/server/DefaultArlStorage.h>
+#endif
+
 #if CHIP_DEVICE_LAYER_TARGET_DARWIN
 #include <platform/Darwin/NetworkCommissioningDriver.h>
 #if CHIP_DEVICE_CONFIG_ENABLE_WIFI
@@ -121,6 +126,7 @@ using namespace chip::DeviceLayer;
 using namespace chip::Inet;
 using namespace chip::Transport;
 using namespace chip::app::Clusters;
+using namespace chip::Access;
 
 // Network comissioning implementation
 namespace {
@@ -593,6 +599,18 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl)
     chip::app::RuntimeOptionsProvider::Instance().SetSimulateNoInternalTime(
         LinuxDeviceOptions::GetInstance().mSimulateNoInternalTime);
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    if (LinuxDeviceOptions::GetInstance().accessRestrictionEntries.HasValue())
+    {
+        initParams.accessRestriction = new ExampleAccessRestriction();
+        initParams.arlStorage        = new app::DefaultArlStorage();
+        for (const auto & entry : LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value())
+        {
+            VerifyOrDie(AccessRestriction::CreateCommissioningEntry(entry) == CHIP_NO_ERROR);
+        }
+    }
+#endif
+
     // Init ZCL Data Model and CHIP App Server
     Server::GetInstance().Init(initParams);
 
diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn
index 1fcee183f131b3..b37e73f6debb9c 100644
--- a/examples/platform/linux/BUILD.gn
+++ b/examples/platform/linux/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("//build_overrides/chip.gni")
+import("//build_overrides/jsoncpp.gni")
 import("${chip_root}/examples/common/pigweed/pigweed_rpcs.gni")
 import("${chip_root}/src/app/common_flags.gni")
 import("${chip_root}/src/lib/core/core.gni")
@@ -94,6 +95,7 @@ source_set("app-main") {
     "${chip_root}/src/controller:gen_check_chip_controller_headers",
     "${chip_root}/src/lib",
     "${chip_root}/src/platform/logging:default",
+    jsoncpp_root,
   ]
   deps = [
     ":ota-test-event-trigger",
diff --git a/examples/platform/linux/ExampleAccessRestriction.h b/examples/platform/linux/ExampleAccessRestriction.h
new file mode 100644
index 00000000000000..0262e42bab2d57
--- /dev/null
+++ b/examples/platform/linux/ExampleAccessRestriction.h
@@ -0,0 +1,55 @@
+/*
+ *
+ *    Copyright (c) 2024 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.
+ */
+
+/*
+ * AccessRestriction implementation for Linux examples.
+ */
+
+#pragma once
+
+#include <access/AccessRestriction.h>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/EventLogging.h>
+
+namespace chip {
+namespace Access {
+
+class ExampleAccessRestriction : public AccessRestriction
+{
+public:
+    ExampleAccessRestriction() : AccessRestriction() {}
+
+    ~ExampleAccessRestriction() {}
+
+protected:
+    CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl)
+    {
+        // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events
+        while (Access::GetAccessControl().GetAccessRestriction()->DeleteEntry(0, fabricIndex) == CHIP_NO_ERROR)
+            ;
+
+        chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .fabricIndex = fabricIndex };
+        EventNumber eventNumber;
+        ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber));
+
+        return CHIP_NO_ERROR;
+    }
+};
+
+} // namespace Access
+} // namespace chip
diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp
index 9b83d126c1f495..85da5f07397d4d 100644
--- a/examples/platform/linux/Options.cpp
+++ b/examples/platform/linux/Options.cpp
@@ -26,6 +26,7 @@
 #include <app/server/OnboardingCodesUtil.h>
 
 #include <crypto/CHIPCryptoPAL.h>
+#include <json/json.h>
 #include <lib/core/CHIPError.h>
 #include <lib/support/Base64.h>
 #include <lib/support/BytesToHex.h>
@@ -47,6 +48,11 @@
 
 using namespace chip;
 using namespace chip::ArgParser;
+using namespace chip::Platform;
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+using namespace chip::Access;
+#endif
 
 namespace {
 LinuxDeviceOptions gDeviceOptions;
@@ -82,6 +88,9 @@ enum
     kDeviceOption_TraceFile,
     kDeviceOption_TraceLog,
     kDeviceOption_TraceDecode,
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    kDeviceOption_UseAccessRestrictions,
+#endif
     kOptionCSRResponseCSRIncorrectType,
     kOptionCSRResponseCSRNonceIncorrectType,
     kOptionCSRResponseCSRNonceTooLong,
@@ -154,6 +163,9 @@ OptionDef sDeviceOptionDefs[] = {
     { "trace_log", kArgumentRequired, kDeviceOption_TraceLog },
     { "trace_decode", kArgumentRequired, kDeviceOption_TraceDecode },
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    { "enable-access-restrictions", kArgumentRequired, kDeviceOption_UseAccessRestrictions },
+#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     { "cert_error_csr_incorrect_type", kNoArgument, kOptionCSRResponseCSRIncorrectType },
     { "cert_error_csr_existing_keypair", kNoArgument, kOptionCSRResponseCSRExistingKeyPair },
     { "cert_error_csr_nonce_incorrect_type", kNoArgument, kOptionCSRResponseCSRNonceIncorrectType },
@@ -280,6 +292,11 @@ const char * sDeviceOptionHelp =
     "  --trace_decode <1/0>\n"
     "       A value of 1 enables traces decoding, 0 disables this (default 0).\n"
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    "  --enable-access-restrictions <CommissioningARL JSON>\n"
+    "       Enable ACL cluster access restrictions with the provided JSON CommissioningARL. Example:\n"
+    "       \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 2,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 3}]}]\"\n"
+#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     "  --cert_error_csr_incorrect_type\n"
     "       Configure the CSRResponse to be built with an invalid CSR type.\n"
     "  --cert_error_csr_existing_keypair\n"
@@ -320,6 +337,40 @@ const char * sDeviceOptionHelp =
 #endif
     "\n";
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+bool ParseAccessRestrictionEntriesFromJson(const char * jsonString,
+                                           std::vector<Platform::SharedPtr<AccessRestriction::Entry>> & entries)
+{
+    Json::Value root;
+    Json::Reader reader;
+    VerifyOrReturnValue(reader.parse(jsonString, root), false);
+
+    for (Json::Value::const_iterator eIt = root.begin(); eIt != root.end(); eIt++)
+    {
+        auto entry = MakeShared<AccessRestriction::Entry>();
+
+        entry->endpointNumber = static_cast<EndpointId>((*eIt)["endpoint"].asUInt());
+        entry->clusterId      = static_cast<ClusterId>((*eIt)["cluster"].asUInt());
+
+        Json::Value restrictions = (*eIt)["restrictions"];
+        for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++)
+        {
+            AccessRestriction::Restriction restriction;
+            restriction.restrictionType = static_cast<AccessRestriction::Type>((*rIt)["type"].asInt());
+            if ((*rIt).isMember("id"))
+            {
+                restriction.id.SetValue((*rIt)["id"].asUInt());
+            }
+            entry->restrictions.push_back(restriction);
+        }
+
+        entries.push_back(entry);
+    }
+
+    return true;
+}
+#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+
 bool Base64ArgToVector(const char * arg, size_t maxSize, std::vector<uint8_t> & outVector)
 {
     size_t maxBase64Size = BASE64_ENCODED_LEN(maxSize);
@@ -529,6 +580,18 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier,
         break;
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    case kDeviceOption_UseAccessRestrictions: {
+        std::vector<Platform::SharedPtr<AccessRestriction::Entry>> accessRestrictionEntries;
+        retval = ParseAccessRestrictionEntriesFromJson(aValue, accessRestrictionEntries);
+        if (retval)
+        {
+            LinuxDeviceOptions::GetInstance().accessRestrictionEntries.SetValue(std::move(accessRestrictionEntries));
+        }
+    }
+    break;
+#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+
     case kOptionCSRResponseCSRIncorrectType:
         LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrIncorrectType = true;
         break;
diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h
index f921bee4ced554..895037664232c3 100644
--- a/examples/platform/linux/Options.h
+++ b/examples/platform/linux/Options.h
@@ -28,6 +28,7 @@
 #include <string>
 #include <vector>
 
+#include <access/AccessConfig.h>
 #include <inet/InetInterface.h>
 #include <lib/core/CHIPError.h>
 #include <lib/core/Optional.h>
@@ -38,6 +39,10 @@
 #include <credentials/DeviceAttestationCredsProvider.h>
 #include <testing/CustomCSRResponse.h>
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+#include <access/AccessRestriction.h>
+#endif
+
 struct LinuxDeviceOptions
 {
     chip::PayloadContents payload;
@@ -81,6 +86,9 @@ struct LinuxDeviceOptions
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     int32_t subscriptionCapacity                   = CHIP_IM_MAX_NUM_SUBSCRIPTIONS;
     int32_t subscriptionResumptionRetryIntervalSec = -1;
+#endif
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    chip::Optional<std::vector<chip::Platform::SharedPtr<chip::Access::AccessRestriction::Entry>>> accessRestrictionEntries;
 #endif
     static LinuxDeviceOptions & GetInstance();
 };
diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py
index 2af375d7c4bdfd..efe66d57caed04 100644
--- a/scripts/tools/check_includes_config.py
+++ b/scripts/tools/check_includes_config.py
@@ -185,4 +185,5 @@
     'src/app/icd/client/DefaultICDStorageKey.h': {'vector'},
     'src/controller/CHIPDeviceController.cpp': {'string'},
     'src/qrcodetool/setup_payload_commands.cpp': {'string'},
+    'src/access/AccessRestriction.h': {'vector', 'map'},
 }
diff --git a/src/access/AccessConfig.h b/src/access/AccessConfig.h
new file mode 100644
index 00000000000000..c4cb6f51f3114d
--- /dev/null
+++ b/src/access/AccessConfig.h
@@ -0,0 +1,22 @@
+/*
+ *
+ *    Copyright (c) 2020-2024 Project CHIP Authors
+ *
+ *    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
+
+#if CHIP_HAVE_CONFIG_H
+#include <access/AccessBuildConfig.h>
+#endif
diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index fcb5a43d975f8e..6d519f9a227adf 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -325,7 +325,11 @@ void AccessControl::RemoveEntryListener(EntryListener & listener)
 
 bool AccessControl::IsAccessRestrictionListSupported() const
 {
-    return false; // not yet supported
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    return mAccessRestriction != nullptr;
+#else
+    return false;
+#endif
 }
 
 CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
@@ -352,6 +356,19 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
         VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT);
     }
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    if (mAccessRestriction != nullptr)
+    {
+        CHIP_ERROR result = mAccessRestriction->Check(subjectDescriptor, requestPath);
+        if (result != CHIP_NO_ERROR)
+        {
+            ChipLogProgress(DataManagement, "AccessControl: %s",
+                            (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
+            return result;
+        }
+    }
+#endif
+
     {
         CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege);
         if (result != CHIP_ERROR_NOT_IMPLEMENTED)
diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h
index a7c3472f5d99b4..6f2f29803f9cdf 100644
--- a/src/access/AccessControl.h
+++ b/src/access/AccessControl.h
@@ -18,6 +18,12 @@
 
 #pragma once
 
+#include <access/AccessConfig.h>
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+#include "AccessRestriction.h"
+#endif
+
 #include "Privilege.h"
 #include "RequestPath.h"
 #include "SubjectDescriptor.h"
@@ -627,6 +633,13 @@ class AccessControl
     // 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 SetAccessRestriction(AccessRestriction * accessRestriction) { mAccessRestriction = accessRestriction; }
+
+    AccessRestriction * GetAccessRestriction() { return mAccessRestriction; }
+#endif
+
     /**
      * Check whether or not Access Restriction List is supported.
      *
@@ -638,6 +651,8 @@ class AccessControl
      * Check whether access (by a subject descriptor, to a request path,
      * requiring a privilege) should be allowed or denied.
      *
+     * If an AccessRestriction 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.
@@ -662,6 +677,10 @@ class AccessControl
     DeviceTypeResolver * mDeviceTypeResolver = nullptr;
 
     EntryListener * mEntryListener = nullptr;
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    AccessRestriction * mAccessRestriction;
+#endif
 };
 
 /**
diff --git a/src/access/AccessRestriction.cpp b/src/access/AccessRestriction.cpp
new file mode 100644
index 00000000000000..cadb70a0ba0088
--- /dev/null
+++ b/src/access/AccessRestriction.cpp
@@ -0,0 +1,280 @@
+/*
+ *
+ *    Copyright (c) 2024 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 "AccessRestriction.h"
+
+#include <algorithm>
+#include <lib/core/Global.h>
+
+using namespace chip::Platform;
+
+namespace chip {
+namespace Access {
+
+std::vector<SharedPtr<AccessRestriction::Entry>> AccessRestriction::sCommissioningEntries;
+
+CHIP_ERROR AccessRestriction::CreateFabricEntries(const FabricIndex fabricIndex)
+{
+    for (auto & entry : sCommissioningEntries)
+    {
+        CreateEntry(nullptr, *entry, fabricIndex);
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+void AccessRestriction::AddListener(EntryListener & listener)
+{
+    if (mListeners == nullptr)
+    {
+        mListeners     = &listener;
+        listener.mNext = nullptr;
+        return;
+    }
+
+    for (EntryListener * l = mListeners; /**/; l = l->mNext)
+    {
+        if (l == &listener)
+        {
+            return;
+        }
+
+        if (l->mNext == nullptr)
+        {
+            l->mNext       = &listener;
+            listener.mNext = nullptr;
+            return;
+        }
+    }
+}
+
+void AccessRestriction::RemoveListener(EntryListener & listener)
+{
+    if (mListeners == &listener)
+    {
+        mListeners     = listener.mNext;
+        listener.mNext = nullptr;
+        return;
+    }
+
+    for (EntryListener * l = mListeners; l != nullptr; l = l->mNext)
+    {
+        if (l->mNext == &listener)
+        {
+            l->mNext       = listener.mNext;
+            listener.mNext = nullptr;
+            return;
+        }
+    }
+}
+
+SharedPtr<AccessRestriction::Entry> AccessRestriction::GetEntry(FabricIndex fabricIndex, size_t index)
+{
+    if (mFabricEntries.find(fabricIndex) != mFabricEntries.end() && index < mFabricEntries[fabricIndex].size())
+    {
+        return mFabricEntries[fabricIndex][index];
+    }
+
+    return nullptr;
+}
+
+CHIP_ERROR AccessRestriction::CreateCommissioningEntry(SharedPtr<Entry> entry)
+{
+    if (!entry->IsValid())
+    {
+        ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry");
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    sCommissioningEntries.push_back(entry);
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR AccessRestriction::CreateEntry(size_t * index, const Entry & entry, FabricIndex fabricIndex)
+{
+    if (!entry.IsValid())
+    {
+        ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry");
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    auto localEntry = MakeShared<Entry>(entry);
+    size_t newIndex;
+
+    localEntry->fabricIndex = fabricIndex;
+
+    if (mFabricEntries.find(fabricIndex) == mFabricEntries.end())
+    {
+        mFabricEntries[fabricIndex] = std::vector<SharedPtr<Entry>>();
+        mFabricEntries[fabricIndex].push_back(localEntry);
+    }
+    else
+    {
+        mFabricEntries[fabricIndex].push_back(localEntry);
+    }
+
+    newIndex = mFabricEntries[fabricIndex].size() - 1;
+
+    for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext)
+    {
+        listener->OnEntryChanged(fabricIndex, newIndex, localEntry, EntryListener::ChangeType::kAdded);
+    }
+
+    if (index != nullptr)
+    {
+        *index = newIndex;
+    }
+
+    ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, newIndex);
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR AccessRestriction::DeleteEntry(size_t index, const FabricIndex fabricIndex)
+{
+    ChipLogProgress(DataManagement, "AccessRestriction: delete entry f=%u i=%lu", fabricIndex, index);
+
+    auto entry = GetEntry(fabricIndex, index);
+    if (entry == nullptr)
+    {
+        return CHIP_ERROR_NOT_FOUND;
+    }
+    else
+    {
+        mFabricEntries[fabricIndex].erase(mFabricEntries[fabricIndex].begin() + static_cast<int>(index));
+
+        for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext)
+        {
+            listener->OnEntryChanged(fabricIndex, index, entry, EntryListener::ChangeType::kRemoved);
+        }
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR AccessRestriction::UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex)
+{
+    ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, index);
+
+    auto localEntry = GetEntry(fabricIndex, index);
+    if (localEntry != nullptr)
+    {
+        *localEntry = entry;
+
+        for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext)
+        {
+            listener->OnEntryChanged(fabricIndex, index, localEntry, EntryListener::ChangeType::kUpdated);
+        }
+
+        return CHIP_NO_ERROR;
+    }
+
+    return CHIP_ERROR_NOT_FOUND;
+}
+
+CHIP_ERROR AccessRestriction::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
+{
+    ChipLogProgress(DataManagement, "AccessRestriction: action %d", static_cast<int>(requestPath.requestType));
+
+    if (requestPath.requestType == RequestType::kRequestTypeUnknown)
+    {
+        ChipLogError(DataManagement, "AccessRestriction: RequestPath type is unknown");
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and
+    // we want that request to succeed (when generating the report, this method will be called with the specific
+    // event id). All other requests require an entity id
+    if (!requestPath.entityId.has_value())
+    {
+        if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
+        {
+            return CHIP_NO_ERROR;
+        }
+        else
+        {
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+    }
+
+    for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex])
+    {
+        if (entry->endpointNumber != requestPath.endpoint || entry->clusterId != requestPath.cluster)
+        {
+            continue;
+        }
+
+        for (auto & restriction : entry->restrictions)
+        {
+            // a missing id is a wildcard
+            bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value();
+            if (!idMatch)
+            {
+                continue;
+            }
+
+            switch (restriction.restrictionType)
+            {
+            case Type::kAttributeAccessForbidden:
+                if (requestPath.requestType == RequestType::kAttributeReadRequest ||
+                    requestPath.requestType == RequestType::kAttributeWriteRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            case Type::kAttributeWriteForbidden:
+                if (requestPath.requestType == RequestType::kAttributeWriteRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            case Type::kCommandForbidden:
+                if (requestPath.requestType == RequestType::kCommandInvokeRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            case Type::kEventForbidden:
+                if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            }
+        }
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+void AccessRestriction::ClearData()
+{
+    // iterate over mFabricEntries and call DeleteEntry for each entry
+    for (auto & fabricEntry : mFabricEntries)
+    {
+        for (size_t i = 0; i < fabricEntry.second.size(); i++)
+        {
+            DeleteEntry(i, fabricEntry.first);
+        }
+    }
+
+    sCommissioningEntries.clear();
+}
+
+} // namespace Access
+} // namespace chip
diff --git a/src/access/AccessRestriction.h b/src/access/AccessRestriction.h
new file mode 100644
index 00000000000000..34d1f27562767b
--- /dev/null
+++ b/src/access/AccessRestriction.h
@@ -0,0 +1,288 @@
+/*
+ *
+ *    Copyright (c) 2024 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 "RequestPath.h"
+#include "SubjectDescriptor.h"
+#include <algorithm>
+#include <app-common/zap-generated/cluster-objects.h>
+#include <cstdint>
+#include <lib/core/CHIPError.h>
+#include <lib/core/DataModelTypes.h>
+#include <lib/core/Optional.h>
+#include <lib/support/CHIPMem.h>
+#include <map>
+#include <memory>
+#include <protocols/interaction_model/Constants.h>
+#include <vector>
+
+namespace chip {
+namespace Access {
+
+template <typename T>
+using SharedPtr = chip::Platform::SharedPtr<T>;
+
+class AccessRestriction
+{
+public:
+    static constexpr size_t kNumberOfFabrics      = CHIP_CONFIG_MAX_FABRICS;
+    static constexpr size_t kEntriesPerFabric     = CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC;
+    static constexpr size_t kRestrictionsPerEntry = CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY;
+
+    /**
+     * Defines the type of access restriction, which is used to determine the meaning of the restriction's id.
+     */
+    enum class Type : uint8_t
+    {
+        kAttributeAccessForbidden = 0,
+        kAttributeWriteForbidden  = 1,
+        kCommandForbidden         = 2,
+        kEventForbidden           = 3
+    };
+
+    /**
+     * Defines a single restriction on an attribute, command, or event.
+     *
+     * If id is not set, the restriction applies to all attributes, commands, or events of the given type (wildcard).
+     */
+    struct Restriction
+    {
+        Type restrictionType;
+        Optional<uint32_t> id;
+    };
+
+    /**
+     * Defines a single entry in the access restriction list, which contains a list of restrictions
+     * for a cluster on an endpoint.
+     */
+    struct Entry
+    {
+        FabricIndex fabricIndex;
+        EndpointId endpointNumber;
+        ClusterId clusterId;
+        std::vector<Restriction> restrictions;
+
+        bool IsValid() const
+        {
+            return endpointNumber != 0 && clusterId != app::Clusters::NetworkCommissioning::Id &&
+                clusterId != app::Clusters::Descriptor::Id;
+        }
+    };
+
+    /**
+     * Used to notify of changes in the access restriction list and active reviews.
+     */
+    class EntryListener
+    {
+    public:
+        enum class ChangeType
+        {
+            kAdded   = 1,
+            kRemoved = 2,
+            kUpdated = 3
+        };
+
+        virtual ~EntryListener() = default;
+
+        /**
+         * Notifies of a change in the access restriction list.
+         *
+         * @param [in] fabricIndex  The index of the fabric in which the entry has changed.
+         * @param [in] index        Index of entry to which has changed (relative to fabric).
+         * @param [in] entry        The latest value of the entry which changed.
+         * @param [in] changeType   The type of change that occurred.
+         */
+        virtual void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) = 0;
+
+        /**
+         * Notifies of an update to an active review with instructions and an optional redirect URL.
+         *
+         * @param [in] fabricIndex  The index of the fabric in which the entry has changed.
+         * @param [in] token        The token of the review being updated (obtained from ReviewFabricRestrictionsResponse)
+         * @param [in] instruction  The instructions to be displayed to the user.
+         * @param [in] redirectUrl  An optional URL to redirect the user to for more information.  May be null.
+         */
+        virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
+                                                     const char * redirectUrl) = 0;
+
+    private:
+        EntryListener * mNext = nullptr;
+
+        friend class AccessRestriction;
+    };
+
+    AccessRestriction()          = default;
+    virtual ~AccessRestriction() = default;
+
+    AccessRestriction(const AccessRestriction &)             = delete;
+    AccessRestriction & operator=(const AccessRestriction &) = delete;
+
+    /**
+     * Create restriction entries for a fabric by populating from the commissioning entries.
+     * This should be called when the device is commissioned to a new fabric.
+     *
+     * @param [in] fabricIndex  The index of the fabric for which to create entries.
+     */
+    CHIP_ERROR CreateFabricEntries(const FabricIndex fabricIndex);
+
+    /**
+     * Add a listener to be notified of changes in the access restriction list and active reviews.
+     *
+     * @param [in] listener  The listener to add.
+     */
+    void AddListener(EntryListener & listener);
+
+    /**
+     * Remove a listener from being notified of changes in the access restriction list and active reviews.
+     *
+     * @param [in] listener  The listener to remove.
+     */
+    void RemoveListener(EntryListener & listener);
+
+    /**
+     * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action.
+     * These restrictions are are only a part of overall access evaluation.
+     *
+     * If access is not restricted, CHIP_NO_ERROR will be returned.
+     *
+     * @retval #CHIP_ERROR_ACCESS_DENIED if access is denied.
+     * @retval other errors should also be treated as restricted/denied.
+     * @retval #CHIP_NO_ERROR if access is not restricted/denied.
+     */
+    CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath);
+
+    /**
+     * Request a review of the access restrictions for a fabric.
+     *
+     * @param [in]  fabricIndex  The index of the fabric requesting a review.
+     * @param [in]  arl          An optinal list of access restriction entries to review.  If null, all entries will be reviewed.
+     * @param [out] token        The unique token for the review, which can be matched to a review update event.
+     */
+    CHIP_ERROR RequestFabricRestrictionReview(FabricIndex fabricIndex, const std::vector<Entry> & arl, uint64_t & token)
+    {
+        token = ++mNextToken;
+        return DoRequestFabricRestrictionReview(fabricIndex, token, arl);
+    }
+
+    using EntryIterator = std::vector<SharedPtr<Entry>>::iterator;
+
+    /**
+     * Get iterator over the commissioning entries, which are used during commissioning
+     * and also installed as defaults for a fabric upon completion of commissioning.
+     *
+     * @param [out] begin iterator pointing to the beginning of the entries
+     * @param [out] end iterator pointing to the end of the entries
+     */
+    static CHIP_ERROR CommissioningEntries(EntryIterator & begin, EntryIterator & end)
+    {
+        begin = sCommissioningEntries.begin();
+        end   = sCommissioningEntries.end();
+
+        return CHIP_NO_ERROR;
+    }
+
+    /**
+     * Get iterator over entries in the access restriction list by fabric.
+     *
+     * @param [in]  fabricIndex Iteration is confined to fabric
+     * @param [out] begin iterator pointing to the beginning of the entries
+     * @param [out] end iterator pointing to the end of the entries
+     */
+    CHIP_ERROR Entries(const FabricIndex fabricIndex, EntryIterator & begin, EntryIterator & end)
+    {
+        if (mFabricEntries.find(fabricIndex) == mFabricEntries.end())
+        {
+            return CHIP_ERROR_NOT_FOUND;
+        }
+
+        begin = mFabricEntries[fabricIndex].begin();
+        end   = mFabricEntries[fabricIndex].end();
+
+        return CHIP_NO_ERROR;
+    }
+
+    /**
+     * Add a restriction entry to the commissioning access restriction list. This list is automatically
+     * applied to fabrics upon completion of commissioning. This request will fail if there is already
+     * an entry with the same cluster and endpoint.
+     *
+     * @param [in]  entry       The entry to add to the commissioning access restriction list.
+     * @return                  CHIP_NO_ERROR if the entry was successfully created, or an error code.
+     */
+    static CHIP_ERROR CreateCommissioningEntry(SharedPtr<Entry> entry);
+
+    /**
+     * Add a restriction entry to the access restriction list and notify any listeners. This request
+     * will fail if there is already an entry with the same cluster and endpoint.
+     *
+     * @param [out] index       (If not nullptr) index of created entry (relative to fabric).
+     * @param [in]  entry       The entry to add to the access restriction list.
+     * @param [in]  fabricIndex The index of the fabric in which the entry should be added.
+     * @return                  CHIP_NO_ERROR if the entry was successfully created, or an error code.
+     */
+    CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, const FabricIndex fabricIndex);
+
+    /**
+     * Delete a restriction entry from the access restriction list and notify any listeners.
+     *
+     * @param [in] index           Index of entry to delete.  May be relative to fabric.
+     * @param [in] fabricIndex     Fabric to which entry `index` is relative.
+     * @return                     CHIP_NO_ERROR if the entry was successfully deleted, or an error code.
+     */
+    CHIP_ERROR DeleteEntry(size_t index, const FabricIndex fabricIndex);
+
+    /**
+     * Update a restriction entry in the access restriction list and notify any listeners.
+     *
+     * @param [in] index           Index of entry to delete.  May be relative to fabric.
+     * @param [in] entry        The updated entry to replace the existing entry.
+     * @param [in] fabricIndex  The index of the fabric in which the entry should be updated.
+     * @return                  CHIP_NO_ERROR if the entry was successfully updated, or an error code.
+     */
+    CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex);
+
+protected:
+    /**
+     * Initiate a review of the access restrictions for a fabric. This method should be implemented by the platform and be
+     * non-blocking.
+     *
+     * @param [in] fabricIndex  The index of the fabric requesting a review.
+     * @param [in] token        The unique token for the review, which can be matched to a review update event.
+     * @param [in] arl          An optinal list of access restriction entries to review.  If null, all entries will be reviewed.
+     * @return                  CHIP_NO_ERROR if the review was successfully requested, or an error code if the request failed.
+     */
+    virtual CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token,
+                                                        const std::vector<Entry> & arl) = 0;
+
+    /**
+     * Clear all access restriction data.
+     */
+    void ClearData();
+
+private:
+    SharedPtr<Entry> GetEntry(FabricIndex fabricIndex, size_t index);
+
+    static std::vector<Platform::SharedPtr<Entry>> sCommissioningEntries;
+    uint64_t mNextToken        = 1;
+    EntryListener * mListeners = nullptr;
+    std::map<FabricIndex, std::vector<Platform::SharedPtr<Entry>>> mFabricEntries;
+};
+
+} // namespace Access
+} // namespace chip
diff --git a/src/access/BUILD.gn b/src/access/BUILD.gn
index 8d2c4b504975ee..ae2a64d70906f8 100644
--- a/src/access/BUILD.gn
+++ b/src/access/BUILD.gn
@@ -13,6 +13,25 @@
 # limitations under the License.
 
 import("//build_overrides/chip.gni")
+import("${chip_root}/build/chip/buildconfig_header.gni")
+import("${chip_root}/src/access/access.gni")
+
+buildconfig_header("access_buildconfig") {
+  header = "AccessBuildConfig.h"
+  header_dir = "access"
+
+  defines = [
+    "CHIP_CONFIG_USE_ACCESS_RESTRICTIONS=${chip_enable_access_restrictions}",
+  ]
+
+  visibility = [ ":access_config" ]
+}
+
+source_set("access_config") {
+  sources = [ "AccessConfig.h" ]
+
+  deps = [ ":access_buildconfig" ]
+}
 
 source_set("types") {
   sources = [
@@ -23,6 +42,7 @@ source_set("types") {
   ]
 
   public_deps = [
+    ":access_config",
     "${chip_root}/src/lib/core",
     "${chip_root}/src/lib/core:types",
   ]
@@ -43,10 +63,18 @@ static_library("access") {
   cflags = [ "-Wconversion" ]
 
   public_deps = [
+    ":access_config",
     ":types",
     "${chip_root}/src/lib/core",
     "${chip_root}/src/lib/core:types",
     "${chip_root}/src/lib/support",
     "${chip_root}/src/platform",
   ]
+
+  if (chip_enable_access_restrictions) {
+    sources += [
+      "AccessRestriction.cpp",
+      "AccessRestriction.h",
+    ]
+  }
 }
diff --git a/src/access/access.gni b/src/access/access.gni
new file mode 100644
index 00000000000000..bd18f1387b66b9
--- /dev/null
+++ b/src/access/access.gni
@@ -0,0 +1,18 @@
+# Copyright (c) 2024 Project CHIP Authors
+#
+# 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.
+
+declare_args() {
+  # Enable ARL features of Access Control
+  chip_enable_access_restrictions = false
+}
diff --git a/src/access/tests/BUILD.gn b/src/access/tests/BUILD.gn
index d8b43e6a17a01d..c790ad4066be73 100644
--- a/src/access/tests/BUILD.gn
+++ b/src/access/tests/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/build.gni")
 import("//build_overrides/chip.gni")
 import("//build_overrides/pigweed.gni")
+import("${chip_root}/src/access/access.gni")
 
 import("${chip_root}/build/chip/chip_test_suite.gni")
 
@@ -29,4 +30,8 @@ chip_test_suite("tests") {
     "${chip_root}/src/lib/support:test_utils",
     "${dir_pw_unit_test}",
   ]
+
+  if (chip_enable_access_restrictions) {
+    test_sources += [ "TestAccessRestriction.cpp" ]
+  }
 }
diff --git a/src/access/tests/TestAccessRestriction.cpp b/src/access/tests/TestAccessRestriction.cpp
new file mode 100644
index 00000000000000..3a3db93be3f640
--- /dev/null
+++ b/src/access/tests/TestAccessRestriction.cpp
@@ -0,0 +1,463 @@
+/*
+ *
+ *    Copyright (c) 2024 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 "access/AccessControl.h"
+#include "access/AccessRestriction.h"
+#include "access/examples/ExampleAccessControlDelegate.h"
+
+#include <pw_unit_test/framework.h>
+
+#include <lib/core/CHIPCore.h>
+#include <lib/core/StringBuilderAdapters.h>
+
+namespace chip {
+namespace Access {
+
+class TestAccessRestrictionImpl : public AccessRestriction
+{
+    CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl)
+    {
+        return CHIP_NO_ERROR;
+    }
+
+public:
+    void Clear() { ClearData(); }
+};
+
+AccessControl accessControl;
+TestAccessRestrictionImpl accessRestriction;
+
+constexpr ClusterId kNetworkCommissioningCluster = 0x0000'0031; // must not be blocked by access restrictions on any endpoint
+constexpr ClusterId kDescriptorCluster           = 0x0000'001d; // must not be blocked by access restrictions on any endpoint
+constexpr ClusterId kOnOffCluster                = 0x0000'0006;
+
+constexpr NodeId kOperationalNodeId1 = 0x1111111111111111;
+constexpr NodeId kOperationalNodeId2 = 0x2222222222222222;
+constexpr NodeId kOperationalNodeId3 = 0x3333333333333333;
+
+struct AclEntryData
+{
+    FabricIndex fabricIndex = kUndefinedFabricIndex;
+    Privilege privilege     = Privilege::kView;
+    AuthMode authMode       = AuthMode::kNone;
+    NodeId subject;
+};
+
+constexpr AclEntryData aclEntryData[] = {
+    {
+        .fabricIndex = 1,
+        .privilege   = Privilege::kAdminister,
+        .authMode    = AuthMode::kCase,
+        .subject     = kOperationalNodeId1,
+    },
+    {
+        .fabricIndex = 2,
+        .privilege   = Privilege::kAdminister,
+        .authMode    = AuthMode::kCase,
+        .subject     = kOperationalNodeId2,
+    },
+};
+constexpr size_t aclEntryDataCount = ArraySize(aclEntryData);
+
+struct CheckData
+{
+    SubjectDescriptor subjectDescriptor;
+    RequestPath requestPath;
+    Privilege privilege;
+    bool allow;
+};
+
+constexpr CheckData checkDataNoRestrictions[] = {
+    // Checks for implicit PASE
+    { .subjectDescriptor = { .fabricIndex = 0, .authMode = AuthMode::kPase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 0, .authMode = AuthMode::kPase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subject = kOperationalNodeId2 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subject = kOperationalNodeId3 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    // Checks for entry 0
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    // Checks for entry 1
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+};
+
+CHIP_ERROR LoadEntry(AccessControl::Entry & entry, const AclEntryData & entryData)
+{
+    ReturnErrorOnFailure(entry.SetAuthMode(entryData.authMode));
+    ReturnErrorOnFailure(entry.SetFabricIndex(entryData.fabricIndex));
+    ReturnErrorOnFailure(entry.SetPrivilege(entryData.privilege));
+    ReturnErrorOnFailure(entry.AddSubject(nullptr, entryData.subject));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LoadAccessControl(AccessControl & ac, const AclEntryData * entryData, size_t count)
+{
+    AccessControl::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;
+}
+
+void RunChecks(const CheckData * checkData, size_t count)
+{
+    for (size_t i = 0; i < count; i++)
+    {
+        CHIP_ERROR expectedResult = checkData[i].allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED;
+        EXPECT_EQ(accessControl.Check(checkData[i].subjectDescriptor, checkData[i].requestPath, checkData[i].privilege),
+                  expectedResult);
+    }
+}
+
+class DeviceTypeResolver : public AccessControl::DeviceTypeResolver
+{
+public:
+    bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return false; }
+} testDeviceTypeResolver;
+
+class TestAccessRestriction : public ::testing::Test
+{
+public: // protected
+    void SetUp() override { accessRestriction.Clear(); }
+    static void SetUpTestSuite()
+    {
+        ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
+        AccessControl::Delegate * delegate = Examples::GetAccessControlDelegate();
+        SetAccessControl(accessControl);
+        GetAccessControl().SetAccessRestriction(&accessRestriction);
+        VerifyOrDie(GetAccessControl().Init(delegate, testDeviceTypeResolver) == CHIP_NO_ERROR);
+        EXPECT_EQ(LoadAccessControl(accessControl, aclEntryData, aclEntryDataCount), CHIP_NO_ERROR);
+    }
+    static void TearDownTestSuite()
+    {
+        GetAccessControl().Finish();
+        ResetAccessControlToDefault();
+    }
+};
+
+// basic data check without restrictions
+TEST_F(TestAccessRestriction, MetaTest)
+{
+    for (const auto & checkData : checkDataNoRestrictions)
+    {
+        CHIP_ERROR expectedResult = checkData.allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED;
+        EXPECT_EQ(accessControl.Check(checkData.subjectDescriptor, checkData.requestPath, checkData.privilege), expectedResult);
+    }
+}
+
+// ensure adding restrictons on endpoint 0 (any cluster) or for network commissioning and descriptor clusters fail
+TEST_F(TestAccessRestriction, InvalidRestrictionsTest)
+{
+    AccessRestriction::Entry entry;
+    entry.fabricIndex = 1;
+    entry.clusterId   = kOnOffCluster;
+    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden });
+
+    // must not restrict endpoint 0
+    entry.endpointNumber = 0;
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT);
+
+    // must not restrict network commissioning cluster
+    entry.endpointNumber = 1;
+    entry.clusterId      = kNetworkCommissioningCluster;
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT);
+
+    // must not restrict descriptor cluster
+    entry.clusterId = kDescriptorCluster;
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT);
+}
+
+constexpr CheckData accessAttributeRestrictionTestData[] = {
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster     = kOnOffCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+};
+
+TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
+{
+    AccessRestriction::Entry entry;
+    entry.fabricIndex    = 1;
+    entry.endpointNumber = 1;
+    entry.clusterId      = kOnOffCluster;
+    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden });
+
+    // test wildcarded entity id
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData));
+
+    // test specific entity id
+    accessRestriction.Clear();
+    entry.restrictions[0].id.SetValue(1);
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData));
+}
+
+constexpr CheckData writeAttributeRestrictionTestData[] = {
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster     = kOnOffCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+};
+
+TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
+{
+    AccessRestriction::Entry entry;
+    entry.fabricIndex    = 1;
+    entry.endpointNumber = 1;
+    entry.clusterId      = kOnOffCluster;
+    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden });
+
+    // test wildcarded entity id
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData));
+
+    // test specific entity id
+    accessRestriction.Clear();
+    entry.restrictions[0].id.SetValue(1);
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData));
+}
+
+constexpr CheckData commandAttributeRestrictionTestData[] = {
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster     = kOnOffCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+};
+
+TEST_F(TestAccessRestriction, CommandRestrictionTest)
+{
+    AccessRestriction::Entry entry;
+    entry.fabricIndex    = 1;
+    entry.endpointNumber = 1;
+    entry.clusterId      = kOnOffCluster;
+    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden });
+
+    // test wildcarded entity id
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData));
+
+    // test specific entity id
+    accessRestriction.Clear();
+    entry.restrictions[0].id.SetValue(1);
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData));
+}
+
+constexpr CheckData eventAttributeRestrictionTestData[] = {
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster     = kOnOffCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
+};
+
+TEST_F(TestAccessRestriction, EventRestrictionTest)
+{
+    AccessRestriction::Entry entry;
+    entry.fabricIndex    = 1;
+    entry.endpointNumber = 1;
+    entry.clusterId      = kOnOffCluster;
+    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kEventForbidden });
+
+    // test wildcarded entity id
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData));
+
+    // test specific entity id
+    accessRestriction.Clear();
+    entry.restrictions[0].id.SetValue(1);
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData));
+}
+
+constexpr CheckData combinedRestrictionTestData[] = {
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
+      .requestPath       = { .cluster     = kOnOffCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .entityId    = 5 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+};
+
+TEST_F(TestAccessRestriction, CombinedRestrictionTest)
+{
+    // a restriction for all access to attribute 1 and 2, attributes 3 and 4 are allowed
+    AccessRestriction::Entry entry1;
+    entry1.fabricIndex    = 1;
+    entry1.endpointNumber = 1;
+    entry1.clusterId      = kOnOffCluster;
+    entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden });
+    entry1.restrictions[0].id.SetValue(1);
+    entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden });
+    entry1.restrictions[1].id.SetValue(2);
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry1, 1), CHIP_NO_ERROR);
+
+    // a restriction for fabric 2 that forbids command 1 and 2.  Check that command 1 is blocked on invoke, but attribute 2 write is
+    // allowed
+    AccessRestriction::Entry entry2;
+    entry2.fabricIndex    = 2;
+    entry2.endpointNumber = 1;
+    entry2.clusterId      = kOnOffCluster;
+    entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden });
+    entry2.restrictions[0].id.SetValue(1);
+    entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden });
+    entry2.restrictions[1].id.SetValue(2);
+    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry2, 2), CHIP_NO_ERROR);
+
+    RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData));
+}
+
+} // namespace Access
+} // namespace chip
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 321f7aa92a483a..c8eda434079168 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -17,6 +17,11 @@
 
 #include <access/AccessControl.h>
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+#include <access/AccessRestriction.h>
+#include <app/server/ArlStorage.h>
+#endif
+
 #include <app-common/zap-generated/cluster-objects.h>
 
 #include <app/AttributeAccessInterface.h>
@@ -41,6 +46,15 @@ using Entry          = AccessControl::Entry;
 using EntryListener  = AccessControl::EntryListener;
 using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type;
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+using ArlChangedEvent = Clusters::AccessControl::Events::AccessRestrictionEntryChanged::Type;
+using ArlReviewEvent  = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type;
+
+constexpr uint16_t kMaxInstructionStringLength = 512;
+
+constexpr uint16_t kMaxRedirectUrlStringLength = 256;
+#endif
+
 // TODO(#13590): generated code doesn't automatically handle max length so do it manually
 constexpr int kExtensionDataMaxLength = 128;
 
@@ -48,7 +62,12 @@ constexpr uint16_t kClusterRevision = 1;
 
 namespace {
 
-class AccessControlAttribute : public AttributeAccessInterface, public EntryListener
+class AccessControlAttribute : public AttributeAccessInterface,
+                               public AccessControl::EntryListener
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    ,
+                               public AccessRestriction::EntryListener
+#endif
 {
 public:
     AccessControlAttribute() : AttributeAccessInterface(Optional<EndpointId>(0), AccessControlCluster::Id) {}
@@ -64,8 +83,16 @@ class AccessControlAttribute : public AttributeAccessInterface, public EntryList
     CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override;
 
 public:
-    void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry,
-                        ChangeType changeType) override;
+    void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index,
+                        const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override;
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    void OnEntryChanged(FabricIndex fabricIndex, size_t index, Platform::SharedPtr<AccessRestriction::Entry> entry,
+                        AccessRestriction::EntryListener::ChangeType changeType) override;
+
+    void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
+                                         const char * redirectUrl) override;
+#endif
 
 private:
     /// Business logic implementation of write, returns generic CHIP_ERROR.
@@ -78,6 +105,10 @@ class AccessControlAttribute : public AttributeAccessInterface, public EntryList
     CHIP_ERROR ReadExtension(AttributeValueEncoder & aEncoder);
     CHIP_ERROR WriteAcl(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder);
     CHIP_ERROR WriteExtension(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder);
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    CHIP_ERROR ReadCommissioningArl(AttributeValueEncoder & aEncoder);
+    CHIP_ERROR ReadArl(AttributeValueEncoder & aEncoder);
+#endif
 } sAttribute;
 
 CHIP_ERROR LogExtensionChangedEvent(const AccessControlCluster::Structs::AccessControlExtensionStruct::Type & item,
@@ -159,6 +190,12 @@ CHIP_ERROR AccessControlAttribute::ReadImpl(const ConcreteReadAttributePath & aP
         ReturnErrorOnFailure(GetAccessControl().GetMaxEntriesPerFabric(value));
         return aEncoder.Encode(static_cast<uint16_t>(value));
     }
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    case AccessControlCluster::Attributes::CommissioningARL::Id:
+        return ReadCommissioningArl(aEncoder);
+    case AccessControlCluster::Attributes::Arl::Id:
+        return ReadArl(aEncoder);
+#endif
     case AccessControlCluster::Attributes::ClusterRevision::Id:
         return aEncoder.Encode(kClusterRevision);
     }
@@ -375,7 +412,7 @@ CHIP_ERROR AccessControlAttribute::WriteExtension(const ConcreteDataAttributePat
 }
 
 void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index,
-                                            const Entry * entry, ChangeType changeType)
+                                            const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType)
 {
     // NOTE: If the entry was changed internally by the system (e.g. creating
     // entries at startup from persistent storage, or deleting entries when a
@@ -389,11 +426,11 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes
     CHIP_ERROR err;
     AclEvent event{ .changeType = ChangeTypeEnum::kChanged, .fabricIndex = subjectDescriptor->fabricIndex };
 
-    if (changeType == ChangeType::kAdded)
+    if (changeType == AccessControl::EntryListener::ChangeType::kAdded)
     {
         event.changeType = ChangeTypeEnum::kAdded;
     }
-    else if (changeType == ChangeType::kRemoved)
+    else if (changeType == AccessControl::EntryListener::ChangeType::kRemoved)
     {
         event.changeType = ChangeTypeEnum::kRemoved;
     }
@@ -428,6 +465,97 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes
     ChipLogError(DataManagement, "AccessControlCluster: event failed %" CHIP_ERROR_FORMAT, err.Format());
 }
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder)
+{
+    auto accessRestriction = GetAccessControl().GetAccessRestriction();
+    if (accessRestriction == nullptr)
+    {
+        return CHIP_ERROR_NOT_IMPLEMENTED;
+    }
+
+    return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
+        AccessRestriction::EntryIterator begin;
+        AccessRestriction::EntryIterator end;
+        ReturnErrorOnFailure(accessRestriction->CommissioningEntries(begin, end));
+
+        for (AccessRestriction::EntryIterator it = begin; it != end; ++it)
+        {
+            ArlStorage::EncodableEntry encodableEntry(*it);
+            ReturnErrorOnFailure(encoder.Encode(encodableEntry));
+        }
+        return CHIP_NO_ERROR;
+    });
+}
+
+CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
+{
+    auto accessRestriction = GetAccessControl().GetAccessRestriction();
+    if (accessRestriction == nullptr)
+    {
+        return CHIP_ERROR_NOT_IMPLEMENTED;
+    }
+
+    return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
+        for (auto & info : Server::GetInstance().GetFabricTable())
+        {
+            auto fabric = info.GetFabricIndex();
+            AccessRestriction::EntryIterator begin;
+            AccessRestriction::EntryIterator end;
+            ReturnErrorOnFailure(accessRestriction->Entries(fabric, begin, end));
+
+            for (AccessRestriction::EntryIterator it = begin; it != end; ++it)
+            {
+                ArlStorage::EncodableEntry encodableEntry(*it);
+                ReturnErrorOnFailure(encoder.Encode(encodableEntry));
+            }
+        }
+        return CHIP_NO_ERROR;
+    });
+}
+
+void AccessControlAttribute::OnEntryChanged(FabricIndex fabricIndex, size_t index,
+                                            Platform::SharedPtr<AccessRestriction::Entry> entry,
+                                            AccessRestriction::EntryListener::ChangeType changeType)
+{
+    CHIP_ERROR err;
+    ArlChangedEvent event{ .fabricIndex = fabricIndex };
+
+    EventNumber eventNumber;
+    SuccessOrExit(err = LogEvent(event, 0, eventNumber));
+
+    return;
+
+exit:
+    ChipLogError(DataManagement, "AccessControlCluster: restriction event failed %" CHIP_ERROR_FORMAT, err.Format());
+}
+
+void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
+                                                             const char * redirectUrl)
+{
+    CHIP_ERROR err;
+    ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex };
+
+    if (instruction != nullptr)
+    {
+        event.instruction.SetNonNull(chip::CharSpan(instruction, strnlen(instruction, kMaxInstructionStringLength)));
+    }
+
+    if (redirectUrl != nullptr)
+    {
+        event.redirectURL.SetNonNull(chip::CharSpan(redirectUrl, strnlen(redirectUrl, kMaxRedirectUrlStringLength)));
+    }
+
+    EventNumber eventNumber;
+    SuccessOrExit(err = LogEvent(event, 0, eventNumber));
+
+    return;
+
+exit:
+    ChipLogError(DataManagement, "AccessControlCluster: review event failed %" CHIP_ERROR_FORMAT, err.Format());
+}
+#endif
+
 CHIP_ERROR ChipErrorToImErrorMap(CHIP_ERROR err)
 {
     // Map some common errors into an underlying IM error
@@ -473,6 +601,16 @@ CHIP_ERROR AccessControlAttribute::Write(const ConcreteDataAttributePath & aPath
     return ChipErrorToImErrorMap(WriteImpl(aPath, aDecoder));
 }
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
+{
+    if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete)
+    {
+        Access::GetAccessControl().GetAccessRestriction()->CreateFabricEntries(event->CommissioningComplete.fabricIndex);
+    }
+}
+#endif
+
 } // namespace
 
 void MatterAccessControlPluginServerInitCallback()
@@ -481,4 +619,70 @@ void MatterAccessControlPluginServerInitCallback()
 
     AttributeAccessInterfaceRegistry::Instance().Register(&sAttribute);
     GetAccessControl().AddEntryListener(sAttribute);
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    auto accessRestriction = GetAccessControl().GetAccessRestriction();
+    if (accessRestriction != nullptr)
+    {
+        accessRestriction->AddListener(sAttribute);
+    }
+
+    DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler);
+#endif
+}
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
+    CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
+    const Clusters::AccessControl::Commands::ReviewFabricRestrictions::DecodableType & commandData)
+{
+    if (commandPath.mEndpointId != 0)
+    {
+        ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request");
+        return true;
+    }
+
+    uint64_t token;
+    std::vector<AccessRestriction::Entry> entries;
+    auto entryIter = commandData.arl.begin();
+    while (entryIter.Next())
+    {
+        AccessRestriction::Entry entry;
+        entry.fabricIndex    = commandObj->GetAccessingFabricIndex();
+        entry.endpointNumber = entryIter.GetValue().endpoint;
+        entry.clusterId      = entryIter.GetValue().cluster;
+
+        auto restrictionIter = entryIter.GetValue().restrictions.begin();
+        while (restrictionIter.Next())
+        {
+            AccessRestriction::Restriction restriction;
+            restriction.restrictionType = static_cast<AccessRestriction::Type>(restrictionIter.GetValue().type);
+            if (!restrictionIter.GetValue().id.IsNull())
+            {
+                restriction.id.SetValue(restrictionIter.GetValue().id.Value());
+            }
+            entry.restrictions.push_back(restriction);
+        }
+
+        entries.push_back(entry);
+    }
+
+    CHIP_ERROR err = GetAccessControl().GetAccessRestriction()->RequestFabricRestrictionReview(
+        commandObj->GetAccessingFabricIndex(), entries, token);
+
+    if (err == CHIP_NO_ERROR)
+    {
+        Clusters::AccessControl::Commands::ReviewFabricRestrictionsResponse::Type response;
+        response.token = token;
+        commandObj->AddResponse(commandPath, response);
+    }
+    else
+    {
+        ChipLogError(DataManagement, "AccessControlCluster: restriction check failed: %" CHIP_ERROR_FORMAT, err.Format());
+
+        // return error to client?
+    }
+
+    return true;
 }
+#endif
diff --git a/src/app/server/ArlStorage.cpp b/src/app/server/ArlStorage.cpp
new file mode 100644
index 00000000000000..983f9a16ad8cd6
--- /dev/null
+++ b/src/app/server/ArlStorage.cpp
@@ -0,0 +1,158 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    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 <app/server/ArlStorage.h>
+
+#include <lib/support/DefaultStorageKeyAllocator.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::Access;
+
+using Entry                  = AccessRestriction::Entry;
+using EntryListener          = AccessRestriction::EntryListener;
+using StagingRestrictionType = Clusters::AccessControl::AccessRestrictionTypeEnum;
+using StagingRestriction     = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type;
+
+namespace {
+
+CHIP_ERROR Convert(StagingRestrictionType from, AccessRestriction::Type & to)
+{
+    switch (from)
+    {
+    case StagingRestrictionType::kAttributeAccessForbidden:
+        to = AccessRestriction::Type::kAttributeAccessForbidden;
+        break;
+    case StagingRestrictionType::kAttributeWriteForbidden:
+        to = AccessRestriction::Type::kAttributeWriteForbidden;
+        break;
+    case StagingRestrictionType::kCommandForbidden:
+        to = AccessRestriction::Type::kCommandForbidden;
+        break;
+    case StagingRestrictionType::kEventForbidden:
+        to = AccessRestriction::Type::kEventForbidden;
+        break;
+    default:
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR Convert(AccessRestriction::Type from, StagingRestrictionType & to)
+{
+    switch (from)
+    {
+    case AccessRestriction::Type::kAttributeAccessForbidden:
+        to = StagingRestrictionType::kAttributeAccessForbidden;
+        break;
+    case AccessRestriction::Type::kAttributeWriteForbidden:
+        to = StagingRestrictionType::kAttributeWriteForbidden;
+        break;
+    case AccessRestriction::Type::kCommandForbidden:
+        to = StagingRestrictionType::kCommandForbidden;
+        break;
+    case AccessRestriction::Type::kEventForbidden:
+        to = StagingRestrictionType::kEventForbidden;
+        break;
+    default:
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+    return CHIP_NO_ERROR;
+}
+
+} // namespace
+
+namespace chip {
+namespace app {
+
+CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader)
+{
+    ReturnErrorOnFailure(mStagingEntry.Decode(reader));
+
+    mEntry.fabricIndex    = mStagingEntry.GetFabricIndex();
+    mEntry.endpointNumber = mStagingEntry.endpoint;
+    mEntry.clusterId      = mStagingEntry.cluster;
+
+    auto iterator = mStagingEntry.restrictions.begin();
+    while (iterator.Next())
+    {
+        auto & tmp = iterator.GetValue();
+        AccessRestriction::Restriction restriction;
+        ReturnErrorOnFailure(Convert(tmp.type, restriction.restrictionType));
+
+        if (!tmp.id.IsNull())
+        {
+            restriction.id.SetValue(tmp.id.Value());
+        }
+
+        mEntry.restrictions.push_back(restriction);
+    }
+    ReturnErrorOnFailure(iterator.GetStatus());
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ArlStorage::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const
+{
+    ReturnErrorOnFailure(Stage());
+    ReturnErrorOnFailure(mStagingEntry.EncodeForRead(writer, tag, fabric));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const
+{
+    ReturnErrorOnFailure(Stage());
+    ReturnErrorOnFailure(mStagingEntry.EncodeForWrite(writer, tag));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ArlStorage::EncodableEntry::Stage() const
+{
+    mStagingEntry.fabricIndex = mEntry->fabricIndex;
+    mStagingEntry.endpoint    = mEntry->endpointNumber;
+    mStagingEntry.cluster     = mEntry->clusterId;
+
+    {
+        size_t count = mEntry->restrictions.size();
+        if (count > 0 && count <= CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY)
+        {
+            for (size_t i = 0; i < count; i++)
+            {
+                auto restriction = mEntry->restrictions[i];
+                StagingRestriction tmp;
+                ReturnErrorOnFailure(Convert(restriction.restrictionType, tmp.type));
+
+                if (restriction.id.HasValue())
+                {
+                    tmp.id.SetNonNull(restriction.id.Value());
+                }
+
+                mStagingRestrictions[i] = tmp;
+            }
+            mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, count);
+        }
+        else
+        {
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/server/ArlStorage.h b/src/app/server/ArlStorage.h
new file mode 100644
index 00000000000000..34fb616d226e4f
--- /dev/null
+++ b/src/app/server/ArlStorage.h
@@ -0,0 +1,148 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    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/AccessRestriction.h>
+#include <credentials/FabricTable.h>
+#include <lib/core/CHIPPersistentStorageDelegate.h>
+
+#include <app-common/zap-generated/cluster-objects.h>
+
+namespace chip {
+namespace app {
+
+/**
+ * Storage specifically for access restriction entries, which correspond to
+ * the ARL attribute of the access control cluster.
+ *
+ * An object of this class should be initialized directly after the access
+ * control module is initialized, as it will populate entries in the system
+ * module from storage, and also install a listener in the system module to
+ * keep storage up to date as entries change.
+ *
+ * This class also provides facilities for converting between access restriction
+ * entries (as used by the system module) and access restriction entries (as used
+ * by the generated cluster code).
+ */
+class ArlStorage
+{
+public:
+    /**
+     * Used for decoding access restriction entries.
+     *
+     * Typically used temporarily on the stack to decode:
+     * - source: TLV
+     * - staging: generated cluster level code
+     * - destination: system level access restriction entry
+     */
+    class DecodableEntry
+    {
+        using Entry        = Access::AccessRestriction::Entry;
+        using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::DecodableType;
+
+    public:
+        DecodableEntry() = default;
+
+        /**
+         * Reader decodes into a staging entry, which is then unstaged
+         * into a member entry.
+         */
+        CHIP_ERROR Decode(TLV::TLVReader & reader);
+
+        Entry & GetEntry() { return mEntry; }
+
+        const Entry & GetEntry() const { return mEntry; }
+
+    public:
+        static constexpr bool kIsFabricScoped = true;
+
+        void SetFabricIndex(FabricIndex fabricIndex) { mEntry.fabricIndex = fabricIndex; }
+
+    private:
+        Entry mEntry;
+
+        StagingEntry mStagingEntry;
+    };
+
+    /**
+     * Used for encoding access restriction entries.
+     *
+     * Typically used temporarily on the stack to encode:
+     * - source: system level access restriction entry
+     * - staging: generated cluster level code
+     * - destination: TLV
+     */
+    class EncodableEntry
+    {
+        using Entry              = Access::AccessRestriction::Entry;
+        using StagingEntry       = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type;
+        using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type;
+
+    public:
+        EncodableEntry(std::shared_ptr<Entry> entry) : mEntry(entry) {}
+
+        /**
+         * Constructor-provided entry is staged into a staging entry,
+         * which is then encoded into a writer.
+         */
+        CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const;
+
+        /**
+         * Constructor-provided entry is staged into a staging entry,
+         * which is then encoded into a writer.
+         */
+        CHIP_ERROR EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const;
+
+        /**
+         * Constructor-provided entry is staged into a staging entry.
+         */
+        CHIP_ERROR Stage() const;
+
+        StagingEntry & GetStagingEntry() { return mStagingEntry; }
+
+        const StagingEntry & GetStagingEntry() const { return mStagingEntry; }
+
+    public:
+        static constexpr bool kIsFabricScoped = true;
+
+        FabricIndex GetFabricIndex() const { return mEntry->fabricIndex; }
+
+    private:
+        std::shared_ptr<Entry> mEntry;
+
+        mutable StagingEntry mStagingEntry;
+        mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY];
+    };
+
+    virtual ~ArlStorage() = default;
+
+    /**
+     * Initialize should be called after chip::Access::AccessControl is initialized.
+     *
+     * Implementations should take this opportunity to populate AccessControl with ARL entries
+     * loaded from persistent storage. A half-open range of fabrics [first, last) is provided
+     * so this can be done on a per-fabric basis.
+     *
+     * Implementations should also install an entry change listener on AccessControl to maintain
+     * ARL entries in persistent storage as they are changed.
+     */
+    virtual CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) = 0;
+};
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn
index 401356d7b753a4..1a82cd911f0c51 100644
--- a/src/app/server/BUILD.gn
+++ b/src/app/server/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("//build_overrides/chip.gni")
+import("${chip_root}/src/access/access.gni")
 import("${chip_root}/src/app/common_flags.gni")
 import("${chip_root}/src/app/icd/icd.gni")
 
@@ -79,4 +80,13 @@ static_library("server") {
           [ "${chip_root}/src/app/icd/server:default-check-in-back-off" ]
     }
   }
+
+  if (chip_enable_access_restrictions) {
+    sources += [
+      "ArlStorage.cpp",
+      "ArlStorage.h",
+      "DefaultArlStorage.cpp",
+      "DefaultArlStorage.h",
+    ]
+  }
 }
diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp
new file mode 100644
index 00000000000000..b7ab1aff23e4f0
--- /dev/null
+++ b/src/app/server/DefaultArlStorage.cpp
@@ -0,0 +1,203 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    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 <app/server/DefaultArlStorage.h>
+
+#include <access/AccessControl.h>
+#include <lib/support/DefaultStorageKeyAllocator.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::Access;
+
+using EncodableEntry     = ArlStorage::EncodableEntry;
+using Entry              = AccessRestriction::Entry;
+using EntryListener      = AccessRestriction::EntryListener;
+using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type;
+using Restriction        = AccessRestriction::Restriction;
+
+namespace {
+
+/*
+Size calculation for TLV encoded entry.
+
+Because EncodeForWrite is used without an accessing fabric, the fabric index is
+not encoded. However, let's assume it is. This yields 17 bytes total overhead,
+but it's wise to add a few more for safety.
+
+Each restriction requires 11 bytes.
+
+DATA                                    C   T   L   V   NOTES
+structure (anonymous)                   1               0x15
+  field 0 endpoint                      1   1       1
+  field 1 cluster                       1   1       4
+  field 2 restrictions                  1   1
+    structure (anonymous)               1               per restriction
+      field 0 type                      1   1       1
+      field 1 id                        1   1       4
+    end structure                       1
+  end list                              1               0x18
+  field 254 fabric index                1   1       1   not written
+end structure                           1               0x18
+*/
+
+constexpr int kEncodedEntryOverheadBytes    = 17 + 8;
+constexpr int kEncodedEntryRestrictionBytes = 11 * CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY;
+constexpr int kEncodedEntryTotalBytes       = kEncodedEntryOverheadBytes + kEncodedEntryRestrictionBytes;
+
+class : public EntryListener
+{
+public:
+    void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) override
+    {
+        CHIP_ERROR err;
+
+        uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
+
+        VerifyOrExit(mPersistentStorage != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+
+        if (changeType == ChangeType::kRemoved)
+        {
+            // Shuffle down entries past index, then delete entry at last index.
+            while (true)
+            {
+                uint16_t size = static_cast<uint16_t>(sizeof(buffer));
+                err           = mPersistentStorage->SyncGetKeyValue(
+                    DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index + 1).KeyName(), buffer, size);
+                if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
+                {
+                    break;
+                }
+                SuccessOrExit(err);
+                SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
+                                  DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer, size));
+                index++;
+            }
+            SuccessOrExit(err = mPersistentStorage->SyncDeleteKeyValue(
+                              DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName()));
+        }
+        else
+        {
+            // Write added/updated entry at index.
+            VerifyOrExit(entry != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+            TLV::TLVWriter writer;
+            writer.Init(buffer);
+            EncodableEntry encodableEntry(entry);
+            SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag()));
+            SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
+                              DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer,
+                              static_cast<uint16_t>(writer.GetLengthWritten())));
+        }
+
+        return;
+
+    exit:
+        ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format());
+    }
+
+    void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
+                                         const char * redirectUrl) override
+    {}
+
+    // Must initialize before use.
+    void Init(PersistentStorageDelegate & persistentStorage) { mPersistentStorage = &persistentStorage; }
+
+private:
+    PersistentStorageDelegate * mPersistentStorage = nullptr;
+
+} sEntryListener;
+
+} // namespace
+
+namespace chip {
+namespace app {
+
+CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first,
+                                   ConstFabricIterator last)
+{
+    ChipLogProgress(DataManagement, "DefaultArlStorage: initializing");
+
+    CHIP_ERROR err;
+
+    [[maybe_unused]] size_t count = 0;
+
+    for (size_t index = 0; /**/; ++index)
+    {
+        uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
+        uint16_t size                           = static_cast<uint16_t>(sizeof(buffer));
+        err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName(),
+                                                buffer, size);
+        if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
+        {
+            break;
+        }
+        SuccessOrExit(err);
+
+        TLV::TLVReader reader;
+        reader.Init(buffer, size);
+        SuccessOrExit(err = reader.Next());
+
+        DecodableEntry decodableEntry;
+        SuccessOrExit(err = decodableEntry.Decode(reader));
+
+        SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateCommissioningEntry(
+                          Platform::MakeShared<Entry>(decodableEntry.GetEntry())));
+        count++;
+    }
+
+    for (auto it = first; it != last; ++it)
+    {
+        auto fabric = it->GetFabricIndex();
+        for (size_t index = 0; /**/; ++index)
+        {
+            uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
+            uint16_t size                           = static_cast<uint16_t>(sizeof(buffer));
+            err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlArlEntry(fabric, index).KeyName(),
+                                                    buffer, size);
+            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
+            {
+                break;
+            }
+            SuccessOrExit(err);
+
+            TLV::TLVReader reader;
+            reader.Init(buffer, size);
+            SuccessOrExit(err = reader.Next());
+
+            DecodableEntry decodableEntry;
+            SuccessOrExit(err = decodableEntry.Decode(reader));
+
+            Entry & entry = decodableEntry.GetEntry();
+            SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateEntry(nullptr, entry, fabric));
+            count++;
+        }
+    }
+
+    ChipLogProgress(DataManagement, "DefaultArlStorage: %u entries loaded", (unsigned) count);
+
+    sEntryListener.Init(persistentStorage);
+    GetAccessControl().GetAccessRestriction()->AddListener(sEntryListener);
+
+    return CHIP_NO_ERROR;
+
+exit:
+    ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format());
+    return err;
+}
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/server/DefaultArlStorage.h b/src/app/server/DefaultArlStorage.h
new file mode 100644
index 00000000000000..ebdc23bbf65913
--- /dev/null
+++ b/src/app/server/DefaultArlStorage.h
@@ -0,0 +1,37 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    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 <app/server/ArlStorage.h>
+
+namespace chip {
+namespace app {
+
+class DefaultArlStorage : public ArlStorage
+{
+public:
+    /**
+     * Initialize must be called. It loads ARL entries for all fabrics from persistent storage,
+     * then installs a listener for the access restriction system module to maintain ARL entries in
+     * persistent storage so they remain in sync with entries in the access restriction system module.
+     */
+    CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) override;
+};
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp
index 22cd274ba87e39..fbdae0ccb213f8 100644
--- a/src/app/server/Server.cpp
+++ b/src/app/server/Server.cpp
@@ -180,6 +180,17 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams)
     mAclStorage = initParams.aclStorage;
     SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    if (initParams.accessRestriction != nullptr && initParams.arlStorage != nullptr)
+    {
+        mAccessRestriction = initParams.accessRestriction;
+        mArlStorage        = initParams.arlStorage;
+
+        mAccessControl.SetAccessRestriction(mAccessRestriction);
+        SuccessOrExit(err = mArlStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
+    }
+#endif
+
     mGroupsProvider = initParams.groupDataProvider;
     SetGroupDataProvider(mGroupsProvider);
 
diff --git a/src/app/server/Server.h b/src/app/server/Server.h
index 2f6126a4ace635..b8f07e526f80d4 100644
--- a/src/app/server/Server.h
+++ b/src/app/server/Server.h
@@ -31,6 +31,9 @@
 #include <app/TestEventTriggerDelegate.h>
 #include <app/server/AclStorage.h>
 #include <app/server/AppDelegate.h>
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+#include <app/server/ArlStorage.h>
+#endif
 #include <app/server/CommissioningWindowManager.h>
 #include <app/server/DefaultAclStorage.h>
 #include <credentials/CertificateValidityPolicy.h>
@@ -163,6 +166,16 @@ struct ServerInitParams
     // ACL storage: MUST be injected. Used to store ACL entries in persistent storage. Must NOT
     // be initialized before being provided.
     app::AclStorage * aclStorage = nullptr;
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce
+    // access restrictions that are managed by the device.
+    Access::AccessRestriction * accessRestriction = nullptr;
+    // ARL storage: MUST be injected if MNGD feature enabled. Used to store ACL entries in
+    // persistent storage. Must NOT be initialized before being provided.
+    app::ArlStorage * arlStorage = nullptr;
+#endif
+
     // Network native params can be injected depending on the
     // selected Endpoint implementation
     void * endpointNativeParams = nullptr;
@@ -679,6 +692,11 @@ class Server
     Access::AccessControl mAccessControl;
     app::AclStorage * mAclStorage;
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    Access::AccessRestriction * mAccessRestriction;
+    app::ArlStorage * mArlStorage;
+#endif
+
     TestEventTriggerDelegate * mTestEventTriggerDelegate;
     Crypto::OperationalKeystore * mOperationalKeystore;
     Credentials::OperationalCertificateStore * mOpCertStore;
diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h
index e9c317dfc79d5c..a811f8b6012394 100644
--- a/src/lib/core/CHIPConfig.h
+++ b/src/lib/core/CHIPConfig.h
@@ -1206,6 +1206,27 @@ extern const char CHIP_NON_PRODUCTION_MARKER[];
     "Please enable at least one of CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT or CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT"
 #endif
 
+/**
+ * @def CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC
+ *
+ * Defines the maximum number of access restriction list entries per
+ * fabric in the access control code's ARL attribute.
+ */
+#ifndef CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC
+#define CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC 10
+#endif
+
+/**
+ * @def CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY
+ *
+ * Defines the maximum number of access restrictions for each entry
+ * in the ARL attribute (each entry is for a specific cluster on an
+ * endpoint on a fabric).
+ */
+#ifndef CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY
+#define CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY 10
+#endif
+
 /**
  * @def CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE
  *
diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h
index 9ed8a2f56cfd77..dce9975cd35f2b 100644
--- a/src/lib/support/DefaultStorageKeyAllocator.h
+++ b/src/lib/support/DefaultStorageKeyAllocator.h
@@ -130,6 +130,16 @@ class DefaultStorageKeyAllocator
 
     static StorageKeyName AccessControlExtensionEntry(FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/ac/1", fabric); }
 
+    static StorageKeyName AccessControlCommissioningArlEntry(size_t index)
+    {
+        return StorageKeyName::Formatted("g/car/0/%x", static_cast<unsigned>(index));
+    }
+
+    static StorageKeyName AccessControlArlEntry(FabricIndex fabric, size_t index)
+    {
+        return StorageKeyName::Formatted("f/%x/ar/0/%x", fabric, static_cast<unsigned>(index));
+    }
+
     // Group Message Counters
     static StorageKeyName GroupDataCounter() { return StorageKeyName::FromConst("g/gdc"); }
     static StorageKeyName GroupControlCounter() { return StorageKeyName::FromConst("g/gcc"); }

From 20f0b6a9f729c057239f136d7cc6831044b3ca8c Mon Sep 17 00:00:00 2001
From: Thomas Lea <35579828+tleacmcsa@users.noreply.github.com>
Date: Mon, 19 Aug 2024 09:50:36 -0500
Subject: [PATCH 02/22] Update src/access/AccessConfig.h

Co-authored-by: C Freeman <cecille@google.com>
---
 src/access/AccessConfig.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/access/AccessConfig.h b/src/access/AccessConfig.h
index c4cb6f51f3114d..b9318a10d9e4d5 100644
--- a/src/access/AccessConfig.h
+++ b/src/access/AccessConfig.h
@@ -1,6 +1,6 @@
 /*
  *
- *    Copyright (c) 2020-2024 Project CHIP Authors
+ *    Copyright (c) 2024 Project CHIP Authors
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
  *    you may not use this file except in compliance with the License.

From 04651f9d6d08202d77eedb221b6b212f3fc51201 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Tue, 20 Aug 2024 22:22:18 +0000
Subject: [PATCH 03/22] Reworked data manipulators and other cleanup

---
 examples/network-manager-app/linux/args.gni   |   2 +
 examples/platform/linux/AppMain.cpp           |  14 +-
 ...n.h => ExampleAccessRestrictionProvider.h} |  11 +-
 examples/platform/linux/Options.cpp           |  17 +-
 examples/platform/linux/Options.h             |   4 +-
 src/access/AccessControl.cpp                  |   6 +-
 src/access/AccessControl.h                    |  13 +-
 src/access/AccessRestriction.cpp              | 280 ------------------
 src/access/AccessRestrictionProvider.cpp      | 193 ++++++++++++
 ...triction.h => AccessRestrictionProvider.h} | 151 +++-------
 src/access/BUILD.gn                           |   4 +-
 src/access/tests/BUILD.gn                     |   2 +-
 ....cpp => TestAccessRestrictionProvider.cpp} | 108 ++++---
 .../access-control-server.cpp                 |  77 +++--
 src/app/server/ArlStorage.cpp                 |  36 +--
 src/app/server/ArlStorage.h                   |  12 +-
 src/app/server/DefaultArlStorage.cpp          |  98 +++---
 src/app/server/Server.cpp                     |   8 +-
 src/app/server/Server.h                       |   4 +-
 19 files changed, 469 insertions(+), 571 deletions(-)
 rename examples/platform/linux/{ExampleAccessRestriction.h => ExampleAccessRestrictionProvider.h} (80%)
 delete mode 100644 src/access/AccessRestriction.cpp
 create mode 100644 src/access/AccessRestrictionProvider.cpp
 rename src/access/{AccessRestriction.h => AccessRestrictionProvider.h} (52%)
 rename src/access/tests/{TestAccessRestriction.cpp => TestAccessRestrictionProvider.cpp} (87%)

diff --git a/examples/network-manager-app/linux/args.gni b/examples/network-manager-app/linux/args.gni
index 97661ce32a8bf7..e97ddb13e7c46e 100644
--- a/examples/network-manager-app/linux/args.gni
+++ b/examples/network-manager-app/linux/args.gni
@@ -22,4 +22,6 @@ chip_project_config_include_dirs = [
 ]
 
 chip_config_network_layer_ble = false
+
+# This enables AccessRestrictionList (ARL) support used by the NIM sample app
 chip_enable_access_restrictions = true
diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index 147a970124c0f2..df7fa6081331d4 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -104,7 +104,7 @@
 #include "CommissionableInit.h"
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-#include "ExampleAccessRestriction.h"
+#include "ExampleAccessRestrictionProvider.h"
 #include <app/server/DefaultArlStorage.h>
 #endif
 
@@ -602,12 +602,12 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl)
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     if (LinuxDeviceOptions::GetInstance().accessRestrictionEntries.HasValue())
     {
-        initParams.accessRestriction = new ExampleAccessRestriction();
-        initParams.arlStorage        = new app::DefaultArlStorage();
-        for (const auto & entry : LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value())
-        {
-            VerifyOrDie(AccessRestriction::CreateCommissioningEntry(entry) == CHIP_NO_ERROR);
-        }
+        auto exampleAccessRestrictionProvider = new ExampleAccessRestrictionProvider();
+        exampleAccessRestrictionProvider->SetCommissioningEntries(
+            LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value());
+
+        initParams.accessRestrictionProvider = exampleAccessRestrictionProvider;
+        initParams.arlStorage                = new app::DefaultArlStorage();
     }
 #endif
 
diff --git a/examples/platform/linux/ExampleAccessRestriction.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h
similarity index 80%
rename from examples/platform/linux/ExampleAccessRestriction.h
rename to examples/platform/linux/ExampleAccessRestrictionProvider.h
index 0262e42bab2d57..28ca38a327a555 100644
--- a/examples/platform/linux/ExampleAccessRestriction.h
+++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h
@@ -22,26 +22,25 @@
 
 #pragma once
 
-#include <access/AccessRestriction.h>
+#include <access/AccessRestrictionProvider.h>
 #include <app-common/zap-generated/cluster-objects.h>
 #include <app/EventLogging.h>
 
 namespace chip {
 namespace Access {
 
-class ExampleAccessRestriction : public AccessRestriction
+class ExampleAccessRestrictionProvider : public AccessRestrictionProvider
 {
 public:
-    ExampleAccessRestriction() : AccessRestriction() {}
+    ExampleAccessRestrictionProvider() : AccessRestrictionProvider() {}
 
-    ~ExampleAccessRestriction() {}
+    ~ExampleAccessRestrictionProvider() {}
 
 protected:
     CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl)
     {
         // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events
-        while (Access::GetAccessControl().GetAccessRestriction()->DeleteEntry(0, fabricIndex) == CHIP_NO_ERROR)
-            ;
+        Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector<Entry>{});
 
         chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .fabricIndex = fabricIndex };
         EventNumber eventNumber;
diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp
index 85da5f07397d4d..abea582d1f1b43 100644
--- a/examples/platform/linux/Options.cpp
+++ b/examples/platform/linux/Options.cpp
@@ -338,8 +338,7 @@ const char * sDeviceOptionHelp =
     "\n";
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-bool ParseAccessRestrictionEntriesFromJson(const char * jsonString,
-                                           std::vector<Platform::SharedPtr<AccessRestriction::Entry>> & entries)
+bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, std::vector<AccessRestrictionProvider::Entry> & entries)
 {
     Json::Value root;
     Json::Reader reader;
@@ -347,21 +346,21 @@ bool ParseAccessRestrictionEntriesFromJson(const char * jsonString,
 
     for (Json::Value::const_iterator eIt = root.begin(); eIt != root.end(); eIt++)
     {
-        auto entry = MakeShared<AccessRestriction::Entry>();
+        AccessRestrictionProvider::Entry entry;
 
-        entry->endpointNumber = static_cast<EndpointId>((*eIt)["endpoint"].asUInt());
-        entry->clusterId      = static_cast<ClusterId>((*eIt)["cluster"].asUInt());
+        entry.endpointNumber = static_cast<EndpointId>((*eIt)["endpoint"].asUInt());
+        entry.clusterId      = static_cast<ClusterId>((*eIt)["cluster"].asUInt());
 
         Json::Value restrictions = (*eIt)["restrictions"];
         for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++)
         {
-            AccessRestriction::Restriction restriction;
-            restriction.restrictionType = static_cast<AccessRestriction::Type>((*rIt)["type"].asInt());
+            AccessRestrictionProvider::Restriction restriction;
+            restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>((*rIt)["type"].asInt());
             if ((*rIt).isMember("id"))
             {
                 restriction.id.SetValue((*rIt)["id"].asUInt());
             }
-            entry->restrictions.push_back(restriction);
+            entry.restrictions.push_back(restriction);
         }
 
         entries.push_back(entry);
@@ -582,7 +581,7 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier,
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     case kDeviceOption_UseAccessRestrictions: {
-        std::vector<Platform::SharedPtr<AccessRestriction::Entry>> accessRestrictionEntries;
+        std::vector<AccessRestrictionProvider::Entry> accessRestrictionEntries;
         retval = ParseAccessRestrictionEntriesFromJson(aValue, accessRestrictionEntries);
         if (retval)
         {
diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h
index 895037664232c3..8fdacd8b64ea5c 100644
--- a/examples/platform/linux/Options.h
+++ b/examples/platform/linux/Options.h
@@ -40,7 +40,7 @@
 #include <testing/CustomCSRResponse.h>
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-#include <access/AccessRestriction.h>
+#include <access/AccessRestrictionProvider.h>
 #endif
 
 struct LinuxDeviceOptions
@@ -88,7 +88,7 @@ struct LinuxDeviceOptions
     int32_t subscriptionResumptionRetryIntervalSec = -1;
 #endif
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    chip::Optional<std::vector<chip::Platform::SharedPtr<chip::Access::AccessRestriction::Entry>>> accessRestrictionEntries;
+    chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> accessRestrictionEntries;
 #endif
     static LinuxDeviceOptions & GetInstance();
 };
diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index 6d519f9a227adf..d7d597f46b2665 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -326,7 +326,7 @@ void AccessControl::RemoveEntryListener(EntryListener & listener)
 bool AccessControl::IsAccessRestrictionListSupported() const
 {
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    return mAccessRestriction != nullptr;
+    return mAccessRestrictionProvider != nullptr;
 #else
     return false;
 #endif
@@ -357,9 +357,9 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
     }
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    if (mAccessRestriction != nullptr)
+    if (mAccessRestrictionProvider != nullptr)
     {
-        CHIP_ERROR result = mAccessRestriction->Check(subjectDescriptor, requestPath);
+        CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
         if (result != CHIP_NO_ERROR)
         {
             ChipLogProgress(DataManagement, "AccessControl: %s",
diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h
index 6f2f29803f9cdf..855efa04346a3a 100644
--- a/src/access/AccessControl.h
+++ b/src/access/AccessControl.h
@@ -21,7 +21,7 @@
 #include <access/AccessConfig.h>
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-#include "AccessRestriction.h"
+#include "AccessRestrictionProvider.h"
 #endif
 
 #include "Privilege.h"
@@ -635,9 +635,12 @@ class AccessControl
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     // Set an optional AcceessRestriction object for MNGD feature.
-    void SetAccessRestriction(AccessRestriction * accessRestriction) { mAccessRestriction = accessRestriction; }
+    void SetAccessRestrictionProvider(AccessRestrictionProvider * accessRestrictionProvider)
+    {
+        mAccessRestrictionProvider = accessRestrictionProvider;
+    }
 
-    AccessRestriction * GetAccessRestriction() { return mAccessRestriction; }
+    AccessRestrictionProvider * GetAccessRestrictionProvider() { return mAccessRestrictionProvider; }
 #endif
 
     /**
@@ -651,7 +654,7 @@ class AccessControl
      * Check whether access (by a subject descriptor, to a request path,
      * requiring a privilege) should be allowed or denied.
      *
-     * If an AccessRestriction object is set, it will be checked for additional access restrictions.
+     * 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.
@@ -679,7 +682,7 @@ class AccessControl
     EntryListener * mEntryListener = nullptr;
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    AccessRestriction * mAccessRestriction;
+    AccessRestrictionProvider * mAccessRestrictionProvider;
 #endif
 };
 
diff --git a/src/access/AccessRestriction.cpp b/src/access/AccessRestriction.cpp
deleted file mode 100644
index cadb70a0ba0088..00000000000000
--- a/src/access/AccessRestriction.cpp
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- *
- *    Copyright (c) 2024 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 "AccessRestriction.h"
-
-#include <algorithm>
-#include <lib/core/Global.h>
-
-using namespace chip::Platform;
-
-namespace chip {
-namespace Access {
-
-std::vector<SharedPtr<AccessRestriction::Entry>> AccessRestriction::sCommissioningEntries;
-
-CHIP_ERROR AccessRestriction::CreateFabricEntries(const FabricIndex fabricIndex)
-{
-    for (auto & entry : sCommissioningEntries)
-    {
-        CreateEntry(nullptr, *entry, fabricIndex);
-    }
-
-    return CHIP_NO_ERROR;
-}
-
-void AccessRestriction::AddListener(EntryListener & listener)
-{
-    if (mListeners == nullptr)
-    {
-        mListeners     = &listener;
-        listener.mNext = nullptr;
-        return;
-    }
-
-    for (EntryListener * l = mListeners; /**/; l = l->mNext)
-    {
-        if (l == &listener)
-        {
-            return;
-        }
-
-        if (l->mNext == nullptr)
-        {
-            l->mNext       = &listener;
-            listener.mNext = nullptr;
-            return;
-        }
-    }
-}
-
-void AccessRestriction::RemoveListener(EntryListener & listener)
-{
-    if (mListeners == &listener)
-    {
-        mListeners     = listener.mNext;
-        listener.mNext = nullptr;
-        return;
-    }
-
-    for (EntryListener * l = mListeners; l != nullptr; l = l->mNext)
-    {
-        if (l->mNext == &listener)
-        {
-            l->mNext       = listener.mNext;
-            listener.mNext = nullptr;
-            return;
-        }
-    }
-}
-
-SharedPtr<AccessRestriction::Entry> AccessRestriction::GetEntry(FabricIndex fabricIndex, size_t index)
-{
-    if (mFabricEntries.find(fabricIndex) != mFabricEntries.end() && index < mFabricEntries[fabricIndex].size())
-    {
-        return mFabricEntries[fabricIndex][index];
-    }
-
-    return nullptr;
-}
-
-CHIP_ERROR AccessRestriction::CreateCommissioningEntry(SharedPtr<Entry> entry)
-{
-    if (!entry->IsValid())
-    {
-        ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry");
-        return CHIP_ERROR_INVALID_ARGUMENT;
-    }
-
-    sCommissioningEntries.push_back(entry);
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR AccessRestriction::CreateEntry(size_t * index, const Entry & entry, FabricIndex fabricIndex)
-{
-    if (!entry.IsValid())
-    {
-        ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry");
-        return CHIP_ERROR_INVALID_ARGUMENT;
-    }
-
-    auto localEntry = MakeShared<Entry>(entry);
-    size_t newIndex;
-
-    localEntry->fabricIndex = fabricIndex;
-
-    if (mFabricEntries.find(fabricIndex) == mFabricEntries.end())
-    {
-        mFabricEntries[fabricIndex] = std::vector<SharedPtr<Entry>>();
-        mFabricEntries[fabricIndex].push_back(localEntry);
-    }
-    else
-    {
-        mFabricEntries[fabricIndex].push_back(localEntry);
-    }
-
-    newIndex = mFabricEntries[fabricIndex].size() - 1;
-
-    for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext)
-    {
-        listener->OnEntryChanged(fabricIndex, newIndex, localEntry, EntryListener::ChangeType::kAdded);
-    }
-
-    if (index != nullptr)
-    {
-        *index = newIndex;
-    }
-
-    ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, newIndex);
-
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR AccessRestriction::DeleteEntry(size_t index, const FabricIndex fabricIndex)
-{
-    ChipLogProgress(DataManagement, "AccessRestriction: delete entry f=%u i=%lu", fabricIndex, index);
-
-    auto entry = GetEntry(fabricIndex, index);
-    if (entry == nullptr)
-    {
-        return CHIP_ERROR_NOT_FOUND;
-    }
-    else
-    {
-        mFabricEntries[fabricIndex].erase(mFabricEntries[fabricIndex].begin() + static_cast<int>(index));
-
-        for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext)
-        {
-            listener->OnEntryChanged(fabricIndex, index, entry, EntryListener::ChangeType::kRemoved);
-        }
-    }
-
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR AccessRestriction::UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex)
-{
-    ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, index);
-
-    auto localEntry = GetEntry(fabricIndex, index);
-    if (localEntry != nullptr)
-    {
-        *localEntry = entry;
-
-        for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext)
-        {
-            listener->OnEntryChanged(fabricIndex, index, localEntry, EntryListener::ChangeType::kUpdated);
-        }
-
-        return CHIP_NO_ERROR;
-    }
-
-    return CHIP_ERROR_NOT_FOUND;
-}
-
-CHIP_ERROR AccessRestriction::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
-{
-    ChipLogProgress(DataManagement, "AccessRestriction: action %d", static_cast<int>(requestPath.requestType));
-
-    if (requestPath.requestType == RequestType::kRequestTypeUnknown)
-    {
-        ChipLogError(DataManagement, "AccessRestriction: RequestPath type is unknown");
-        return CHIP_ERROR_INVALID_ARGUMENT;
-    }
-
-    // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and
-    // we want that request to succeed (when generating the report, this method will be called with the specific
-    // event id). All other requests require an entity id
-    if (!requestPath.entityId.has_value())
-    {
-        if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
-        {
-            return CHIP_NO_ERROR;
-        }
-        else
-        {
-            return CHIP_ERROR_INVALID_ARGUMENT;
-        }
-    }
-
-    for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex])
-    {
-        if (entry->endpointNumber != requestPath.endpoint || entry->clusterId != requestPath.cluster)
-        {
-            continue;
-        }
-
-        for (auto & restriction : entry->restrictions)
-        {
-            // a missing id is a wildcard
-            bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value();
-            if (!idMatch)
-            {
-                continue;
-            }
-
-            switch (restriction.restrictionType)
-            {
-            case Type::kAttributeAccessForbidden:
-                if (requestPath.requestType == RequestType::kAttributeReadRequest ||
-                    requestPath.requestType == RequestType::kAttributeWriteRequest)
-                {
-                    return CHIP_ERROR_ACCESS_DENIED;
-                }
-                break;
-            case Type::kAttributeWriteForbidden:
-                if (requestPath.requestType == RequestType::kAttributeWriteRequest)
-                {
-                    return CHIP_ERROR_ACCESS_DENIED;
-                }
-                break;
-            case Type::kCommandForbidden:
-                if (requestPath.requestType == RequestType::kCommandInvokeRequest)
-                {
-                    return CHIP_ERROR_ACCESS_DENIED;
-                }
-                break;
-            case Type::kEventForbidden:
-                if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
-                {
-                    return CHIP_ERROR_ACCESS_DENIED;
-                }
-                break;
-            }
-        }
-    }
-
-    return CHIP_NO_ERROR;
-}
-
-void AccessRestriction::ClearData()
-{
-    // iterate over mFabricEntries and call DeleteEntry for each entry
-    for (auto & fabricEntry : mFabricEntries)
-    {
-        for (size_t i = 0; i < fabricEntry.second.size(); i++)
-        {
-            DeleteEntry(i, fabricEntry.first);
-        }
-    }
-
-    sCommissioningEntries.clear();
-}
-
-} // namespace Access
-} // namespace chip
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
new file mode 100644
index 00000000000000..0ef962ca9f939f
--- /dev/null
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -0,0 +1,193 @@
+/*
+ *
+ *    Copyright (c) 2024 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 "AccessRestrictionProvider.h"
+
+#include <algorithm>
+#include <lib/core/Global.h>
+
+using namespace chip::Platform;
+
+namespace chip {
+namespace Access {
+
+void AccessRestrictionProvider::AddListener(Listener & listener)
+{
+    if (mListeners == nullptr)
+    {
+        mListeners     = &listener;
+        listener.mNext = nullptr;
+        return;
+    }
+
+    for (Listener * l = mListeners; /**/; l = l->mNext)
+    {
+        if (l == &listener)
+        {
+            return;
+        }
+
+        if (l->mNext == nullptr)
+        {
+            l->mNext       = &listener;
+            listener.mNext = nullptr;
+            return;
+        }
+    }
+}
+
+void AccessRestrictionProvider::RemoveListener(Listener & listener)
+{
+    if (mListeners == &listener)
+    {
+        mListeners     = listener.mNext;
+        listener.mNext = nullptr;
+        return;
+    }
+
+    for (Listener * l = mListeners; l != nullptr; l = l->mNext)
+    {
+        if (l->mNext == &listener)
+        {
+            l->mNext       = listener.mNext;
+            listener.mNext = nullptr;
+            return;
+        }
+    }
+}
+
+CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector<Entry> & entries)
+{
+    // check that the input entries are valid
+    for (auto & entry : entries)
+    {
+        if (!IsEntryValid(entry))
+        {
+            ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry");
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+    }
+
+    mCommissioningEntries = entries;
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, const std::vector<Entry> & entries)
+{
+    // check that the input entries are valid
+    for (auto & entry : entries)
+    {
+        if (!IsEntryValid(entry))
+        {
+            ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry");
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+    }
+
+    for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
+    {
+        listener->RestrictionListChanged(fabricIndex);
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
+{
+    ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType));
+
+    if (requestPath.requestType == RequestType::kRequestTypeUnknown)
+    {
+        ChipLogError(DataManagement, "AccessRestrictionProvider: RequestPath type is unknown");
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and
+    // we want that request to succeed (when generating the report, this method will be called with the specific
+    // event id). All other requests require an entity id
+    if (!requestPath.entityId.has_value())
+    {
+        if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
+        {
+            return CHIP_NO_ERROR;
+        }
+        else
+        {
+            return CHIP_ERROR_INVALID_ARGUMENT;
+        }
+    }
+
+    for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex])
+    {
+        if (entry.endpointNumber != requestPath.endpoint || entry.clusterId != requestPath.cluster)
+        {
+            continue;
+        }
+
+        for (auto & restriction : entry.restrictions)
+        {
+            // a missing id is a wildcard
+            bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value();
+            if (!idMatch)
+            {
+                continue;
+            }
+
+            switch (restriction.restrictionType)
+            {
+            case Type::kAttributeAccessForbidden:
+                if (requestPath.requestType == RequestType::kAttributeReadRequest ||
+                    requestPath.requestType == RequestType::kAttributeWriteRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            case Type::kAttributeWriteForbidden:
+                if (requestPath.requestType == RequestType::kAttributeWriteRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            case Type::kCommandForbidden:
+                if (requestPath.requestType == RequestType::kCommandInvokeRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            case Type::kEventForbidden:
+                if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
+                {
+                    return CHIP_ERROR_ACCESS_DENIED;
+                }
+                break;
+            }
+        }
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+bool AccessRestrictionProvider::IsEntryValid(const Entry & entry) const
+{
+    return entry.endpointNumber != 0 && entry.clusterId != app::Clusters::NetworkCommissioning::Id &&
+        entry.clusterId != app::Clusters::Descriptor::Id;
+}
+
+} // namespace Access
+} // namespace chip
diff --git a/src/access/AccessRestriction.h b/src/access/AccessRestrictionProvider.h
similarity index 52%
rename from src/access/AccessRestriction.h
rename to src/access/AccessRestrictionProvider.h
index 34d1f27562767b..9178d8685a42f0 100644
--- a/src/access/AccessRestriction.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -35,10 +35,7 @@
 namespace chip {
 namespace Access {
 
-template <typename T>
-using SharedPtr = chip::Platform::SharedPtr<T>;
-
-class AccessRestriction
+class AccessRestrictionProvider
 {
 public:
     static constexpr size_t kNumberOfFabrics      = CHIP_CONFIG_MAX_FABRICS;
@@ -77,38 +74,27 @@ class AccessRestriction
         EndpointId endpointNumber;
         ClusterId clusterId;
         std::vector<Restriction> restrictions;
-
-        bool IsValid() const
-        {
-            return endpointNumber != 0 && clusterId != app::Clusters::NetworkCommissioning::Id &&
-                clusterId != app::Clusters::Descriptor::Id;
-        }
     };
 
     /**
      * Used to notify of changes in the access restriction list and active reviews.
      */
-    class EntryListener
+    class Listener
     {
     public:
-        enum class ChangeType
-        {
-            kAdded   = 1,
-            kRemoved = 2,
-            kUpdated = 3
-        };
+        virtual ~Listener() = default;
 
-        virtual ~EntryListener() = default;
+        /**
+         * Notifies of a change in the commissioning access restriction list.
+         */
+        virtual void CommissioningRestrictionListChanged() = 0;
 
         /**
          * Notifies of a change in the access restriction list.
          *
-         * @param [in] fabricIndex  The index of the fabric in which the entry has changed.
-         * @param [in] index        Index of entry to which has changed (relative to fabric).
-         * @param [in] entry        The latest value of the entry which changed.
-         * @param [in] changeType   The type of change that occurred.
+         * @param [in] fabricIndex  The index of the fabric in which the list has changed.
          */
-        virtual void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) = 0;
+        virtual void RestrictionListChanged(FabricIndex fabricIndex) = 0;
 
         /**
          * Notifies of an update to an active review with instructions and an optional redirect URL.
@@ -122,38 +108,45 @@ class AccessRestriction
                                                      const char * redirectUrl) = 0;
 
     private:
-        EntryListener * mNext = nullptr;
+        Listener * mNext = nullptr;
 
-        friend class AccessRestriction;
+        friend class AccessRestrictionProvider;
     };
 
-    AccessRestriction()          = default;
-    virtual ~AccessRestriction() = default;
+    AccessRestrictionProvider()          = default;
+    virtual ~AccessRestrictionProvider() = default;
+
+    AccessRestrictionProvider(const AccessRestrictionProvider &)             = delete;
+    AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete;
 
-    AccessRestriction(const AccessRestriction &)             = delete;
-    AccessRestriction & operator=(const AccessRestriction &) = delete;
+    /**
+     * Set the restriction entries that are to be used during commissioning when there is no accessing fabric.
+     *
+     * @param [in] entries  The entries to set.
+     */
+    CHIP_ERROR SetCommissioningEntries(const std::vector<Entry> & entries);
 
     /**
-     * Create restriction entries for a fabric by populating from the commissioning entries.
-     * This should be called when the device is commissioned to a new fabric.
+     * Set the restriction entries for a fabric.
      *
      * @param [in] fabricIndex  The index of the fabric for which to create entries.
+     * @param [in] entries  The entries to set for the fabric.
      */
-    CHIP_ERROR CreateFabricEntries(const FabricIndex fabricIndex);
+    CHIP_ERROR SetEntries(const FabricIndex, const std::vector<Entry> & entries);
 
     /**
      * Add a listener to be notified of changes in the access restriction list and active reviews.
      *
      * @param [in] listener  The listener to add.
      */
-    void AddListener(EntryListener & listener);
+    void AddListener(Listener & listener);
 
     /**
      * Remove a listener from being notified of changes in the access restriction list and active reviews.
      *
      * @param [in] listener  The listener to remove.
      */
-    void RemoveListener(EntryListener & listener);
+    void RemoveListener(Listener & listener);
 
     /**
      * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action.
@@ -161,9 +154,9 @@ class AccessRestriction
      *
      * If access is not restricted, CHIP_NO_ERROR will be returned.
      *
-     * @retval #CHIP_ERROR_ACCESS_DENIED if access is denied.
+     * @retval CHIP_ERROR_ACCESS_DENIED if access is denied.
      * @retval other errors should also be treated as restricted/denied.
-     * @retval #CHIP_NO_ERROR if access is not restricted/denied.
+     * @retval CHIP_NO_ERROR if access is not restricted/denied.
      */
     CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath);
 
@@ -180,83 +173,32 @@ class AccessRestriction
         return DoRequestFabricRestrictionReview(fabricIndex, token, arl);
     }
 
-    using EntryIterator = std::vector<SharedPtr<Entry>>::iterator;
-
     /**
-     * Get iterator over the commissioning entries, which are used during commissioning
-     * and also installed as defaults for a fabric upon completion of commissioning.
+     * Get the commissioning restriction entries.
      *
-     * @param [out] begin iterator pointing to the beginning of the entries
-     * @param [out] end iterator pointing to the end of the entries
+     * @retval the commissioning restriction entries.
      */
-    static CHIP_ERROR CommissioningEntries(EntryIterator & begin, EntryIterator & end)
-    {
-        begin = sCommissioningEntries.begin();
-        end   = sCommissioningEntries.end();
-
-        return CHIP_NO_ERROR;
-    }
+    const std::vector<Entry> & GetCommissioningEntries() const { return mCommissioningEntries; }
 
     /**
-     * Get iterator over entries in the access restriction list by fabric.
+     * Get the restriction entries for a fabric.
      *
-     * @param [in]  fabricIndex Iteration is confined to fabric
-     * @param [out] begin iterator pointing to the beginning of the entries
-     * @param [out] end iterator pointing to the end of the entries
+     * @param [in]  fabricIndex the index of the fabric for which to get entries.
+     * @param [out] entries reference to a const vector to hold the entries.
      */
-    CHIP_ERROR Entries(const FabricIndex fabricIndex, EntryIterator & begin, EntryIterator & end)
+    CHIP_ERROR GetEntries(const FabricIndex fabricIndex, const std::vector<Entry> *& entries) const
     {
-        if (mFabricEntries.find(fabricIndex) == mFabricEntries.end())
+        auto it = mFabricEntries.find(fabricIndex);
+        if (it == mFabricEntries.end())
         {
             return CHIP_ERROR_NOT_FOUND;
         }
 
-        begin = mFabricEntries[fabricIndex].begin();
-        end   = mFabricEntries[fabricIndex].end();
+        entries = &(it->second);
 
         return CHIP_NO_ERROR;
     }
 
-    /**
-     * Add a restriction entry to the commissioning access restriction list. This list is automatically
-     * applied to fabrics upon completion of commissioning. This request will fail if there is already
-     * an entry with the same cluster and endpoint.
-     *
-     * @param [in]  entry       The entry to add to the commissioning access restriction list.
-     * @return                  CHIP_NO_ERROR if the entry was successfully created, or an error code.
-     */
-    static CHIP_ERROR CreateCommissioningEntry(SharedPtr<Entry> entry);
-
-    /**
-     * Add a restriction entry to the access restriction list and notify any listeners. This request
-     * will fail if there is already an entry with the same cluster and endpoint.
-     *
-     * @param [out] index       (If not nullptr) index of created entry (relative to fabric).
-     * @param [in]  entry       The entry to add to the access restriction list.
-     * @param [in]  fabricIndex The index of the fabric in which the entry should be added.
-     * @return                  CHIP_NO_ERROR if the entry was successfully created, or an error code.
-     */
-    CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, const FabricIndex fabricIndex);
-
-    /**
-     * Delete a restriction entry from the access restriction list and notify any listeners.
-     *
-     * @param [in] index           Index of entry to delete.  May be relative to fabric.
-     * @param [in] fabricIndex     Fabric to which entry `index` is relative.
-     * @return                     CHIP_NO_ERROR if the entry was successfully deleted, or an error code.
-     */
-    CHIP_ERROR DeleteEntry(size_t index, const FabricIndex fabricIndex);
-
-    /**
-     * Update a restriction entry in the access restriction list and notify any listeners.
-     *
-     * @param [in] index           Index of entry to delete.  May be relative to fabric.
-     * @param [in] entry        The updated entry to replace the existing entry.
-     * @param [in] fabricIndex  The index of the fabric in which the entry should be updated.
-     * @return                  CHIP_NO_ERROR if the entry was successfully updated, or an error code.
-     */
-    CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex);
-
 protected:
     /**
      * Initiate a review of the access restrictions for a fabric. This method should be implemented by the platform and be
@@ -270,18 +212,13 @@ class AccessRestriction
     virtual CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token,
                                                         const std::vector<Entry> & arl) = 0;
 
-    /**
-     * Clear all access restriction data.
-     */
-    void ClearData();
-
 private:
-    SharedPtr<Entry> GetEntry(FabricIndex fabricIndex, size_t index);
+    bool IsEntryValid(const Entry & entry) const;
 
-    static std::vector<Platform::SharedPtr<Entry>> sCommissioningEntries;
-    uint64_t mNextToken        = 1;
-    EntryListener * mListeners = nullptr;
-    std::map<FabricIndex, std::vector<Platform::SharedPtr<Entry>>> mFabricEntries;
+    uint64_t mNextToken   = 1;
+    Listener * mListeners = nullptr;
+    std::vector<Entry> mCommissioningEntries;
+    std::map<FabricIndex, std::vector<Entry>> mFabricEntries;
 };
 
 } // namespace Access
diff --git a/src/access/BUILD.gn b/src/access/BUILD.gn
index ae2a64d70906f8..3f3aaafe8a6a9b 100644
--- a/src/access/BUILD.gn
+++ b/src/access/BUILD.gn
@@ -73,8 +73,8 @@ static_library("access") {
 
   if (chip_enable_access_restrictions) {
     sources += [
-      "AccessRestriction.cpp",
-      "AccessRestriction.h",
+      "AccessRestrictionProvider.cpp",
+      "AccessRestrictionProvider.h",
     ]
   }
 }
diff --git a/src/access/tests/BUILD.gn b/src/access/tests/BUILD.gn
index c790ad4066be73..64226adfacc260 100644
--- a/src/access/tests/BUILD.gn
+++ b/src/access/tests/BUILD.gn
@@ -32,6 +32,6 @@ chip_test_suite("tests") {
   ]
 
   if (chip_enable_access_restrictions) {
-    test_sources += [ "TestAccessRestriction.cpp" ]
+    test_sources += [ "TestAccessRestrictionProvider.cpp" ]
   }
 }
diff --git a/src/access/tests/TestAccessRestriction.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp
similarity index 87%
rename from src/access/tests/TestAccessRestriction.cpp
rename to src/access/tests/TestAccessRestrictionProvider.cpp
index 3a3db93be3f640..073d3e6081a47c 100644
--- a/src/access/tests/TestAccessRestriction.cpp
+++ b/src/access/tests/TestAccessRestrictionProvider.cpp
@@ -17,7 +17,7 @@
  */
 
 #include "access/AccessControl.h"
-#include "access/AccessRestriction.h"
+#include "access/AccessRestrictionProvider.h"
 #include "access/examples/ExampleAccessControlDelegate.h"
 
 #include <pw_unit_test/framework.h>
@@ -28,19 +28,16 @@
 namespace chip {
 namespace Access {
 
-class TestAccessRestrictionImpl : public AccessRestriction
+class TestAccessRestrictionProvider : public AccessRestrictionProvider
 {
     CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl)
     {
         return CHIP_NO_ERROR;
     }
-
-public:
-    void Clear() { ClearData(); }
 };
 
 AccessControl accessControl;
-TestAccessRestrictionImpl accessRestriction;
+TestAccessRestrictionProvider accessRestrictionProvider;
 
 constexpr ClusterId kNetworkCommissioningCluster = 0x0000'0031; // must not be blocked by access restrictions on any endpoint
 constexpr ClusterId kDescriptorCluster           = 0x0000'001d; // must not be blocked by access restrictions on any endpoint
@@ -176,13 +173,20 @@ class DeviceTypeResolver : public AccessControl::DeviceTypeResolver
 class TestAccessRestriction : public ::testing::Test
 {
 public: // protected
-    void SetUp() override { accessRestriction.Clear(); }
+    void SetUp() override
+    {
+        accessRestrictionProvider.SetCommissioningEntries(std::vector<AccessRestrictionProvider::Entry>());
+        accessRestrictionProvider.SetEntries(0, std::vector<AccessRestrictionProvider::Entry>());
+        accessRestrictionProvider.SetEntries(1, std::vector<AccessRestrictionProvider::Entry>());
+        accessRestrictionProvider.SetEntries(2, std::vector<AccessRestrictionProvider::Entry>());
+    }
+
     static void SetUpTestSuite()
     {
         ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
         AccessControl::Delegate * delegate = Examples::GetAccessControlDelegate();
         SetAccessControl(accessControl);
-        GetAccessControl().SetAccessRestriction(&accessRestriction);
+        GetAccessControl().SetAccessRestrictionProvider(&accessRestrictionProvider);
         VerifyOrDie(GetAccessControl().Init(delegate, testDeviceTypeResolver) == CHIP_NO_ERROR);
         EXPECT_EQ(LoadAccessControl(accessControl, aclEntryData, aclEntryDataCount), CHIP_NO_ERROR);
     }
@@ -206,23 +210,29 @@ TEST_F(TestAccessRestriction, MetaTest)
 // ensure adding restrictons on endpoint 0 (any cluster) or for network commissioning and descriptor clusters fail
 TEST_F(TestAccessRestriction, InvalidRestrictionsTest)
 {
-    AccessRestriction::Entry entry;
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
     entry.fabricIndex = 1;
     entry.clusterId   = kOnOffCluster;
-    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden });
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
 
     // must not restrict endpoint 0
     entry.endpointNumber = 0;
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
 
     // must not restrict network commissioning cluster
+    entries.clear();
     entry.endpointNumber = 1;
     entry.clusterId      = kNetworkCommissioningCluster;
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
 
     // must not restrict descriptor cluster
+    entries.clear();
     entry.clusterId = kDescriptorCluster;
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
 }
 
 constexpr CheckData accessAttributeRestrictionTestData[] = {
@@ -249,20 +259,23 @@ constexpr CheckData accessAttributeRestrictionTestData[] = {
 
 TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
 {
-    AccessRestriction::Entry entry;
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
     entry.clusterId      = kOnOffCluster;
-    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden });
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
 
     // test wildcarded entity id
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData));
 
     // test specific entity id
-    accessRestriction.Clear();
+    entries.clear();
     entry.restrictions[0].id.SetValue(1);
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData));
 }
 
@@ -290,20 +303,23 @@ constexpr CheckData writeAttributeRestrictionTestData[] = {
 
 TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
 {
-    AccessRestriction::Entry entry;
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
     entry.clusterId      = kOnOffCluster;
-    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden });
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden });
 
     // test wildcarded entity id
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData));
 
     // test specific entity id
-    accessRestriction.Clear();
+    entries.clear();
     entry.restrictions[0].id.SetValue(1);
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData));
 }
 
@@ -331,20 +347,23 @@ constexpr CheckData commandAttributeRestrictionTestData[] = {
 
 TEST_F(TestAccessRestriction, CommandRestrictionTest)
 {
-    AccessRestriction::Entry entry;
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
     entry.clusterId      = kOnOffCluster;
-    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden });
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
 
     // test wildcarded entity id
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData));
 
     // test specific entity id
-    accessRestriction.Clear();
+    entries.clear();
     entry.restrictions[0].id.SetValue(1);
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData));
 }
 
@@ -372,20 +391,23 @@ constexpr CheckData eventAttributeRestrictionTestData[] = {
 
 TEST_F(TestAccessRestriction, EventRestrictionTest)
 {
-    AccessRestriction::Entry entry;
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
     entry.clusterId      = kOnOffCluster;
-    entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kEventForbidden });
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kEventForbidden });
 
     // test wildcarded entity id
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData));
 
     // test specific entity id
-    accessRestriction.Clear();
+    entries.clear();
     entry.restrictions[0].id.SetValue(1);
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR);
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
     RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData));
 }
 
@@ -434,27 +456,31 @@ constexpr CheckData combinedRestrictionTestData[] = {
 TEST_F(TestAccessRestriction, CombinedRestrictionTest)
 {
     // a restriction for all access to attribute 1 and 2, attributes 3 and 4 are allowed
-    AccessRestriction::Entry entry1;
+    std::vector<AccessRestrictionProvider::Entry> entries1;
+    AccessRestrictionProvider::Entry entry1;
     entry1.fabricIndex    = 1;
     entry1.endpointNumber = 1;
     entry1.clusterId      = kOnOffCluster;
-    entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden });
+    entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden });
     entry1.restrictions[0].id.SetValue(1);
-    entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden });
+    entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
     entry1.restrictions[1].id.SetValue(2);
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry1, 1), CHIP_NO_ERROR);
+    entries1.push_back(entry1);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries1), CHIP_NO_ERROR);
 
     // a restriction for fabric 2 that forbids command 1 and 2.  Check that command 1 is blocked on invoke, but attribute 2 write is
     // allowed
-    AccessRestriction::Entry entry2;
+    std::vector<AccessRestrictionProvider::Entry> entries2;
+    AccessRestrictionProvider::Entry entry2;
     entry2.fabricIndex    = 2;
     entry2.endpointNumber = 1;
     entry2.clusterId      = kOnOffCluster;
-    entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden });
+    entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
     entry2.restrictions[0].id.SetValue(1);
-    entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden });
+    entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
     entry2.restrictions[1].id.SetValue(2);
-    EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry2, 2), CHIP_NO_ERROR);
+    entries2.push_back(entry2);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(2, entries2), CHIP_NO_ERROR);
 
     RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData));
 }
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index c8eda434079168..99c460d4a49b38 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -18,7 +18,7 @@
 #include <access/AccessControl.h>
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-#include <access/AccessRestriction.h>
+#include <access/AccessRestrictionProvider.h>
 #include <app/server/ArlStorage.h>
 #endif
 
@@ -30,6 +30,7 @@
 #include <app/ConcreteCommandPath.h>
 #include <app/EventLogging.h>
 #include <app/data-model/Encode.h>
+#include <app/reporting/reporting.h>
 #include <app/server/AclStorage.h>
 #include <app/server/Server.h>
 #include <app/util/attribute-storage.h>
@@ -66,7 +67,7 @@ class AccessControlAttribute : public AttributeAccessInterface,
                                public AccessControl::EntryListener
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     ,
-                               public AccessRestriction::EntryListener
+                               public AccessRestrictionProvider::Listener
 #endif
 {
 public:
@@ -87,8 +88,9 @@ class AccessControlAttribute : public AttributeAccessInterface,
                         const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override;
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    void OnEntryChanged(FabricIndex fabricIndex, size_t index, Platform::SharedPtr<AccessRestriction::Entry> entry,
-                        AccessRestriction::EntryListener::ChangeType changeType) override;
+    void CommissioningRestrictionListChanged() override;
+
+    void RestrictionListChanged(FabricIndex fabricIndex) override;
 
     void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
                                          const char * redirectUrl) override;
@@ -468,20 +470,18 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
 CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder)
 {
-    auto accessRestriction = GetAccessControl().GetAccessRestriction();
-    if (accessRestriction == nullptr)
+    auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider();
+    if (accessRestrictionProvider == nullptr)
     {
         return CHIP_ERROR_NOT_IMPLEMENTED;
     }
 
     return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
-        AccessRestriction::EntryIterator begin;
-        AccessRestriction::EntryIterator end;
-        ReturnErrorOnFailure(accessRestriction->CommissioningEntries(begin, end));
+        auto entries = accessRestrictionProvider->GetCommissioningEntries();
 
-        for (AccessRestriction::EntryIterator it = begin; it != end; ++it)
+        for (auto & entry : entries)
         {
-            ArlStorage::EncodableEntry encodableEntry(*it);
+            ArlStorage::EncodableEntry encodableEntry(entry);
             ReturnErrorOnFailure(encoder.Encode(encodableEntry));
         }
         return CHIP_NO_ERROR;
@@ -490,8 +490,8 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder &
 
 CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
 {
-    auto accessRestriction = GetAccessControl().GetAccessRestriction();
-    if (accessRestriction == nullptr)
+    auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider();
+    if (accessRestrictionProvider == nullptr)
     {
         return CHIP_ERROR_NOT_IMPLEMENTED;
     }
@@ -500,25 +500,30 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
         for (auto & info : Server::GetInstance().GetFabricTable())
         {
             auto fabric = info.GetFabricIndex();
-            AccessRestriction::EntryIterator begin;
-            AccessRestriction::EntryIterator end;
-            ReturnErrorOnFailure(accessRestriction->Entries(fabric, begin, end));
-
-            for (AccessRestriction::EntryIterator it = begin; it != end; ++it)
+            // get entries for fabric
+            const std::vector<AccessRestrictionProvider::Entry> * entries = nullptr;
+            ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries));
+            VerifyOrReturnError(entries != nullptr, CHIP_ERROR_INCORRECT_STATE);
+            for (auto & entry : *entries)
             {
-                ArlStorage::EncodableEntry encodableEntry(*it);
+                ArlStorage::EncodableEntry encodableEntry(entry);
                 ReturnErrorOnFailure(encoder.Encode(encodableEntry));
             }
         }
         return CHIP_NO_ERROR;
     });
 }
+void AccessControlAttribute::CommissioningRestrictionListChanged()
+{
+    MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id);
+}
 
-void AccessControlAttribute::OnEntryChanged(FabricIndex fabricIndex, size_t index,
-                                            Platform::SharedPtr<AccessRestriction::Entry> entry,
-                                            AccessRestriction::EntryListener::ChangeType changeType)
+void AccessControlAttribute::RestrictionListChanged(FabricIndex fabricIndex)
 {
     CHIP_ERROR err;
+
+    MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id);
+
     ArlChangedEvent event{ .fabricIndex = fabricIndex };
 
     EventNumber eventNumber;
@@ -601,16 +606,6 @@ CHIP_ERROR AccessControlAttribute::Write(const ConcreteDataAttributePath & aPath
     return ChipErrorToImErrorMap(WriteImpl(aPath, aDecoder));
 }
 
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
-{
-    if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete)
-    {
-        Access::GetAccessControl().GetAccessRestriction()->CreateFabricEntries(event->CommissioningComplete.fabricIndex);
-    }
-}
-#endif
-
 } // namespace
 
 void MatterAccessControlPluginServerInitCallback()
@@ -621,13 +616,11 @@ void MatterAccessControlPluginServerInitCallback()
     GetAccessControl().AddEntryListener(sAttribute);
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    auto accessRestriction = GetAccessControl().GetAccessRestriction();
-    if (accessRestriction != nullptr)
+    auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider();
+    if (accessRestrictionProvider != nullptr)
     {
-        accessRestriction->AddListener(sAttribute);
+        accessRestrictionProvider->AddListener(sAttribute);
     }
-
-    DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler);
 #endif
 }
 
@@ -643,11 +636,11 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
     }
 
     uint64_t token;
-    std::vector<AccessRestriction::Entry> entries;
+    std::vector<AccessRestrictionProvider::Entry> entries;
     auto entryIter = commandData.arl.begin();
     while (entryIter.Next())
     {
-        AccessRestriction::Entry entry;
+        AccessRestrictionProvider::Entry entry;
         entry.fabricIndex    = commandObj->GetAccessingFabricIndex();
         entry.endpointNumber = entryIter.GetValue().endpoint;
         entry.clusterId      = entryIter.GetValue().cluster;
@@ -655,8 +648,8 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
         auto restrictionIter = entryIter.GetValue().restrictions.begin();
         while (restrictionIter.Next())
         {
-            AccessRestriction::Restriction restriction;
-            restriction.restrictionType = static_cast<AccessRestriction::Type>(restrictionIter.GetValue().type);
+            AccessRestrictionProvider::Restriction restriction;
+            restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>(restrictionIter.GetValue().type);
             if (!restrictionIter.GetValue().id.IsNull())
             {
                 restriction.id.SetValue(restrictionIter.GetValue().id.Value());
@@ -667,7 +660,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
         entries.push_back(entry);
     }
 
-    CHIP_ERROR err = GetAccessControl().GetAccessRestriction()->RequestFabricRestrictionReview(
+    CHIP_ERROR err = GetAccessControl().GetAccessRestrictionProvider()->RequestFabricRestrictionReview(
         commandObj->GetAccessingFabricIndex(), entries, token);
 
     if (err == CHIP_NO_ERROR)
diff --git a/src/app/server/ArlStorage.cpp b/src/app/server/ArlStorage.cpp
index 983f9a16ad8cd6..a0d6c38c855029 100644
--- a/src/app/server/ArlStorage.cpp
+++ b/src/app/server/ArlStorage.cpp
@@ -23,28 +23,28 @@ using namespace chip;
 using namespace chip::app;
 using namespace chip::Access;
 
-using Entry                  = AccessRestriction::Entry;
-using EntryListener          = AccessRestriction::EntryListener;
+using Entry                  = AccessRestrictionProvider::Entry;
+using EntryListener          = AccessRestrictionProvider::Listener;
 using StagingRestrictionType = Clusters::AccessControl::AccessRestrictionTypeEnum;
 using StagingRestriction     = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type;
 
 namespace {
 
-CHIP_ERROR Convert(StagingRestrictionType from, AccessRestriction::Type & to)
+CHIP_ERROR Convert(StagingRestrictionType from, AccessRestrictionProvider::Type & to)
 {
     switch (from)
     {
     case StagingRestrictionType::kAttributeAccessForbidden:
-        to = AccessRestriction::Type::kAttributeAccessForbidden;
+        to = AccessRestrictionProvider::Type::kAttributeAccessForbidden;
         break;
     case StagingRestrictionType::kAttributeWriteForbidden:
-        to = AccessRestriction::Type::kAttributeWriteForbidden;
+        to = AccessRestrictionProvider::Type::kAttributeWriteForbidden;
         break;
     case StagingRestrictionType::kCommandForbidden:
-        to = AccessRestriction::Type::kCommandForbidden;
+        to = AccessRestrictionProvider::Type::kCommandForbidden;
         break;
     case StagingRestrictionType::kEventForbidden:
-        to = AccessRestriction::Type::kEventForbidden;
+        to = AccessRestrictionProvider::Type::kEventForbidden;
         break;
     default:
         return CHIP_ERROR_INVALID_ARGUMENT;
@@ -52,20 +52,20 @@ CHIP_ERROR Convert(StagingRestrictionType from, AccessRestriction::Type & to)
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR Convert(AccessRestriction::Type from, StagingRestrictionType & to)
+CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType & to)
 {
     switch (from)
     {
-    case AccessRestriction::Type::kAttributeAccessForbidden:
+    case AccessRestrictionProvider::Type::kAttributeAccessForbidden:
         to = StagingRestrictionType::kAttributeAccessForbidden;
         break;
-    case AccessRestriction::Type::kAttributeWriteForbidden:
+    case AccessRestrictionProvider::Type::kAttributeWriteForbidden:
         to = StagingRestrictionType::kAttributeWriteForbidden;
         break;
-    case AccessRestriction::Type::kCommandForbidden:
+    case AccessRestrictionProvider::Type::kCommandForbidden:
         to = StagingRestrictionType::kCommandForbidden;
         break;
-    case AccessRestriction::Type::kEventForbidden:
+    case AccessRestrictionProvider::Type::kEventForbidden:
         to = StagingRestrictionType::kEventForbidden;
         break;
     default:
@@ -91,7 +91,7 @@ CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader)
     while (iterator.Next())
     {
         auto & tmp = iterator.GetValue();
-        AccessRestriction::Restriction restriction;
+        AccessRestrictionProvider::Restriction restriction;
         ReturnErrorOnFailure(Convert(tmp.type, restriction.restrictionType));
 
         if (!tmp.id.IsNull())
@@ -122,17 +122,17 @@ CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, T
 
 CHIP_ERROR ArlStorage::EncodableEntry::Stage() const
 {
-    mStagingEntry.fabricIndex = mEntry->fabricIndex;
-    mStagingEntry.endpoint    = mEntry->endpointNumber;
-    mStagingEntry.cluster     = mEntry->clusterId;
+    mStagingEntry.fabricIndex = mEntry.fabricIndex;
+    mStagingEntry.endpoint    = mEntry.endpointNumber;
+    mStagingEntry.cluster     = mEntry.clusterId;
 
     {
-        size_t count = mEntry->restrictions.size();
+        size_t count = mEntry.restrictions.size();
         if (count > 0 && count <= CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY)
         {
             for (size_t i = 0; i < count; i++)
             {
-                auto restriction = mEntry->restrictions[i];
+                auto restriction = mEntry.restrictions[i];
                 StagingRestriction tmp;
                 ReturnErrorOnFailure(Convert(restriction.restrictionType, tmp.type));
 
diff --git a/src/app/server/ArlStorage.h b/src/app/server/ArlStorage.h
index 34fb616d226e4f..c87f2e0d99a2f5 100644
--- a/src/app/server/ArlStorage.h
+++ b/src/app/server/ArlStorage.h
@@ -17,7 +17,7 @@
 
 #pragma once
 
-#include <access/AccessRestriction.h>
+#include <access/AccessRestrictionProvider.h>
 #include <credentials/FabricTable.h>
 #include <lib/core/CHIPPersistentStorageDelegate.h>
 
@@ -52,7 +52,7 @@ class ArlStorage
      */
     class DecodableEntry
     {
-        using Entry        = Access::AccessRestriction::Entry;
+        using Entry        = Access::AccessRestrictionProvider::Entry;
         using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::DecodableType;
 
     public:
@@ -89,12 +89,12 @@ class ArlStorage
      */
     class EncodableEntry
     {
-        using Entry              = Access::AccessRestriction::Entry;
+        using Entry              = Access::AccessRestrictionProvider::Entry;
         using StagingEntry       = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type;
         using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type;
 
     public:
-        EncodableEntry(std::shared_ptr<Entry> entry) : mEntry(entry) {}
+        EncodableEntry(const Entry & entry) : mEntry(entry) {}
 
         /**
          * Constructor-provided entry is staged into a staging entry,
@@ -120,10 +120,10 @@ class ArlStorage
     public:
         static constexpr bool kIsFabricScoped = true;
 
-        FabricIndex GetFabricIndex() const { return mEntry->fabricIndex; }
+        FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; }
 
     private:
-        std::shared_ptr<Entry> mEntry;
+        Entry mEntry;
 
         mutable StagingEntry mStagingEntry;
         mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY];
diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp
index b7ab1aff23e4f0..9f43054a817d0f 100644
--- a/src/app/server/DefaultArlStorage.cpp
+++ b/src/app/server/DefaultArlStorage.cpp
@@ -25,10 +25,10 @@ using namespace chip::app;
 using namespace chip::Access;
 
 using EncodableEntry     = ArlStorage::EncodableEntry;
-using Entry              = AccessRestriction::Entry;
-using EntryListener      = AccessRestriction::EntryListener;
+using Entry              = AccessRestrictionProvider::Entry;
+using Listener           = AccessRestrictionProvider::Listener;
 using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type;
-using Restriction        = AccessRestriction::Restriction;
+using Restriction        = AccessRestrictionProvider::Restriction;
 
 namespace {
 
@@ -59,47 +59,70 @@ constexpr int kEncodedEntryOverheadBytes    = 17 + 8;
 constexpr int kEncodedEntryRestrictionBytes = 11 * CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY;
 constexpr int kEncodedEntryTotalBytes       = kEncodedEntryOverheadBytes + kEncodedEntryRestrictionBytes;
 
-class : public EntryListener
+class : public Listener
 {
 public:
-    void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) override
+    void CommissioningRestrictionListChanged() override
     {
+        // first clear out any existing entries
         CHIP_ERROR err;
+        for (size_t index = 0; /**/; ++index)
+        {
+            err = mPersistentStorage->SyncDeleteKeyValue(
+                DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName());
+            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
+            {
+                break;
+            }
+        }
 
-        uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
+        auto entries = GetAccessControl().GetAccessRestrictionProvider()->GetCommissioningEntries();
+        size_t index = 0;
+        for (auto & entry : entries)
+        {
+            uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
+            TLV::TLVWriter writer;
+            writer.Init(buffer);
+            EncodableEntry encodableEntry(entry);
+            SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag()));
+            SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
+                              DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index++).KeyName(), buffer,
+                              static_cast<uint16_t>(writer.GetLengthWritten())));
+        }
+
+        return;
 
-        VerifyOrExit(mPersistentStorage != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+    exit:
+        ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format());
+    }
 
-        if (changeType == ChangeType::kRemoved)
+    void RestrictionListChanged(FabricIndex fabricIndex) override
+    {
+        // first clear out any existing entries
+        CHIP_ERROR err;
+        size_t index = 0;
+        for (size_t index = 0; /**/; ++index)
         {
-            // Shuffle down entries past index, then delete entry at last index.
-            while (true)
+            err = mPersistentStorage->SyncDeleteKeyValue(
+                DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName());
+            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
             {
-                uint16_t size = static_cast<uint16_t>(sizeof(buffer));
-                err           = mPersistentStorage->SyncGetKeyValue(
-                    DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index + 1).KeyName(), buffer, size);
-                if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
-                {
-                    break;
-                }
-                SuccessOrExit(err);
-                SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
-                                  DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer, size));
-                index++;
+                break;
             }
-            SuccessOrExit(err = mPersistentStorage->SyncDeleteKeyValue(
-                              DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName()));
         }
-        else
+
+        const std::vector<Entry> * entries;
+        SuccessOrExit(err = GetAccessControl().GetAccessRestrictionProvider()->GetEntries(fabricIndex, entries));
+        VerifyOrExit(entries != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+        for (auto & entry : *entries)
         {
-            // Write added/updated entry at index.
-            VerifyOrExit(entry != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+            uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
             TLV::TLVWriter writer;
             writer.Init(buffer);
             EncodableEntry encodableEntry(entry);
             SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag()));
             SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
-                              DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer,
+                              DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index++).KeyName(), buffer,
                               static_cast<uint16_t>(writer.GetLengthWritten())));
         }
 
@@ -132,9 +155,10 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage
     ChipLogProgress(DataManagement, "DefaultArlStorage: initializing");
 
     CHIP_ERROR err;
+    size_t commissioningCount = 0;
+    size_t count              = 0;
 
-    [[maybe_unused]] size_t count = 0;
-
+    std::vector<Entry> commissioningEntries;
     for (size_t index = 0; /**/; ++index)
     {
         uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
@@ -154,13 +178,14 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage
         DecodableEntry decodableEntry;
         SuccessOrExit(err = decodableEntry.Decode(reader));
 
-        SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateCommissioningEntry(
-                          Platform::MakeShared<Entry>(decodableEntry.GetEntry())));
-        count++;
+        commissioningEntries.push_back(decodableEntry.GetEntry());
+        commissioningCount++;
     }
+    GetAccessControl().GetAccessRestrictionProvider()->SetCommissioningEntries(commissioningEntries);
 
     for (auto it = first; it != last; ++it)
     {
+        std::vector<Entry> entries;
         auto fabric = it->GetFabricIndex();
         for (size_t index = 0; /**/; ++index)
         {
@@ -181,16 +206,17 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage
             DecodableEntry decodableEntry;
             SuccessOrExit(err = decodableEntry.Decode(reader));
 
-            Entry & entry = decodableEntry.GetEntry();
-            SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateEntry(nullptr, entry, fabric));
+            entries.push_back(decodableEntry.GetEntry());
             count++;
         }
+        GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, commissioningEntries);
     }
 
-    ChipLogProgress(DataManagement, "DefaultArlStorage: %u entries loaded", (unsigned) count);
+    ChipLogProgress(DataManagement, "DefaultArlStorage: %u commissioning entries loaded, %u fabric entries loaded",
+                    (unsigned) commissioningCount, (unsigned) count);
 
     sEntryListener.Init(persistentStorage);
-    GetAccessControl().GetAccessRestriction()->AddListener(sEntryListener);
+    GetAccessControl().GetAccessRestrictionProvider()->AddListener(sEntryListener);
 
     return CHIP_NO_ERROR;
 
diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp
index fbdae0ccb213f8..0484669f97b742 100644
--- a/src/app/server/Server.cpp
+++ b/src/app/server/Server.cpp
@@ -181,12 +181,12 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams)
     SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    if (initParams.accessRestriction != nullptr && initParams.arlStorage != nullptr)
+    if (initParams.accessRestrictionProvider != nullptr && initParams.arlStorage != nullptr)
     {
-        mAccessRestriction = initParams.accessRestriction;
-        mArlStorage        = initParams.arlStorage;
+        mAccessRestrictionProvider = initParams.accessRestrictionProvider;
+        mArlStorage                = initParams.arlStorage;
 
-        mAccessControl.SetAccessRestriction(mAccessRestriction);
+        mAccessControl.SetAccessRestrictionProvider(mAccessRestrictionProvider);
         SuccessOrExit(err = mArlStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
     }
 #endif
diff --git a/src/app/server/Server.h b/src/app/server/Server.h
index b8f07e526f80d4..d49850d52dfa73 100644
--- a/src/app/server/Server.h
+++ b/src/app/server/Server.h
@@ -170,7 +170,7 @@ struct ServerInitParams
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce
     // access restrictions that are managed by the device.
-    Access::AccessRestriction * accessRestriction = nullptr;
+    Access::AccessRestrictionProvider * accessRestrictionProvider = nullptr;
     // ARL storage: MUST be injected if MNGD feature enabled. Used to store ACL entries in
     // persistent storage. Must NOT be initialized before being provided.
     app::ArlStorage * arlStorage = nullptr;
@@ -693,7 +693,7 @@ class Server
     app::AclStorage * mAclStorage;
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    Access::AccessRestriction * mAccessRestriction;
+    Access::AccessRestrictionProvider * mAccessRestrictionProvider;
     app::ArlStorage * mArlStorage;
 #endif
 

From 03ea9d056c4e6bad4eba82ea18810629a29578af Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Wed, 21 Aug 2024 13:57:18 -0500
Subject: [PATCH 04/22] Fixed encode/decode so reading CommissioningARL and Arl
 attributes work

---
 examples/platform/linux/AppMain.cpp           |  27 +++--
 examples/platform/linux/Options.cpp           |  32 ++++--
 examples/platform/linux/Options.h             |   3 +-
 src/access/AccessRestrictionProvider.cpp      |  13 +++
 .../access-control-server.cpp                 |   2 +-
 src/app/server/ArlStorage.cpp                 | 103 +++++++++++-------
 src/app/server/ArlStorage.h                   |  77 +++++++++++++
 src/app/server/DefaultArlStorage.cpp          |   5 +-
 src/lib/support/DefaultStorageKeyAllocator.h  |   2 +-
 9 files changed, 203 insertions(+), 61 deletions(-)

diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index df7fa6081331d4..ab0f6214fae068 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -186,6 +186,10 @@ Optional<app::Clusters::NetworkCommissioning::Instance> sWiFiNetworkCommissionin
 app::Clusters::NetworkCommissioning::Instance sEthernetNetworkCommissioningInstance(kRootEndpointId, &sEthernetDriver);
 #endif // CHIP_APP_MAIN_HAS_ETHERNET_DRIVER
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+auto exampleAccessRestrictionProvider = std::make_unique<ExampleAccessRestrictionProvider>();
+#endif
+
 void EnableThreadNetworkCommissioning()
 {
 #if CHIP_APP_MAIN_HAS_THREAD_DRIVER
@@ -600,20 +604,27 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl)
         LinuxDeviceOptions::GetInstance().mSimulateNoInternalTime);
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    if (LinuxDeviceOptions::GetInstance().accessRestrictionEntries.HasValue())
+    initParams.accessRestrictionProvider = exampleAccessRestrictionProvider.get();
+    initParams.arlStorage                = new app::DefaultArlStorage();
+#endif
+
+    // Init ZCL Data Model and CHIP App Server
+    Server::GetInstance().Init(initParams);
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    if (LinuxDeviceOptions::GetInstance().commissioningArlEntries.HasValue())
     {
-        auto exampleAccessRestrictionProvider = new ExampleAccessRestrictionProvider();
         exampleAccessRestrictionProvider->SetCommissioningEntries(
-            LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value());
+            LinuxDeviceOptions::GetInstance().commissioningArlEntries.Value());
+    }
 
-        initParams.accessRestrictionProvider = exampleAccessRestrictionProvider;
-        initParams.arlStorage                = new app::DefaultArlStorage();
+    if (LinuxDeviceOptions::GetInstance().arlEntries.HasValue())
+    {
+        //This example use of the ARL feature proactively installs the provided entries on fabric index 1
+        exampleAccessRestrictionProvider->SetEntries(1, LinuxDeviceOptions::GetInstance().arlEntries.Value());
     }
 #endif
 
-    // Init ZCL Data Model and CHIP App Server
-    Server::GetInstance().Init(initParams);
-
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     // Set ReadHandler Capacity for Subscriptions
     chip::app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions(
diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp
index abea582d1f1b43..c7e6c26ed8bb21 100644
--- a/examples/platform/linux/Options.cpp
+++ b/examples/platform/linux/Options.cpp
@@ -89,7 +89,8 @@ enum
     kDeviceOption_TraceLog,
     kDeviceOption_TraceDecode,
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    kDeviceOption_UseAccessRestrictions,
+    kDeviceOption_CommissioningArlEntries,
+    kDeviceOption_ArlEntries,
 #endif
     kOptionCSRResponseCSRIncorrectType,
     kOptionCSRResponseCSRNonceIncorrectType,
@@ -164,7 +165,8 @@ OptionDef sDeviceOptionDefs[] = {
     { "trace_decode", kArgumentRequired, kDeviceOption_TraceDecode },
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    { "enable-access-restrictions", kArgumentRequired, kDeviceOption_UseAccessRestrictions },
+    { "commissioning-arl-entries", kArgumentRequired, kDeviceOption_CommissioningArlEntries },
+    { "arl-entries", kArgumentRequired, kDeviceOption_ArlEntries },
 #endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     { "cert_error_csr_incorrect_type", kNoArgument, kOptionCSRResponseCSRIncorrectType },
     { "cert_error_csr_existing_keypair", kNoArgument, kOptionCSRResponseCSRExistingKeyPair },
@@ -293,9 +295,12 @@ const char * sDeviceOptionHelp =
     "       A value of 1 enables traces decoding, 0 disables this (default 0).\n"
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    "  --enable-access-restrictions <CommissioningARL JSON>\n"
-    "       Enable ACL cluster access restrictions with the provided JSON CommissioningARL. Example:\n"
-    "       \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 2,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 3}]}]\"\n"
+    "  --commissioning-arl-entries <CommissioningARL JSON>\n"
+    "       Enable ACL cluster access restrictions used during commissioning with the provided JSON. Example:\n"
+    "       \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 1105,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 0}]}]\"\n"
+    "  --arl-entries <ARL JSON>\n"
+    "       Enable ACL cluster access restrictions applied to fabric index 1 with the provided JSON. Example:\n"
+    "       \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 1105,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 0}]}]\"\n"
 #endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     "  --cert_error_csr_incorrect_type\n"
     "       Configure the CSRResponse to be built with an invalid CSR type.\n"
@@ -580,12 +585,21 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier,
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    case kDeviceOption_UseAccessRestrictions: {
-        std::vector<AccessRestrictionProvider::Entry> accessRestrictionEntries;
-        retval = ParseAccessRestrictionEntriesFromJson(aValue, accessRestrictionEntries);
+    case kDeviceOption_CommissioningArlEntries: {
+        std::vector<AccessRestrictionProvider::Entry> entries;
+        retval = ParseAccessRestrictionEntriesFromJson(aValue, entries);
         if (retval)
         {
-            LinuxDeviceOptions::GetInstance().accessRestrictionEntries.SetValue(std::move(accessRestrictionEntries));
+            LinuxDeviceOptions::GetInstance().commissioningArlEntries.SetValue(std::move(entries));
+        }
+    }
+    break;
+    case kDeviceOption_ArlEntries: {
+        std::vector<AccessRestrictionProvider::Entry> entries;
+        retval = ParseAccessRestrictionEntriesFromJson(aValue, entries);
+        if (retval)
+        {
+            LinuxDeviceOptions::GetInstance().arlEntries.SetValue(std::move(entries));
         }
     }
     break;
diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h
index 8fdacd8b64ea5c..11a9061efcade8 100644
--- a/examples/platform/linux/Options.h
+++ b/examples/platform/linux/Options.h
@@ -88,7 +88,8 @@ struct LinuxDeviceOptions
     int32_t subscriptionResumptionRetryIntervalSec = -1;
 #endif
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> accessRestrictionEntries;
+    chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> commissioningArlEntries;
+    chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> arlEntries;
 #endif
     static LinuxDeviceOptions & GetInstance();
 };
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index 0ef962ca9f939f..bcaed16afd71ce 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -85,11 +85,18 @@ CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector<
 
     mCommissioningEntries = entries;
 
+    for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
+    {
+        listener->CommissioningRestrictionListChanged();
+    }
+
     return CHIP_NO_ERROR;
 }
 
 CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, const std::vector<Entry> & entries)
 {
+    std::vector<Entry> updatedEntries;
+
     // check that the input entries are valid
     for (auto & entry : entries)
     {
@@ -98,8 +105,14 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
             ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry");
             return CHIP_ERROR_INVALID_ARGUMENT;
         }
+
+        Entry updatedEntry = entry;
+        updatedEntry.fabricIndex = fabricIndex;
+        updatedEntries.push_back(updatedEntry);
     }
 
+    mFabricEntries[fabricIndex] = std::move(updatedEntries);
+
     for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     {
         listener->RestrictionListChanged(fabricIndex);
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 99c460d4a49b38..4876e54652b2f0 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -481,7 +481,7 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder &
 
         for (auto & entry : entries)
         {
-            ArlStorage::EncodableEntry encodableEntry(entry);
+            ArlStorage::CommissioningEncodableEntry encodableEntry(entry);
             ReturnErrorOnFailure(encoder.Encode(encodableEntry));
         }
         return CHIP_NO_ERROR;
diff --git a/src/app/server/ArlStorage.cpp b/src/app/server/ArlStorage.cpp
index a0d6c38c855029..6193f59ff0075a 100644
--- a/src/app/server/ArlStorage.cpp
+++ b/src/app/server/ArlStorage.cpp
@@ -74,20 +74,10 @@ CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType
     return CHIP_NO_ERROR;
 }
 
-} // namespace
-
-namespace chip {
-namespace app {
-
-CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader)
+CHIP_ERROR ParseRestrictions(const chip::app::DataModel::DecodableList<Clusters::AccessControl::Structs::AccessRestrictionStruct::DecodableType> & source,
+                             std::vector<AccessRestrictionProvider::Restriction> & destination)
 {
-    ReturnErrorOnFailure(mStagingEntry.Decode(reader));
-
-    mEntry.fabricIndex    = mStagingEntry.GetFabricIndex();
-    mEntry.endpointNumber = mStagingEntry.endpoint;
-    mEntry.clusterId      = mStagingEntry.cluster;
-
-    auto iterator = mStagingEntry.restrictions.begin();
+    auto iterator = source.begin();
     while (iterator.Next())
     {
         auto & tmp = iterator.GetValue();
@@ -99,13 +89,61 @@ CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader)
             restriction.id.SetValue(tmp.id.Value());
         }
 
-        mEntry.restrictions.push_back(restriction);
+        destination.push_back(restriction);
     }
     ReturnErrorOnFailure(iterator.GetStatus());
 
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::Restriction> & source,
+                                  StagingRestriction destination[], size_t destinationCount)
+{
+    size_t count = source.size();
+    if (count > 0 && count <= destinationCount)
+    {
+        for (size_t i = 0; i < count; i++)
+        {
+            auto restriction = source[i];
+            ReturnErrorOnFailure(Convert(restriction.restrictionType, destination[i].type));
+
+            if (restriction.id.HasValue())
+            {
+                destination[i].id.SetNonNull(restriction.id.Value());
+            }
+        }
+    }
+    else
+    {
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+} // namespace
+
+namespace chip {
+namespace app {
+
+CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader)
+{
+    ReturnErrorOnFailure(mStagingEntry.Decode(reader));
+
+    mEntry.fabricIndex    = mStagingEntry.GetFabricIndex();
+    mEntry.endpointNumber = mStagingEntry.endpoint;
+    mEntry.clusterId      = mStagingEntry.cluster;
+    ReturnErrorOnFailure(ParseRestrictions(mStagingEntry.restrictions, mEntry.restrictions));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const
+{
+    ReturnErrorOnFailure(Stage());
+    ReturnErrorOnFailure(mStagingEntry.Encode(writer, tag));
+    return CHIP_NO_ERROR;
+}
+
 CHIP_ERROR ArlStorage::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const
 {
     ReturnErrorOnFailure(Stage());
@@ -120,36 +158,23 @@ CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, T
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ArlStorage::EncodableEntry::Stage() const
+CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Stage() const
 {
-    mStagingEntry.fabricIndex = mEntry.fabricIndex;
     mStagingEntry.endpoint    = mEntry.endpointNumber;
     mStagingEntry.cluster     = mEntry.clusterId;
+    ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0])));
+    mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size());
 
-    {
-        size_t count = mEntry.restrictions.size();
-        if (count > 0 && count <= CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY)
-        {
-            for (size_t i = 0; i < count; i++)
-            {
-                auto restriction = mEntry.restrictions[i];
-                StagingRestriction tmp;
-                ReturnErrorOnFailure(Convert(restriction.restrictionType, tmp.type));
-
-                if (restriction.id.HasValue())
-                {
-                    tmp.id.SetNonNull(restriction.id.Value());
-                }
+    return CHIP_NO_ERROR;
+}
 
-                mStagingRestrictions[i] = tmp;
-            }
-            mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, count);
-        }
-        else
-        {
-            return CHIP_ERROR_INVALID_ARGUMENT;
-        }
-    }
+CHIP_ERROR ArlStorage::EncodableEntry::Stage() const
+{
+    mStagingEntry.fabricIndex = mEntry.fabricIndex;
+    mStagingEntry.endpoint    = mEntry.endpointNumber;
+    mStagingEntry.cluster     = mEntry.clusterId;
+    ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0])));
+    mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size());
 
     return CHIP_NO_ERROR;
 }
diff --git a/src/app/server/ArlStorage.h b/src/app/server/ArlStorage.h
index c87f2e0d99a2f5..bf1f5e975b8990 100644
--- a/src/app/server/ArlStorage.h
+++ b/src/app/server/ArlStorage.h
@@ -42,6 +42,41 @@ namespace app {
 class ArlStorage
 {
 public:
+    /**
+     * Used for decoding commissioning access restriction entries.
+     *
+     * Typically used temporarily on the stack to decode:
+     * - source: TLV
+     * - staging: generated cluster level code
+     * - destination: system level access restriction entry
+     */
+    class CommissioningDecodableEntry
+    {
+        using Entry        = Access::AccessRestrictionProvider::Entry;
+        using StagingEntry = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::DecodableType;
+
+    public:
+        CommissioningDecodableEntry() = default;
+
+        /**
+         * Reader decodes into a staging entry, which is then unstaged
+         * into a member entry.
+         */
+        CHIP_ERROR Decode(TLV::TLVReader & reader);
+
+        Entry & GetEntry() { return mEntry; }
+
+        const Entry & GetEntry() const { return mEntry; }
+
+    public:
+        static constexpr bool kIsFabricScoped = false;
+
+    private:
+        Entry mEntry;
+
+        StagingEntry mStagingEntry;
+    };
+
     /**
      * Used for decoding access restriction entries.
      *
@@ -79,6 +114,48 @@ class ArlStorage
         StagingEntry mStagingEntry;
     };
 
+    /**
+     * Used for encoding commissionable access restriction entries.
+     *
+     * Typically used temporarily on the stack to encode:
+     * - source: system level access restriction entry
+     * - staging: generated cluster level code
+     * - destination: TLV
+     */
+    class CommissioningEncodableEntry
+    {
+        using Entry              = Access::AccessRestrictionProvider::Entry;
+        using StagingEntry       = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::Type;
+        using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type;
+
+    public:
+        CommissioningEncodableEntry(const Entry & entry) : mEntry(entry) {}
+
+        /**
+         * Constructor-provided entry is staged into a staging entry,
+         * which is then encoded into a writer.
+         */
+        CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const;
+
+        /**
+         * Constructor-provided entry is staged into a staging entry.
+         */
+        CHIP_ERROR Stage() const;
+
+        StagingEntry & GetStagingEntry() { return mStagingEntry; }
+
+        const StagingEntry & GetStagingEntry() const { return mStagingEntry; }
+
+    public:
+        static constexpr bool kIsFabricScoped = false;
+
+    private:
+        Entry mEntry;
+
+        mutable StagingEntry mStagingEntry;
+        mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY];
+    };
+
     /**
      * Used for encoding access restriction entries.
      *
diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp
index 9f43054a817d0f..2bc5e24a56246e 100644
--- a/src/app/server/DefaultArlStorage.cpp
+++ b/src/app/server/DefaultArlStorage.cpp
@@ -101,7 +101,7 @@ class : public Listener
         // first clear out any existing entries
         CHIP_ERROR err;
         size_t index = 0;
-        for (size_t index = 0; /**/; ++index)
+        for (index = 0; /**/; ++index)
         {
             err = mPersistentStorage->SyncDeleteKeyValue(
                 DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName());
@@ -114,6 +114,7 @@ class : public Listener
         const std::vector<Entry> * entries;
         SuccessOrExit(err = GetAccessControl().GetAccessRestrictionProvider()->GetEntries(fabricIndex, entries));
         VerifyOrExit(entries != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
+        index = 0;
         for (auto & entry : *entries)
         {
             uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
@@ -209,7 +210,7 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage
             entries.push_back(decodableEntry.GetEntry());
             count++;
         }
-        GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, commissioningEntries);
+        GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, entries);
     }
 
     ChipLogProgress(DataManagement, "DefaultArlStorage: %u commissioning entries loaded, %u fabric entries loaded",
diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h
index dce9975cd35f2b..8cc1bd67a5784c 100644
--- a/src/lib/support/DefaultStorageKeyAllocator.h
+++ b/src/lib/support/DefaultStorageKeyAllocator.h
@@ -132,7 +132,7 @@ class DefaultStorageKeyAllocator
 
     static StorageKeyName AccessControlCommissioningArlEntry(size_t index)
     {
-        return StorageKeyName::Formatted("g/car/0/%x", static_cast<unsigned>(index));
+        return StorageKeyName::Formatted("g/car/%x", static_cast<unsigned>(index));
     }
 
     static StorageKeyName AccessControlArlEntry(FabricIndex fabric, size_t index)

From 5a161e723ce7560f5a149746ddad720a2d2190df Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Thu, 22 Aug 2024 09:08:05 -0500
Subject: [PATCH 05/22] Reworked ARL storage

Previously ARL related data was persisted in KVS.  This has been
removed and now the responsibility for managing/maintaining the
related data (CommissioningARL and ARL attributes) is up to the
app to set on AccessRestrictionProvider class.
---
 examples/platform/linux/AppMain.cpp           |   2 -
 src/app/chip_data_model.gni                   |   6 +
 .../access-control-server/ArlEncoder.cpp}     | 123 ++++------
 .../access-control-server/ArlEncoder.h}       | 107 +-------
 .../access-control-server.cpp                 |  13 +-
 src/app/server/BUILD.gn                       |   9 -
 src/app/server/DefaultArlStorage.cpp          | 230 ------------------
 src/app/server/DefaultArlStorage.h            |  37 ---
 src/app/server/Server.cpp                     |  14 +-
 src/app/server/Server.h                       |  11 -
 10 files changed, 75 insertions(+), 477 deletions(-)
 rename src/app/{server/ArlStorage.cpp => clusters/access-control-server/ArlEncoder.cpp} (66%)
 rename src/app/{server/ArlStorage.h => clusters/access-control-server/ArlEncoder.h} (55%)
 delete mode 100644 src/app/server/DefaultArlStorage.cpp
 delete mode 100644 src/app/server/DefaultArlStorage.h

diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index ab0f6214fae068..ed9003fbe36c23 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -105,7 +105,6 @@
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
 #include "ExampleAccessRestrictionProvider.h"
-#include <app/server/DefaultArlStorage.h>
 #endif
 
 #if CHIP_DEVICE_LAYER_TARGET_DARWIN
@@ -605,7 +604,6 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl)
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
     initParams.accessRestrictionProvider = exampleAccessRestrictionProvider.get();
-    initParams.arlStorage                = new app::DefaultArlStorage();
 #endif
 
     // Init ZCL Data Model and CHIP App Server
diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni
index 3e4448a3bee467..397726914642c5 100644
--- a/src/app/chip_data_model.gni
+++ b/src/app/chip_data_model.gni
@@ -434,6 +434,12 @@ template("chip_data_model") {
           "${_app_root}/clusters/${cluster}/PresetStructWithOwnedMembers.h",
           "${_app_root}/clusters/${cluster}/thermostat-delegate.h",
         ]
+      } else if (cluster == "access-control-server") {
+        sources += [
+          "${_app_root}/clusters/${cluster}/${cluster}.cpp",
+          "${_app_root}/clusters/${cluster}/ArlEncoder.cpp",
+          "${_app_root}/clusters/${cluster}/ArlEncoder.h",
+        ]
       } else {
         sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ]
       }
diff --git a/src/app/server/ArlStorage.cpp b/src/app/clusters/access-control-server/ArlEncoder.cpp
similarity index 66%
rename from src/app/server/ArlStorage.cpp
rename to src/app/clusters/access-control-server/ArlEncoder.cpp
index 6193f59ff0075a..843a74553aacbc 100644
--- a/src/app/server/ArlStorage.cpp
+++ b/src/app/clusters/access-control-server/ArlEncoder.cpp
@@ -15,9 +15,7 @@
  *    limitations under the License.
  */
 
-#include <app/server/ArlStorage.h>
-
-#include <lib/support/DefaultStorageKeyAllocator.h>
+#include "ArlEncoder.h"
 
 using namespace chip;
 using namespace chip::app;
@@ -30,7 +28,38 @@ using StagingRestriction     = Clusters::AccessControl::Structs::AccessRestricti
 
 namespace {
 
-CHIP_ERROR Convert(StagingRestrictionType from, AccessRestrictionProvider::Type & to)
+CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::Restriction> & source,
+                                  StagingRestriction destination[], size_t destinationCount)
+{
+    size_t count = source.size();
+    if (count > 0 && count <= destinationCount)
+    {
+        for (size_t i = 0; i < count; i++)
+        {
+            auto restriction = source[i];
+            ReturnErrorOnFailure(ArlEncoder::Convert(restriction.restrictionType, destination[i].type));
+
+            if (restriction.id.HasValue())
+            {
+                destination[i].id.SetNonNull(restriction.id.Value());
+            }
+        }
+    }
+    else
+    {
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+} // namespace
+
+namespace chip {
+namespace app {
+
+CHIP_ERROR ArlEncoder::Convert(Clusters::AccessControl::AccessRestrictionTypeEnum from,
+                               Access::AccessRestrictionProvider::Type & to)
 {
     switch (from)
     {
@@ -52,7 +81,8 @@ CHIP_ERROR Convert(StagingRestrictionType from, AccessRestrictionProvider::Type
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType & to)
+CHIP_ERROR ArlEncoder::Convert(Access::AccessRestrictionProvider::Type from,
+                               Clusters::AccessControl::AccessRestrictionTypeEnum & to)
 {
     switch (from)
     {
@@ -74,106 +104,45 @@ CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ParseRestrictions(const chip::app::DataModel::DecodableList<Clusters::AccessControl::Structs::AccessRestrictionStruct::DecodableType> & source,
-                             std::vector<AccessRestrictionProvider::Restriction> & destination)
-{
-    auto iterator = source.begin();
-    while (iterator.Next())
-    {
-        auto & tmp = iterator.GetValue();
-        AccessRestrictionProvider::Restriction restriction;
-        ReturnErrorOnFailure(Convert(tmp.type, restriction.restrictionType));
-
-        if (!tmp.id.IsNull())
-        {
-            restriction.id.SetValue(tmp.id.Value());
-        }
-
-        destination.push_back(restriction);
-    }
-    ReturnErrorOnFailure(iterator.GetStatus());
-
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::Restriction> & source,
-                                  StagingRestriction destination[], size_t destinationCount)
-{
-    size_t count = source.size();
-    if (count > 0 && count <= destinationCount)
-    {
-        for (size_t i = 0; i < count; i++)
-        {
-            auto restriction = source[i];
-            ReturnErrorOnFailure(Convert(restriction.restrictionType, destination[i].type));
-
-            if (restriction.id.HasValue())
-            {
-                destination[i].id.SetNonNull(restriction.id.Value());
-            }
-        }
-    }
-    else
-    {
-        return CHIP_ERROR_INVALID_ARGUMENT;
-    }
-
-    return CHIP_NO_ERROR;
-}
-
-} // namespace
-
-namespace chip {
-namespace app {
-
-CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader)
-{
-    ReturnErrorOnFailure(mStagingEntry.Decode(reader));
-
-    mEntry.fabricIndex    = mStagingEntry.GetFabricIndex();
-    mEntry.endpointNumber = mStagingEntry.endpoint;
-    mEntry.clusterId      = mStagingEntry.cluster;
-    ReturnErrorOnFailure(ParseRestrictions(mStagingEntry.restrictions, mEntry.restrictions));
-    return CHIP_NO_ERROR;
-}
-
-CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const
+CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const
 {
     ReturnErrorOnFailure(Stage());
     ReturnErrorOnFailure(mStagingEntry.Encode(writer, tag));
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ArlStorage::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const
+CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const
 {
     ReturnErrorOnFailure(Stage());
     ReturnErrorOnFailure(mStagingEntry.EncodeForRead(writer, tag, fabric));
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const
+CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const
 {
     ReturnErrorOnFailure(Stage());
     ReturnErrorOnFailure(mStagingEntry.EncodeForWrite(writer, tag));
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Stage() const
+CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Stage() const
 {
-    mStagingEntry.endpoint    = mEntry.endpointNumber;
-    mStagingEntry.cluster     = mEntry.clusterId;
-    ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0])));
+    mStagingEntry.endpoint = mEntry.endpointNumber;
+    mStagingEntry.cluster  = mEntry.clusterId;
+    ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions,
+                                                sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0])));
     mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size());
 
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ArlStorage::EncodableEntry::Stage() const
+CHIP_ERROR ArlEncoder::EncodableEntry::Stage() const
 {
     mStagingEntry.fabricIndex = mEntry.fabricIndex;
     mStagingEntry.endpoint    = mEntry.endpointNumber;
     mStagingEntry.cluster     = mEntry.clusterId;
-    ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0])));
+    ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions,
+                                                sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0])));
     mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size());
 
     return CHIP_NO_ERROR;
diff --git a/src/app/server/ArlStorage.h b/src/app/clusters/access-control-server/ArlEncoder.h
similarity index 55%
rename from src/app/server/ArlStorage.h
rename to src/app/clusters/access-control-server/ArlEncoder.h
index bf1f5e975b8990..6832c739dfb3d6 100644
--- a/src/app/server/ArlStorage.h
+++ b/src/app/clusters/access-control-server/ArlEncoder.h
@@ -18,101 +18,28 @@
 #pragma once
 
 #include <access/AccessRestrictionProvider.h>
-#include <credentials/FabricTable.h>
-#include <lib/core/CHIPPersistentStorageDelegate.h>
-
 #include <app-common/zap-generated/cluster-objects.h>
+#include <credentials/FabricTable.h>
 
 namespace chip {
 namespace app {
 
 /**
- * Storage specifically for access restriction entries, which correspond to
- * the ARL attribute of the access control cluster.
- *
- * An object of this class should be initialized directly after the access
- * control module is initialized, as it will populate entries in the system
- * module from storage, and also install a listener in the system module to
- * keep storage up to date as entries change.
- *
- * This class also provides facilities for converting between access restriction
+ * This class provides facilities for converting between access restriction
  * entries (as used by the system module) and access restriction entries (as used
  * by the generated cluster code).
  */
-class ArlStorage
+class ArlEncoder
 {
 public:
-    /**
-     * Used for decoding commissioning access restriction entries.
-     *
-     * Typically used temporarily on the stack to decode:
-     * - source: TLV
-     * - staging: generated cluster level code
-     * - destination: system level access restriction entry
-     */
-    class CommissioningDecodableEntry
-    {
-        using Entry        = Access::AccessRestrictionProvider::Entry;
-        using StagingEntry = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::DecodableType;
-
-    public:
-        CommissioningDecodableEntry() = default;
-
-        /**
-         * Reader decodes into a staging entry, which is then unstaged
-         * into a member entry.
-         */
-        CHIP_ERROR Decode(TLV::TLVReader & reader);
+    ArlEncoder()  = default;
+    ~ArlEncoder() = default;
 
-        Entry & GetEntry() { return mEntry; }
+    static CHIP_ERROR Convert(Clusters::AccessControl::AccessRestrictionTypeEnum from,
+                              Access::AccessRestrictionProvider::Type & to);
 
-        const Entry & GetEntry() const { return mEntry; }
-
-    public:
-        static constexpr bool kIsFabricScoped = false;
-
-    private:
-        Entry mEntry;
-
-        StagingEntry mStagingEntry;
-    };
-
-    /**
-     * Used for decoding access restriction entries.
-     *
-     * Typically used temporarily on the stack to decode:
-     * - source: TLV
-     * - staging: generated cluster level code
-     * - destination: system level access restriction entry
-     */
-    class DecodableEntry
-    {
-        using Entry        = Access::AccessRestrictionProvider::Entry;
-        using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::DecodableType;
-
-    public:
-        DecodableEntry() = default;
-
-        /**
-         * Reader decodes into a staging entry, which is then unstaged
-         * into a member entry.
-         */
-        CHIP_ERROR Decode(TLV::TLVReader & reader);
-
-        Entry & GetEntry() { return mEntry; }
-
-        const Entry & GetEntry() const { return mEntry; }
-
-    public:
-        static constexpr bool kIsFabricScoped = true;
-
-        void SetFabricIndex(FabricIndex fabricIndex) { mEntry.fabricIndex = fabricIndex; }
-
-    private:
-        Entry mEntry;
-
-        StagingEntry mStagingEntry;
-    };
+    static CHIP_ERROR Convert(Access::AccessRestrictionProvider::Type from,
+                              Clusters::AccessControl::AccessRestrictionTypeEnum & to);
 
     /**
      * Used for encoding commissionable access restriction entries.
@@ -120,7 +47,6 @@ class ArlStorage
      * Typically used temporarily on the stack to encode:
      * - source: system level access restriction entry
      * - staging: generated cluster level code
-     * - destination: TLV
      */
     class CommissioningEncodableEntry
     {
@@ -162,7 +88,6 @@ class ArlStorage
      * Typically used temporarily on the stack to encode:
      * - source: system level access restriction entry
      * - staging: generated cluster level code
-     * - destination: TLV
      */
     class EncodableEntry
     {
@@ -205,20 +130,6 @@ class ArlStorage
         mutable StagingEntry mStagingEntry;
         mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY];
     };
-
-    virtual ~ArlStorage() = default;
-
-    /**
-     * Initialize should be called after chip::Access::AccessControl is initialized.
-     *
-     * Implementations should take this opportunity to populate AccessControl with ARL entries
-     * loaded from persistent storage. A half-open range of fabrics [first, last) is provided
-     * so this can be done on a per-fabric basis.
-     *
-     * Implementations should also install an entry change listener on AccessControl to maintain
-     * ARL entries in persistent storage as they are changed.
-     */
-    virtual CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) = 0;
 };
 
 } // namespace app
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 4876e54652b2f0..3707af022d3ff0 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -18,8 +18,8 @@
 #include <access/AccessControl.h>
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+#include "ArlEncoder.h"
 #include <access/AccessRestrictionProvider.h>
-#include <app/server/ArlStorage.h>
 #endif
 
 #include <app-common/zap-generated/cluster-objects.h>
@@ -481,7 +481,7 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder &
 
         for (auto & entry : entries)
         {
-            ArlStorage::CommissioningEncodableEntry encodableEntry(entry);
+            ArlEncoder::CommissioningEncodableEntry encodableEntry(entry);
             ReturnErrorOnFailure(encoder.Encode(encodableEntry));
         }
         return CHIP_NO_ERROR;
@@ -506,7 +506,7 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
             VerifyOrReturnError(entries != nullptr, CHIP_ERROR_INCORRECT_STATE);
             for (auto & entry : *entries)
             {
-                ArlStorage::EncodableEntry encodableEntry(entry);
+                ArlEncoder::EncodableEntry encodableEntry(entry);
                 ReturnErrorOnFailure(encoder.Encode(encodableEntry));
             }
         }
@@ -649,7 +649,12 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
         while (restrictionIter.Next())
         {
             AccessRestrictionProvider::Restriction restriction;
-            restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>(restrictionIter.GetValue().type);
+            if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR)
+            {
+                ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion");
+                return true;
+            }
+
             if (!restrictionIter.GetValue().id.IsNull())
             {
                 restriction.id.SetValue(restrictionIter.GetValue().id.Value());
diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn
index 1a82cd911f0c51..58524c3a648aab 100644
--- a/src/app/server/BUILD.gn
+++ b/src/app/server/BUILD.gn
@@ -80,13 +80,4 @@ static_library("server") {
           [ "${chip_root}/src/app/icd/server:default-check-in-back-off" ]
     }
   }
-
-  if (chip_enable_access_restrictions) {
-    sources += [
-      "ArlStorage.cpp",
-      "ArlStorage.h",
-      "DefaultArlStorage.cpp",
-      "DefaultArlStorage.h",
-    ]
-  }
 }
diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp
deleted file mode 100644
index 2bc5e24a56246e..00000000000000
--- a/src/app/server/DefaultArlStorage.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- *
- *    Copyright (c) 2024 Project CHIP Authors
- *
- *    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 <app/server/DefaultArlStorage.h>
-
-#include <access/AccessControl.h>
-#include <lib/support/DefaultStorageKeyAllocator.h>
-
-using namespace chip;
-using namespace chip::app;
-using namespace chip::Access;
-
-using EncodableEntry     = ArlStorage::EncodableEntry;
-using Entry              = AccessRestrictionProvider::Entry;
-using Listener           = AccessRestrictionProvider::Listener;
-using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type;
-using Restriction        = AccessRestrictionProvider::Restriction;
-
-namespace {
-
-/*
-Size calculation for TLV encoded entry.
-
-Because EncodeForWrite is used without an accessing fabric, the fabric index is
-not encoded. However, let's assume it is. This yields 17 bytes total overhead,
-but it's wise to add a few more for safety.
-
-Each restriction requires 11 bytes.
-
-DATA                                    C   T   L   V   NOTES
-structure (anonymous)                   1               0x15
-  field 0 endpoint                      1   1       1
-  field 1 cluster                       1   1       4
-  field 2 restrictions                  1   1
-    structure (anonymous)               1               per restriction
-      field 0 type                      1   1       1
-      field 1 id                        1   1       4
-    end structure                       1
-  end list                              1               0x18
-  field 254 fabric index                1   1       1   not written
-end structure                           1               0x18
-*/
-
-constexpr int kEncodedEntryOverheadBytes    = 17 + 8;
-constexpr int kEncodedEntryRestrictionBytes = 11 * CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY;
-constexpr int kEncodedEntryTotalBytes       = kEncodedEntryOverheadBytes + kEncodedEntryRestrictionBytes;
-
-class : public Listener
-{
-public:
-    void CommissioningRestrictionListChanged() override
-    {
-        // first clear out any existing entries
-        CHIP_ERROR err;
-        for (size_t index = 0; /**/; ++index)
-        {
-            err = mPersistentStorage->SyncDeleteKeyValue(
-                DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName());
-            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
-            {
-                break;
-            }
-        }
-
-        auto entries = GetAccessControl().GetAccessRestrictionProvider()->GetCommissioningEntries();
-        size_t index = 0;
-        for (auto & entry : entries)
-        {
-            uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
-            TLV::TLVWriter writer;
-            writer.Init(buffer);
-            EncodableEntry encodableEntry(entry);
-            SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag()));
-            SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
-                              DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index++).KeyName(), buffer,
-                              static_cast<uint16_t>(writer.GetLengthWritten())));
-        }
-
-        return;
-
-    exit:
-        ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format());
-    }
-
-    void RestrictionListChanged(FabricIndex fabricIndex) override
-    {
-        // first clear out any existing entries
-        CHIP_ERROR err;
-        size_t index = 0;
-        for (index = 0; /**/; ++index)
-        {
-            err = mPersistentStorage->SyncDeleteKeyValue(
-                DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName());
-            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
-            {
-                break;
-            }
-        }
-
-        const std::vector<Entry> * entries;
-        SuccessOrExit(err = GetAccessControl().GetAccessRestrictionProvider()->GetEntries(fabricIndex, entries));
-        VerifyOrExit(entries != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
-        index = 0;
-        for (auto & entry : *entries)
-        {
-            uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
-            TLV::TLVWriter writer;
-            writer.Init(buffer);
-            EncodableEntry encodableEntry(entry);
-            SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag()));
-            SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue(
-                              DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index++).KeyName(), buffer,
-                              static_cast<uint16_t>(writer.GetLengthWritten())));
-        }
-
-        return;
-
-    exit:
-        ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format());
-    }
-
-    void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
-                                         const char * redirectUrl) override
-    {}
-
-    // Must initialize before use.
-    void Init(PersistentStorageDelegate & persistentStorage) { mPersistentStorage = &persistentStorage; }
-
-private:
-    PersistentStorageDelegate * mPersistentStorage = nullptr;
-
-} sEntryListener;
-
-} // namespace
-
-namespace chip {
-namespace app {
-
-CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first,
-                                   ConstFabricIterator last)
-{
-    ChipLogProgress(DataManagement, "DefaultArlStorage: initializing");
-
-    CHIP_ERROR err;
-    size_t commissioningCount = 0;
-    size_t count              = 0;
-
-    std::vector<Entry> commissioningEntries;
-    for (size_t index = 0; /**/; ++index)
-    {
-        uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
-        uint16_t size                           = static_cast<uint16_t>(sizeof(buffer));
-        err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName(),
-                                                buffer, size);
-        if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
-        {
-            break;
-        }
-        SuccessOrExit(err);
-
-        TLV::TLVReader reader;
-        reader.Init(buffer, size);
-        SuccessOrExit(err = reader.Next());
-
-        DecodableEntry decodableEntry;
-        SuccessOrExit(err = decodableEntry.Decode(reader));
-
-        commissioningEntries.push_back(decodableEntry.GetEntry());
-        commissioningCount++;
-    }
-    GetAccessControl().GetAccessRestrictionProvider()->SetCommissioningEntries(commissioningEntries);
-
-    for (auto it = first; it != last; ++it)
-    {
-        std::vector<Entry> entries;
-        auto fabric = it->GetFabricIndex();
-        for (size_t index = 0; /**/; ++index)
-        {
-            uint8_t buffer[kEncodedEntryTotalBytes] = { 0 };
-            uint16_t size                           = static_cast<uint16_t>(sizeof(buffer));
-            err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlArlEntry(fabric, index).KeyName(),
-                                                    buffer, size);
-            if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
-            {
-                break;
-            }
-            SuccessOrExit(err);
-
-            TLV::TLVReader reader;
-            reader.Init(buffer, size);
-            SuccessOrExit(err = reader.Next());
-
-            DecodableEntry decodableEntry;
-            SuccessOrExit(err = decodableEntry.Decode(reader));
-
-            entries.push_back(decodableEntry.GetEntry());
-            count++;
-        }
-        GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, entries);
-    }
-
-    ChipLogProgress(DataManagement, "DefaultArlStorage: %u commissioning entries loaded, %u fabric entries loaded",
-                    (unsigned) commissioningCount, (unsigned) count);
-
-    sEntryListener.Init(persistentStorage);
-    GetAccessControl().GetAccessRestrictionProvider()->AddListener(sEntryListener);
-
-    return CHIP_NO_ERROR;
-
-exit:
-    ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format());
-    return err;
-}
-
-} // namespace app
-} // namespace chip
diff --git a/src/app/server/DefaultArlStorage.h b/src/app/server/DefaultArlStorage.h
deleted file mode 100644
index ebdc23bbf65913..00000000000000
--- a/src/app/server/DefaultArlStorage.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- *
- *    Copyright (c) 2024 Project CHIP Authors
- *
- *    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 <app/server/ArlStorage.h>
-
-namespace chip {
-namespace app {
-
-class DefaultArlStorage : public ArlStorage
-{
-public:
-    /**
-     * Initialize must be called. It loads ARL entries for all fabrics from persistent storage,
-     * then installs a listener for the access restriction system module to maintain ARL entries in
-     * persistent storage so they remain in sync with entries in the access restriction system module.
-     */
-    CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) override;
-};
-
-} // namespace app
-} // namespace chip
diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp
index 0484669f97b742..426e3c686572c0 100644
--- a/src/app/server/Server.cpp
+++ b/src/app/server/Server.cpp
@@ -177,20 +177,16 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams)
     SuccessOrExit(err = mAccessControl.Init(initParams.accessDelegate, sDeviceTypeResolver));
     Access::SetAccessControl(mAccessControl);
 
-    mAclStorage = initParams.aclStorage;
-    SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
-
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    if (initParams.accessRestrictionProvider != nullptr && initParams.arlStorage != nullptr)
+    if (initParams.accessRestrictionProvider != nullptr)
     {
-        mAccessRestrictionProvider = initParams.accessRestrictionProvider;
-        mArlStorage                = initParams.arlStorage;
-
-        mAccessControl.SetAccessRestrictionProvider(mAccessRestrictionProvider);
-        SuccessOrExit(err = mArlStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
+        mAccessControl.SetAccessRestrictionProvider(initParams.accessRestrictionProvider);
     }
 #endif
 
+    mAclStorage = initParams.aclStorage;
+    SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end()));
+
     mGroupsProvider = initParams.groupDataProvider;
     SetGroupDataProvider(mGroupsProvider);
 
diff --git a/src/app/server/Server.h b/src/app/server/Server.h
index d49850d52dfa73..27c850563f6e98 100644
--- a/src/app/server/Server.h
+++ b/src/app/server/Server.h
@@ -31,9 +31,6 @@
 #include <app/TestEventTriggerDelegate.h>
 #include <app/server/AclStorage.h>
 #include <app/server/AppDelegate.h>
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-#include <app/server/ArlStorage.h>
-#endif
 #include <app/server/CommissioningWindowManager.h>
 #include <app/server/DefaultAclStorage.h>
 #include <credentials/CertificateValidityPolicy.h>
@@ -171,9 +168,6 @@ struct ServerInitParams
     // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce
     // access restrictions that are managed by the device.
     Access::AccessRestrictionProvider * accessRestrictionProvider = nullptr;
-    // ARL storage: MUST be injected if MNGD feature enabled. Used to store ACL entries in
-    // persistent storage. Must NOT be initialized before being provided.
-    app::ArlStorage * arlStorage = nullptr;
 #endif
 
     // Network native params can be injected depending on the
@@ -692,11 +686,6 @@ class Server
     Access::AccessControl mAccessControl;
     app::AclStorage * mAclStorage;
 
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    Access::AccessRestrictionProvider * mAccessRestrictionProvider;
-    app::ArlStorage * mArlStorage;
-#endif
-
     TestEventTriggerDelegate * mTestEventTriggerDelegate;
     Crypto::OperationalKeystore * mOperationalKeystore;
     Credentials::OperationalCertificateStore * mOpCertStore;

From 459bee42fb4533fb95126636a676342de6648fe4 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Thu, 22 Aug 2024 10:06:54 -0500
Subject: [PATCH 06/22] Review fixes

cleanup ArlEncoder interface.
return error to client if arl review request fails
return token to client in FabricRestrictionReviewUpdate
---
 .../linux/ExampleAccessRestrictionProvider.h  |  2 +-
 .../access-control-server/ArlEncoder.h        | 38 +++++--------------
 .../access-control-server.cpp                 |  5 +--
 3 files changed, 13 insertions(+), 32 deletions(-)

diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h
index 28ca38a327a555..9346acdedae29a 100644
--- a/examples/platform/linux/ExampleAccessRestrictionProvider.h
+++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h
@@ -42,7 +42,7 @@ class ExampleAccessRestrictionProvider : public AccessRestrictionProvider
         // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events
         Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector<Entry>{});
 
-        chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .fabricIndex = fabricIndex };
+        chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, .fabricIndex = fabricIndex };
         EventNumber eventNumber;
         ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber));
 
diff --git a/src/app/clusters/access-control-server/ArlEncoder.h b/src/app/clusters/access-control-server/ArlEncoder.h
index 6832c739dfb3d6..ce0d6336c68c96 100644
--- a/src/app/clusters/access-control-server/ArlEncoder.h
+++ b/src/app/clusters/access-control-server/ArlEncoder.h
@@ -58,26 +58,16 @@ class ArlEncoder
         CommissioningEncodableEntry(const Entry & entry) : mEntry(entry) {}
 
         /**
-         * Constructor-provided entry is staged into a staging entry,
-         * which is then encoded into a writer.
+         * Encode the constructor-provided entry into the TLV writer.
          */
         CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const;
 
-        /**
-         * Constructor-provided entry is staged into a staging entry.
-         */
-        CHIP_ERROR Stage() const;
-
-        StagingEntry & GetStagingEntry() { return mStagingEntry; }
-
-        const StagingEntry & GetStagingEntry() const { return mStagingEntry; }
-
-    public:
         static constexpr bool kIsFabricScoped = false;
 
     private:
-        Entry mEntry;
+        CHIP_ERROR Stage() const;
 
+        Entry mEntry;
         mutable StagingEntry mStagingEntry;
         mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY];
     };
@@ -99,34 +89,26 @@ class ArlEncoder
         EncodableEntry(const Entry & entry) : mEntry(entry) {}
 
         /**
-         * Constructor-provided entry is staged into a staging entry,
-         * which is then encoded into a writer.
+         * Encode the constructor-provided entry into the TLV writer.
          */
         CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const;
 
         /**
-         * Constructor-provided entry is staged into a staging entry,
-         * which is then encoded into a writer.
+         * Encode the constructor-provided entry into the TLV writer.
          */
         CHIP_ERROR EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const;
 
+        FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; }
+
+        static constexpr bool kIsFabricScoped = true;
+
+    private:
         /**
          * Constructor-provided entry is staged into a staging entry.
          */
         CHIP_ERROR Stage() const;
 
-        StagingEntry & GetStagingEntry() { return mStagingEntry; }
-
-        const StagingEntry & GetStagingEntry() const { return mStagingEntry; }
-
-    public:
-        static constexpr bool kIsFabricScoped = true;
-
-        FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; }
-
-    private:
         Entry mEntry;
-
         mutable StagingEntry mStagingEntry;
         mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY];
     };
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 3707af022d3ff0..038905e44cf3fb 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -676,9 +676,8 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
     }
     else
     {
-        ChipLogError(DataManagement, "AccessControlCluster: restriction check failed: %" CHIP_ERROR_FORMAT, err.Format());
-
-        // return error to client?
+        ChipLogError(DataManagement, "AccessControlCluster: restriction review failed: %" CHIP_ERROR_FORMAT, err.Format());
+        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Failure);
     }
 
     return true;

From d0928e7eeee88cfba09388edce556f397f03f7f7 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Thu, 22 Aug 2024 11:03:02 -0500
Subject: [PATCH 07/22] Fixed GetEntries vector pointer arg

---
 src/access/AccessRestrictionProvider.h                    | 8 ++++----
 .../access-control-server/access-control-server.cpp       | 5 ++---
 2 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h
index 9178d8685a42f0..0b7be0095f748a 100644
--- a/src/access/AccessRestrictionProvider.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -116,7 +116,7 @@ class AccessRestrictionProvider
     AccessRestrictionProvider()          = default;
     virtual ~AccessRestrictionProvider() = default;
 
-    AccessRestrictionProvider(const AccessRestrictionProvider &)             = delete;
+    AccessRestrictionProvider(const AccessRestrictionProvider &) = delete;
     AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete;
 
     /**
@@ -184,9 +184,9 @@ class AccessRestrictionProvider
      * Get the restriction entries for a fabric.
      *
      * @param [in]  fabricIndex the index of the fabric for which to get entries.
-     * @param [out] entries reference to a const vector to hold the entries.
+     * @param [out] entries vector to hold the entries.
      */
-    CHIP_ERROR GetEntries(const FabricIndex fabricIndex, const std::vector<Entry> *& entries) const
+    CHIP_ERROR GetEntries(const FabricIndex fabricIndex, std::vector<Entry> & entries) const
     {
         auto it = mFabricEntries.find(fabricIndex);
         if (it == mFabricEntries.end())
@@ -194,7 +194,7 @@ class AccessRestrictionProvider
             return CHIP_ERROR_NOT_FOUND;
         }
 
-        entries = &(it->second);
+        entries = (it->second);
 
         return CHIP_NO_ERROR;
     }
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 038905e44cf3fb..3c1412d57a6401 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -501,10 +501,9 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
         {
             auto fabric = info.GetFabricIndex();
             // get entries for fabric
-            const std::vector<AccessRestrictionProvider::Entry> * entries = nullptr;
+            std::vector<AccessRestrictionProvider::Entry> entries;
             ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries));
-            VerifyOrReturnError(entries != nullptr, CHIP_ERROR_INCORRECT_STATE);
-            for (auto & entry : *entries)
+            for (auto & entry : entries)
             {
                 ArlEncoder::EncodableEntry encodableEntry(entry);
                 ReturnErrorOnFailure(encoder.Encode(encodableEntry));

From dcc85c70bd1bfa3fcb7ca971f019207dcb56fe41 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Thu, 22 Aug 2024 12:09:46 -0500
Subject: [PATCH 08/22] Updated core restriction logic/integration

---
 src/access/AccessControl.cpp             | 55 +++++++++++++++++-------
 src/access/AccessRestrictionProvider.cpp |  9 ++++
 src/access/AccessRestrictionProvider.h   | 20 +++++++++
 3 files changed, 68 insertions(+), 16 deletions(-)

diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index d7d597f46b2665..d6667e059ff1d2 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -356,28 +356,13 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
         VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT);
     }
 
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    if (mAccessRestrictionProvider != nullptr)
-    {
-        CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
-        if (result != CHIP_NO_ERROR)
-        {
-            ChipLogProgress(DataManagement, "AccessControl: %s",
-                            (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
-            return result;
-        }
-    }
-#endif
-
     {
         CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege);
         if (result != CHIP_ERROR_NOT_IMPLEMENTED)
         {
 #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0
             ChipLogProgress(DataManagement, "AccessControl: %s (delegate)",
-                            (result == CHIP_NO_ERROR)                  ? "allowed"
-                                : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied"
-                                                                       : "error");
+                            (result == CHIP_NO_ERROR) ? "allowed" : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error");
 #else
             if (result != CHIP_NO_ERROR)
             {
@@ -385,6 +370,19 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
                                 (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error");
             }
 #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+            if (result == CHIP_NO_ERROR && mAccessRestrictionProvider != nullptr)
+            {
+                result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
+                if (result != CHIP_NO_ERROR)
+                {
+                    ChipLogProgress(DataManagement, "AccessControl: %s",
+                                    (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
+                    return result;
+                }
+            }
+#endif
             return result;
         }
     }
@@ -397,6 +395,18 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
 #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1
         ChipLogProgress(DataManagement, "AccessControl: implicit admin (PASE)");
 #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+        if (mAccessRestrictionProvider != nullptr)
+        {
+            CHIP_ERROR result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath);
+            if (result != CHIP_NO_ERROR)
+            {
+                ChipLogProgress(DataManagement, "AccessControl: %s",
+                                (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
+                return result;
+            }
+        }
+#endif
         return CHIP_NO_ERROR;
     }
 
@@ -506,6 +516,19 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
         ChipLogProgress(DataManagement, "AccessControl: allowed");
 #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+        if (mAccessRestrictionProvider != nullptr)
+        {
+            CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
+            if (result != CHIP_NO_ERROR)
+            {
+                ChipLogProgress(DataManagement, "AccessControl: %s",
+                                (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
+                return result;
+            }
+        }
+#endif
+
         return CHIP_NO_ERROR;
     }
 
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index bcaed16afd71ce..a0caccc544081a 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -121,7 +121,16 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
+{
+    return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath);
+}
 CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
+{
+    return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath);
+}
+
+CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
 {
     ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType));
 
diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h
index 0b7be0095f748a..1880e62085ccfb 100644
--- a/src/access/AccessRestrictionProvider.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -148,6 +148,20 @@ class AccessRestrictionProvider
      */
     void RemoveListener(Listener & listener);
 
+    /**
+     * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action
+     * during commissioning by using the CommissioningEntries.
+     *
+     * These restrictions are are only a part of overall access evaluation.
+     *
+     * If access is not restricted, CHIP_NO_ERROR will be returned.
+     *
+     * @retval CHIP_ERROR_ACCESS_DENIED if access is denied.
+     * @retval other errors should also be treated as restricted/denied.
+     * @retval CHIP_NO_ERROR if access is not restricted/denied.
+     */
+    CHIP_ERROR CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath);
+
     /**
      * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action.
      * These restrictions are are only a part of overall access evaluation.
@@ -215,6 +229,12 @@ class AccessRestrictionProvider
 private:
     bool IsEntryValid(const Entry & entry) const;
 
+    /**
+     * Perform the access restriction check using the given entries.
+     */
+    CHIP_ERROR DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor,
+                       const RequestPath & requestPath);
+
     uint64_t mNextToken   = 1;
     Listener * mListeners = nullptr;
     std::vector<Entry> mCommissioningEntries;

From a3d130067988b0827d3efad7ec87257a7225f7e0 Mon Sep 17 00:00:00 2001
From: "Restyled.io" <commits@restyled.io>
Date: Thu, 22 Aug 2024 17:10:26 +0000
Subject: [PATCH 09/22] Restyled by clang-format

---
 examples/platform/linux/AppMain.cpp                       | 2 +-
 .../platform/linux/ExampleAccessRestrictionProvider.h     | 3 ++-
 src/access/AccessControl.cpp                              | 4 +++-
 src/access/AccessRestrictionProvider.cpp                  | 8 +++++---
 src/access/AccessRestrictionProvider.h                    | 2 +-
 5 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index ed9003fbe36c23..074f078af003b4 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -618,7 +618,7 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl)
 
     if (LinuxDeviceOptions::GetInstance().arlEntries.HasValue())
     {
-        //This example use of the ARL feature proactively installs the provided entries on fabric index 1
+        // This example use of the ARL feature proactively installs the provided entries on fabric index 1
         exampleAccessRestrictionProvider->SetEntries(1, LinuxDeviceOptions::GetInstance().arlEntries.Value());
     }
 #endif
diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h
index 9346acdedae29a..d3958266f62c0d 100644
--- a/examples/platform/linux/ExampleAccessRestrictionProvider.h
+++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h
@@ -42,7 +42,8 @@ class ExampleAccessRestrictionProvider : public AccessRestrictionProvider
         // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events
         Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector<Entry>{});
 
-        chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, .fabricIndex = fabricIndex };
+        chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token       = token,
+                                                                                               .fabricIndex = fabricIndex };
         EventNumber eventNumber;
         ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber));
 
diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index d6667e059ff1d2..5ebe98a4305a62 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -362,7 +362,9 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
         {
 #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0
             ChipLogProgress(DataManagement, "AccessControl: %s (delegate)",
-                            (result == CHIP_NO_ERROR) ? "allowed" : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error");
+                            (result == CHIP_NO_ERROR)                  ? "allowed"
+                                : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied"
+                                                                       : "error");
 #else
             if (result != CHIP_NO_ERROR)
             {
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index a0caccc544081a..cb0d61d81d88d3 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -106,7 +106,7 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
             return CHIP_ERROR_INVALID_ARGUMENT;
         }
 
-        Entry updatedEntry = entry;
+        Entry updatedEntry       = entry;
         updatedEntry.fabricIndex = fabricIndex;
         updatedEntries.push_back(updatedEntry);
     }
@@ -121,7 +121,8 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
+CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor,
+                                                            const RequestPath & requestPath)
 {
     return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath);
 }
@@ -130,7 +131,8 @@ CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDes
     return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath);
 }
 
-CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
+CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor,
+                                              const RequestPath & requestPath)
 {
     ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType));
 
diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h
index 1880e62085ccfb..8cec176678f489 100644
--- a/src/access/AccessRestrictionProvider.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -116,7 +116,7 @@ class AccessRestrictionProvider
     AccessRestrictionProvider()          = default;
     virtual ~AccessRestrictionProvider() = default;
 
-    AccessRestrictionProvider(const AccessRestrictionProvider &) = delete;
+    AccessRestrictionProvider(const AccessRestrictionProvider &)             = delete;
     AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete;
 
     /**

From 81effc2b763741abea71a7dae059f68337073408 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Thu, 22 Aug 2024 15:54:15 -0500
Subject: [PATCH 10/22] fixed include check for renamed
 AccessRestrictionProvider.h file

---
 scripts/tools/check_includes_config.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py
index efe66d57caed04..b5195f4ab05eda 100644
--- a/scripts/tools/check_includes_config.py
+++ b/scripts/tools/check_includes_config.py
@@ -185,5 +185,5 @@
     'src/app/icd/client/DefaultICDStorageKey.h': {'vector'},
     'src/controller/CHIPDeviceController.cpp': {'string'},
     'src/qrcodetool/setup_payload_commands.cpp': {'string'},
-    'src/access/AccessRestriction.h': {'vector', 'map'},
+    'src/access/AccessRestrictionProvider.h': {'vector', 'map'},
 }

From 1c34d9697b226ea81f580fca52a3136dbfc18a74 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Fri, 23 Aug 2024 13:51:21 -0500
Subject: [PATCH 11/22] M-ACL updates

- refactored AccessControl::Check into CheckACL and CheckARL
- added placeholders for the upcoming CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL
- extracted ARL exception processing to standalone class for better
  testing
---
 src/access/AccessControl.cpp             | 99 ++++++++++++++----------
 src/access/AccessControl.h               | 13 ++++
 src/access/AccessRestrictionProvider.cpp | 35 +++++++++
 src/access/AccessRestrictionProvider.h   | 38 +++++++++
 4 files changed, 142 insertions(+), 43 deletions(-)

diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index 5ebe98a4305a62..152f38cba60d65 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -333,10 +333,25 @@ bool AccessControl::IsAccessRestrictionListSupported() const
 }
 
 CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
-                                Privilege requestPrivilege)
+                                   Privilege requestPrivilege)
 {
     VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
 
+    CHIP_ERROR result = CheckACL(subjectDescriptor, requestPath, requestPrivilege);
+
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    if (result == CHIP_NO_ERROR)
+    {
+        result = CheckARL(subjectDescriptor, requestPath, requestPrivilege);
+    }
+#endif
+
+    return result;
+}
+
+CHIP_ERROR AccessControl::CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
+                                   Privilege requestPrivilege)
+{
 #if CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1
     {
         constexpr size_t kMaxCatsToLog = 6;
@@ -351,11 +366,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
     }
 #endif // CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1
 
-    if (IsAccessRestrictionListSupported())
-    {
-        VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT);
-    }
-
     {
         CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege);
         if (result != CHIP_ERROR_NOT_IMPLEMENTED)
@@ -373,18 +383,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
             }
 #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0
 
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-            if (result == CHIP_NO_ERROR && mAccessRestrictionProvider != nullptr)
-            {
-                result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
-                if (result != CHIP_NO_ERROR)
-                {
-                    ChipLogProgress(DataManagement, "AccessControl: %s",
-                                    (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
-                    return result;
-                }
-            }
-#endif
             return result;
         }
     }
@@ -397,18 +395,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
 #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1
         ChipLogProgress(DataManagement, "AccessControl: implicit admin (PASE)");
 #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-        if (mAccessRestrictionProvider != nullptr)
-        {
-            CHIP_ERROR result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath);
-            if (result != CHIP_NO_ERROR)
-            {
-                ChipLogProgress(DataManagement, "AccessControl: %s",
-                                (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
-                return result;
-            }
-        }
-#endif
         return CHIP_NO_ERROR;
     }
 
@@ -518,19 +504,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
         ChipLogProgress(DataManagement, "AccessControl: allowed");
 #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0
 
-#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-        if (mAccessRestrictionProvider != nullptr)
-        {
-            CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
-            if (result != CHIP_NO_ERROR)
-            {
-                ChipLogProgress(DataManagement, "AccessControl: %s",
-                                (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
-                return result;
-            }
-        }
-#endif
-
         return CHIP_NO_ERROR;
     }
 
@@ -539,6 +512,46 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con
     return CHIP_ERROR_ACCESS_DENIED;
 }
 
+#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
+                                   Privilege requestPrivilege)
+{
+    CHIP_ERROR result = CHIP_NO_ERROR;
+
+    VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT);
+
+    if (!IsAccessRestrictionListSupported())
+    {
+        // Access Restriction support is compiled in, but not configured/enabled. Nothing to restrict.
+        return CHIP_NO_ERROR;
+    }
+
+    // If we are in PASE or if there is a pending fabric, we need to check against the CommissioningARL
+    if (subjectDescriptor.authMode == AuthMode::kPase)
+    {
+        result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath);
+    }
+    else
+    {
+        result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath);
+    }
+
+    if (result != CHIP_NO_ERROR)
+    {
+        ChipLogProgress(DataManagement, "AccessControl: %s",
+#if 0
+                        //TODO: new error code coming with issue #35177
+                        (result == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL) ? "denied (restricted)" : "denied (restriction error)");
+#else
+                        (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
+#endif
+        return result;
+    }
+
+    return result;
+}
+#endif
+
 #if CHIP_ACCESS_CONTROL_DUMP_ENABLED
 CHIP_ERROR AccessControl::Dump(const Entry & entry)
 {
diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h
index 855efa04346a3a..df986864b8ff4c 100644
--- a/src/access/AccessControl.h
+++ b/src/access/AccessControl.h
@@ -674,6 +674,19 @@ class AccessControl
     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;
 
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index cb0d61d81d88d3..034fb836f28dce 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -121,11 +121,30 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
     return CHIP_NO_ERROR;
 }
 
+bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor,
+                                                                         const RequestPath & requestPath)
+{
+    if (requestPath.endpoint == 0 ||
+        requestPath.cluster == app::Clusters::NetworkCommissioning::Id ||
+        requestPath.cluster == app::Clusters::Descriptor::Id)
+    {
+        return true;
+    }
+
+    return false;
+}
+
 CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor,
                                                             const RequestPath & requestPath)
 {
+    if (mExceptionChecker.AreRestrictionsDisallowed(subjectDescriptor, requestPath))
+    {
+        return CHIP_NO_ERROR;
+    }
+
     return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath);
 }
+
 CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath)
 {
     return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath);
@@ -179,25 +198,41 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
                 if (requestPath.requestType == RequestType::kAttributeReadRequest ||
                     requestPath.requestType == RequestType::kAttributeWriteRequest)
                 {
+#if 0
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+#else
                     return CHIP_ERROR_ACCESS_DENIED;
+#endif
                 }
                 break;
             case Type::kAttributeWriteForbidden:
                 if (requestPath.requestType == RequestType::kAttributeWriteRequest)
                 {
+#if 0
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+#else
                     return CHIP_ERROR_ACCESS_DENIED;
+#endif
                 }
                 break;
             case Type::kCommandForbidden:
                 if (requestPath.requestType == RequestType::kCommandInvokeRequest)
                 {
+#if 0
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+#else
                     return CHIP_ERROR_ACCESS_DENIED;
+#endif
                 }
                 break;
             case Type::kEventForbidden:
                 if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
                 {
+#if 0
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+#else
                     return CHIP_ERROR_ACCESS_DENIED;
+#endif
                 }
                 break;
             }
diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h
index 8cec176678f489..7cedeb75cdbd52 100644
--- a/src/access/AccessRestrictionProvider.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -20,6 +20,7 @@
 
 #include "RequestPath.h"
 #include "SubjectDescriptor.h"
+#include "Privilege.h"
 #include <algorithm>
 #include <app-common/zap-generated/cluster-objects.h>
 #include <cstdint>
@@ -76,6 +77,42 @@ class AccessRestrictionProvider
         std::vector<Restriction> restrictions;
     };
 
+    /**
+     * Defines the interface for a checker for access restriction exceptions.
+     */
+    class AccessRestrictionExceptionChecker
+    {
+        public:
+            virtual ~AccessRestrictionExceptionChecker() = default;
+
+            /**
+             * Check if any restrictions are allowed to be applied to the given request.
+             *
+             * @retval true if restrictions may NOT be applied
+             */
+            virtual bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor,
+                                                   const RequestPath & requestPath) = 0;
+    };
+
+    /**
+     * Define a standard implementation of the AccessRestrictionExceptionChecker interface
+     * which is the default implementation used by AccessResrictionProvider.
+     */
+    class StandardAccessRestrictionExceptionChecker : public AccessRestrictionExceptionChecker
+    {
+        public:
+            StandardAccessRestrictionExceptionChecker() = default;
+            ~StandardAccessRestrictionExceptionChecker() = default;
+
+            /**
+             * Check if any restrictions are allowed to be applied to the given request.
+             *
+             * @retval true if restrictions may NOT be applied
+             */
+            bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor,
+                                           const RequestPath & requestPath) override;
+    };
+
     /**
      * Used to notify of changes in the access restriction list and active reviews.
      */
@@ -237,6 +274,7 @@ class AccessRestrictionProvider
 
     uint64_t mNextToken   = 1;
     Listener * mListeners = nullptr;
+    StandardAccessRestrictionExceptionChecker mExceptionChecker;
     std::vector<Entry> mCommissioningEntries;
     std::map<FabricIndex, std::vector<Entry>> mFabricEntries;
 };

From 9809516d97da84047a859a589fecccab21a6d3fd Mon Sep 17 00:00:00 2001
From: "tennessee.carmelveilleux@gmail.com"
 <tennessee.carmelveilleux@gmail.com>
Date: Fri, 23 Aug 2024 15:40:25 -0400
Subject: [PATCH 12/22] Add plumbing for subject descriptor IsCommissioning
 field

- Make session manager update that state on a message-per-message basis
- Add tests

Missing test: MRP test against a not-yet-committed fabric over CASE showing
that IsCommissioning is true.
---
 src/access/SubjectDescriptor.h                |  4 +
 src/credentials/FabricTable.cpp               | 79 ++++++++++++-------
 src/credentials/FabricTable.h                 | 17 ++++
 src/credentials/tests/TestFabricTable.cpp     | 18 +++++
 .../tests/TestReliableMessageProtocol.cpp     | 10 ++-
 src/transport/SecureSession.cpp               | 34 ++++++--
 src/transport/SecureSession.h                 | 10 +++
 src/transport/Session.h                       |  6 ++
 src/transport/SessionManager.cpp              |  7 ++
 src/transport/tests/TestSessionManager.cpp    | 10 ++-
 10 files changed, 158 insertions(+), 37 deletions(-)

diff --git a/src/access/SubjectDescriptor.h b/src/access/SubjectDescriptor.h
index ec6abec0b38a30..9cde4102750d25 100644
--- a/src/access/SubjectDescriptor.h
+++ b/src/access/SubjectDescriptor.h
@@ -42,6 +42,10 @@ struct SubjectDescriptor
 
     // CASE Authenticated Tags (CATs) only valid if auth mode is CASE.
     CATValues cats;
+
+    // Whether the subject is currently a pending commissionee. See `IsCommissioning`
+    // definition in Core Specification's ACL Architecture pseudocode.
+    bool isCommissioning = false;
 };
 
 } // namespace Access
diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp
index b847addbe10773..9a9eef9929c0db 100644
--- a/src/credentials/FabricTable.cpp
+++ b/src/credentials/FabricTable.cpp
@@ -71,6 +71,42 @@ constexpr size_t IndexInfoTLVMaxSize()
     return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1);
 }
 
+CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
+                                            const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
+{
+    VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
+
+    CHIP_ERROR err = CHIP_ERROR_INTERNAL;
+
+    Crypto::P256Keypair injectedOpKey;
+    Crypto::P256SerializedKeypair injectedOpKeysSerialized;
+
+    Crypto::P256Keypair * opKey = nullptr;
+    if (!opKeySpan.empty())
+    {
+        VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT);
+
+        memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size());
+        SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size()));
+        SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized));
+        opKey = &injectedOpKey;
+    }
+
+    SuccessOrExit(err = fabricTable.AddNewPendingTrustedRootCert(rootCert));
+    SuccessOrExit(err = fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey,
+                                                             /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex));
+    if (!leavePending)
+    {
+        SuccessOrExit(err = fabricTable.CommitPendingFabricData());
+    }
+exit:
+    if (err != CHIP_NO_ERROR)
+    {
+        fabricTable.RevertPendingFabricData();
+    }
+    return err;
+}
+
 } // anonymous namespace
 
 CHIP_ERROR FabricInfo::Init(const FabricInfo::InitParams & initParams)
@@ -695,34 +731,13 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabr
 CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
                                             const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
 {
-    VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
-
-    CHIP_ERROR err = CHIP_ERROR_INTERNAL;
-
-    Crypto::P256Keypair injectedOpKey;
-    Crypto::P256SerializedKeypair injectedOpKeysSerialized;
-
-    Crypto::P256Keypair * opKey = nullptr;
-    if (!opKeySpan.empty())
-    {
-        VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT);
-
-        memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size());
-        SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size()));
-        SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized));
-        opKey = &injectedOpKey;
-    }
+    return AddNewFabricForTestInternal(*this, /*leavePending=*/ false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
+}
 
-    SuccessOrExit(err = AddNewPendingTrustedRootCert(rootCert));
-    SuccessOrExit(err = AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey,
-                                                             /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex));
-    SuccessOrExit(err = CommitPendingFabricData());
-exit:
-    if (err != CHIP_NO_ERROR)
-    {
-        RevertPendingFabricData();
-    }
-    return err;
+CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
+                                                       const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
+{
+    return AddNewFabricForTestInternal(*this, /*leavePending=*/ true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
 }
 
 /*
@@ -1546,6 +1561,16 @@ bool FabricTable::SetPendingDataFabricIndex(FabricIndex fabricIndex)
     return isLegal;
 }
 
+FabricIndex FabricTable::GetPendingNewFabricIndex() const
+{
+    if (mStateFlags.Has(StateFlags::kIsAddPending))
+    {
+        return mFabricIndexWithPendingState;
+    }
+
+    return kUndefinedFabricIndex;
+}
+
 CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr)
 {
     // We can only manage commissionable pending fail-safe state if we have a keystore
diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h
index aef60f6a17fbbe..1bb13485873c9d 100644
--- a/src/credentials/FabricTable.h
+++ b/src/credentials/FabricTable.h
@@ -736,6 +736,18 @@ class DLL_EXPORT FabricTable
      */
     bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const;
 
+    /**
+     * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex in none are pending.
+     *
+     * A newly-added fabric is pending if AddNOC has been previously called successfully but the
+     * fabric is not yet fully committed by CommissioningComplete.
+     *
+     * NOTE: that this never returns a value other than kUndefinedFabricIndex when UpdateNOC is pending.
+     *
+     * @return the fabric index of the pending fabric, or kUndefinedFabricIndex if no fabrics are pending.
+     */
+    FabricIndex GetPendingNewFabricIndex() const;
+
     /**
      * @brief Returns the operational keystore. This is used for
      *        CASE and the only way the keystore should be used.
@@ -968,6 +980,11 @@ class DLL_EXPORT FabricTable
     CHIP_ERROR AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
                                    const ByteSpan & opKeySpan, FabricIndex * outFabricIndex);
 
+    // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will
+    // get copied (directly) into the fabric table. The fabric will NOT be committed, and will remain pending.
+    CHIP_ERROR AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
+                                              const ByteSpan & opKeySpan, FabricIndex * outFabricIndex);
+
     // Same as AddNewFabricForTest, but ignore if we are colliding with same <Root Public Key, Fabric Id>, so
     // that a single fabric table can have N nodes for same fabric. This usually works, but is bad form.
     CHIP_ERROR AddNewFabricForTestIgnoringCollisions(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
diff --git a/src/credentials/tests/TestFabricTable.cpp b/src/credentials/tests/TestFabricTable.cpp
index c5b78fc13e5b4f..0d34f48bce6599 100644
--- a/src/credentials/tests/TestFabricTable.cpp
+++ b/src/credentials/tests/TestFabricTable.cpp
@@ -551,6 +551,7 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow)
     FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
 
     EXPECT_EQ(fabricTable.FabricCount(), 0);
+    EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
 
     {
         FabricIndex nextFabricIndex = kUndefinedFabricIndex;
@@ -604,6 +605,7 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow)
             EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_NO_ERROR);
             EXPECT_TRUE(fetchedSpan.data_equal(rcac));
         }
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
 
         FabricIndex newFabricIndex = kUndefinedFabricIndex;
         bool keyIsExternallyOwned  = true;
@@ -614,6 +616,11 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow)
                   CHIP_NO_ERROR);
         EXPECT_EQ(newFabricIndex, 1);
         EXPECT_EQ(fabricTable.FabricCount(), 1);
+
+        // After adding the pending new fabric (equivalent of AddNOC processing), the new
+        // fabric must be pending.
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), 1);
+
         {
             // No more pending root cert; it's associated with a fabric now.
             MutableByteSpan fetchedSpan{ rcacBuf };
@@ -661,6 +668,9 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow)
             EXPECT_EQ(nextFabricIndex, 2);
         }
 
+        // Fabric can't be pending anymore.
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
+
         // Validate contents
         const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
         ASSERT_NE(fabricInfo, nullptr);
@@ -732,12 +742,16 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow)
         }
 
         EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
+
         FabricIndex newFabricIndex = kUndefinedFabricIndex;
 
         EXPECT_EQ(fabricTable.FabricCount(), 1);
         EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
         EXPECT_EQ(fabricTable.FabricCount(), 2);
         EXPECT_EQ(newFabricIndex, 2);
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), 2);
+
         // No storage yet
         EXPECT_EQ(storage.GetNumKeys(), numStorageAfterFirstAdd);
         // Next fabric index has not been updated yet.
@@ -1897,6 +1911,8 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe)
         uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
         MutableByteSpan csrSpan{ csrBuf };
 
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
+
         // Make sure to tag fabric index to pending opkey: otherwise the UpdateNOC fails
         EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan),
                   CHIP_NO_ERROR);
@@ -1908,6 +1924,7 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe)
 
         EXPECT_EQ(fabricTable.FabricCount(), 1);
         EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}), CHIP_NO_ERROR);
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
         EXPECT_EQ(fabricTable.FabricCount(), 1);
 
         // No storage yet
@@ -1936,6 +1953,7 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe)
         // Revert, should see Node ID 999 again
         fabricTable.RevertPendingFabricData();
         EXPECT_EQ(fabricTable.FabricCount(), 1);
+        EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex);
 
         EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd);
 
diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp
index 68b6ed2852d377..f655fb8e47319c 100644
--- a/src/messaging/tests/TestReliableMessageProtocol.cpp
+++ b/src/messaging/tests/TestReliableMessageProtocol.cpp
@@ -125,6 +125,9 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat
 
         EXPECT_EQ(buffer->TotalLength(), sizeof(PAYLOAD));
         EXPECT_EQ(memcmp(buffer->Start(), PAYLOAD, buffer->TotalLength()), 0);
+
+        mLastSubjectDescriptor = ec->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
+
         return CHIP_NO_ERROR;
     }
 
@@ -151,6 +154,8 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat
         }
     }
 
+    Access::SubjectDescriptor mLastSubjectDescriptor{};
+
     bool IsOnMessageReceivedCalled = false;
     bool mReceivedPiggybackAck     = false;
     bool mRetainExchange           = false;
@@ -1830,9 +1835,12 @@ TEST_F(TestReliableMessageProtocol, CheckApplicationResponseDelayed)
     EXPECT_EQ(loopback.mSentMessageCount, kMaxMRPTransmits);
     EXPECT_EQ(loopback.mDroppedMessageCount, kMaxMRPTransmits - 1);
     EXPECT_EQ(rm->TestGetCountRetransTable(), 1);        // We have no ack yet.
-    EXPECT_TRUE(mockReceiver.IsOnMessageReceivedCalled); // Other side got the message.
+    ASSERT_TRUE(mockReceiver.IsOnMessageReceivedCalled); // Other side got the message.
     EXPECT_FALSE(mockSender.IsOnMessageReceivedCalled);  // We did not get a response.
 
+    // It was not a commissioning CASE session so that is lined-up properly.
+    EXPECT_FALSE(mockReceiver.mLastSubjectDescriptor.isCommissioning);
+
     // Ensure there will be no more weirdness with acks and that our MRP timer is restarted properly.
     mockReceiver.SetDropAckResponse(false);
 
diff --git a/src/transport/SecureSession.cpp b/src/transport/SecureSession.cpp
index c96f9cdf908756..7694df2e2e3aca 100644
--- a/src/transport/SecureSession.cpp
+++ b/src/transport/SecureSession.cpp
@@ -160,10 +160,11 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const
     Access::SubjectDescriptor subjectDescriptor;
     if (IsOperationalNodeId(mPeerNodeId))
     {
-        subjectDescriptor.authMode    = Access::AuthMode::kCase;
-        subjectDescriptor.subject     = mPeerNodeId;
-        subjectDescriptor.cats        = mPeerCATs;
-        subjectDescriptor.fabricIndex = GetFabricIndex();
+        subjectDescriptor.authMode        = Access::AuthMode::kCase;
+        subjectDescriptor.subject         = mPeerNodeId;
+        subjectDescriptor.cats            = mPeerCATs;
+        subjectDescriptor.fabricIndex     = GetFabricIndex();
+        subjectDescriptor.isCommissioning = IsCommissioningSession();
     }
     else if (IsPAKEKeyId(mPeerNodeId))
     {
@@ -171,9 +172,10 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const
         // Initiator (aka commissioner) leaves subject descriptor unfilled.
         if (GetCryptoContext().IsResponder())
         {
-            subjectDescriptor.authMode    = Access::AuthMode::kPase;
-            subjectDescriptor.subject     = mPeerNodeId;
-            subjectDescriptor.fabricIndex = GetFabricIndex();
+            subjectDescriptor.authMode        = Access::AuthMode::kPase;
+            subjectDescriptor.subject         = mPeerNodeId;
+            subjectDescriptor.fabricIndex     = GetFabricIndex();
+            subjectDescriptor.isCommissioning = IsCommissioningSession();
         }
     }
     else
@@ -183,6 +185,24 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const
     return subjectDescriptor;
 }
 
+bool SecureSession::IsCommissioningSession() const
+{
+    // PASE session is always a commissioning session.
+    if (IsPASESession())
+    {
+        return true;
+    }
+
+    // CASE session is a commissioning session if it was marked as such.
+    // The SessionManager is what keeps track.
+    if (IsCASESession() && mIsCaseCommissioningSession)
+    {
+        return true;
+    }
+
+    return false;
+}
+
 void SecureSession::Retain()
 {
 #if CHIP_CONFIG_SECURE_SESSION_REFCOUNT_LOGGING
diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h
index fe70d6714e4a0f..b73ccf5b2793a7 100644
--- a/src/transport/SecureSession.h
+++ b/src/transport/SecureSession.h
@@ -156,6 +156,8 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec
 
     Access::SubjectDescriptor GetSubjectDescriptor() const override;
 
+    bool IsCommissioningSession() const override;
+
     bool AllowsMRP() const override { return GetPeerAddress().GetTransportType() == Transport::Type::kUdp; }
 
     bool AllowsLargePayload() const override { return GetPeerAddress().GetTransportType() == Transport::Type::kTcp; }
@@ -245,6 +247,12 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec
         }
     }
 
+    void SetCaseCommissioningSessionStatus(bool isCaseCommissioningSession)
+    {
+        VerifyOrDie(GetSecureSessionType() == Type::kCASE);
+        mIsCaseCommissioningSession = isCaseCommissioningSession;
+    }
+
     bool IsPeerActive() const
     {
         return ((System::SystemClock().GetMonotonicTimestamp() - GetLastPeerActivityTime()) <
@@ -340,6 +348,8 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec
     SessionParameters mRemoteSessionParams;
     CryptoContext mCryptoContext;
     SessionMessageCounter mSessionMessageCounter;
+
+    bool mIsCaseCommissioningSession = false;
 };
 
 } // namespace Transport
diff --git a/src/transport/Session.h b/src/transport/Session.h
index fc656c85ac12e5..e527a7887f2dd9 100644
--- a/src/transport/Session.h
+++ b/src/transport/Session.h
@@ -244,6 +244,12 @@ class Session
     virtual bool AllowsLargePayload() const                              = 0;
     virtual const SessionParameters & GetRemoteSessionParameters() const = 0;
     virtual System::Clock::Timestamp GetMRPBaseTimeout() const           = 0;
+
+    // Returns true if `subjectDescriptor.IsCommissioning` (based on Core Specification
+    // pseudocode in ACL Architecture chapter) should be true when computing a
+    // subject descriptor for that session.
+    virtual bool IsCommissioningSession() const { return false; }
+
     // GetAckTimeout is the estimate for how long it could take for the other
     // side to receive our message (accounting for our MRP retransmits if it
     // gets lost) and send a response.
diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp
index d9582479458617..78b822b19acd87 100644
--- a/src/transport/SessionManager.cpp
+++ b/src/transport/SessionManager.cpp
@@ -1024,6 +1024,13 @@ void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & partialPa
         MATTER_LOG_MESSAGE_RECEIVED(chip::Tracing::IncomingMessageType::kSecureUnicast, &payloadHeader, &packetHeader,
                                     secureSession, &peerAddress, chip::ByteSpan(msg->Start(), msg->TotalLength()));
         CHIP_TRACE_MESSAGE_RECEIVED(payloadHeader, packetHeader, secureSession, peerAddress, msg->Start(), msg->TotalLength());
+
+        // Always recompute whether a message is for a commissioning session based on the latest knowledge of
+        // the fabric table.
+        if (secureSession->IsCASESession())
+        {
+            secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() == mFabricTable->GetPendingNewFabricIndex());
+        }
         mCB->OnMessageReceived(packetHeader, payloadHeader, session.Value(), isDuplicate, std::move(msg));
     }
     else
diff --git a/src/transport/tests/TestSessionManager.cpp b/src/transport/tests/TestSessionManager.cpp
index 154071a56418c5..dfe3ca4fd5c5ff 100644
--- a/src/transport/tests/TestSessionManager.cpp
+++ b/src/transport/tests/TestSessionManager.cpp
@@ -28,6 +28,7 @@
 #define CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API // Up here in case some other header
                                               // includes SessionManager.h indirectly
 
+#include <access/SubjectDescriptor.h>
 #include <credentials/PersistentStorageOpCertStore.h>
 #include <credentials/tests/CHIPCert_unit_test_vectors.h>
 #include <crypto/DefaultSessionKeystore.h>
@@ -112,10 +113,12 @@ class TestSessMgrCallback : public SessionMessageDelegate
         }
 
         ReceiveHandlerCallCount++;
+        lastSubjectDescriptor = session->GetSubjectDescriptor();
     }
 
     int ReceiveHandlerCallCount = 0;
     bool LargeMessageSent       = false;
+    Access::SubjectDescriptor lastSubjectDescriptor{};
 };
 
 class TestSessionManager : public ::testing::Test
@@ -141,7 +144,7 @@ TEST_F(TestSessionManager, CheckSimpleInitTest)
                                   &fabricTableHolder.GetFabricTable(), sessionKeystore));
 }
 
-TEST_F(TestSessionManager, CheckMessageTest)
+TEST_F(TestSessionManager, CheckMessageOverPaseTest)
 {
     uint16_t payload_len = sizeof(PAYLOAD);
 
@@ -213,7 +216,10 @@ TEST_F(TestSessionManager, CheckMessageTest)
     EXPECT_EQ(err, CHIP_NO_ERROR);
 
     mContext.DrainAndServiceIO();
-    EXPECT_EQ(callback.ReceiveHandlerCallCount, 1);
+    ASSERT_EQ(callback.ReceiveHandlerCallCount, 1);
+
+    // This was a PASE session so we expect the subject descriptor to indicate it's for commissioning.
+    EXPECT_TRUE(callback.lastSubjectDescriptor.isCommissioning);
 
     // Let's send the max sized message and make sure it is received
     chip::System::PacketBufferHandle large_buffer = chip::MessagePacketBuffer::NewWithData(LARGE_PAYLOAD, kMaxAppMessageLen);

From 8ac75f8eb6139d32e12706fb885a9ca04e01082b Mon Sep 17 00:00:00 2001
From: "tennessee.carmelveilleux@gmail.com"
 <tennessee.carmelveilleux@gmail.com>
Date: Fri, 23 Aug 2024 16:53:07 -0400
Subject: [PATCH 13/22] Fix crash

---
 src/messaging/ExchangeContext.h                     | 2 +-
 src/messaging/tests/TestReliableMessageProtocol.cpp | 8 ++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/messaging/ExchangeContext.h b/src/messaging/ExchangeContext.h
index e10ef84ce911be..7d3aa146481195 100644
--- a/src/messaging/ExchangeContext.h
+++ b/src/messaging/ExchangeContext.h
@@ -163,7 +163,7 @@ class DLL_EXPORT ExchangeContext : public ReliableMessageContext,
         return std::move(sessionHandle.Value());
     }
 
-    bool HasSessionHandle() const { return mSession; }
+    bool HasSessionHandle() const { return (bool)mSession; }
 
     uint16_t GetExchangeId() const { return mExchangeId; }
 
diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp
index f655fb8e47319c..6390e4eca1920e 100644
--- a/src/messaging/tests/TestReliableMessageProtocol.cpp
+++ b/src/messaging/tests/TestReliableMessageProtocol.cpp
@@ -92,6 +92,12 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat
                                  System::PacketBufferHandle && buffer) override
     {
         IsOnMessageReceivedCalled = true;
+
+        if (ec->HasSessionHandle() && ec->GetSessionHolder()->IsSecureSession())
+        {
+            mLastSubjectDescriptor = ec->GetSessionHolder()->AsSecureSession()->GetSubjectDescriptor();
+        }
+
         if (payloadHeader.IsAckMsg())
         {
             mReceivedPiggybackAck = true;
@@ -126,8 +132,6 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat
         EXPECT_EQ(buffer->TotalLength(), sizeof(PAYLOAD));
         EXPECT_EQ(memcmp(buffer->Start(), PAYLOAD, buffer->TotalLength()), 0);
 
-        mLastSubjectDescriptor = ec->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
-
         return CHIP_NO_ERROR;
     }
 

From b151a0f7b8c636bea167e65dc3b6449a1d2128a6 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Fri, 23 Aug 2024 16:07:05 -0500
Subject: [PATCH 14/22] Use new IsCommissioning in ARL check

---
 examples/platform/linux/Options.cpp | 2 +-
 src/access/AccessControl.cpp        | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp
index c7e6c26ed8bb21..6ed9d95d45db3b 100644
--- a/examples/platform/linux/Options.cpp
+++ b/examples/platform/linux/Options.cpp
@@ -360,7 +360,7 @@ bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, std::vector<
         for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++)
         {
             AccessRestrictionProvider::Restriction restriction;
-            restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>((*rIt)["type"].asInt());
+            restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>((*rIt)["type"].asUInt());
             if ((*rIt).isMember("id"))
             {
                 restriction.id.SetValue((*rIt)["id"].asUInt());
diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index 152f38cba60d65..e84e223cf4f0ab 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -526,8 +526,7 @@ CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor,
         return CHIP_NO_ERROR;
     }
 
-    // If we are in PASE or if there is a pending fabric, we need to check against the CommissioningARL
-    if (subjectDescriptor.authMode == AuthMode::kPase)
+    if (subjectDescriptor.isCommissioning)
     {
         result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath);
     }

From 1d463a01d2522dd7ceb0468a54cb0f0b08e7b5d7 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Sun, 25 Aug 2024 11:19:22 -0500
Subject: [PATCH 15/22] Updates for review comments

---
 .../linux/ExampleAccessRestrictionProvider.h  |   2 +-
 examples/platform/linux/Options.cpp           |   1 +
 src/access/AccessControl.cpp                  |   4 +-
 src/access/AccessRestrictionProvider.cpp      |  61 +++++-----
 src/access/AccessRestrictionProvider.h        |  34 +++---
 src/access/RequestPath.h                      |   2 +-
 .../tests/TestAccessRestrictionProvider.cpp   |  16 +--
 src/app/EventManagement.cpp                   |   2 +-
 src/app/InteractionModelEngine.cpp            |   4 +-
 .../access-control-server/ArlEncoder.cpp      |   9 +-
 .../access-control-server/ArlEncoder.h        |   5 -
 .../access-control-server.cpp                 | 108 ++++++++----------
 src/app/reporting/Engine.cpp                  |   2 +-
 src/credentials/FabricTable.h                 |   2 +-
 src/lib/support/DefaultStorageKeyAllocator.h  |  10 --
 src/messaging/ExchangeContext.h               |   2 +-
 src/transport/SecureSession.h                 |   3 +-
 src/transport/Session.h                       |   3 +-
 18 files changed, 114 insertions(+), 156 deletions(-)

diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h
index d3958266f62c0d..731a8ae5845a21 100644
--- a/examples/platform/linux/ExampleAccessRestrictionProvider.h
+++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h
@@ -45,7 +45,7 @@ class ExampleAccessRestrictionProvider : public AccessRestrictionProvider
         chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token       = token,
                                                                                                .fabricIndex = fabricIndex };
         EventNumber eventNumber;
-        ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber));
+        ReturnErrorOnFailure(chip::app::LogEvent(event, kRootEndpointId, eventNumber));
 
         return CHIP_NO_ERROR;
     }
diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp
index 6ed9d95d45db3b..6f8afea5bb496b 100644
--- a/examples/platform/linux/Options.cpp
+++ b/examples/platform/linux/Options.cpp
@@ -585,6 +585,7 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier,
 #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
+    // TODO(#35189): change to use a path to JSON files instead
     case kDeviceOption_CommissioningArlEntries: {
         std::vector<AccessRestrictionProvider::Entry> entries;
         retval = ParseAccessRestrictionEntriesFromJson(aValue, entries);
diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index e84e223cf4f0ab..abb15b02da240d 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -181,7 +181,7 @@ char GetRequestTypeStringForLogging(RequestType requestType)
         return 'w';
     case RequestType::kCommandInvokeRequest:
         return 'i';
-    case RequestType::kEventReadOrSubscribeRequest:
+    case RequestType::kEventReadRequest:
         return 'e';
     default:
         return '?';
@@ -539,7 +539,7 @@ CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor,
     {
         ChipLogProgress(DataManagement, "AccessControl: %s",
 #if 0
-                        //TODO: new error code coming with issue #35177
+                        // TODO(#35177): new error code coming when access check plumbing are fixed in callers
                         (result == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL) ? "denied (restricted)" : "denied (restriction error)");
 #else
                         (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)");
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index 034fb836f28dce..6c6a4c6aadf342 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -73,10 +73,9 @@ void AccessRestrictionProvider::RemoveListener(Listener & listener)
 
 CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector<Entry> & entries)
 {
-    // check that the input entries are valid
     for (auto & entry : entries)
     {
-        if (!IsEntryValid(entry))
+        if (!mExceptionChecker.AreRestrictionsAllowed(entry.endpointNumber, entry.clusterId))
         {
             ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry");
             return CHIP_ERROR_INVALID_ARGUMENT;
@@ -87,7 +86,7 @@ CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector<
 
     for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     {
-        listener->CommissioningRestrictionListChanged();
+        listener->MarkCommissioningRestrictionListChanged();
     }
 
     return CHIP_NO_ERROR;
@@ -97,10 +96,9 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
 {
     std::vector<Entry> updatedEntries;
 
-    // check that the input entries are valid
     for (auto & entry : entries)
     {
-        if (!IsEntryValid(entry))
+        if (!mExceptionChecker.AreRestrictionsAllowed(entry.endpointNumber, entry.clusterId))
         {
             ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry");
             return CHIP_ERROR_INVALID_ARGUMENT;
@@ -115,18 +113,18 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
 
     for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     {
-        listener->RestrictionListChanged(fabricIndex);
+        listener->MarkRestrictionListChanged(fabricIndex);
     }
 
     return CHIP_NO_ERROR;
 }
 
-bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor,
-                                                                         const RequestPath & requestPath)
+bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster)
 {
-    if (requestPath.endpoint == 0 ||
-        requestPath.cluster == app::Clusters::NetworkCommissioning::Id ||
-        requestPath.cluster == app::Clusters::Descriptor::Id)
+    if (endpoint != 0 &&
+        (cluster == app::Clusters::WiFiNetworkManagement::Id ||
+         cluster == app::Clusters::ThreadBorderRouterManagement::Id ||
+         cluster == app::Clusters::ThreadNetworkDirectory::Id))
     {
         return true;
     }
@@ -137,11 +135,6 @@ bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRe
 CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor,
                                                             const RequestPath & requestPath)
 {
-    if (mExceptionChecker.AreRestrictionsDisallowed(subjectDescriptor, requestPath))
-    {
-        return CHIP_NO_ERROR;
-    }
-
     return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath);
 }
 
@@ -150,10 +143,16 @@ CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDes
     return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath);
 }
 
-CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor,
+CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> & entries, const SubjectDescriptor & subjectDescriptor,
                                               const RequestPath & requestPath)
 {
-    ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType));
+    if (!mExceptionChecker.AreRestrictionsAllowed(requestPath.endpoint, requestPath.cluster))
+    {
+        ChipLogProgress(DataManagement, "AccessRestrictionProvider: skipping checks for unrestrictable request path");
+        return CHIP_NO_ERROR;
+    }
+
+    ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", to_underlying(requestPath.requestType));
 
     if (requestPath.requestType == RequestType::kRequestTypeUnknown)
     {
@@ -166,7 +165,7 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
     // event id). All other requests require an entity id
     if (!requestPath.entityId.has_value())
     {
-        if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
+        if (requestPath.requestType == RequestType::kEventReadRequest)
         {
             return CHIP_NO_ERROR;
         }
@@ -176,7 +175,7 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
         }
     }
 
-    for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex])
+    for (auto & entry : entries)
     {
         if (entry.endpointNumber != requestPath.endpoint || entry.clusterId != requestPath.cluster)
         {
@@ -185,7 +184,7 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
 
         for (auto & restriction : entry.restrictions)
         {
-            // a missing id is a wildcard
+            // A missing id is a wildcard
             bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value();
             if (!idMatch)
             {
@@ -199,7 +198,8 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
                     requestPath.requestType == RequestType::kAttributeWriteRequest)
                 {
 #if 0
-                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+                    // TODO(#35177): use new ARL error code when access checks are fixed
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL;
 #else
                     return CHIP_ERROR_ACCESS_DENIED;
 #endif
@@ -209,7 +209,8 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
                 if (requestPath.requestType == RequestType::kAttributeWriteRequest)
                 {
 #if 0
-                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+                    // TODO(#35177): use new ARL error code when access checks are fixed
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL;
 #else
                     return CHIP_ERROR_ACCESS_DENIED;
 #endif
@@ -219,17 +220,19 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
                 if (requestPath.requestType == RequestType::kCommandInvokeRequest)
                 {
 #if 0
-                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+                    // TODO(#35177): use new ARL error code when access checks are fixed
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL;
 #else
                     return CHIP_ERROR_ACCESS_DENIED;
 #endif
                 }
                 break;
             case Type::kEventForbidden:
-                if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest)
+                if (requestPath.requestType == RequestType::kEventReadRequest)
                 {
 #if 0
-                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177
+                    // TODO(#35177): use new ARL error code when access checks are fixed
+                    return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL;
 #else
                     return CHIP_ERROR_ACCESS_DENIED;
 #endif
@@ -242,11 +245,5 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries,
     return CHIP_NO_ERROR;
 }
 
-bool AccessRestrictionProvider::IsEntryValid(const Entry & entry) const
-{
-    return entry.endpointNumber != 0 && entry.clusterId != app::Clusters::NetworkCommissioning::Id &&
-        entry.clusterId != app::Clusters::Descriptor::Id;
-}
-
 } // namespace Access
 } // namespace chip
diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h
index 7cedeb75cdbd52..77d8fb400255ea 100644
--- a/src/access/AccessRestrictionProvider.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -86,12 +86,12 @@ class AccessRestrictionProvider
             virtual ~AccessRestrictionExceptionChecker() = default;
 
             /**
-             * Check if any restrictions are allowed to be applied to the given request.
+             * Check if any restrictions are allowed to be applied on the given endpoint and cluster
+             * because of constraints against their use in ARLs.
              *
-             * @retval true if restrictions may NOT be applied
+             * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise
              */
-            virtual bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor,
-                                                   const RequestPath & requestPath) = 0;
+            virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0;
     };
 
     /**
@@ -104,13 +104,7 @@ class AccessRestrictionProvider
             StandardAccessRestrictionExceptionChecker() = default;
             ~StandardAccessRestrictionExceptionChecker() = default;
 
-            /**
-             * Check if any restrictions are allowed to be applied to the given request.
-             *
-             * @retval true if restrictions may NOT be applied
-             */
-            bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor,
-                                           const RequestPath & requestPath) override;
+            bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override;
     };
 
     /**
@@ -124,25 +118,25 @@ class AccessRestrictionProvider
         /**
          * Notifies of a change in the commissioning access restriction list.
          */
-        virtual void CommissioningRestrictionListChanged() = 0;
+        virtual void MarkCommissioningRestrictionListChanged() = 0;
 
         /**
          * Notifies of a change in the access restriction list.
          *
          * @param [in] fabricIndex  The index of the fabric in which the list has changed.
          */
-        virtual void RestrictionListChanged(FabricIndex fabricIndex) = 0;
+        virtual void MarkRestrictionListChanged(FabricIndex fabricIndex) = 0;
 
         /**
          * Notifies of an update to an active review with instructions and an optional redirect URL.
          *
          * @param [in] fabricIndex  The index of the fabric in which the entry has changed.
          * @param [in] token        The token of the review being updated (obtained from ReviewFabricRestrictionsResponse)
-         * @param [in] instruction  The instructions to be displayed to the user.
-         * @param [in] redirectUrl  An optional URL to redirect the user to for more information.  May be null.
+         * @param [in] instruction  Optional instructions to be displayed to the user.
+         * @param [in] redirectUrl  An optional URL to redirect the user to for more information.
          */
-        virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
-                                                     const char * redirectUrl) = 0;
+        virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction,
+                                                     Optional<CharSpan> redirectUrl) = 0;
 
     private:
         Listener * mNext = nullptr;
@@ -220,7 +214,7 @@ class AccessRestrictionProvider
      */
     CHIP_ERROR RequestFabricRestrictionReview(FabricIndex fabricIndex, const std::vector<Entry> & arl, uint64_t & token)
     {
-        token = ++mNextToken;
+        token = mNextToken++;
         return DoRequestFabricRestrictionReview(fabricIndex, token, arl);
     }
 
@@ -264,12 +258,10 @@ class AccessRestrictionProvider
                                                         const std::vector<Entry> & arl) = 0;
 
 private:
-    bool IsEntryValid(const Entry & entry) const;
-
     /**
      * Perform the access restriction check using the given entries.
      */
-    CHIP_ERROR DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor,
+    CHIP_ERROR DoCheck(const std::vector<Entry> & entries, const SubjectDescriptor & subjectDescriptor,
                        const RequestPath & requestPath);
 
     uint64_t mNextToken   = 1;
diff --git a/src/access/RequestPath.h b/src/access/RequestPath.h
index af791d73eb151d..920d5fed372eb1 100644
--- a/src/access/RequestPath.h
+++ b/src/access/RequestPath.h
@@ -30,7 +30,7 @@ enum class RequestType : uint8_t
     kAttributeReadRequest,
     kAttributeWriteRequest,
     kCommandInvokeRequest,
-    kEventReadOrSubscribeRequest
+    kEventReadRequest
 };
 
 struct RequestPath
diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp
index 073d3e6081a47c..f7db6927424e33 100644
--- a/src/access/tests/TestAccessRestrictionProvider.cpp
+++ b/src/access/tests/TestAccessRestrictionProvider.cpp
@@ -94,7 +94,7 @@ constexpr CheckData checkDataNoRestrictions[] = {
       .privilege         = Privilege::kAdminister,
       .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subject = kOperationalNodeId3 },
-      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
     // Checks for entry 0
@@ -111,7 +111,7 @@ constexpr CheckData checkDataNoRestrictions[] = {
       .privilege         = Privilege::kAdminister,
       .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
     // Checks for entry 1
@@ -128,7 +128,7 @@ constexpr CheckData checkDataNoRestrictions[] = {
       .privilege         = Privilege::kAdminister,
       .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
-      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 },
+      .requestPath       = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
 };
@@ -251,7 +251,7 @@ constexpr CheckData accessAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
       .requestPath       = { .cluster     = kOnOffCluster,
                              .endpoint    = 1,
-                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .requestType = RequestType::kEventReadRequest,
                              .entityId    = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
@@ -295,7 +295,7 @@ constexpr CheckData writeAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
       .requestPath       = { .cluster     = kOnOffCluster,
                              .endpoint    = 1,
-                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .requestType = RequestType::kEventReadRequest,
                              .entityId    = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
@@ -339,7 +339,7 @@ constexpr CheckData commandAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
       .requestPath       = { .cluster     = kOnOffCluster,
                              .endpoint    = 1,
-                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .requestType = RequestType::kEventReadRequest,
                              .entityId    = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
@@ -383,7 +383,7 @@ constexpr CheckData eventAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
       .requestPath       = { .cluster     = kOnOffCluster,
                              .endpoint    = 1,
-                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .requestType = RequestType::kEventReadRequest,
                              .entityId    = 1 },
       .privilege         = Privilege::kAdminister,
       .allow             = false },
@@ -439,7 +439,7 @@ constexpr CheckData combinedRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
       .requestPath       = { .cluster     = kOnOffCluster,
                              .endpoint    = 1,
-                             .requestType = RequestType::kEventReadOrSubscribeRequest,
+                             .requestType = RequestType::kEventReadRequest,
                              .entityId    = 5 },
       .privilege         = Privilege::kAdminister,
       .allow             = true },
diff --git a/src/app/EventManagement.cpp b/src/app/EventManagement.cpp
index 4c5faab3cdcda4..2419d564d0bc7a 100644
--- a/src/app/EventManagement.cpp
+++ b/src/app/EventManagement.cpp
@@ -556,7 +556,7 @@ CHIP_ERROR EventManagement::CheckEventContext(EventLoadOutContext * eventLoadOut
 
     Access::RequestPath requestPath{ .cluster     = event.mClusterId,
                                      .endpoint    = event.mEndpointId,
-                                     .requestType = Access::RequestType::kEventReadOrSubscribeRequest,
+                                     .requestType = Access::RequestType::kEventReadRequest,
                                      .entityId    = event.mEventId };
     Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path);
     CHIP_ERROR accessControlError =
diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp
index 64d30bc6c0e42f..88c1777b6c8224 100644
--- a/src/app/InteractionModelEngine.cpp
+++ b/src/app/InteractionModelEngine.cpp
@@ -544,7 +544,7 @@ static bool CanAccessEvent(const Access::SubjectDescriptor & aSubjectDescriptor,
 {
     Access::RequestPath requestPath{ .cluster     = aPath.mClusterId,
                                      .endpoint    = aPath.mEndpointId,
-                                     .requestType = Access::RequestType::kEventReadOrSubscribeRequest };
+                                     .requestType = Access::RequestType::kEventReadRequest };
     // leave requestPath.entityId optional value unset to indicate wildcard
     CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, aNeededPrivilege);
     return (err == CHIP_NO_ERROR);
@@ -555,7 +555,7 @@ static bool CanAccessEvent(const Access::SubjectDescriptor & aSubjectDescriptor,
 {
     Access::RequestPath requestPath{ .cluster     = aPath.mClusterId,
                                      .endpoint    = aPath.mEndpointId,
-                                     .requestType = Access::RequestType::kEventReadOrSubscribeRequest,
+                                     .requestType = Access::RequestType::kEventReadRequest,
                                      .entityId    = aPath.mEventId };
     CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, RequiredPrivilege::ForReadEvent(aPath));
     return (err == CHIP_NO_ERROR);
diff --git a/src/app/clusters/access-control-server/ArlEncoder.cpp b/src/app/clusters/access-control-server/ArlEncoder.cpp
index 843a74553aacbc..810c7345a13c7e 100644
--- a/src/app/clusters/access-control-server/ArlEncoder.cpp
+++ b/src/app/clusters/access-control-server/ArlEncoder.cpp
@@ -36,7 +36,7 @@ CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::R
     {
         for (size_t i = 0; i < count; i++)
         {
-            auto restriction = source[i];
+            const auto & restriction = source[i];
             ReturnErrorOnFailure(ArlEncoder::Convert(restriction.restrictionType, destination[i].type));
 
             if (restriction.id.HasValue())
@@ -118,13 +118,6 @@ CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TL
     return CHIP_NO_ERROR;
 }
 
-CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const
-{
-    ReturnErrorOnFailure(Stage());
-    ReturnErrorOnFailure(mStagingEntry.EncodeForWrite(writer, tag));
-    return CHIP_NO_ERROR;
-}
-
 CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Stage() const
 {
     mStagingEntry.endpoint = mEntry.endpointNumber;
diff --git a/src/app/clusters/access-control-server/ArlEncoder.h b/src/app/clusters/access-control-server/ArlEncoder.h
index ce0d6336c68c96..8050bf4d739db6 100644
--- a/src/app/clusters/access-control-server/ArlEncoder.h
+++ b/src/app/clusters/access-control-server/ArlEncoder.h
@@ -93,11 +93,6 @@ class ArlEncoder
          */
         CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const;
 
-        /**
-         * Encode the constructor-provided entry into the TLV writer.
-         */
-        CHIP_ERROR EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const;
-
         FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; }
 
         static constexpr bool kIsFabricScoped = true;
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 3c1412d57a6401..3a1bfb29cbb219 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -48,12 +48,7 @@ using EntryListener  = AccessControl::EntryListener;
 using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type;
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-using ArlChangedEvent = Clusters::AccessControl::Events::AccessRestrictionEntryChanged::Type;
 using ArlReviewEvent  = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type;
-
-constexpr uint16_t kMaxInstructionStringLength = 512;
-
-constexpr uint16_t kMaxRedirectUrlStringLength = 256;
 #endif
 
 // TODO(#13590): generated code doesn't automatically handle max length so do it manually
@@ -88,12 +83,12 @@ class AccessControlAttribute : public AttributeAccessInterface,
                         const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override;
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-    void CommissioningRestrictionListChanged() override;
+    void MarkCommissioningRestrictionListChanged() override;
 
-    void RestrictionListChanged(FabricIndex fabricIndex) override;
+    void MarkRestrictionListChanged(FabricIndex fabricIndex) override;
 
-    void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
-                                         const char * redirectUrl) override;
+    void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction,
+                                         Optional<CharSpan> redirectUrl) override;
 #endif
 
 private:
@@ -147,8 +142,8 @@ CHIP_ERROR CheckExtensionEntryDataFormat(const ByteSpan & data)
     TLV::TLVReader reader;
     reader.Init(data);
 
-    auto containerType = chip::TLV::kTLVType_List;
-    err                = reader.Next(containerType, chip::TLV::AnonymousTag());
+    auto containerType = TLV::kTLVType_List;
+    err                = reader.Next(containerType, TLV::AnonymousTag());
     VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
 
     err = reader.EnterContainer(containerType);
@@ -156,7 +151,7 @@ CHIP_ERROR CheckExtensionEntryDataFormat(const ByteSpan & data)
 
     while ((err = reader.Next()) == CHIP_NO_ERROR)
     {
-        VerifyOrReturnError(chip::TLV::IsProfileTag(reader.GetTag()), CHIP_IM_GLOBAL_STATUS(ConstraintError));
+        VerifyOrReturnError(TLV::IsProfileTag(reader.GetTag()), CHIP_IM_GLOBAL_STATUS(ConstraintError));
     }
     VerifyOrReturnError(err == CHIP_END_OF_TLV, CHIP_IM_GLOBAL_STATUS(ConstraintError));
 
@@ -471,18 +466,17 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes
 CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder)
 {
     auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider();
-    if (accessRestrictionProvider == nullptr)
-    {
-        return CHIP_ERROR_NOT_IMPLEMENTED;
-    }
 
     return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
-        auto entries = accessRestrictionProvider->GetCommissioningEntries();
-
-        for (auto & entry : entries)
+        if (accessRestrictionProvider != nullptr)
         {
-            ArlEncoder::CommissioningEncodableEntry encodableEntry(entry);
-            ReturnErrorOnFailure(encoder.Encode(encodableEntry));
+            auto entries = accessRestrictionProvider->GetCommissioningEntries();
+
+            for (auto & entry : entries)
+            {
+                ArlEncoder::CommissioningEncodableEntry encodableEntry(entry);
+                ReturnErrorOnFailure(encoder.Encode(encodableEntry));
+            }
         }
         return CHIP_NO_ERROR;
     });
@@ -491,72 +485,59 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder &
 CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
 {
     auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider();
-    if (accessRestrictionProvider == nullptr)
-    {
-        return CHIP_ERROR_NOT_IMPLEMENTED;
-    }
 
     return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR {
-        for (auto & info : Server::GetInstance().GetFabricTable())
+        if (accessRestrictionProvider != nullptr)
         {
-            auto fabric = info.GetFabricIndex();
-            // get entries for fabric
-            std::vector<AccessRestrictionProvider::Entry> entries;
-            ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries));
-            for (auto & entry : entries)
+            for (const auto & info : Server::GetInstance().GetFabricTable())
             {
-                ArlEncoder::EncodableEntry encodableEntry(entry);
-                ReturnErrorOnFailure(encoder.Encode(encodableEntry));
+                auto fabric = info.GetFabricIndex();
+                // get entries for fabric
+                std::vector<AccessRestrictionProvider::Entry> entries;
+                ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries));
+                for (const auto & entry : entries)
+                {
+                    ArlEncoder::EncodableEntry encodableEntry(entry);
+                    ReturnErrorOnFailure(encoder.Encode(encodableEntry));
+                }
             }
         }
         return CHIP_NO_ERROR;
     });
 }
-void AccessControlAttribute::CommissioningRestrictionListChanged()
+void AccessControlAttribute::MarkCommissioningRestrictionListChanged()
 {
-    MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id);
+    MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id);
 }
 
-void AccessControlAttribute::RestrictionListChanged(FabricIndex fabricIndex)
+void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex)
 {
-    CHIP_ERROR err;
-
-    MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id);
-
-    ArlChangedEvent event{ .fabricIndex = fabricIndex };
-
-    EventNumber eventNumber;
-    SuccessOrExit(err = LogEvent(event, 0, eventNumber));
-
-    return;
-
-exit:
-    ChipLogError(DataManagement, "AccessControlCluster: restriction event failed %" CHIP_ERROR_FORMAT, err.Format());
+    MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id);
 }
 
-void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction,
-                                                             const char * redirectUrl)
+void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction,
+                                                            Optional<CharSpan> redirectUrl)
 {
     CHIP_ERROR err;
     ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex };
 
-    if (instruction != nullptr)
+    if (instruction.HasValue())
     {
-        event.instruction.SetNonNull(chip::CharSpan(instruction, strnlen(instruction, kMaxInstructionStringLength)));
+        event.instruction.SetNonNull(instruction.Value());
     }
 
-    if (redirectUrl != nullptr)
+    if (redirectUrl.HasValue())
     {
-        event.redirectURL.SetNonNull(chip::CharSpan(redirectUrl, strnlen(redirectUrl, kMaxRedirectUrlStringLength)));
+        event.redirectURL.SetNonNull(redirectUrl.Value());
     }
 
     EventNumber eventNumber;
-    SuccessOrExit(err = LogEvent(event, 0, eventNumber));
+    SuccessOrExit(err = LogEvent(event, kRootEndpointId, eventNumber));
 
     return;
 
 exit:
-    ChipLogError(DataManagement, "AccessControlCluster: review event failed %" CHIP_ERROR_FORMAT, err.Format());
+    ChipLogError(DataManagement, "AccessControlCluster: review event failed: %" CHIP_ERROR_FORMAT, err.Format());
 }
 #endif
 
@@ -628,9 +609,10 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
     CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
     const Clusters::AccessControl::Commands::ReviewFabricRestrictions::DecodableType & commandData)
 {
-    if (commandPath.mEndpointId != 0)
+    if (commandPath.mEndpointId != kRootEndpointId)
     {
         ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request");
+        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
         return true;
     }
 
@@ -651,6 +633,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
             if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR)
             {
                 ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion");
+                commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
                 return true;
             }
 
@@ -664,6 +647,13 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
         entries.push_back(entry);
     }
 
+    if (entryIter.GetStatus() != CHIP_NO_ERROR)
+    {
+        ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data");
+        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
+        return true;
+    }
+
     CHIP_ERROR err = GetAccessControl().GetAccessRestrictionProvider()->RequestFabricRestrictionReview(
         commandObj->GetAccessingFabricIndex(), entries, token);
 
@@ -676,7 +666,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
     else
     {
         ChipLogError(DataManagement, "AccessControlCluster: restriction review failed: %" CHIP_ERROR_FORMAT, err.Format());
-        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Failure);
+        commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err));
     }
 
     return true;
diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp
index 6d79e9e7b34af0..99d038a8c35471 100644
--- a/src/app/reporting/Engine.cpp
+++ b/src/app/reporting/Engine.cpp
@@ -341,7 +341,7 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool &
 
         Access::RequestPath requestPath{ .cluster     = current->mValue.mClusterId,
                                          .endpoint    = current->mValue.mEndpointId,
-                                         .requestType = RequestType::kEventReadOrSubscribeRequest,
+                                         .requestType = RequestType::kEventReadRequest,
                                          .entityId    = current->mValue.mEventId };
         Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path);
 
diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h
index 1bb13485873c9d..af90d781283963 100644
--- a/src/credentials/FabricTable.h
+++ b/src/credentials/FabricTable.h
@@ -737,7 +737,7 @@ class DLL_EXPORT FabricTable
     bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const;
 
     /**
-     * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex in none are pending.
+     * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex if none are pending.
      *
      * A newly-added fabric is pending if AddNOC has been previously called successfully but the
      * fabric is not yet fully committed by CommissioningComplete.
diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h
index 8cc1bd67a5784c..9ed8a2f56cfd77 100644
--- a/src/lib/support/DefaultStorageKeyAllocator.h
+++ b/src/lib/support/DefaultStorageKeyAllocator.h
@@ -130,16 +130,6 @@ class DefaultStorageKeyAllocator
 
     static StorageKeyName AccessControlExtensionEntry(FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/ac/1", fabric); }
 
-    static StorageKeyName AccessControlCommissioningArlEntry(size_t index)
-    {
-        return StorageKeyName::Formatted("g/car/%x", static_cast<unsigned>(index));
-    }
-
-    static StorageKeyName AccessControlArlEntry(FabricIndex fabric, size_t index)
-    {
-        return StorageKeyName::Formatted("f/%x/ar/0/%x", fabric, static_cast<unsigned>(index));
-    }
-
     // Group Message Counters
     static StorageKeyName GroupDataCounter() { return StorageKeyName::FromConst("g/gdc"); }
     static StorageKeyName GroupControlCounter() { return StorageKeyName::FromConst("g/gcc"); }
diff --git a/src/messaging/ExchangeContext.h b/src/messaging/ExchangeContext.h
index 7d3aa146481195..e10ef84ce911be 100644
--- a/src/messaging/ExchangeContext.h
+++ b/src/messaging/ExchangeContext.h
@@ -163,7 +163,7 @@ class DLL_EXPORT ExchangeContext : public ReliableMessageContext,
         return std::move(sessionHandle.Value());
     }
 
-    bool HasSessionHandle() const { return (bool)mSession; }
+    bool HasSessionHandle() const { return mSession; }
 
     uint16_t GetExchangeId() const { return mExchangeId; }
 
diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h
index b73ccf5b2793a7..6dd8667cd63e95 100644
--- a/src/transport/SecureSession.h
+++ b/src/transport/SecureSession.h
@@ -331,6 +331,7 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec
     SecureSessionTable & mTable;
     State mState;
     const Type mSecureSessionType;
+    bool mIsCaseCommissioningSession = false;
     NodeId mLocalNodeId = kUndefinedNodeId;
     NodeId mPeerNodeId  = kUndefinedNodeId;
     CATValues mPeerCATs = CATValues{};
@@ -348,8 +349,6 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec
     SessionParameters mRemoteSessionParams;
     CryptoContext mCryptoContext;
     SessionMessageCounter mSessionMessageCounter;
-
-    bool mIsCaseCommissioningSession = false;
 };
 
 } // namespace Transport
diff --git a/src/transport/Session.h b/src/transport/Session.h
index e527a7887f2dd9..b49d461acb744c 100644
--- a/src/transport/Session.h
+++ b/src/transport/Session.h
@@ -247,7 +247,8 @@ class Session
 
     // Returns true if `subjectDescriptor.IsCommissioning` (based on Core Specification
     // pseudocode in ACL Architecture chapter) should be true when computing a
-    // subject descriptor for that session.
+    // subject descriptor for that session. This is only valid to call during
+    // synchronous processing of a message received on the session.
     virtual bool IsCommissioningSession() const { return false; }
 
     // GetAckTimeout is the estimate for how long it could take for the other

From b1ae8e3d4738cdfcc37827f80c10e6176060711e Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Sun, 25 Aug 2024 15:51:24 -0500
Subject: [PATCH 16/22] restyled

---
 src/access/AccessControl.cpp                  |  2 +-
 src/access/AccessRestrictionProvider.cpp      |  6 +--
 src/access/AccessRestrictionProvider.h        | 30 ++++++-------
 .../tests/TestAccessRestrictionProvider.cpp   | 45 +++++++------------
 .../access-control-server.cpp                 |  9 ++--
 src/credentials/FabricTable.cpp               | 19 ++++----
 src/transport/SecureSession.h                 |  6 +--
 src/transport/SessionManager.cpp              |  3 +-
 8 files changed, 55 insertions(+), 65 deletions(-)

diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp
index abb15b02da240d..8302fb0b122265 100644
--- a/src/access/AccessControl.cpp
+++ b/src/access/AccessControl.cpp
@@ -333,7 +333,7 @@ bool AccessControl::IsAccessRestrictionListSupported() const
 }
 
 CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
-                                   Privilege requestPrivilege)
+                                Privilege requestPrivilege)
 {
     VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
 
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index 6c6a4c6aadf342..1bf9175ce62fd9 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -119,11 +119,11 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
     return CHIP_NO_ERROR;
 }
 
-bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster)
+bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint,
+                                                                                                  ClusterId cluster)
 {
     if (endpoint != 0 &&
-        (cluster == app::Clusters::WiFiNetworkManagement::Id ||
-         cluster == app::Clusters::ThreadBorderRouterManagement::Id ||
+        (cluster == app::Clusters::WiFiNetworkManagement::Id || cluster == app::Clusters::ThreadBorderRouterManagement::Id ||
          cluster == app::Clusters::ThreadNetworkDirectory::Id))
     {
         return true;
diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h
index 77d8fb400255ea..705d9c365f8f1f 100644
--- a/src/access/AccessRestrictionProvider.h
+++ b/src/access/AccessRestrictionProvider.h
@@ -18,9 +18,9 @@
 
 #pragma once
 
+#include "Privilege.h"
 #include "RequestPath.h"
 #include "SubjectDescriptor.h"
-#include "Privilege.h"
 #include <algorithm>
 #include <app-common/zap-generated/cluster-objects.h>
 #include <cstdint>
@@ -82,16 +82,16 @@ class AccessRestrictionProvider
      */
     class AccessRestrictionExceptionChecker
     {
-        public:
-            virtual ~AccessRestrictionExceptionChecker() = default;
-
-            /**
-             * Check if any restrictions are allowed to be applied on the given endpoint and cluster
-             * because of constraints against their use in ARLs.
-             *
-             * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise
-             */
-            virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0;
+    public:
+        virtual ~AccessRestrictionExceptionChecker() = default;
+
+        /**
+         * Check if any restrictions are allowed to be applied on the given endpoint and cluster
+         * because of constraints against their use in ARLs.
+         *
+         * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise
+         */
+        virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0;
     };
 
     /**
@@ -100,11 +100,11 @@ class AccessRestrictionProvider
      */
     class StandardAccessRestrictionExceptionChecker : public AccessRestrictionExceptionChecker
     {
-        public:
-            StandardAccessRestrictionExceptionChecker() = default;
-            ~StandardAccessRestrictionExceptionChecker() = default;
+    public:
+        StandardAccessRestrictionExceptionChecker()  = default;
+        ~StandardAccessRestrictionExceptionChecker() = default;
 
-            bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override;
+        bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override;
     };
 
     /**
diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp
index f7db6927424e33..d0dc87d7dcf966 100644
--- a/src/access/tests/TestAccessRestrictionProvider.cpp
+++ b/src/access/tests/TestAccessRestrictionProvider.cpp
@@ -249,12 +249,9 @@ constexpr CheckData accessAttributeRestrictionTestData[] = {
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath       = { .cluster     = kOnOffCluster,
-                             .endpoint    = 1,
-                             .requestType = RequestType::kEventReadRequest,
-                             .entityId    = 1 },
-      .privilege         = Privilege::kAdminister,
-      .allow             = true },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
 };
 
 TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
@@ -293,12 +290,9 @@ constexpr CheckData writeAttributeRestrictionTestData[] = {
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath       = { .cluster     = kOnOffCluster,
-                             .endpoint    = 1,
-                             .requestType = RequestType::kEventReadRequest,
-                             .entityId    = 1 },
-      .privilege         = Privilege::kAdminister,
-      .allow             = true },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
 };
 
 TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
@@ -337,12 +331,9 @@ constexpr CheckData commandAttributeRestrictionTestData[] = {
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath       = { .cluster     = kOnOffCluster,
-                             .endpoint    = 1,
-                             .requestType = RequestType::kEventReadRequest,
-                             .entityId    = 1 },
-      .privilege         = Privilege::kAdminister,
-      .allow             = true },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
 };
 
 TEST_F(TestAccessRestriction, CommandRestrictionTest)
@@ -381,12 +372,9 @@ constexpr CheckData eventAttributeRestrictionTestData[] = {
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath       = { .cluster     = kOnOffCluster,
-                             .endpoint    = 1,
-                             .requestType = RequestType::kEventReadRequest,
-                             .entityId    = 1 },
-      .privilege         = Privilege::kAdminister,
-      .allow             = false },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
 };
 
 TEST_F(TestAccessRestriction, EventRestrictionTest)
@@ -437,12 +425,9 @@ constexpr CheckData combinedRestrictionTestData[] = {
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath       = { .cluster     = kOnOffCluster,
-                             .endpoint    = 1,
-                             .requestType = RequestType::kEventReadRequest,
-                             .entityId    = 5 },
-      .privilege         = Privilege::kAdminister,
-      .allow             = true },
+      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
       .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 3a1bfb29cbb219..e43aad01490b56 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -48,7 +48,7 @@ using EntryListener  = AccessControl::EntryListener;
 using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type;
 
 #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
-using ArlReviewEvent  = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type;
+using ArlReviewEvent = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type;
 #endif
 
 // TODO(#13590): generated code doesn't automatically handle max length so do it manually
@@ -507,7 +507,8 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder)
 }
 void AccessControlAttribute::MarkCommissioningRestrictionListChanged()
 {
-    MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id);
+    MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id,
+                                           AccessControlCluster::Attributes::CommissioningARL::Id);
 }
 
 void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex)
@@ -515,8 +516,8 @@ void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex)
     MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id);
 }
 
-void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction,
-                                                            Optional<CharSpan> redirectUrl)
+void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token,
+                                                             Optional<CharSpan> instruction, Optional<CharSpan> redirectUrl)
 {
     CHIP_ERROR err;
     ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex };
diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp
index 9a9eef9929c0db..f999cf7a4f9a9d 100644
--- a/src/credentials/FabricTable.cpp
+++ b/src/credentials/FabricTable.cpp
@@ -71,8 +71,9 @@ constexpr size_t IndexInfoTLVMaxSize()
     return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1);
 }
 
-CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
-                                            const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
+CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert,
+                                       const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan,
+                                       FabricIndex * outFabricIndex)
 {
     VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
 
@@ -93,8 +94,9 @@ CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePend
     }
 
     SuccessOrExit(err = fabricTable.AddNewPendingTrustedRootCert(rootCert));
-    SuccessOrExit(err = fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey,
-                                                             /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex));
+    SuccessOrExit(err =
+                      fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey,
+                                                                       /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex));
     if (!leavePending)
     {
         SuccessOrExit(err = fabricTable.CommitPendingFabricData());
@@ -731,13 +733,14 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabr
 CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
                                             const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
 {
-    return AddNewFabricForTestInternal(*this, /*leavePending=*/ false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
+    return AddNewFabricForTestInternal(*this, /*leavePending=*/false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
 }
 
-CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
-                                                       const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
+CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert,
+                                                       const ByteSpan & nocCert, const ByteSpan & opKeySpan,
+                                                       FabricIndex * outFabricIndex)
 {
-    return AddNewFabricForTestInternal(*this, /*leavePending=*/ true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
+    return AddNewFabricForTestInternal(*this, /*leavePending=*/true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex);
 }
 
 /*
diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h
index 6dd8667cd63e95..ba586a3095e3dd 100644
--- a/src/transport/SecureSession.h
+++ b/src/transport/SecureSession.h
@@ -332,9 +332,9 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec
     State mState;
     const Type mSecureSessionType;
     bool mIsCaseCommissioningSession = false;
-    NodeId mLocalNodeId = kUndefinedNodeId;
-    NodeId mPeerNodeId  = kUndefinedNodeId;
-    CATValues mPeerCATs = CATValues{};
+    NodeId mLocalNodeId              = kUndefinedNodeId;
+    NodeId mPeerNodeId               = kUndefinedNodeId;
+    CATValues mPeerCATs              = CATValues{};
     const uint16_t mLocalSessionId;
     uint16_t mPeerSessionId = 0;
 
diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp
index 78b822b19acd87..82287339117182 100644
--- a/src/transport/SessionManager.cpp
+++ b/src/transport/SessionManager.cpp
@@ -1029,7 +1029,8 @@ void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & partialPa
         // the fabric table.
         if (secureSession->IsCASESession())
         {
-            secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() == mFabricTable->GetPendingNewFabricIndex());
+            secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() ==
+                                                             mFabricTable->GetPendingNewFabricIndex());
         }
         mCB->OnMessageReceived(packetHeader, payloadHeader, session.Value(), isDuplicate, std::move(msg));
     }

From 7cf925cdeae665c2a60e3cd4b4d37972c3dc7cb3 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Mon, 26 Aug 2024 11:55:44 -0500
Subject: [PATCH 17/22] Review updates

- fixed return type for some command failures
- enhanced unit tests
---
 .../tests/TestAccessRestrictionProvider.cpp   | 231 ++++++++++++++----
 .../access-control-server.cpp                 |   4 +-
 2 files changed, 187 insertions(+), 48 deletions(-)

diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp
index d0dc87d7dcf966..b1908a4980ae63 100644
--- a/src/access/tests/TestAccessRestrictionProvider.cpp
+++ b/src/access/tests/TestAccessRestrictionProvider.cpp
@@ -22,9 +22,9 @@
 
 #include <pw_unit_test/framework.h>
 
+#include <app-common/zap-generated/ids/Attributes.h>
 #include <lib/core/CHIPCore.h>
 #include <lib/core/StringBuilderAdapters.h>
-
 namespace chip {
 namespace Access {
 
@@ -39,9 +39,14 @@ class TestAccessRestrictionProvider : public AccessRestrictionProvider
 AccessControl accessControl;
 TestAccessRestrictionProvider accessRestrictionProvider;
 
-constexpr ClusterId kNetworkCommissioningCluster = 0x0000'0031; // must not be blocked by access restrictions on any endpoint
-constexpr ClusterId kDescriptorCluster           = 0x0000'001d; // must not be blocked by access restrictions on any endpoint
-constexpr ClusterId kOnOffCluster                = 0x0000'0006;
+constexpr ClusterId kNetworkCommissioningCluster = app::Clusters::NetworkCommissioning::Id;
+constexpr ClusterId kDescriptorCluster           = app::Clusters::Descriptor::Id;
+constexpr ClusterId kOnOffCluster                = app::Clusters::OnOff::Id;
+
+// Clusters allowed to have restrictions
+constexpr ClusterId kWiFiNetworkManagementCluster  = app::Clusters::WiFiNetworkManagement::Id;
+constexpr ClusterId kThreadBorderRouterMgmtCluster = app::Clusters::ThreadBorderRouterManagement::Id;
+constexpr ClusterId kThreadNetworkDirectoryCluster = app::Clusters::ThreadNetworkDirectory::Id;
 
 constexpr NodeId kOperationalNodeId1 = 0x1111111111111111;
 constexpr NodeId kOperationalNodeId2 = 0x2222222222222222;
@@ -207,49 +212,103 @@ TEST_F(TestAccessRestriction, MetaTest)
     }
 }
 
-// ensure adding restrictons on endpoint 0 (any cluster) or for network commissioning and descriptor clusters fail
-TEST_F(TestAccessRestriction, InvalidRestrictionsTest)
+// ensure failure when adding restrictons on endpoint 0 (any cluster, including those allowed on other endpoints)
+TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointZeroTest)
 {
     std::vector<AccessRestrictionProvider::Entry> entries;
     AccessRestrictionProvider::Entry entry;
-    entry.fabricIndex = 1;
-    entry.clusterId   = kOnOffCluster;
+    entry.endpointNumber = 0;
+    entry.fabricIndex    = 1;
     entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
 
-    // must not restrict endpoint 0
-    entry.endpointNumber = 0;
+    entry.clusterId = kDescriptorCluster;
     entries.push_back(entry);
     EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
 
-    // must not restrict network commissioning cluster
     entries.clear();
-    entry.endpointNumber = 1;
-    entry.clusterId      = kNetworkCommissioningCluster;
+    entry.clusterId = kNetworkCommissioningCluster;
     entries.push_back(entry);
     EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
 
-    // must not restrict descriptor cluster
     entries.clear();
-    entry.clusterId = kDescriptorCluster;
+    entry.clusterId = kWiFiNetworkManagementCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
+
+    entries.clear();
+    entry.clusterId = kThreadBorderRouterMgmtCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
+
+    entries.clear();
+    entry.clusterId = kThreadNetworkDirectoryCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
+
+    // also test a cluster on endpoint 0 that isnt in the special allowed list
+    entries.clear();
+    entry.clusterId = kOnOffCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
+}
+
+// ensure no failure adding restrictions on endpoint 1 for allowed clusters only:
+// wifi network management, thread border router, thread network directory
+TEST_F(TestAccessRestriction, ValidRestrictionsOnEndpointOneTest)
+{
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
+    entry.endpointNumber = 1;
+    entry.fabricIndex    = 1;
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
+
+    entry.clusterId = kWiFiNetworkManagementCluster;
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
+
+    entries.clear();
+    entry.clusterId = kThreadBorderRouterMgmtCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
+
+    entries.clear();
+    entry.clusterId = kThreadNetworkDirectoryCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
+
+    // also test a cluster on endpoint 0 that isnt in the special allowed list
+    entries.clear();
+    entry.clusterId = kOnOffCluster;
+    entries.push_back(entry);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
+}
+
+TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointOneTest)
+{
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry;
+    entry.endpointNumber = 1;
+    entry.fabricIndex    = 1;
+    entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
+    entry.clusterId = kOnOffCluster;
     entries.push_back(entry);
     EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT);
 }
 
 constexpr CheckData accessAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
 };
@@ -260,7 +319,7 @@ TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
     AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
-    entry.clusterId      = kOnOffCluster;
+    entry.clusterId      = kWiFiNetworkManagementCluster;
     entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
 
     // test wildcarded entity id
@@ -278,19 +337,19 @@ TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
 
 constexpr CheckData writeAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
 };
@@ -301,7 +360,7 @@ TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
     AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
-    entry.clusterId      = kOnOffCluster;
+    entry.clusterId      = kWiFiNetworkManagementCluster;
     entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden });
 
     // test wildcarded entity id
@@ -319,19 +378,19 @@ TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
 
 constexpr CheckData commandAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
 };
@@ -342,7 +401,7 @@ TEST_F(TestAccessRestriction, CommandRestrictionTest)
     AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
-    entry.clusterId      = kOnOffCluster;
+    entry.clusterId      = kWiFiNetworkManagementCluster;
     entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
 
     // test wildcarded entity id
@@ -360,19 +419,19 @@ TEST_F(TestAccessRestriction, CommandRestrictionTest)
 
 constexpr CheckData eventAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
 };
@@ -383,7 +442,7 @@ TEST_F(TestAccessRestriction, EventRestrictionTest)
     AccessRestrictionProvider::Entry entry;
     entry.fabricIndex    = 1;
     entry.endpointNumber = 1;
-    entry.clusterId      = kOnOffCluster;
+    entry.clusterId      = kWiFiNetworkManagementCluster;
     entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kEventForbidden });
 
     // test wildcarded entity id
@@ -401,39 +460,39 @@ TEST_F(TestAccessRestriction, EventRestrictionTest)
 
 constexpr CheckData combinedRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
       .privilege   = Privilege::kAdminister,
       .allow       = false },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
-      .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 },
       .privilege   = Privilege::kAdminister,
       .allow       = true },
 };
@@ -445,7 +504,7 @@ TEST_F(TestAccessRestriction, CombinedRestrictionTest)
     AccessRestrictionProvider::Entry entry1;
     entry1.fabricIndex    = 1;
     entry1.endpointNumber = 1;
-    entry1.clusterId      = kOnOffCluster;
+    entry1.clusterId      = kWiFiNetworkManagementCluster;
     entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden });
     entry1.restrictions[0].id.SetValue(1);
     entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
@@ -459,7 +518,7 @@ TEST_F(TestAccessRestriction, CombinedRestrictionTest)
     AccessRestrictionProvider::Entry entry2;
     entry2.fabricIndex    = 2;
     entry2.endpointNumber = 1;
-    entry2.clusterId      = kOnOffCluster;
+    entry2.clusterId      = kWiFiNetworkManagementCluster;
     entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
     entry2.restrictions[0].id.SetValue(1);
     entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
@@ -470,5 +529,85 @@ TEST_F(TestAccessRestriction, CombinedRestrictionTest)
     RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData));
 }
 
+TEST_F(TestAccessRestriction, AttributeStorageSeperationTest)
+{
+    std::vector<AccessRestrictionProvider::Entry> commissioningEntries;
+    AccessRestrictionProvider::Entry entry1;
+    entry1.fabricIndex    = 1;
+    entry1.endpointNumber = 1;
+    entry1.clusterId      = kWiFiNetworkManagementCluster;
+    entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden });
+    entry1.restrictions[0].id.SetValue(1);
+    commissioningEntries.push_back(entry1);
+    EXPECT_EQ(accessRestrictionProvider.SetCommissioningEntries(commissioningEntries), CHIP_NO_ERROR);
+
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry2;
+    entry2.fabricIndex    = 2;
+    entry2.endpointNumber = 2;
+    entry2.clusterId      = kThreadBorderRouterMgmtCluster;
+    entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden });
+    entry2.restrictions[0].id.SetValue(2);
+    entries.push_back(entry2);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(2, entries), CHIP_NO_ERROR);
+
+    auto commissioningEntriesFetched = accessRestrictionProvider.GetCommissioningEntries();
+    std::vector<AccessRestrictionProvider::Entry> arlEntriesFetched;
+    EXPECT_EQ(accessRestrictionProvider.GetEntries(2, arlEntriesFetched), CHIP_NO_ERROR);
+
+    EXPECT_NE(commissioningEntriesFetched[0].fabricIndex, arlEntriesFetched[0].fabricIndex);
+    EXPECT_NE(commissioningEntriesFetched[0].endpointNumber, arlEntriesFetched[0].endpointNumber);
+    EXPECT_NE(commissioningEntriesFetched[0].clusterId, arlEntriesFetched[0].clusterId);
+    EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].restrictionType, arlEntriesFetched[0].restrictions[0].restrictionType);
+    EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].id, arlEntriesFetched[0].restrictions[0].id);
+}
+
+constexpr CheckData listSelectionDuringCommissioningData[] = {
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true },
+      .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false },
+      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = false },
+    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false },
+      .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
+      .privilege   = Privilege::kAdminister,
+      .allow       = true },
+};
+
+TEST_F(TestAccessRestriction, ListSelectiondDuringCommissioningTest)
+{
+    // during commissioning, read is allowed on WifiNetworkManagement and disallowed on ThreadBorderRouterMgmt
+    // after commissioning, read is disallowed on WifiNetworkManagement and allowed on ThreadBorderRouterMgmt
+
+    std::vector<AccessRestrictionProvider::Entry> entries;
+    AccessRestrictionProvider::Entry entry1;
+    entry1.fabricIndex    = 1;
+    entry1.endpointNumber = 1;
+    entry1.clusterId      = kThreadBorderRouterMgmtCluster;
+    entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
+    entry1.restrictions[0].id.SetValue(1);
+    entries.push_back(entry1);
+    EXPECT_EQ(accessRestrictionProvider.SetCommissioningEntries(entries), CHIP_NO_ERROR);
+
+    entries.clear();
+    AccessRestrictionProvider::Entry entry2;
+    entry2.fabricIndex    = 1;
+    entry2.endpointNumber = 1;
+    entry2.clusterId      = kWiFiNetworkManagementCluster;
+    entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden });
+    entry2.restrictions[0].id.SetValue(1);
+    entries.push_back(entry2);
+    EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
+
+    RunChecks(listSelectionDuringCommissioningData, ArraySize(listSelectionDuringCommissioningData));
+}
+
 } // namespace Access
 } // namespace chip
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index e43aad01490b56..9925cda8ede49a 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -613,7 +613,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
     if (commandPath.mEndpointId != kRootEndpointId)
     {
         ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request");
-        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
+        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand);
         return true;
     }
 
@@ -634,7 +634,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
             if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR)
             {
                 ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion");
-                commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
+                commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand);
                 return true;
             }
 

From d27dfb6f7d51caff31d1445467b5aef0c7ec6100 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Mon, 26 Aug 2024 11:57:27 -0500
Subject: [PATCH 18/22] restyled

---
 .../tests/TestAccessRestrictionProvider.cpp   | 281 ++++++++++++------
 1 file changed, 190 insertions(+), 91 deletions(-)

diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp
index b1908a4980ae63..73ccd2cdb47cd7 100644
--- a/src/access/tests/TestAccessRestrictionProvider.cpp
+++ b/src/access/tests/TestAccessRestrictionProvider.cpp
@@ -296,21 +296,33 @@ TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointOneTest)
 
 constexpr CheckData accessAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kCommandInvokeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
 };
 
 TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
@@ -337,21 +349,33 @@ TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest)
 
 constexpr CheckData writeAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kCommandInvokeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
 };
 
 TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
@@ -378,21 +402,33 @@ TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest)
 
 constexpr CheckData commandAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kCommandInvokeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
 };
 
 TEST_F(TestAccessRestriction, CommandRestrictionTest)
@@ -419,21 +455,33 @@ TEST_F(TestAccessRestriction, CommandRestrictionTest)
 
 constexpr CheckData eventAttributeRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kCommandInvokeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
 };
 
 TEST_F(TestAccessRestriction, EventRestrictionTest)
@@ -460,41 +508,68 @@ TEST_F(TestAccessRestriction, EventRestrictionTest)
 
 constexpr CheckData combinedRestrictionTestData[] = {
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 2 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 3 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 4 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 3 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kCommandInvokeRequest,
+                             .entityId    = 4 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kEventReadRequest,
+                             .entityId    = 5 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kCommandInvokeRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
     { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeWriteRequest,
+                             .entityId    = 2 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
 };
 
 TEST_F(TestAccessRestriction, CombinedRestrictionTest)
@@ -563,22 +638,46 @@ TEST_F(TestAccessRestriction, AttributeStorageSeperationTest)
 }
 
 constexpr CheckData listSelectionDuringCommissioningData[] = {
-    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
-    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true },
-      .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
-    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false },
-      .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = false },
-    { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false },
-      .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 },
-      .privilege   = Privilege::kAdminister,
-      .allow       = true },
+    { .subjectDescriptor = { .fabricIndex     = 1,
+                             .authMode        = AuthMode::kCase,
+                             .subject         = kOperationalNodeId1,
+                             .isCommissioning = true },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
+    { .subjectDescriptor = { .fabricIndex     = 1,
+                             .authMode        = AuthMode::kCase,
+                             .subject         = kOperationalNodeId1,
+                             .isCommissioning = true },
+      .requestPath       = { .cluster     = kThreadBorderRouterMgmtCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
+    { .subjectDescriptor = { .fabricIndex     = 1,
+                             .authMode        = AuthMode::kCase,
+                             .subject         = kOperationalNodeId1,
+                             .isCommissioning = false },
+      .requestPath       = { .cluster     = kWiFiNetworkManagementCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = false },
+    { .subjectDescriptor = { .fabricIndex     = 1,
+                             .authMode        = AuthMode::kCase,
+                             .subject         = kOperationalNodeId1,
+                             .isCommissioning = false },
+      .requestPath       = { .cluster     = kThreadBorderRouterMgmtCluster,
+                             .endpoint    = 1,
+                             .requestType = RequestType::kAttributeReadRequest,
+                             .entityId    = 1 },
+      .privilege         = Privilege::kAdminister,
+      .allow             = true },
 };
 
 TEST_F(TestAccessRestriction, ListSelectiondDuringCommissioningTest)

From cbcd55adead25f5487f13fae959478f1cf29ceae Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Mon, 26 Aug 2024 16:06:08 -0500
Subject: [PATCH 19/22] Updated ARL tests per review comments

---
 .../tests/TestAccessRestrictionProvider.cpp   | 24 +++++++++++++------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp
index 73ccd2cdb47cd7..ddf58ae2488f4d 100644
--- a/src/access/tests/TestAccessRestrictionProvider.cpp
+++ b/src/access/tests/TestAccessRestrictionProvider.cpp
@@ -52,6 +52,17 @@ constexpr NodeId kOperationalNodeId1 = 0x1111111111111111;
 constexpr NodeId kOperationalNodeId2 = 0x2222222222222222;
 constexpr NodeId kOperationalNodeId3 = 0x3333333333333333;
 
+bool operator==(const AccessRestrictionProvider::Restriction & lhs, const AccessRestrictionProvider::Restriction & rhs)
+{
+    return lhs.restrictionType == rhs.restrictionType && lhs.id == rhs.id;
+}
+
+bool operator==(const AccessRestrictionProvider::Entry & lhs, const AccessRestrictionProvider::Entry & rhs)
+{
+    return lhs.fabricIndex == rhs.fabricIndex && lhs.endpointNumber == rhs.endpointNumber && lhs.clusterId == rhs.clusterId &&
+        lhs.restrictions == rhs.restrictions;
+}
+
 struct AclEntryData
 {
     FabricIndex fabricIndex = kUndefinedFabricIndex;
@@ -275,7 +286,7 @@ TEST_F(TestAccessRestriction, ValidRestrictionsOnEndpointOneTest)
     entries.push_back(entry);
     EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR);
 
-    // also test a cluster on endpoint 0 that isnt in the special allowed list
+    // also test a cluster on endpoint 1 that isnt in the special allowed list
     entries.clear();
     entry.clusterId = kOnOffCluster;
     entries.push_back(entry);
@@ -629,12 +640,11 @@ TEST_F(TestAccessRestriction, AttributeStorageSeperationTest)
     auto commissioningEntriesFetched = accessRestrictionProvider.GetCommissioningEntries();
     std::vector<AccessRestrictionProvider::Entry> arlEntriesFetched;
     EXPECT_EQ(accessRestrictionProvider.GetEntries(2, arlEntriesFetched), CHIP_NO_ERROR);
-
-    EXPECT_NE(commissioningEntriesFetched[0].fabricIndex, arlEntriesFetched[0].fabricIndex);
-    EXPECT_NE(commissioningEntriesFetched[0].endpointNumber, arlEntriesFetched[0].endpointNumber);
-    EXPECT_NE(commissioningEntriesFetched[0].clusterId, arlEntriesFetched[0].clusterId);
-    EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].restrictionType, arlEntriesFetched[0].restrictions[0].restrictionType);
-    EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].id, arlEntriesFetched[0].restrictions[0].id);
+    EXPECT_EQ(commissioningEntriesFetched[0], entry1);
+    EXPECT_EQ(commissioningEntriesFetched.size(), static_cast<size_t>(1));
+    EXPECT_EQ(arlEntriesFetched[0], entry2);
+    EXPECT_EQ(arlEntriesFetched.size(), static_cast<size_t>(1));
+    EXPECT_FALSE(commissioningEntriesFetched[0] == arlEntriesFetched[0]);
 }
 
 constexpr CheckData listSelectionDuringCommissioningData[] = {

From 25bd6508cdb728bfafae60be730ca5f4270dd9f0 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Tue, 27 Aug 2024 09:23:59 -0500
Subject: [PATCH 20/22] work around nuttx and jsoncpp contention

---
 examples/platform/linux/BUILD.gn | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn
index b37e73f6debb9c..ca6f2056a78e0e 100644
--- a/examples/platform/linux/BUILD.gn
+++ b/examples/platform/linux/BUILD.gn
@@ -13,13 +13,16 @@
 # limitations under the License.
 
 import("//build_overrides/chip.gni")
-import("//build_overrides/jsoncpp.gni")
 import("${chip_root}/examples/common/pigweed/pigweed_rpcs.gni")
 import("${chip_root}/src/app/common_flags.gni")
 import("${chip_root}/src/lib/core/core.gni")
 import("${chip_root}/src/lib/lib.gni")
 import("${chip_root}/src/tracing/tracing_args.gni")
 
+if (current_os != "nuttx") {
+  import("//build_overrides/jsoncpp.gni")
+}
+
 declare_args() {
   chip_enable_smoke_co_trigger = false
   chip_enable_boolean_state_configuration_trigger = false

From 141c55574653c59938b015d3a12e07cedac9c43e Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Tue, 27 Aug 2024 10:27:20 -0500
Subject: [PATCH 21/22] Review comments and nuttx build failure fix attempt

---
 examples/platform/linux/BUILD.gn                           | 5 ++++-
 src/access/AccessRestrictionProvider.cpp                   | 2 +-
 .../access-control-server/access-control-server.cpp        | 7 +++++++
 3 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn
index ca6f2056a78e0e..e588afa752ac02 100644
--- a/examples/platform/linux/BUILD.gn
+++ b/examples/platform/linux/BUILD.gn
@@ -98,7 +98,6 @@ source_set("app-main") {
     "${chip_root}/src/controller:gen_check_chip_controller_headers",
     "${chip_root}/src/lib",
     "${chip_root}/src/platform/logging:default",
-    jsoncpp_root,
   ]
   deps = [
     ":ota-test-event-trigger",
@@ -106,6 +105,10 @@ source_set("app-main") {
     "${chip_root}/src/app/server",
   ]
 
+  if (current_os != "nuttx") {
+      public_deps += [ jsoncpp_root ]
+  }
+
   if (chip_enable_pw_rpc) {
     defines += [ "PW_RPC_ENABLED" ]
   }
diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp
index 1bf9175ce62fd9..23e8082353abc8 100644
--- a/src/access/AccessRestrictionProvider.cpp
+++ b/src/access/AccessRestrictionProvider.cpp
@@ -122,7 +122,7 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex,
 bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint,
                                                                                                   ClusterId cluster)
 {
-    if (endpoint != 0 &&
+    if (endpoint != kRootEndpointId &&
         (cluster == app::Clusters::WiFiNetworkManagement::Id || cluster == app::Clusters::ThreadBorderRouterManagement::Id ||
          cluster == app::Clusters::ThreadNetworkDirectory::Id))
     {
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 9925cda8ede49a..435ea33740e837 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -645,6 +645,13 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
             entry.restrictions.push_back(restriction);
         }
 
+        if (restrictionIter.GetStatus() != CHIP_NO_ERROR)
+        {
+            ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data");
+            commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
+            return true;
+        }
+
         entries.push_back(entry);
     }
 

From 3d976bf5843a465c7ad3837e380d46c2f5322fa1 Mon Sep 17 00:00:00 2001
From: Thomas Lea <thomas_lea@comcast.com>
Date: Tue, 27 Aug 2024 15:58:18 +0000
Subject: [PATCH 22/22] review updates

---
 examples/platform/linux/BUILD.gn                              | 2 +-
 .../clusters/access-control-server/access-control-server.cpp  | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn
index e588afa752ac02..336534aae65043 100644
--- a/examples/platform/linux/BUILD.gn
+++ b/examples/platform/linux/BUILD.gn
@@ -106,7 +106,7 @@ source_set("app-main") {
   ]
 
   if (current_os != "nuttx") {
-      public_deps += [ jsoncpp_root ]
+    public_deps += [ jsoncpp_root ]
   }
 
   if (chip_enable_pw_rpc) {
diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp
index 435ea33740e837..295b5535df2f95 100644
--- a/src/app/clusters/access-control-server/access-control-server.cpp
+++ b/src/app/clusters/access-control-server/access-control-server.cpp
@@ -648,7 +648,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
         if (restrictionIter.GetStatus() != CHIP_NO_ERROR)
         {
             ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data");
-            commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
+            commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand);
             return true;
         }
 
@@ -658,7 +658,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback(
     if (entryIter.GetStatus() != CHIP_NO_ERROR)
     {
         ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data");
-        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue);
+        commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand);
         return true;
     }