Add new commissioned device as a synchronized device to Fabric Bridge (#33908)

diff --git a/examples/fabric-bridge-app/linux/Device.cpp b/examples/fabric-bridge-app/linux/Device.cpp
index 215db48..e72fd85 100644
--- a/examples/fabric-bridge-app/linux/Device.cpp
+++ b/examples/fabric-bridge-app/linux/Device.cpp
@@ -25,11 +25,11 @@
 
 using namespace chip::app::Clusters::Actions;
 
-Device::Device(chip::NodeId nodeId, const char * name)
+Device::Device(chip::NodeId nodeId)
 {
-    chip::Platform::CopyString(mName, name);
     mReachable  = false;
-    mEndpointId = 0;
+    mNodeId     = nodeId;
+    mEndpointId = chip::kInvalidEndpointId;
 }
 
 bool Device::IsReachable()
diff --git a/examples/fabric-bridge-app/linux/DeviceManager.cpp b/examples/fabric-bridge-app/linux/DeviceManager.cpp
index 3b158b8..bbfff03 100644
--- a/examples/fabric-bridge-app/linux/DeviceManager.cpp
+++ b/examples/fabric-bridge-app/linux/DeviceManager.cpp
@@ -46,6 +46,77 @@
 namespace {
 
 constexpr uint8_t kMaxRetries = 10;
+constexpr int kNodeLabelSize  = 32;
+
+// Current ZCL implementation of Struct uses a max-size array of 254 bytes
+constexpr int kDescriptorAttributeArraySize = 254;
+
+// 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
+
+// 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)
+DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::NodeLabel::Id, CHAR_STRING, kNodeLabelSize, 0), /* NodeLabel */
+    DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::Reachable::Id, BOOLEAN, 1, 0),              /* Reachable */
+    DECLARE_DYNAMIC_ATTRIBUTE(BridgedDeviceBasicInformation::Attributes::FeatureMap::Id, BITMAP32, 4, 0), /* feature map */
+    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),              /* NodeLabel */
+    DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminFabricIndex::Id, FABRIC_IDX, 1, 0), /* Reachable */
+    DECLARE_DYNAMIC_ATTRIBUTE(AdministratorCommissioning::Attributes::AdminVendorId::Id, VENDOR_ID, 2, 0),     /* Reachable */
+    DECLARE_DYNAMIC_ATTRIBUTE_LIST_END();
+
+constexpr CommandId administratorCommissioningCommands[] = {
+    app::Clusters::AdministratorCommissioning::Commands::OpenCommissioningWindow::Id,
+    app::Clusters::AdministratorCommissioning::Commands::OpenBasicCommissioningWindow::Id,
+    app::Clusters::AdministratorCommissioning::Commands::RevokeCommissioning::Id,
+    kInvalidCommandId,
+};
+
+// 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(AdministratorCommissioning::Id, AdministratorCommissioningAttrs, ZAP_CLUSTER_MASK(SERVER),
+                            administratorCommissioningCommands, nullptr) DECLARE_DYNAMIC_CLUSTER_LIST_END;
+
+// Declare Bridged Node endpoint
+DECLARE_DYNAMIC_ENDPOINT(sBridgedNodeEndpoint, bridgedNodeClusters);
+DataVersion sBridgedNodeDataVersions[ArraySize(bridgedNodeClusters)];
+
+const EmberAfDeviceType sBridgedDeviceTypes[] = { { DEVICE_TYPE_BRIDGED_NODE, DEVICE_VERSION_DEFAULT } };
 
 } // namespace
 
@@ -60,11 +131,13 @@
     mCurrentEndpointId = mFirstDynamicEndpointId;
 }
 
-int DeviceManager::AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep,
-                                     const chip::Span<const EmberAfDeviceType> & deviceTypeList,
-                                     const chip::Span<chip::DataVersion> & dataVersionStorage, chip::EndpointId parentEndpointId)
+int DeviceManager::AddDeviceEndpoint(Device * dev, chip::EndpointId parentEndpointId)
 {
-    uint8_t index = 0;
+    uint8_t index                                              = 0;
+    EmberAfEndpointType * ep                                   = &sBridgedNodeEndpoint;
+    const chip::Span<const EmberAfDeviceType> & deviceTypeList = Span<const EmberAfDeviceType>(sBridgedDeviceTypes);
+    const chip::Span<chip::DataVersion> & dataVersionStorage   = Span<DataVersion>(sBridgedNodeDataVersions);
+
     while (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
     {
         if (nullptr == mDevices[index])
@@ -81,8 +154,9 @@
                     emberAfSetDynamicEndpoint(index, mCurrentEndpointId, ep, dataVersionStorage, deviceTypeList, parentEndpointId);
                 if (err == CHIP_NO_ERROR)
                 {
-                    ChipLogProgress(NotSpecified, "Added device %s to dynamic endpoint %d (index=%d)", dev->GetName(),
-                                    mCurrentEndpointId, index);
+                    ChipLogProgress(NotSpecified,
+                                    "Added device with nodeId=0x" ChipLogFormatX64 " to dynamic endpoint %d (index=%d)",
+                                    ChipLogValueX64(dev->GetNodeId()), mCurrentEndpointId, index);
                     return index;
                 }
                 if (err != CHIP_ERROR_ENDPOINT_EXISTS)
@@ -125,11 +199,43 @@
     return -1;
 }
 
-Device * DeviceManager::GetDevice(uint16_t index) const
+Device * DeviceManager::GetDevice(chip::EndpointId endpointId) const
 {
-    if (index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT)
+    for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
     {
-        return mDevices[index];
+        if (mDevices[index] && mDevices[index]->GetEndpointId() == endpointId)
+        {
+            return mDevices[index];
+        }
     }
     return nullptr;
 }
+
+Device * DeviceManager::GetDeviceByNodeId(chip::NodeId nodeId) const
+{
+    for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
+    {
+        if (mDevices[index] && mDevices[index]->GetNodeId() == nodeId)
+        {
+            return mDevices[index];
+        }
+    }
+    return nullptr;
+}
+
+int DeviceManager::RemoveDeviceByNodeId(chip::NodeId nodeId)
+{
+    for (uint8_t index = 0; index < CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; ++index)
+    {
+        if (mDevices[index] && mDevices[index]->GetNodeId() == nodeId)
+        {
+            DeviceLayer::StackLock lock;
+            EndpointId ep   = emberAfClearDynamicEndpoint(index);
+            mDevices[index] = nullptr;
+            ChipLogProgress(NotSpecified, "Removed device with NodeId=0x" ChipLogFormatX64 " from dynamic endpoint %d (index=%d)",
+                            ChipLogValueX64(nodeId), ep, index);
+            return index;
+        }
+    }
+    return -1;
+}
diff --git a/examples/fabric-bridge-app/linux/RpcClient.cpp b/examples/fabric-bridge-app/linux/RpcClient.cpp
index c09a447..815250f 100644
--- a/examples/fabric-bridge-app/linux/RpcClient.cpp
+++ b/examples/fabric-bridge-app/linux/RpcClient.cpp
@@ -65,7 +65,7 @@
 
 CHIP_ERROR OpenCommissioningWindow(NodeId nodeId)
 {
-    ChipLogProgress(NotSpecified, "OpenCommissioningWindow\n");
+    ChipLogProgress(NotSpecified, "OpenCommissioningWindow with Node Id 0x:" ChipLogFormatX64, ChipLogValueX64(nodeId));
 
     if (openCommissioningWindowCall.active())
     {
diff --git a/examples/fabric-bridge-app/linux/RpcServer.cpp b/examples/fabric-bridge-app/linux/RpcServer.cpp
index c971811..088fc9a 100644
--- a/examples/fabric-bridge-app/linux/RpcServer.cpp
+++ b/examples/fabric-bridge-app/linux/RpcServer.cpp
@@ -20,27 +20,50 @@
 #include "pw_rpc_system_server/rpc_server.h"
 #include "pw_rpc_system_server/socket.h"
 
-#include <system/SystemClock.h>
+#include <lib/core/CHIPError.h>
+
+#include <string>
 #include <thread>
 
 #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE
 #include "pigweed/rpc_services/FabricBridge.h"
 #endif
 
+#include "Device.h"
+#include "DeviceManager.h"
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+
 namespace {
 
 #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE
 class FabricBridge final : public chip::rpc::FabricBridge
 {
 public:
-    pw::Status AddSynchronizedDevice(const chip_rpc_SynchronizedDevice & request, pw_protobuf_Empty & response) override
-    {
-        chip::NodeId nodeId = request.node_id;
-        ChipLogProgress(NotSpecified, "Received AddSynchronizedDevice: " ChipLogFormatX64, ChipLogValueX64(nodeId));
-        return pw::OkStatus();
-    }
+    pw::Status AddSynchronizedDevice(const chip_rpc_SynchronizedDevice & request, pw_protobuf_Empty & response) override;
 };
 
+pw::Status FabricBridge::AddSynchronizedDevice(const chip_rpc_SynchronizedDevice & request, pw_protobuf_Empty & response)
+{
+    NodeId nodeId = request.node_id;
+    ChipLogProgress(NotSpecified, "Received AddSynchronizedDevice: " ChipLogFormatX64, ChipLogValueX64(nodeId));
+
+    Device * device = new Device(nodeId);
+    device->SetReachable(true);
+
+    int result = DeviceMgr().AddDeviceEndpoint(device, 1);
+    if (result == -1)
+    {
+        delete device;
+        ChipLogError(NotSpecified, "Failed to add device with nodeId=0x" ChipLogFormatX64, ChipLogValueX64(nodeId));
+        return pw::Status::Unknown();
+    }
+
+    return pw::OkStatus();
+}
+
 FabricBridge fabric_bridge_service;
 #endif // defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE
 
diff --git a/examples/fabric-bridge-app/linux/include/Device.h b/examples/fabric-bridge-app/linux/include/Device.h
index 64940f3..c709af8 100644
--- a/examples/fabric-bridge-app/linux/include/Device.h
+++ b/examples/fabric-bridge-app/linux/include/Device.h
@@ -32,7 +32,7 @@
 public:
     static const int kDeviceNameSize = 32;
 
-    Device(chip::NodeId nodeId, const char * name);
+    Device(chip::NodeId nodeId);
     virtual ~Device() {}
 
     bool IsReachable();
@@ -41,6 +41,7 @@
     void SetLocation(std::string location) { mLocation = location; };
     inline void SetEndpointId(chip::EndpointId id) { mEndpointId = id; };
     inline chip::EndpointId GetEndpointId() { return mEndpointId; };
+    inline chip::NodeId GetNodeId() { return mNodeId; };
     inline void SetParentEndpointId(chip::EndpointId id) { mParentEndpointId = id; };
     inline chip::EndpointId GetParentEndpointId() { return mParentEndpointId; };
     inline char * GetName() { return mName; };
diff --git a/examples/fabric-bridge-app/linux/include/DeviceManager.h b/examples/fabric-bridge-app/linux/include/DeviceManager.h
index 2667fc0..b1dd791 100644
--- a/examples/fabric-bridge-app/linux/include/DeviceManager.h
+++ b/examples/fabric-bridge-app/linux/include/DeviceManager.h
@@ -27,6 +27,12 @@
 public:
     DeviceManager() = default;
 
+    /**
+     * @brief Initializes the DeviceManager.
+     *
+     * This function sets up the initial state of the DeviceManager, clearing
+     * any existing devices and setting the starting dynamic endpoint ID.
+     */
     void Init();
 
     /**
@@ -38,15 +44,10 @@
      * dynamic endpoint; otherwise, it returns -1.
      *
      * @param dev A pointer to the device to be added.
-     * @param ep A pointer to the endpoint type.
-     * @param deviceTypeList A span containing the list of device types.
-     * @param dataVersionStorage A span containing the data version storage.
      * @param parentEndpointId The parent endpoint ID. Defaults to an invalid endpoint ID.
      * @return int The index of the dynamic endpoint if successful, -1 otherwise.
      */
-    int AddDeviceEndpoint(Device * dev, EmberAfEndpointType * ep, const chip::Span<const EmberAfDeviceType> & deviceTypeList,
-                          const chip::Span<chip::DataVersion> & dataVersionStorage,
-                          chip::EndpointId parentEndpointId = chip::kInvalidEndpointId);
+    int AddDeviceEndpoint(Device * dev, chip::EndpointId parentEndpointId = chip::kInvalidEndpointId);
 
     /**
      * @brief Removes a device from a dynamic endpoint.
@@ -61,7 +62,40 @@
      */
     int RemoveDeviceEndpoint(Device * dev);
 
-    Device * GetDevice(uint16_t index) const;
+    /**
+     * @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 Device* A pointer to the device if found, nullptr otherwise.
+     */
+    Device * GetDevice(chip::EndpointId endpointId) const;
+
+    /**
+     * @brief Gets a device from its NodeId.
+     *
+     * This function iterates through the available devices and returns the device that matches the
+     * specified NodeId. If no device matches the NodeId, it returns nullptr.
+     *
+     * @param nodeId The NodeId of the device to be retrieved.
+     * @return Device* A pointer to the device if found, nullptr otherwise.
+     */
+    Device * GetDeviceByNodeId(chip::NodeId nodeId) const;
+
+    /**
+     * @brief Removes a device from a dynamic endpoint by its NodeId.
+     *
+     * This function attempts to remove a device from a dynamic endpoint by iterating through the
+     * available endpoints and checking if the device matches the specified NodeId. 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 nodeId The NodeId of the device to be removed.
+     * @return int The index of the removed dynamic endpoint if successful, -1 otherwise.
+     */
+    int RemoveDeviceByNodeId(chip::NodeId nodeId);
 
 private:
     friend DeviceManager & DeviceMgr();