Add DeviceSubscription for CADMIN cluster for bridged devices (#35239)

---------

Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: saurabhst <s.kumar9@samsung.com>
diff --git a/examples/fabric-admin/BUILD.gn b/examples/fabric-admin/BUILD.gn
index 51e6bc6..e408cdd 100644
--- a/examples/fabric-admin/BUILD.gn
+++ b/examples/fabric-admin/BUILD.gn
@@ -82,6 +82,8 @@
     "commands/pairing/ToTLVCert.cpp",
     "device_manager/DeviceManager.cpp",
     "device_manager/DeviceManager.h",
+    "device_manager/DeviceSubscription.cpp",
+    "device_manager/DeviceSubscription.h",
     "device_manager/DeviceSynchronization.cpp",
     "device_manager/DeviceSynchronization.h",
   ]
diff --git a/examples/fabric-admin/device_manager/DeviceSubscription.cpp b/examples/fabric-admin/device_manager/DeviceSubscription.cpp
new file mode 100644
index 0000000..73f9098
--- /dev/null
+++ b/examples/fabric-admin/device_manager/DeviceSubscription.cpp
@@ -0,0 +1,160 @@
+/*
+ *   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 "DeviceSubscription.h"
+#include "rpc/RpcClient.h"
+
+#include <app/InteractionModelEngine.h>
+#include <app/server/Server.h>
+
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <device_manager/DeviceManager.h>
+
+using namespace ::chip;
+using namespace ::chip::app;
+using chip::app::ReadClient;
+
+namespace {
+
+void OnDeviceConnectedWrapper(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle)
+{
+    reinterpret_cast<DeviceSubscription *>(context)->OnDeviceConnected(exchangeMgr, sessionHandle);
+}
+
+void OnDeviceConnectionFailureWrapper(void * context, const ScopedNodeId & peerId, CHIP_ERROR error)
+{
+    reinterpret_cast<DeviceSubscription *>(context)->OnDeviceConnectionFailure(peerId, error);
+}
+
+} // namespace
+
+DeviceSubscription::DeviceSubscription() :
+    mOnDeviceConnectedCallback(OnDeviceConnectedWrapper, this),
+    mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureWrapper, this)
+{}
+
+void DeviceSubscription::OnAttributeData(const ConcreteDataAttributePath & path, TLV::TLVReader * data, const StatusIB & status)
+{
+    VerifyOrDie(path.mEndpointId == kRootEndpointId);
+    VerifyOrDie(path.mClusterId == Clusters::AdministratorCommissioning::Id);
+
+    switch (path.mAttributeId)
+    {
+    case Clusters::AdministratorCommissioning::Attributes::WindowStatus::Id: {
+        Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum windowStatus;
+        CHIP_ERROR err = data->Get(windowStatus);
+        VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(NotSpecified, "Failed to read WindowStatus"));
+        VerifyOrReturn(windowStatus != Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kUnknownEnumValue);
+        mCurrentAdministratorCommissioningAttributes.window_status = static_cast<uint32_t>(windowStatus);
+        mChangeDetected                                            = true;
+        break;
+    }
+    case Clusters::AdministratorCommissioning::Attributes::AdminFabricIndex::Id: {
+        FabricIndex fabricIndex;
+        CHIP_ERROR err                                                       = data->Get(fabricIndex);
+        mCurrentAdministratorCommissioningAttributes.has_opener_fabric_index = err == CHIP_NO_ERROR;
+        if (mCurrentAdministratorCommissioningAttributes.has_opener_fabric_index)
+        {
+            mCurrentAdministratorCommissioningAttributes.opener_fabric_index = static_cast<uint32_t>(fabricIndex);
+        }
+        mChangeDetected = true;
+        break;
+    }
+    case Clusters::AdministratorCommissioning::Attributes::AdminVendorId::Id: {
+        chip::VendorId vendorId;
+        CHIP_ERROR err                                                    = data->Get(vendorId);
+        mCurrentAdministratorCommissioningAttributes.has_opener_vendor_id = err == CHIP_NO_ERROR;
+        if (mCurrentAdministratorCommissioningAttributes.has_opener_vendor_id)
+        {
+            mCurrentAdministratorCommissioningAttributes.opener_vendor_id = static_cast<uint32_t>(vendorId);
+        }
+        mChangeDetected = true;
+        break;
+    }
+    default:
+        break;
+    }
+}
+
+void DeviceSubscription::OnReportEnd()
+{
+    // Report end is at the end of all attributes (success)
+    if (mChangeDetected)
+    {
+#if defined(PW_RPC_ENABLED)
+        AdminCommissioningAttributeChanged(mCurrentAdministratorCommissioningAttributes);
+#else
+        ChipLogError(NotSpecified, "Cannot synchronize device with fabric bridge: RPC not enabled");
+#endif
+        mChangeDetected = false;
+    }
+}
+
+void DeviceSubscription::OnDone(ReadClient * apReadClient)
+{
+    // TODO(#35077) In follow up PR we will indicate to a manager DeviceSubscription is terminal.
+}
+
+void DeviceSubscription::OnError(CHIP_ERROR error)
+{
+    ChipLogProgress(NotSpecified, "Error subscribing: %" CHIP_ERROR_FORMAT, error.Format());
+}
+
+void DeviceSubscription::OnDeviceConnected(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle)
+{
+    mClient = std::make_unique<ReadClient>(app::InteractionModelEngine::GetInstance(), &exchangeMgr /* echangeMgr */,
+                                           *this /* callback */, ReadClient::InteractionType::Subscribe);
+    VerifyOrDie(mClient);
+
+    AttributePathParams readPaths[1];
+    readPaths[0] = AttributePathParams(kRootEndpointId, Clusters::AdministratorCommissioning::Id);
+
+    ReadPrepareParams readParams(sessionHandle);
+
+    readParams.mpAttributePathParamsList    = readPaths;
+    readParams.mAttributePathParamsListSize = 1;
+    readParams.mMaxIntervalCeilingSeconds   = 5 * 60;
+
+    CHIP_ERROR err = mClient->SendRequest(readParams);
+
+    if (err != CHIP_NO_ERROR)
+    {
+        ChipLogError(NotSpecified, "Failed to issue subscription to AdministratorCommissioning data");
+        // TODO(#35077) In follow up PR we will indicate to a manager DeviceSubscription is terminal.
+    }
+}
+
+void DeviceSubscription::OnDeviceConnectionFailure(const ScopedNodeId & peerId, CHIP_ERROR error)
+{
+    ChipLogError(NotSpecified, "Device Sync failed to connect to " ChipLogFormatX64, ChipLogValueX64(peerId.GetNodeId()));
+    // TODO(#35077) In follow up PR we will indicate to a manager DeviceSubscription is terminal.
+}
+
+void DeviceSubscription::StartSubscription(Controller::DeviceController & controller, NodeId nodeId)
+{
+    VerifyOrDie(!mSubscriptionStarted);
+
+    mCurrentAdministratorCommissioningAttributes         = chip_rpc_AdministratorCommissioningChanged_init_default;
+    mCurrentAdministratorCommissioningAttributes.node_id = nodeId;
+    mCurrentAdministratorCommissioningAttributes.window_status =
+        static_cast<uint32_t>(Clusters::AdministratorCommissioning::CommissioningWindowStatusEnum::kWindowNotOpen);
+    mSubscriptionStarted = true;
+
+    controller.GetConnectedDevice(nodeId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback);
+}
diff --git a/examples/fabric-admin/device_manager/DeviceSubscription.h b/examples/fabric-admin/device_manager/DeviceSubscription.h
new file mode 100644
index 0000000..7a9e504
--- /dev/null
+++ b/examples/fabric-admin/device_manager/DeviceSubscription.h
@@ -0,0 +1,70 @@
+/*
+ *   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/ReadClient.h>
+#include <controller/CHIPDeviceController.h>
+#include <lib/core/DataModelTypes.h>
+
+#include <memory>
+
+#include "fabric_bridge_service/fabric_bridge_service.pb.h"
+#include "fabric_bridge_service/fabric_bridge_service.rpc.pb.h"
+
+/// Attribute subscription to attributes that are important to keep track and send to fabric-bridge
+/// via RPC when change has been identified.
+///
+/// An instance of DeviceSubscription is intended to be used only once. Once a DeviceSubscription is
+/// terminal, either from an error or from subscriptions getting shut down, we expect the instance
+/// to be deleted. Any new subscription should instantiate another instance of DeviceSubscription.
+class DeviceSubscription : public chip::app::ReadClient::Callback
+{
+public:
+    DeviceSubscription();
+
+    /// Usually called after we have added a synchronized device to fabric-bridge to monitor
+    /// for any changes that need to be propgated to fabric-bridge.
+    void StartSubscription(chip::Controller::DeviceController & controller, chip::NodeId nodeId);
+
+    ///////////////////////////////////////////////////////////////
+    // ReadClient::Callback implementation
+    ///////////////////////////////////////////////////////////////
+    void OnAttributeData(const chip::app::ConcreteDataAttributePath & path, chip::TLV::TLVReader * data,
+                         const chip::app::StatusIB & status) override;
+    void OnReportEnd() override;
+    void OnError(CHIP_ERROR error) override;
+    void OnDone(chip::app::ReadClient * apReadClient) override;
+
+    ///////////////////////////////////////////////////////////////
+    // callbacks for CASE session establishment
+    ///////////////////////////////////////////////////////////////
+    void OnDeviceConnected(chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle);
+    void OnDeviceConnectionFailure(const chip::ScopedNodeId & peerId, CHIP_ERROR error);
+
+private:
+    std::unique_ptr<chip::app::ReadClient> mClient;
+
+    chip::Callback::Callback<chip::OnDeviceConnected> mOnDeviceConnectedCallback;
+    chip::Callback::Callback<chip::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;
+
+    chip_rpc_AdministratorCommissioningChanged mCurrentAdministratorCommissioningAttributes;
+    bool mChangeDetected = false;
+    // Ensures that DeviceSubscription starts a subscription only once.  If instance of
+    // DeviceSubscription  can be reused, the class documentation should be updated accordingly.
+    bool mSubscriptionStarted = false;
+};