[Fabric-Sync] Integrate fabric bridge functionalities into fabric-sync (#36166)

diff --git a/examples/fabric-sync/bridge/BUILD.gn b/examples/fabric-sync/bridge/BUILD.gn
index cdf3671..310496b 100644
--- a/examples/fabric-sync/bridge/BUILD.gn
+++ b/examples/fabric-sync/bridge/BUILD.gn
@@ -40,7 +40,17 @@
 source_set("fabric-bridge-lib") {
   public_configs = [ ":config" ]
 
-  sources = [ "include/CHIPProjectAppConfig.h" ]
+  sources = [
+    "include/BridgedAdministratorCommissioning.h",
+    "include/BridgedDevice.h",
+    "include/BridgedDeviceBasicInformationImpl.h",
+    "include/BridgedDeviceManager.h",
+    "include/CHIPProjectAppConfig.h",
+    "src/BridgedAdministratorCommissioning.cpp",
+    "src/BridgedDevice.cpp",
+    "src/BridgedDeviceBasicInformationImpl.cpp",
+    "src/BridgedDeviceManager.cpp",
+  ]
 
   deps = [
     "${chip_root}/examples/fabric-sync/bridge:fabric-bridge-common",
diff --git a/examples/fabric-sync/bridge/include/BridgedAdministratorCommissioning.h b/examples/fabric-sync/bridge/include/BridgedAdministratorCommissioning.h
new file mode 100644
index 0000000..06fd902
--- /dev/null
+++ b/examples/fabric-sync/bridge/include/BridgedAdministratorCommissioning.h
@@ -0,0 +1,58 @@
+/*
+ *    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 <app-common/zap-generated/cluster-objects.h>
+#include <app/AttributeAccessInterfaceRegistry.h>
+
+/**
+ * @brief CADMIN cluster implementation for handling attribute interactions of bridged device endpoints.
+ *
+ * The current Administrator Commissioning Cluster server's zap generated code will automatically
+ * register an Attribute Access Interface for the root node endpoint implementation. In order to
+ * properly respond to a read attribute for bridged devices we are representing, we override the
+ * currently registered Attribute Interface such that we are first to receive any read attribute
+ * request on Administrator Commissioning Cluster, and if it is not an endpoint for a device we
+ * are a bridge for we redirect to the default cluster server implementation of Administrator
+ * Commissioning Cluster.
+ */
+class BridgedAdministratorCommissioning : public chip::app::AttributeAccessInterface
+{
+public:
+    // Register for the AdministratorCommissioning cluster on all endpoints.
+    BridgedAdministratorCommissioning() :
+        AttributeAccessInterface(chip::NullOptional, chip::app::Clusters::AdministratorCommissioning::Id)
+    {}
+
+    CHIP_ERROR Init();
+
+    CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & aPath, chip::app::AttributeValueEncoder & aEncoder) override;
+
+    // We do not allow writing to CADMIN attributes of a bridged device endpoint. We simply redirect
+    // write requests to the original attribute interface.
+    CHIP_ERROR Write(const chip::app::ConcreteDataAttributePath & aPath, chip::app::AttributeValueDecoder & aDecoder) override
+    {
+        VerifyOrDie(mOriginalAttributeInterface);
+        return mOriginalAttributeInterface->Write(aPath, aDecoder);
+    }
+
+private:
+    // If mOriginalAttributeInterface is removed from here, the class description needs to be updated
+    // to reflect this change.
+    chip::app::AttributeAccessInterface * mOriginalAttributeInterface = nullptr;
+};
diff --git a/examples/fabric-sync/bridge/include/BridgedDevice.h b/examples/fabric-sync/bridge/include/BridgedDevice.h
new file mode 100644
index 0000000..d2c5a64
--- /dev/null
+++ b/examples/fabric-sync/bridge/include/BridgedDevice.h
@@ -0,0 +1,92 @@
+/*
+ *
+ *    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 <app-common/zap-generated/cluster-objects.h>
+#include <app/util/attribute-storage.h>
+
+#include <string>
+
+class BridgedDevice
+{
+public:
+    /// Defines all attributes that we keep track of for a bridged device
+    struct BridgedAttributes
+    {
+        std::string uniqueId;
+        std::string vendorName;
+        uint16_t vendorId = 0;
+        std::string productName;
+        uint16_t productId = 0;
+        std::string nodeLabel;
+        uint16_t hardwareVersion = 0;
+        std::string hardwareVersionString;
+        uint32_t softwareVersion = 0;
+        std::string softwareVersionString;
+    };
+
+    struct AdminCommissioningAttributes
+    {
+        chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum commissioningWindowStatus =
+            chip::app::Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen;
+        std::optional<chip::FabricIndex> openerFabricIndex = std::nullopt;
+        std::optional<chip::VendorId> openerVendorId       = std::nullopt;
+    };
+
+    BridgedDevice(chip::ScopedNodeId scopedNodeId);
+    virtual ~BridgedDevice() = default;
+
+    [[nodiscard]] bool IsReachable() const { return mReachable; }
+    void SetReachable(bool reachable);
+    // Reachability attribute changed and requires marking attribute as dirty and sending
+    // event.
+    void ReachableChanged(bool reachable);
+
+    void LogActiveChangeEvent(uint32_t promisedActiveDurationMs);
+
+    [[nodiscard]] bool IsIcd() const { return mIsIcd; }
+    void SetIcd(bool icd) { mIsIcd = icd; }
+
+    inline void SetEndpointId(chip::EndpointId id) { mEndpointId = id; };
+    inline chip::EndpointId GetEndpointId() { return mEndpointId; };
+    inline chip::ScopedNodeId GetScopedNodeId() { return mScopedNodeId; };
+    inline void SetParentEndpointId(chip::EndpointId id) { mParentEndpointId = id; };
+    inline chip::EndpointId GetParentEndpointId() { return mParentEndpointId; };
+
+    [[nodiscard]] const BridgedAttributes & GetBridgedAttributes() const { return mAttributes; }
+    void SetBridgedAttributes(const BridgedAttributes & value) { mAttributes = value; }
+
+    void SetAdminCommissioningAttributes(const AdminCommissioningAttributes & aAdminCommissioningAttributes);
+    const AdminCommissioningAttributes & GetAdminCommissioningAttributes() const { return mAdminCommissioningAttributes; }
+
+    /// Convenience method to set just the unique id of a bridged device as it
+    /// is one of the few attributes that is not always bulk-set
+    void SetUniqueId(const std::string & value) { mAttributes.uniqueId = value; }
+
+protected:
+    bool mReachable = false;
+    bool mIsIcd     = false;
+
+    chip::ScopedNodeId mScopedNodeId;
+    chip::EndpointId mEndpointId       = 0;
+    chip::EndpointId mParentEndpointId = 0;
+
+    BridgedAttributes mAttributes;
+    AdminCommissioningAttributes mAdminCommissioningAttributes;
+};
diff --git a/examples/fabric-sync/bridge/include/BridgedDeviceBasicInformationImpl.h b/examples/fabric-sync/bridge/include/BridgedDeviceBasicInformationImpl.h
new file mode 100644
index 0000000..2340343
--- /dev/null
+++ b/examples/fabric-sync/bridge/include/BridgedDeviceBasicInformationImpl.h
@@ -0,0 +1,32 @@
+/*
+ *    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-common/zap-generated/ids/Clusters.h>
+#include <app/AttributeAccessInterface.h>
+
+class BridgedDeviceBasicInformationImpl : public chip::app::AttributeAccessInterface
+{
+public:
+    BridgedDeviceBasicInformationImpl() :
+        chip::app::AttributeAccessInterface(chip::NullOptional /* endpointId */,
+                                            chip::app::Clusters::BridgedDeviceBasicInformation::Id)
+    {}
+
+    // AttributeAccessInterface implementation
+    CHIP_ERROR Read(const chip::app::ConcreteReadAttributePath & path, chip::app::AttributeValueEncoder & encoder) override;
+    CHIP_ERROR Write(const chip::app::ConcreteDataAttributePath & path, chip::app::AttributeValueDecoder & decoder) override;
+};
diff --git a/examples/fabric-sync/bridge/include/BridgedDeviceManager.h b/examples/fabric-sync/bridge/include/BridgedDeviceManager.h
new file mode 100644
index 0000000..127898f
--- /dev/null
+++ b/examples/fabric-sync/bridge/include/BridgedDeviceManager.h
@@ -0,0 +1,136 @@
+/*
+ *
+ *    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 <platform/CHIPDeviceLayer.h>
+
+#include "BridgedDevice.h"
+
+#include <memory>
+
+class BridgedDeviceManager
+{
+public:
+    BridgedDeviceManager() = default;
+
+    /**
+     * @brief Initializes the BridgedDeviceManager.
+     *
+     * This function sets up the initial state of the BridgedDeviceManager, clearing
+     * any existing devices and setting the starting dynamic endpoint ID.
+     */
+    void Init();
+
+    /**
+     * @brief Adds a device to a dynamic endpoint.
+     *
+     * This function attempts to add a device to a dynamic endpoint. It tries to find an available
+     * endpoint slot and retries the addition process up to a specified maximum number of times if
+     * the endpoint already exists. If the addition is successful, it returns the index of the
+     * dynamic endpoint;
+     *
+     * Ensures that the device has a unique id:
+     *   - device MUST set its unique id if any BEFORE calling this method
+     *   - if no unique id (i.e. empty string), a new unique id will be generated
+     *   - Add will fail if the unique id is not unique
+     *
+     * @param dev A pointer to the device to be added.
+     * @param parentEndpointId The parent endpoint ID. Defaults to an invalid endpoint ID.
+     * @return int The index of the dynamic endpoint if successful, nullopt otherwise
+     */
+    std::optional<unsigned> AddDeviceEndpoint(std::unique_ptr<BridgedDevice> dev,
+                                              chip::EndpointId parentEndpointId = chip::kInvalidEndpointId);
+
+    /**
+     * @brief Removes a device from a dynamic endpoint.
+     *
+     * This function attempts to remove a device from a dynamic endpoint by iterating through the
+     * available endpoints and checking if the device matches. If the device is found, it clears the
+     * dynamic endpoint, logs the removal, and returns the index of the removed endpoint.
+     * If the device is not found, it returns -1.
+     *
+     * @param dev A pointer to the device to be removed.
+     * @return int The index of the removed dynamic endpoint if successful, -1 otherwise.
+     */
+    int RemoveDeviceEndpoint(BridgedDevice * dev);
+
+    /**
+     * @brief Gets a device from its endpoint ID.
+     *
+     * This function iterates through the available devices and returns the device that matches the
+     * specified endpoint ID. If no device matches the endpoint ID, it returns nullptr.
+     *
+     * @param endpointId The endpoint ID of the device to be retrieved.
+     * @return BridgedDevice* A pointer to the device if found, nullptr otherwise.
+     */
+    BridgedDevice * GetDevice(chip::EndpointId endpointId) const;
+
+    /**
+     * @brief Gets a device from its ScopedNodeId.
+     *
+     * This function iterates through the available devices and returns the device that matches the
+     * specified ScopedNodeId. If no device matches the ScopedNodeId, it returns nullptr.
+     *
+     * @param scopedNodeId The ScopedNodeId of the device to be retrieved.
+     * @return BridgedDevice* A pointer to the device if found, nullptr otherwise.
+     */
+    BridgedDevice * GetDeviceByScopedNodeId(chip::ScopedNodeId scopedNodeId) const;
+
+    /**
+     * @brief Removes a device from a dynamic endpoint by its ScopedNodeId.
+     *
+     * This function attempts to remove a device and the associated dynamic endpoint by iterating through
+     * the available device and checking if the device matches the specified ScopedNodeId. If the device is
+     * found, it removes the dynamic endpoint.
+     *
+     * @param scopedNodeId The ScopedNodeId of the device to be removed.
+     * @return unsigned of the index of the removed dynamic endpoint if successful, nullopt otherwise.
+     */
+    std::optional<unsigned> RemoveDeviceByScopedNodeId(chip::ScopedNodeId scopedNodeId);
+
+    /**
+     * Finds the device with the given unique id (if any)
+     */
+    BridgedDevice * GetDeviceByUniqueId(const std::string & id);
+
+private:
+    friend BridgedDeviceManager & BridgeDeviceMgr();
+
+    /**
+     * Creates a new unique ID that is not used by any other mDevice
+     */
+    std::string GenerateUniqueId();
+
+    static BridgedDeviceManager sInstance;
+
+    chip::EndpointId mCurrentEndpointId;
+    chip::EndpointId mFirstDynamicEndpointId;
+    std::unique_ptr<BridgedDevice> mDevices[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT + 1];
+};
+
+/**
+ * Returns the public interface of the BridgedDeviceManager singleton object.
+ *
+ * Applications should use this to access features of the BridgedDeviceManager
+ * object.
+ */
+inline BridgedDeviceManager & BridgeDeviceMgr()
+{
+    return BridgedDeviceManager::sInstance;
+}
diff --git a/examples/fabric-sync/bridge/src/BridgedAdministratorCommissioning.cpp b/examples/fabric-sync/bridge/src/BridgedAdministratorCommissioning.cpp
new file mode 100644
index 0000000..a0d87cb
--- /dev/null
+++ b/examples/fabric-sync/bridge/src/BridgedAdministratorCommissioning.cpp
@@ -0,0 +1,81 @@
+/*
+ *    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 "BridgedAdministratorCommissioning.h"
+
+#include "BridgedDevice.h"
+#include "BridgedDeviceManager.h"
+#include <app/AttributeAccessInterfaceRegistry.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::AdministratorCommissioning;
+
+CHIP_ERROR BridgedAdministratorCommissioning::Init()
+{
+    // We expect initialization after emberAfInit(). This allows us to unregister the existing
+    // AccessAttributeInterface for AdministratorCommissioning and register ourselves, ensuring we
+    // get the callback for reading attribute. If the read is not intended for a bridged device we will
+    // forward it to the original attribute interface that we are unregistering.
+    mOriginalAttributeInterface = AttributeAccessInterfaceRegistry::Instance().Get(kRootEndpointId, AdministratorCommissioning::Id);
+    VerifyOrReturnError(mOriginalAttributeInterface, CHIP_ERROR_INTERNAL);
+    AttributeAccessInterfaceRegistry::Instance().Unregister(mOriginalAttributeInterface);
+    VerifyOrDie(AttributeAccessInterfaceRegistry::Instance().Register(this));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR BridgedAdministratorCommissioning::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
+{
+    VerifyOrDie(aPath.mClusterId == Clusters::AdministratorCommissioning::Id);
+    EndpointId endpointId  = aPath.mEndpointId;
+    BridgedDevice * device = BridgeDeviceMgr().GetDevice(endpointId);
+
+    if (!device)
+    {
+        VerifyOrDie(mOriginalAttributeInterface);
+        return mOriginalAttributeInterface->Read(aPath, aEncoder);
+    }
+    auto attr = device->GetAdminCommissioningAttributes();
+
+    switch (aPath.mAttributeId)
+    {
+    case Attributes::WindowStatus::Id: {
+        return aEncoder.Encode(attr.commissioningWindowStatus);
+    }
+    case Attributes::AdminFabricIndex::Id: {
+        DataModel::Nullable<FabricIndex> encodeableFabricIndex = DataModel::NullNullable;
+        if (attr.openerFabricIndex.has_value())
+        {
+            encodeableFabricIndex.SetNonNull(attr.openerFabricIndex.value());
+        }
+        return aEncoder.Encode(encodeableFabricIndex);
+    }
+    case Attributes::AdminVendorId::Id: {
+        DataModel::Nullable<VendorId> encodeableVendorId = DataModel::NullNullable;
+        if (attr.openerVendorId.has_value())
+        {
+            encodeableVendorId.SetNonNull(attr.openerVendorId.value());
+        }
+        return aEncoder.Encode(encodeableVendorId);
+    }
+    default:
+        break;
+    }
+
+    return CHIP_NO_ERROR;
+}
diff --git a/examples/fabric-sync/bridge/src/BridgedDevice.cpp b/examples/fabric-sync/bridge/src/BridgedDevice.cpp
new file mode 100644
index 0000000..f462d1c
--- /dev/null
+++ b/examples/fabric-sync/bridge/src/BridgedDevice.cpp
@@ -0,0 +1,118 @@
+/*
+ *
+ *    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 "BridgedDevice.h"
+
+#include <cstdio>
+
+#include <app/EventLogging.h>
+#include <app/reporting/reporting.h>
+#include <platform/CHIPDeviceLayer.h>
+
+using namespace chip;
+using namespace chip::app::Clusters::Actions;
+
+BridgedDevice::BridgedDevice(ScopedNodeId scopedNodeId)
+{
+    mReachable    = false;
+    mScopedNodeId = scopedNodeId;
+    mEndpointId   = kInvalidEndpointId;
+}
+
+void BridgedDevice::LogActiveChangeEvent(uint32_t promisedActiveDurationMs)
+{
+    EndpointId endpointId = mEndpointId;
+
+    DeviceLayer::SystemLayer().ScheduleLambda([endpointId, promisedActiveDurationMs]() {
+        app::Clusters::BridgedDeviceBasicInformation::Events::ActiveChanged::Type event{};
+        event.promisedActiveDuration = promisedActiveDurationMs;
+        EventNumber eventNumber      = 0;
+
+        CHIP_ERROR err = app::LogEvent(event, endpointId, eventNumber);
+        if (err != CHIP_NO_ERROR)
+        {
+            ChipLogProgress(NotSpecified, "LogEvent for ActiveChanged failed %s", err.AsString());
+        }
+    });
+}
+
+void BridgedDevice::SetReachable(bool reachable)
+{
+    mReachable = reachable;
+
+    if (reachable)
+    {
+        ChipLogProgress(NotSpecified, "BridgedDevice[%s]: ONLINE", mAttributes.uniqueId.c_str());
+    }
+    else
+    {
+        ChipLogProgress(NotSpecified, "BridgedDevice[%s]: OFFLINE", mAttributes.uniqueId.c_str());
+    }
+}
+
+void BridgedDevice::ReachableChanged(bool reachable)
+{
+    EndpointId endpointId = mEndpointId;
+    bool reachableChanged = (mReachable != reachable);
+    if (reachableChanged)
+    {
+        SetReachable(reachable);
+        DeviceLayer::SystemLayer().ScheduleLambda([endpointId]() {
+            MatterReportingAttributeChangeCallback(endpointId, app::Clusters::BridgedDeviceBasicInformation::Id,
+                                                   app::Clusters::BridgedDeviceBasicInformation::Attributes::Reachable::Id);
+
+            app::Clusters::BridgedDeviceBasicInformation::Events::ReachableChanged::Type event{};
+            EventNumber eventNumber = 0;
+
+            CHIP_ERROR err = app::LogEvent(event, endpointId, eventNumber);
+            if (err != CHIP_NO_ERROR)
+            {
+                ChipLogProgress(NotSpecified, "LogEvent for ActiveChanged failed %s", err.AsString());
+            }
+        });
+    }
+}
+
+void BridgedDevice::SetAdminCommissioningAttributes(const AdminCommissioningAttributes & aAdminCommissioningAttributes)
+{
+    EndpointId endpointId = mEndpointId;
+    bool windowChanged =
+        (aAdminCommissioningAttributes.commissioningWindowStatus != mAdminCommissioningAttributes.commissioningWindowStatus);
+    bool fabricIndexChanged = (aAdminCommissioningAttributes.openerFabricIndex != mAdminCommissioningAttributes.openerFabricIndex);
+    bool vendorChanged      = (aAdminCommissioningAttributes.openerVendorId != mAdminCommissioningAttributes.openerVendorId);
+
+    mAdminCommissioningAttributes = aAdminCommissioningAttributes;
+
+    DeviceLayer::SystemLayer().ScheduleLambda([endpointId, windowChanged, fabricIndexChanged, vendorChanged]() {
+        if (windowChanged)
+        {
+            MatterReportingAttributeChangeCallback(endpointId, app::Clusters::AdministratorCommissioning::Id,
+                                                   app::Clusters::AdministratorCommissioning::Attributes::WindowStatus::Id);
+        }
+        if (fabricIndexChanged)
+        {
+            MatterReportingAttributeChangeCallback(endpointId, app::Clusters::AdministratorCommissioning::Id,
+                                                   app::Clusters::AdministratorCommissioning::Attributes::AdminFabricIndex::Id);
+        }
+        if (vendorChanged)
+        {
+            MatterReportingAttributeChangeCallback(endpointId, app::Clusters::AdministratorCommissioning::Id,
+                                                   app::Clusters::AdministratorCommissioning::Attributes::AdminVendorId::Id);
+        }
+    });
+}
diff --git a/examples/fabric-sync/bridge/src/BridgedDeviceBasicInformationImpl.cpp b/examples/fabric-sync/bridge/src/BridgedDeviceBasicInformationImpl.cpp
new file mode 100644
index 0000000..7fa48a4
--- /dev/null
+++ b/examples/fabric-sync/bridge/src/BridgedDeviceBasicInformationImpl.cpp
@@ -0,0 +1,107 @@
+/*
+ *    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 "BridgedDeviceBasicInformationImpl.h"
+
+#include "BridgedDeviceManager.h"
+
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+
+#include <app/AttributeAccessInterfaceRegistry.h>
+
+static constexpr unsigned kBridgedDeviceBasicInformationClusterRevision = 4;
+
+using namespace ::chip;
+using namespace ::chip::app;
+using namespace ::chip::app::Clusters;
+
+CHIP_ERROR BridgedDeviceBasicInformationImpl::Read(const ConcreteReadAttributePath & path, AttributeValueEncoder & encoder)
+{
+    // Registration is done for the bridged device basic information only
+    VerifyOrDie(path.mClusterId == app::Clusters::BridgedDeviceBasicInformation::Id);
+
+    BridgedDevice * dev = BridgeDeviceMgr().GetDevice(path.mEndpointId);
+    VerifyOrReturnError(dev != nullptr, CHIP_ERROR_NOT_FOUND);
+
+    switch (path.mAttributeId)
+    {
+    case BasicInformation::Attributes::Reachable::Id:
+        encoder.Encode(dev->IsReachable());
+        break;
+    case BasicInformation::Attributes::NodeLabel::Id:
+        encoder.Encode(CharSpan::fromCharString(dev->GetBridgedAttributes().nodeLabel.c_str()));
+        break;
+    case BasicInformation::Attributes::ClusterRevision::Id:
+        encoder.Encode(kBridgedDeviceBasicInformationClusterRevision);
+        break;
+    case BasicInformation::Attributes::FeatureMap::Id: {
+        BitMask<Clusters::BridgedDeviceBasicInformation::Feature> features;
+        features.Set(Clusters::BridgedDeviceBasicInformation::Feature::kBridgedICDSupport, dev->IsIcd());
+        encoder.Encode(features);
+    }
+    break;
+    case BasicInformation::Attributes::UniqueID::Id:
+        encoder.Encode(CharSpan::fromCharString(dev->GetBridgedAttributes().uniqueId.c_str()));
+        break;
+    case BasicInformation::Attributes::VendorName::Id:
+        encoder.Encode(CharSpan::fromCharString(dev->GetBridgedAttributes().vendorName.c_str()));
+        break;
+    case BasicInformation::Attributes::VendorID::Id:
+        encoder.Encode(dev->GetBridgedAttributes().vendorId);
+        break;
+    case BasicInformation::Attributes::ProductName::Id:
+        encoder.Encode(CharSpan::fromCharString(dev->GetBridgedAttributes().productName.c_str()));
+        break;
+    case BasicInformation::Attributes::ProductID::Id:
+        encoder.Encode(dev->GetBridgedAttributes().productId);
+        break;
+    case BasicInformation::Attributes::HardwareVersion::Id:
+        encoder.Encode(dev->GetBridgedAttributes().hardwareVersion);
+        break;
+    case BasicInformation::Attributes::HardwareVersionString::Id:
+        encoder.Encode(CharSpan::fromCharString(dev->GetBridgedAttributes().hardwareVersionString.c_str()));
+        break;
+    case BasicInformation::Attributes::SoftwareVersion::Id:
+        encoder.Encode(dev->GetBridgedAttributes().softwareVersion);
+        break;
+    case BasicInformation::Attributes::SoftwareVersionString::Id:
+        encoder.Encode(CharSpan::fromCharString(dev->GetBridgedAttributes().softwareVersionString.c_str()));
+        break;
+    default:
+        return CHIP_ERROR_INVALID_ARGUMENT;
+    }
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR BridgedDeviceBasicInformationImpl::Write(const ConcreteDataAttributePath & path, AttributeValueDecoder & decoder)
+{
+    VerifyOrDie(path.mClusterId == app::Clusters::BridgedDeviceBasicInformation::Id);
+
+    BridgedDevice * dev = BridgeDeviceMgr().GetDevice(path.mEndpointId);
+    VerifyOrReturnError(dev != nullptr, CHIP_ERROR_NOT_FOUND);
+
+    if (!dev->IsReachable())
+    {
+        return CHIP_ERROR_NOT_CONNECTED;
+    }
+
+    ChipLogProgress(NotSpecified, "Bridged device basic information attempt to write attribute: ep=%d", path.mAttributeId);
+
+    // nothing writable right now ...
+
+    return CHIP_ERROR_INVALID_ARGUMENT;
+}
diff --git a/examples/fabric-sync/bridge/src/BridgedDeviceManager.cpp b/examples/fabric-sync/bridge/src/BridgedDeviceManager.cpp
new file mode 100644
index 0000000..0265f91
--- /dev/null
+++ b/examples/fabric-sync/bridge/src/BridgedDeviceManager.cpp
@@ -0,0 +1,345 @@
+/*
+ *
+ *    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 "BridgedDeviceManager.h"
+
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/AttributeAccessInterfaceRegistry.h>
+#include <app/ConcreteAttributePath.h>
+#include <app/EventLogging.h>
+#include <app/reporting/reporting.h>
+#include <app/server/Server.h>
+#include <app/util/af-types.h>
+#include <app/util/attribute-storage.h>
+#include <app/util/endpoint-config-api.h>
+#include <app/util/util.h>
+#include <crypto/RandUtils.h>
+#include <lib/support/CHIPMem.h>
+#include <lib/support/ZclString.h>
+
+#include <cstdio>
+#include <optional>
+#include <string>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::Credentials;
+using namespace chip::Inet;
+using namespace chip::Transport;
+using namespace chip::DeviceLayer;
+using namespace chip::app::Clusters;
+
+namespace {
+
+constexpr uint8_t kMaxRetries      = 10;
+constexpr int kNodeLabelSize       = 32;
+constexpr int kUniqueIdSize        = 32;
+constexpr int kVendorNameSize      = 32;
+constexpr int kProductNameSize     = 32;
+constexpr int kHardwareVersionSize = 64;
+constexpr int kSoftwareVersionSize = 64;
+
+// Current ZCL implementation of Struct uses a max-size array of 254 bytes
+constexpr int kDescriptorAttributeArraySize = 254;
+
+#define ZCL_ADMINISTRATOR_COMMISSIONING_CLUSTER_REVISION (1u)
+
+// ENDPOINT DEFINITIONS:
+// =================================================================================
+//
+// Endpoint definitions will be reused across multiple endpoints for every instance of the
+// endpoint type.
+// There will be no intrinsic storage for the endpoint attributes declared here.
+// Instead, all attributes will be treated as EXTERNAL, and therefore all reads
+// or writes to the attributes must be handled within the emberAfExternalAttributeWriteCallback
+// and emberAfExternalAttributeReadCallback functions declared herein. This fits
+// the typical model of a bridge, since a bridge typically maintains its own
+// state database representing the devices connected to it.
+
+// (taken from matter-devices.xml)
+#define DEVICE_TYPE_BRIDGED_NODE 0x0013
+
+// Device Version for dynamic endpoints:
+#define DEVICE_VERSION_DEFAULT 1
+
+// ---------------------------------------------------------------------------
+//
+// SYNCED DEVICE ENDPOINT: contains the following clusters:
+//   - Descriptor
+//   - Bridged Device Basic Information
+//   - Administrator Commissioning
+
+// clang-format off
+// Declare Descriptor cluster attributes
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(descriptorAttrs)
+  DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::DeviceTypeList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* device list */
+  DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ServerList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* server list */
+  DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::ClientList::Id, ARRAY, kDescriptorAttributeArraySize, 0), /* client list */
+  DECLARE_DYNAMIC_ATTRIBUTE(Descriptor::Attributes::PartsList::Id, ARRAY, kDescriptorAttributeArraySize, 0),  /* parts list */
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
+
+// Declare Bridged Device Basic Information cluster attributes
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(bridgedDeviceBasicAttrs)
+  // The attributes below are MANDATORY in the Bridged Device Information Cluster
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::UniqueID::Id, CHAR_STRING, kUniqueIdSize, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::FeatureMap::Id, BITMAP32, 4, 0),
+
+  // The attributes below are OPTIONAL in the bridged device, however they are MANDATORY in the BasicInformation cluster
+  // so they can always be provided if desired
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::VendorName::Id, CHAR_STRING, kVendorNameSize, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::VendorID::Id, INT16U, 2, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductName::Id, CHAR_STRING, kProductNameSize, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::ProductID::Id, INT16U, 2, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, kNodeLabelSize, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::HardwareVersion::Id, INT16U, 2, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::HardwareVersionString::Id, CHAR_STRING,
+                            kHardwareVersionSize, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::SoftwareVersion::Id, INT32U, 4, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::SoftwareVersionString::Id, CHAR_STRING,
+                            kSoftwareVersionSize, 0),
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
+
+// Declare Ecosystem Information cluster attributes
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(ecosystemInformationBasicAttrs)
+    DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::DeviceDirectory::Id, ARRAY, kDescriptorAttributeArraySize, 0),
+    DECLARE_DYNAMIC_ATTRIBUTE(EcosystemInformation::Attributes::LocationDirectory::Id, ARRAY, kDescriptorAttributeArraySize, 0),
+    DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
+
+// Declare Administrator Commissioning cluster attributes
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(AdministratorCommissioningAttrs)
+  DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::WindowStatus::Id, ENUM8, 1, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminFabricIndex::Id, FABRIC_IDX, 1, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminVendorId::Id, VENDOR_ID, 2, 0),
+  DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::ClusterRevision::Id, INT16U, ZCL_ADMINISTRATOR_COMMISSIONING_CLUSTER_REVISION, 0),
+DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
+// clang-format on
+
+constexpr CommandId bridgedDeviceBasicInformationCommands[] = {
+    app::Clusters::BridgedDeviceBasicInformation::Commands::KeepActive::Id,
+    kInvalidCommandId,
+};
+
+constexpr CommandId administratorCommissioningCommands[] = {
+    app::Clusters::AdministratorCommissioning::Commands::OpenCommissioningWindow::Id,
+    app::Clusters::AdministratorCommissioning::Commands::OpenBasicCommissioningWindow::Id,
+    app::Clusters::AdministratorCommissioning::Commands::RevokeCommissioning::Id,
+    kInvalidCommandId,
+};
+
+// clang-format off
+// Declare Cluster List for Bridged Node endpoint
+DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(bridgedNodeClusters)
+    DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
+    DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
+    DECLARE_DYNAMIC_CLUSTER(EcosystemInformation::Id, ecosystemInformationBasicAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
+    DECLARE_DYNAMIC_CLUSTER(AdministratorCommissioning::Id, AdministratorCommissioningAttrs, ZAP_CLUSTER_MASK(SERVER),
+                            administratorCommissioningCommands, nullptr)
+DECLARE_DYNAMIC_CLUSTER_LIST_END;
+
+DECLARE_DYNAMIC_CLUSTER_LIST_BEGIN(icdBridgedNodeClusters)
+    DECLARE_DYNAMIC_CLUSTER(Descriptor::Id, descriptorAttrs, ZAP_CLUSTER_MASK(SERVER), nullptr, nullptr),
+    DECLARE_DYNAMIC_CLUSTER(BridgedDeviceBasicInformation::Id, bridgedDeviceBasicAttrs, ZAP_CLUSTER_MASK(SERVER),
+                            bridgedDeviceBasicInformationCommands, nullptr),
+    DECLARE_DYNAMIC_CLUSTER(AdministratorCommissioning::Id, AdministratorCommissioningAttrs, ZAP_CLUSTER_MASK(SERVER),
+                            administratorCommissioningCommands, nullptr)
+DECLARE_DYNAMIC_CLUSTER_LIST_END;
+// clang-format on
+
+// Declare Bridged Node endpoint
+DECLARE_DYNAMIC_ENDPOINT(sBridgedNodeEndpoint, bridgedNodeClusters);
+DECLARE_DYNAMIC_ENDPOINT(sIcdBridgedNodeEndpoint, icdBridgedNodeClusters);
+
+// TODO: this is a single version array, however we may have many
+//       different clusters that are independent.
+DataVersion sBridgedNodeDataVersions[ArraySize(bridgedNodeClusters)];
+
+const EmberAfDeviceType sBridgedDeviceTypes[] = { { DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT } };
+
+} // namespace
+
+// Define the static member
+BridgedDeviceManager BridgedDeviceManager::sInstance;
+
+void BridgedDeviceManager::Init()
+{
+    mFirstDynamicEndpointId = static_cast<chip::EndpointId>(
+        static_cast<int>(emberAfEndpointFromIndex(static_cast<uint16_t>(emberAfFixedEndpointCount() - 1))) + 1);
+    mCurrentEndpointId = mFirstDynamicEndpointId;
+}
+
+std::optional<unsigned> BridgedDeviceManager::AddDeviceEndpoint(std::unique_ptr<BridgedDevice> dev,
+                                                                chip::EndpointId parentEndpointId)
+{
+    EmberAfEndpointType * ep = dev->IsIcd() ? &sIcdBridgedNodeEndpoint : &sBridgedNodeEndpoint;
+
+    const chip::Span<const EmberAfDeviceType> & deviceTypeList = Span<const EmberAfDeviceType>(sBridgedDeviceTypes);
+
+    // TODO: this shares data version among different clusters, which seems incorrect
+    const chip::Span<chip::DataVersion> & dataVersionStorage = Span<DataVersion>(sBridgedNodeDataVersions);
+
+    if (dev->GetBridgedAttributes().uniqueId.empty())
+    {
+        dev->SetUniqueId(GenerateUniqueId());
+    }
+    else if (GetDeviceByUniqueId(dev->GetBridgedAttributes().uniqueId) != nullptr)
+    {
+        ChipLogProgress(NotSpecified, "A device with unique id '%s' already exists", dev->GetBridgedAttributes().uniqueId.c_str());
+        return std::nullopt;
+    }
+
+    for (unsigned index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; index++)
+    {
+        if (mDevices[index])
+        {
+            continue;
+        }
+
+        for (int retryCount = 0; retryCount < kMaxRetries; retryCount++)
+        {
+            DeviceLayer::StackLock lock;
+            dev->SetEndpointId(mCurrentEndpointId);
+            dev->SetParentEndpointId(parentEndpointId);
+            CHIP_ERROR err =
+                emberAfSetDynamicEndpoint(index, mCurrentEndpointId, ep, dataVersionStorage, deviceTypeList, parentEndpointId);
+            if (err == CHIP_NO_ERROR)
+            {
+                ChipLogProgress(NotSpecified, "Added device with Id=[%d:0x" ChipLogFormatX64 "] to dynamic endpoint %d (index=%d)",
+                                dev->GetScopedNodeId().GetFabricIndex(), ChipLogValueX64(dev->GetScopedNodeId().GetNodeId()),
+                                mCurrentEndpointId, index);
+                mDevices[index] = std::move(dev);
+                return index;
+            }
+            if (err != CHIP_ERROR_ENDPOINT_EXISTS)
+            {
+                return std::nullopt; // Return error as endpoint addition failed due to an error other than endpoint already exists
+            }
+            // Increment the endpoint ID and handle wrap condition
+            if (++mCurrentEndpointId < mFirstDynamicEndpointId)
+            {
+                mCurrentEndpointId = mFirstDynamicEndpointId;
+            }
+        }
+        ChipLogError(NotSpecified, "Failed to add dynamic endpoint after %d retries", kMaxRetries);
+        return std::nullopt; // Return error as all retries are exhausted
+    }
+
+    ChipLogProgress(NotSpecified, "Failed to add dynamic endpoint: No endpoints available!");
+    return std::nullopt;
+}
+
+int BridgedDeviceManager::RemoveDeviceEndpoint(BridgedDevice * dev)
+{
+    uint8_t index = 0;
+    while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
+    {
+        if (mDevices[index].get() == dev)
+        {
+            DeviceLayer::StackLock lock;
+            // Silence complaints about unused ep when progress logging
+            // disabled.
+            [[maybe_unused]] EndpointId ep = emberAfClearDynamicEndpoint(index);
+            mDevices[index]                = nullptr;
+            ChipLogProgress(NotSpecified, "Removed device %s from dynamic endpoint %d (index=%d)",
+                            dev->GetBridgedAttributes().uniqueId.c_str(), ep, index);
+            return index;
+        }
+        index++;
+    }
+    return -1;
+}
+
+BridgedDevice * BridgedDeviceManager::GetDevice(chip::EndpointId endpointId) const
+{
+    for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
+    {
+        if (mDevices[index] && mDevices[index]->GetEndpointId() == endpointId)
+        {
+            return mDevices[index].get();
+        }
+    }
+    return nullptr;
+}
+
+std::string BridgedDeviceManager::GenerateUniqueId()
+{
+    char rand_buffer[kUniqueIdSize + 1];
+    memset(rand_buffer, 0, sizeof(rand_buffer));
+
+    static const char kRandCharChoices[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    while (true)
+    {
+        /// for nice viewing, prefix the generated value
+        memcpy(rand_buffer, "GEN-", 4);
+        for (unsigned idx = 4; idx < kUniqueIdSize; idx++)
+        {
+            rand_buffer[idx] = kRandCharChoices[Crypto::GetRandU8() % (sizeof(kRandCharChoices) - 1)];
+        }
+
+        // we know zero-terminated due to the memset
+        std::string uniqueIdChoice = rand_buffer;
+
+        if (!GetDeviceByUniqueId(uniqueIdChoice))
+        {
+            return uniqueIdChoice;
+        }
+    }
+}
+
+BridgedDevice * BridgedDeviceManager::GetDeviceByUniqueId(const std::string & id)
+{
+    for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
+    {
+        if (mDevices[index] && mDevices[index]->GetBridgedAttributes().uniqueId == id)
+        {
+            return mDevices[index].get();
+        }
+    }
+    return nullptr;
+}
+
+BridgedDevice * BridgedDeviceManager::GetDeviceByScopedNodeId(chip::ScopedNodeId scopedNodeId) const
+{
+    for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
+    {
+        if (mDevices[index] && mDevices[index]->GetScopedNodeId() == scopedNodeId)
+        {
+            return mDevices[index].get();
+        }
+    }
+    return nullptr;
+}
+
+std::optional<unsigned> BridgedDeviceManager::RemoveDeviceByScopedNodeId(chip::ScopedNodeId scopedNodeId)
+{
+    for (unsigned index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
+    {
+        if (mDevices[index] && mDevices[index]->GetScopedNodeId() == scopedNodeId)
+        {
+            DeviceLayer::StackLock lock;
+            EndpointId ep   = emberAfClearDynamicEndpoint(index);
+            mDevices[index] = nullptr;
+            ChipLogProgress(NotSpecified, "Removed device with Id=[%d:0x" ChipLogFormatX64 "] from dynamic endpoint %d (index=%d)",
+                            scopedNodeId.GetFabricIndex(), ChipLogValueX64(scopedNodeId.GetNodeId()), ep, index);
+            return index;
+        }
+    }
+    return std::nullopt;
+}