[clusters] Implemented ThreadDiagnosticDelegate (#32964)

Added ThreadDiagnosticDelegate to enable generation of optional
events from ThreadNetworkDiagnostics cluster.

Additionally implemented generation of ConnectionStatus
and NetworkFaultChanged events when Thread link state
is changed.
diff --git a/src/app/clusters/thread-network-diagnostics-server/thread-network-diagnostics-server.cpp b/src/app/clusters/thread-network-diagnostics-server/thread-network-diagnostics-server.cpp
index 59115c0..c2e1987 100644
--- a/src/app/clusters/thread-network-diagnostics-server/thread-network-diagnostics-server.cpp
+++ b/src/app/clusters/thread-network-diagnostics-server/thread-network-diagnostics-server.cpp
@@ -23,6 +23,7 @@
 #include <app/AttributeAccessInterfaceRegistry.h>
 #include <app/CommandHandler.h>
 #include <app/ConcreteCommandPath.h>
+#include <app/EventLogging.h>
 #include <app/clusters/thread-network-diagnostics-server/thread-network-diagnostics-provider.h>
 #include <app/util/attribute-storage.h>
 #include <lib/core/CHIPEncoding.h>
@@ -30,6 +31,9 @@
 #include <lib/core/TLVTypes.h>
 #include <lib/support/CHIPPlatformMemory.h>
 #include <platform/CHIPDeviceLayer.h>
+#include <platform/DiagnosticDataProvider.h>
+#include <tracing/macros.h>
+#include <tracing/metric_event.h>
 
 using namespace chip;
 using namespace chip::app;
@@ -133,6 +137,60 @@
     return CHIP_NO_ERROR;
 }
 
+class ThreadDiagnosticsDelegate : public DeviceLayer::ThreadDiagnosticsDelegate
+{
+    // Notified when the Node’s connection status to a Thread network has changed.
+    void OnConnectionStatusChanged(ConnectionStatusEnum newConnectionStatus) override
+    {
+        ChipLogProgress(Zcl, "ThreadDiagnosticsDelegate: OnConnectionStatusChanged");
+
+        Events::ConnectionStatus::Type event{ newConnectionStatus };
+
+        // ThreadNetworkDiagnostics cluster should exist only for endpoint 0.
+        if (emberAfContainsServer(kRootEndpointId, ThreadNetworkDiagnostics::Id))
+        {
+            // If Thread Network Diagnostics cluster is implemented on this endpoint
+            EventNumber eventNumber;
+
+            if (CHIP_NO_ERROR != LogEvent(event, kRootEndpointId, eventNumber))
+            {
+                ChipLogError(Zcl, "ThreadDiagnosticsDelegate: Failed to record ConnectionStatus event");
+            }
+        }
+    }
+
+    // Notified when the Node’s faults related to a Thread network have changed.
+    void OnNetworkFaultChanged(const GeneralFaults<kMaxNetworkFaults> & previous,
+                               const GeneralFaults<kMaxNetworkFaults> & current) override
+    {
+        ChipLogProgress(Zcl, "ThreadDiagnosticsDelegate: OnNetworkFaultChanged");
+
+        /* Verify that the data size matches the expected one. */
+        static_assert(sizeof(*current.data()) == sizeof(NetworkFaultEnum));
+
+        DataModel::List<const NetworkFaultEnum> currentList(reinterpret_cast<const NetworkFaultEnum *>(current.data()),
+                                                            current.size());
+        DataModel::List<const NetworkFaultEnum> previousList(reinterpret_cast<const NetworkFaultEnum *>(previous.data()),
+                                                             previous.size());
+
+        Events::NetworkFaultChange::Type event{ currentList, previousList };
+
+        // ThreadNetworkDiagnostics cluster should exist only for endpoint 0.
+        if (emberAfContainsServer(kRootEndpointId, ThreadNetworkDiagnostics::Id))
+        {
+            // If Thread Network Diagnostics cluster is implemented on this endpoint
+            EventNumber eventNumber;
+
+            if (CHIP_NO_ERROR != LogEvent(event, kRootEndpointId, eventNumber))
+            {
+                ChipLogError(Zcl, "ThreadDiagnosticsDelegate: Failed to record NetworkFaultChange event");
+            }
+        }
+    }
+};
+
+ThreadDiagnosticsDelegate gDiagnosticDelegate;
+
 } // anonymous namespace
 
 bool emberAfThreadNetworkDiagnosticsClusterResetCountsCallback(app::CommandHandler * commandObj,
@@ -147,4 +205,5 @@
 void MatterThreadNetworkDiagnosticsPluginServerInitCallback()
 {
     registerAttributeAccessOverride(&gAttrAccess);
+    GetDiagnosticDataProvider().SetThreadDiagnosticsDelegate(&gDiagnosticDelegate);
 }
diff --git a/src/include/platform/DiagnosticDataProvider.h b/src/include/platform/DiagnosticDataProvider.h
index a99ca6e..2430673 100644
--- a/src/include/platform/DiagnosticDataProvider.h
+++ b/src/include/platform/DiagnosticDataProvider.h
@@ -92,6 +92,29 @@
 };
 
 /**
+ * Defines the Thread Diagnostics Delegate class to notify Thread network events.
+ */
+class ThreadDiagnosticsDelegate
+{
+public:
+    virtual ~ThreadDiagnosticsDelegate() {}
+
+    /**
+     * @brief
+     *   Called when the Node’s connection status to a Thread network has changed.
+     */
+    virtual void OnConnectionStatusChanged(app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum newConnectionStatus) {}
+
+    /**
+     * @brief
+     *   Called when the Node detects change in the set of current Thread network faults.
+     */
+    virtual void OnNetworkFaultChanged(const GeneralFaults<kMaxNetworkFaults> & previous,
+                                       const GeneralFaults<kMaxNetworkFaults> & current)
+    {}
+};
+
+/**
  * Provides access to runtime and build-time configuration information for a chip device.
  */
 class DiagnosticDataProvider
@@ -99,6 +122,8 @@
 public:
     void SetWiFiDiagnosticsDelegate(WiFiDiagnosticsDelegate * delegate) { mWiFiDiagnosticsDelegate = delegate; }
     WiFiDiagnosticsDelegate * GetWiFiDiagnosticsDelegate() const { return mWiFiDiagnosticsDelegate; }
+    void SetThreadDiagnosticsDelegate(ThreadDiagnosticsDelegate * delegate) { mThreadDiagnosticsDelegate = delegate; }
+    ThreadDiagnosticsDelegate * GetThreadDiagnosticsDelegate() const { return mThreadDiagnosticsDelegate; }
 
     /**
      * General Diagnostics methods.
@@ -238,7 +263,8 @@
     virtual ~DiagnosticDataProvider() = default;
 
 private:
-    WiFiDiagnosticsDelegate * mWiFiDiagnosticsDelegate = nullptr;
+    WiFiDiagnosticsDelegate * mWiFiDiagnosticsDelegate     = nullptr;
+    ThreadDiagnosticsDelegate * mThreadDiagnosticsDelegate = nullptr;
 
     // No copy, move or assignment.
     DiagnosticDataProvider(const DiagnosticDataProvider &)             = delete;
diff --git a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.h b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.h
index 896c5ef..6b9e1f7 100644
--- a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.h
+++ b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.h
@@ -40,6 +40,7 @@
 #include <app/icd/server/ICDServerConfig.h>
 #include <lib/dnssd/Advertiser.h>
 #include <lib/dnssd/platform/Dnssd.h>
+#include <platform/GeneralFaults.h>
 #include <platform/NetworkCommissioning.h>
 
 namespace chip {
@@ -229,6 +230,7 @@
 
     DnsBrowseCallback mDnsBrowseCallback;
     DnsResolveCallback mDnsResolveCallback;
+    GeneralFaults<kMaxNetworkFaults> mNetworkFaults;
 
     struct DnsServiceTxtEntries
     {
diff --git a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp
index 15f9f80..7c3111a 100644
--- a/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp
+++ b/src/platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.hpp
@@ -52,6 +52,7 @@
 #include <lib/support/FixedBufferAllocator.h>
 #include <lib/support/ThreadOperationalDataset.h>
 #include <lib/support/logging/CHIPLogging.h>
+#include <platform/DiagnosticDataProvider.h>
 #include <platform/OpenThread/GenericNetworkCommissioningThreadDriver.h>
 #include <platform/OpenThread/GenericThreadStackManagerImpl_OpenThread.h>
 #include <platform/OpenThread/OpenThreadUtils.h>
@@ -223,6 +224,22 @@
             {
                 ChipLogError(DeviceLayer, "Failed to post Thread connectivity change: %" CHIP_ERROR_FORMAT, status.Format());
             }
+
+            ThreadDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetThreadDiagnosticsDelegate();
+
+            if (mIsAttached)
+            {
+                delegate->OnConnectionStatusChanged(app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum::kConnected);
+            }
+            else
+            {
+                delegate->OnConnectionStatusChanged(app::Clusters::ThreadNetworkDiagnostics::ConnectionStatusEnum::kNotConnected);
+
+                GeneralFaults<kMaxNetworkFaults> current;
+                current.add(to_underlying(chip::app::Clusters::ThreadNetworkDiagnostics::NetworkFaultEnum::kLinkDown));
+                delegate->OnNetworkFaultChanged(mNetworkFaults, current);
+                mNetworkFaults = current;
+            }
         }
 
 #if CHIP_DETAIL_LOGGING