[Dns-sd] Added Operational Discovery and made browse delegate common (#32750)

* Added operational discovery support

* Restyled by whitespace

* Restyled by clang-format

* Updates as per review feedback

* Restyled by whitespace

* Restyled by clang-format

* Updates based on additional feedback

* Restyled by whitespace

* updates as per feedback from review

---------

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/src/lib/dnssd/IncrementalResolve.cpp b/src/lib/dnssd/IncrementalResolve.cpp
index f083f85..867b1a8 100644
--- a/src/lib/dnssd/IncrementalResolve.cpp
+++ b/src/lib/dnssd/IncrementalResolve.cpp
@@ -137,7 +137,8 @@
     return SerializedQNameIterator(BytesRange(mNameBuffer, mNameBuffer + sizeof(mNameBuffer)), mNameBuffer);
 }
 
-CHIP_ERROR IncrementalResolver::InitializeParsing(mdns::Minimal::SerializedQNameIterator name, const mdns::Minimal::SrvRecord & srv)
+CHIP_ERROR IncrementalResolver::InitializeParsing(mdns::Minimal::SerializedQNameIterator name, const uint64_t ttl,
+                                                  const mdns::Minimal::SrvRecord & srv)
 {
     AutoInactiveResetter inactiveReset(*this);
 
@@ -183,6 +184,7 @@
             {
                 return err;
             }
+            mSpecificResolutionData.Get<OperationalNodeData>().hasZeroTTL = (ttl == 0);
         }
 
         LogFoundOperationalSrvRecord(mSpecificResolutionData.Get<OperationalNodeData>().peerId, mTargetHostName.Get());
@@ -304,7 +306,7 @@
         }
     }
 
-    if (IsActiveBrowseParse())
+    if (IsActiveCommissionParse())
     {
         TxtParser<CommissionNodeData> delegate(mSpecificResolutionData.Get<CommissionNodeData>());
         if (!ParseTxtRecord(data.GetData(), &delegate))
@@ -343,14 +345,17 @@
 
 CHIP_ERROR IncrementalResolver::Take(DiscoveredNodeData & outputData)
 {
-    VerifyOrReturnError(IsActiveBrowseParse(), CHIP_ERROR_INCORRECT_STATE);
+    VerifyOrReturnError(IsActiveCommissionParse(), CHIP_ERROR_INCORRECT_STATE);
 
     IPAddressSorter::Sort(mCommonResolutionData.ipAddress, mCommonResolutionData.numIPs, mCommonResolutionData.interfaceId);
 
-    outputData.Set<CommissionNodeData>();
-    CommissionNodeData & nodeData         = outputData.Get<CommissionNodeData>();
-    nodeData                              = mSpecificResolutionData.Get<CommissionNodeData>();
-    CommonResolutionData & resolutionData = nodeData;
+    // Set the DiscoveredNodeData with CommissionNodeData info specific to commissionable/commisssioner
+    // node available in mSpecificResolutionData.
+    outputData.Set<CommissionNodeData>(mSpecificResolutionData.Get<CommissionNodeData>());
+
+    // IncrementalResolver stored CommonResolutionData separately in mCommonResolutionData hence copy the
+    //  CommonResolutionData info from mCommonResolutionData, to CommissionNodeData within DiscoveredNodeData
+    CommonResolutionData & resolutionData = outputData.Get<CommissionNodeData>();
     resolutionData                        = mCommonResolutionData;
 
     ResetToInactive();
diff --git a/src/lib/dnssd/IncrementalResolve.h b/src/lib/dnssd/IncrementalResolve.h
index 4a8bc0c..319a080 100644
--- a/src/lib/dnssd/IncrementalResolve.h
+++ b/src/lib/dnssd/IncrementalResolve.h
@@ -100,7 +100,7 @@
     /// method.
     bool IsActive() const { return mSpecificResolutionData.Valid(); }
 
-    bool IsActiveBrowseParse() const { return mSpecificResolutionData.Is<CommissionNodeData>(); }
+    bool IsActiveCommissionParse() const { return mSpecificResolutionData.Is<CommissionNodeData>(); }
     bool IsActiveOperationalParse() const { return mSpecificResolutionData.Is<OperationalNodeData>(); }
 
     ServiceNameType GetCurrentType() const { return mServiceNameType; }
@@ -115,7 +115,8 @@
     /// interested on, after which TXT and A/AAAA are looked for.
     ///
     /// If this function returns with error, the object will be in an inactive state.
-    CHIP_ERROR InitializeParsing(mdns::Minimal::SerializedQNameIterator name, const mdns::Minimal::SrvRecord & srv);
+    CHIP_ERROR InitializeParsing(mdns::Minimal::SerializedQNameIterator name, const uint64_t ttl,
+                                 const mdns::Minimal::SrvRecord & srv);
 
     /// Notify that a new record is being processed.
     /// Will handle filtering and processing of data to determine if the entry is relevant for
@@ -150,7 +151,7 @@
 
     /// Take the current value of the object and clear it once returned.
     ///
-    /// Object must be in `IsActiveBrowseParse()` for this to succeed.
+    /// Object must be in `IsActive()` for this to succeed.
     /// Data will be returned (and cleared) even if not yet complete based
     /// on `GetMissingRequiredInformation()`. This method takes as much data as
     /// it was parsed so far.
diff --git a/src/lib/dnssd/ResolverProxy.cpp b/src/lib/dnssd/ResolverProxy.cpp
index e43028e..2fbf5b1 100644
--- a/src/lib/dnssd/ResolverProxy.cpp
+++ b/src/lib/dnssd/ResolverProxy.cpp
@@ -55,6 +55,13 @@
     return mResolver.StartDiscovery(DiscoveryType::kCommissionerNode, filter, *mContext);
 }
 
+CHIP_ERROR ResolverProxy::DiscoverOperationalNodes(DiscoveryFilter filter)
+{
+    VerifyOrReturnError(mContext != nullptr, CHIP_ERROR_INCORRECT_STATE);
+
+    return mResolver.StartDiscovery(DiscoveryType::kOperational, filter, *mContext);
+}
+
 CHIP_ERROR ResolverProxy::StopDiscovery()
 {
     VerifyOrReturnError(mContext != nullptr, CHIP_ERROR_INCORRECT_STATE);
diff --git a/src/lib/dnssd/ResolverProxy.h b/src/lib/dnssd/ResolverProxy.h
index 58617b0..d1888c3 100644
--- a/src/lib/dnssd/ResolverProxy.h
+++ b/src/lib/dnssd/ResolverProxy.h
@@ -54,6 +54,7 @@
 
     CHIP_ERROR DiscoverCommissionableNodes(DiscoveryFilter filter = DiscoveryFilter());
     CHIP_ERROR DiscoverCommissioners(DiscoveryFilter filter = DiscoveryFilter());
+    CHIP_ERROR DiscoverOperationalNodes(DiscoveryFilter filter = DiscoveryFilter());
     CHIP_ERROR StopDiscovery();
 
 private:
diff --git a/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp b/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp
index 8e17ce2..efcdece 100644
--- a/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp
+++ b/src/lib/dnssd/Resolver_ImplMinimalMdns.cpp
@@ -213,7 +213,7 @@
             continue;
         }
 
-        CHIP_ERROR err = resolver.InitializeParsing(data.GetName(), srv);
+        CHIP_ERROR err = resolver.InitializeParsing(data.GetName(), data.GetTtlSeconds(), srv);
         if (err != CHIP_NO_ERROR)
         {
             // Receiving records that we do not need to parse is normal:
@@ -363,6 +363,7 @@
     {
         if (!resolver->IsActive())
         {
+            ChipLogError(Discovery, "resolver inactive, continue to next");
             continue;
         }
 
@@ -370,7 +371,7 @@
 
         if (missing.Has(IncrementalResolver::RequiredInformationBitFlags::kIpAddress))
         {
-            if (resolver->IsActiveBrowseParse())
+            if (resolver->IsActiveCommissionParse())
             {
                 // Browse wants IP addresses
                 ScheduleIpAddressResolve(resolver->GetTargetHostName());
@@ -399,7 +400,7 @@
         }
 
         // SUCCESS. Call the delegates
-        if (resolver->IsActiveBrowseParse())
+        if (resolver->IsActiveCommissionParse())
         {
             MATTER_TRACE_SCOPE("Active commissioning delegate call", "MinMdnsResolver");
             DiscoveredNodeData nodeData;
@@ -452,19 +453,39 @@
         else if (resolver->IsActiveOperationalParse())
         {
             MATTER_TRACE_SCOPE("Active operational delegate call", "MinMdnsResolver");
-            ResolvedNodeData nodeData;
+            ResolvedNodeData nodeResolvedData;
+            CHIP_ERROR err = resolver->Take(nodeResolvedData);
 
-            CHIP_ERROR err = resolver->Take(nodeData);
             if (err != CHIP_NO_ERROR)
             {
-                ChipLogError(Discovery, "Failed to take discovery result: %" CHIP_ERROR_FORMAT, err.Format());
+                ChipLogError(Discovery, "Failed to take NodeData - result: %" CHIP_ERROR_FORMAT, err.Format());
                 continue;
             }
 
-            mActiveResolves.Complete(nodeData.operationalData.peerId);
+            if (mActiveResolves.HasBrowseFor(chip::Dnssd::DiscoveryType::kOperational))
+            {
+                if (mDiscoveryContext != nullptr)
+                {
+                    DiscoveredNodeData nodeData;
+                    OperationalNodeBrowseData opNodeData;
+
+                    opNodeData.peerId     = nodeResolvedData.operationalData.peerId;
+                    opNodeData.hasZeroTTL = nodeResolvedData.operationalData.hasZeroTTL;
+                    nodeData.Set<OperationalNodeBrowseData>(opNodeData);
+                    mDiscoveryContext->OnNodeDiscovered(nodeData);
+                }
+                else
+                {
+#if CHIP_MINMDNS_HIGH_VERBOSITY
+                    ChipLogError(Discovery, "No delegate to report operational node discovery");
+#endif
+                }
+            }
+
+            mActiveResolves.Complete(nodeResolvedData.operationalData.peerId);
             if (mOperationalDelegate != nullptr)
             {
-                mOperationalDelegate->OnOperationalNodeResolved(nodeData);
+                mOperationalDelegate->OnOperationalNodeResolved(nodeResolvedData);
             }
             else
             {
diff --git a/src/lib/dnssd/Types.h b/src/lib/dnssd/Types.h
index c88dd0a..1b9b19c 100644
--- a/src/lib/dnssd/Types.h
+++ b/src/lib/dnssd/Types.h
@@ -197,10 +197,21 @@
 struct OperationalNodeData
 {
     PeerId peerId;
-
+    bool hasZeroTTL;
     void Reset() { peerId = PeerId(); }
 };
 
+struct OperationalNodeBrowseData : public OperationalNodeData
+{
+    OperationalNodeBrowseData() { Reset(); };
+    void LogDetail() const
+    {
+        ChipLogDetail(Discovery, "Discovered Operational node:\r\n");
+        ChipLogDetail(Discovery, "\tNode Instance: " ChipLogFormatPeerId, ChipLogValuePeerId(peerId));
+        ChipLogDetail(Discovery, "\thasZeroTTL: %s\r\n", hasZeroTTL ? "true" : "false");
+    }
+};
+
 inline constexpr size_t kMaxDeviceNameLen         = 32;
 inline constexpr size_t kMaxRotatingIdLen         = 50;
 inline constexpr size_t kMaxPairingInstructionLen = 128;
@@ -297,12 +308,13 @@
     }
 };
 
-using DiscoveredNodeData = Variant<CommissionNodeData>;
+using DiscoveredNodeData = Variant<CommissionNodeData, OperationalNodeBrowseData>;
 
-/// Callbacks for discovering nodes advertising non-operational status:
+/// Callbacks for discovering nodes advertising both operational and non-operational status:
 ///   - Commissioners
 ///   - Nodes in commissioning modes over IP (e.g. ethernet devices, devices already
 ///     connected to thread/wifi or devices with a commissioning window open)
+///   - Operational nodes
 class DiscoverNodeDelegate
 {
 public:
diff --git a/src/lib/dnssd/tests/TestIncrementalResolve.cpp b/src/lib/dnssd/tests/TestIncrementalResolve.cpp
index 0b5f26a..053d0dd 100644
--- a/src/lib/dnssd/tests/TestIncrementalResolve.cpp
+++ b/src/lib/dnssd/tests/TestIncrementalResolve.cpp
@@ -20,6 +20,7 @@
 
 #include <string.h>
 
+#include <lib/dnssd/ServiceNaming.h>
 #include <lib/dnssd/minimal_mdns/core/tests/QNameStrings.h>
 #include <lib/dnssd/minimal_mdns/records/IP.h>
 #include <lib/dnssd/minimal_mdns/records/Ptr.h>
@@ -161,7 +162,7 @@
     IncrementalResolver resolver;
 
     EXPECT_FALSE(resolver.IsActive());
-    EXPECT_FALSE(resolver.IsActiveBrowseParse());
+    EXPECT_FALSE(resolver.IsActiveCommissionParse());
     EXPECT_FALSE(resolver.IsActiveOperationalParse());
     EXPECT_TRUE(
         resolver.GetMissingRequiredInformation().HasOnly(IncrementalResolver::RequiredInformationBitFlags::kSrvInitialization));
@@ -177,10 +178,10 @@
     PreloadSrvRecord(srvRecord);
 
     // test host name is not a 'matter' name
-    EXPECT_NE(resolver.InitializeParsing(kTestHostName.Serialized(), srvRecord), CHIP_NO_ERROR);
+    EXPECT_NE(resolver.InitializeParsing(kTestHostName.Serialized(), 0, srvRecord), CHIP_NO_ERROR);
 
     EXPECT_FALSE(resolver.IsActive());
-    EXPECT_FALSE(resolver.IsActiveBrowseParse());
+    EXPECT_FALSE(resolver.IsActiveCommissionParse());
     EXPECT_FALSE(resolver.IsActiveOperationalParse());
 }
 
@@ -193,10 +194,10 @@
     SrvRecord srvRecord;
     PreloadSrvRecord(srvRecord);
 
-    EXPECT_EQ(resolver.InitializeParsing(kTestOperationalName.Serialized(), srvRecord), CHIP_NO_ERROR);
+    EXPECT_EQ(resolver.InitializeParsing(kTestOperationalName.Serialized(), 1, srvRecord), CHIP_NO_ERROR);
 
     EXPECT_TRUE(resolver.IsActive());
-    EXPECT_FALSE(resolver.IsActiveBrowseParse());
+    EXPECT_FALSE(resolver.IsActiveCommissionParse());
     EXPECT_TRUE(resolver.IsActiveOperationalParse());
     EXPECT_TRUE(resolver.GetMissingRequiredInformation().HasOnly(IncrementalResolver::RequiredInformationBitFlags::kIpAddress));
     EXPECT_EQ(resolver.GetTargetHostName(), kTestHostName.Serialized());
@@ -211,10 +212,10 @@
     SrvRecord srvRecord;
     PreloadSrvRecord(srvRecord);
 
-    EXPECT_EQ(resolver.InitializeParsing(kTestCommissionableNode.Serialized(), srvRecord), CHIP_NO_ERROR);
+    EXPECT_EQ(resolver.InitializeParsing(kTestCommissionableNode.Serialized(), 0, srvRecord), CHIP_NO_ERROR);
 
     EXPECT_TRUE(resolver.IsActive());
-    EXPECT_TRUE(resolver.IsActiveBrowseParse());
+    EXPECT_TRUE(resolver.IsActiveCommissionParse());
     EXPECT_FALSE(resolver.IsActiveOperationalParse());
     EXPECT_TRUE(resolver.GetMissingRequiredInformation().HasOnly(IncrementalResolver::RequiredInformationBitFlags::kIpAddress));
     EXPECT_EQ(resolver.GetTargetHostName(), kTestHostName.Serialized());
@@ -229,10 +230,10 @@
     SrvRecord srvRecord;
     PreloadSrvRecord(srvRecord);
 
-    EXPECT_EQ(resolver.InitializeParsing(kTestCommissionerNode.Serialized(), srvRecord), CHIP_NO_ERROR);
+    EXPECT_EQ(resolver.InitializeParsing(kTestCommissionerNode.Serialized(), 0, srvRecord), CHIP_NO_ERROR);
 
     EXPECT_TRUE(resolver.IsActive());
-    EXPECT_TRUE(resolver.IsActiveBrowseParse());
+    EXPECT_TRUE(resolver.IsActiveCommissionParse());
     EXPECT_FALSE(resolver.IsActiveOperationalParse());
     EXPECT_TRUE(resolver.GetMissingRequiredInformation().HasOnly(IncrementalResolver::RequiredInformationBitFlags::kIpAddress));
     EXPECT_EQ(resolver.GetTargetHostName(), kTestHostName.Serialized());
@@ -247,7 +248,7 @@
     SrvRecord srvRecord;
     PreloadSrvRecord(srvRecord);
 
-    EXPECT_EQ(resolver.InitializeParsing(kTestOperationalName.Serialized(), srvRecord), CHIP_NO_ERROR);
+    EXPECT_EQ(resolver.InitializeParsing(kTestOperationalName.Serialized(), 1, srvRecord), CHIP_NO_ERROR);
 
     // once initialized, parsing should be ready however no IP address is available
     EXPECT_TRUE(resolver.IsActiveOperationalParse());
@@ -304,6 +305,7 @@
     // validate data as it was passed in
     EXPECT_EQ(nodeData.operationalData.peerId,
               PeerId().SetCompressedFabricId(0x1234567898765432LL).SetNodeId(0xABCDEFEDCBAABCDELL));
+    EXPECT_FALSE(nodeData.operationalData.hasZeroTTL);
     EXPECT_EQ(nodeData.resolutionData.numIPs, 1u);
     EXPECT_EQ(nodeData.resolutionData.port, 0x1234);
     EXPECT_FALSE(nodeData.resolutionData.supportsTcp);
@@ -324,10 +326,10 @@
     SrvRecord srvRecord;
     PreloadSrvRecord(srvRecord);
 
-    EXPECT_EQ(resolver.InitializeParsing(kTestCommissionableNode.Serialized(), srvRecord), CHIP_NO_ERROR);
+    EXPECT_EQ(resolver.InitializeParsing(kTestCommissionableNode.Serialized(), 0, srvRecord), CHIP_NO_ERROR);
 
     // once initialized, parsing should be ready however no IP address is available
-    EXPECT_TRUE(resolver.IsActiveBrowseParse());
+    EXPECT_TRUE(resolver.IsActiveCommissionParse());
     EXPECT_TRUE(resolver.GetMissingRequiredInformation().HasOnly(IncrementalResolver::RequiredInformationBitFlags::kIpAddress));
     EXPECT_EQ(resolver.GetTargetHostName(), kTestHostName.Serialized());
 
diff --git a/src/lib/shell/commands/Dns.cpp b/src/lib/shell/commands/Dns.cpp
index 7fac018..c6a9365 100644
--- a/src/lib/shell/commands/Dns.cpp
+++ b/src/lib/shell/commands/Dns.cpp
@@ -83,15 +83,23 @@
 
     AddressResolve::NodeLookupHandle & Handle() { return mSelfHandle; }
 
+    void LogOperationalNodeDiscovered(const Dnssd::OperationalNodeBrowseData & nodeData)
+    {
+        streamer_printf(streamer_get(), "DNS browse operational succeeded: \r\n");
+        streamer_printf(streamer_get(), "   Node Instance: " ChipLogFormatPeerId, ChipLogValuePeerId(nodeData.peerId));
+        streamer_printf(streamer_get(), "   hasZeroTTL: %s\r\n", nodeData.hasZeroTTL ? "true" : "false");
+    }
+
     void OnNodeDiscovered(const Dnssd::DiscoveredNodeData & discNodeData) override
     {
-        if (!discNodeData.Is<Dnssd::CommissionNodeData>())
+        if (discNodeData.Is<Dnssd::OperationalNodeBrowseData>())
         {
-            streamer_printf(streamer_get(), "DNS browse failed - not commission type node \r\n");
+            LogOperationalNodeDiscovered(discNodeData.Get<Dnssd::OperationalNodeBrowseData>());
             return;
         }
 
-        Dnssd::CommissionNodeData nodeData = discNodeData.Get<Dnssd::CommissionNodeData>();
+        const auto & nodeData = discNodeData.Get<Dnssd::CommissionNodeData>();
+
         if (!nodeData.IsValid())
         {
             streamer_printf(streamer_get(), "DNS browse failed - not found valid services \r\n");
@@ -237,6 +245,16 @@
     return sResolverProxy.DiscoverCommissioners(filter);
 }
 
+CHIP_ERROR BrowseOperationalHandler(int argc, char ** argv)
+{
+    Dnssd::DiscoveryFilter filter;
+    VerifyOrReturnError(ParseSubType(argc, argv, filter), CHIP_ERROR_INVALID_ARGUMENT);
+
+    streamer_printf(streamer_get(), "Browsing operational...\r\n");
+
+    return sResolverProxy.DiscoverOperationalNodes(filter);
+}
+
 CHIP_ERROR BrowseHandler(int argc, char ** argv)
 {
     if (argc == 0)
@@ -271,13 +289,14 @@
           "Browse Matter commissionable nodes. Usage: dns browse commissionable [subtype]" },
         { &BrowseCommissionerHandler, "commissioner",
           "Browse Matter commissioner nodes. Usage: dns browse commissioner [subtype]" },
+        { &BrowseOperationalHandler, "operational", "Browse Matter operational nodes. Usage: dns browse operational" },
     };
 
     static const shell_command_t sDnsSubCommands[] = {
         { &ResolveHandler, "resolve",
           "Resolve the DNS service. Usage: dns resolve <fabric-id> <node-id> (e.g. dns resolve 5544332211 1)" },
         { &BrowseHandler, "browse",
-          "Browse DNS services published by Matter nodes. Usage: dns browse <commissionable|commissioner>" },
+          "Browse DNS services published by Matter nodes. Usage: dns browse <commissionable|commissioner|operational>" },
     };
 
     static const shell_command_t sDnsCommand = { &DnsHandler, "dns", "Dns client commands" };