DNS-SD mock for checking platform implementations (#26143)

* DNS-SD mock for checking platform implementations

* Mark POSIX event loop as ready for run if not-internaly managed

* Prevent SEGFAULT in test in case of resolve error

* Document mDNS test cases

* Apply suggestions from code review

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>

---------

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp
index 1b09d29..5c9061a 100644
--- a/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp
+++ b/src/include/platform/internal/GenericPlatformManagerImpl_POSIX.ipp
@@ -163,6 +163,7 @@
     {
         mChipTask = pthread_self();
         mState.store(State::kRunning, std::memory_order_relaxed);
+        mShouldRunEventLoop.store(true, std::memory_order_relaxed);
     }
 
     pthread_mutex_unlock(&mStateLock);
diff --git a/src/platform/tests/BUILD.gn b/src/platform/tests/BUILD.gn
index 1de55b7..70d0965 100644
--- a/src/platform/tests/BUILD.gn
+++ b/src/platform/tests/BUILD.gn
@@ -49,7 +49,10 @@
     if (chip_mdns != "none" && chip_enable_dnssd_tests &&
         (chip_device_platform == "linux" || chip_device_platform == "darwin")) {
       test_sources += [ "TestDnssd.cpp" ]
-      public_deps += [ "${chip_root}/src/lib/dnssd" ]
+      public_deps += [
+        "${chip_root}/src/lib/dnssd",
+        "${chip_root}/src/lib/dnssd/minimal_mdns",
+      ]
     }
 
     # These tests appear to be broken on Mac.
diff --git a/src/platform/tests/TestDnssd.cpp b/src/platform/tests/TestDnssd.cpp
index d7e4ed1..21f4638 100644
--- a/src/platform/tests/TestDnssd.cpp
+++ b/src/platform/tests/TestDnssd.cpp
@@ -21,7 +21,18 @@
 
 #include "lib/dnssd/platform/Dnssd.h"
 #include "platform/CHIPDeviceLayer.h"
+#include "platform/ConnectivityManager.h"
 #include "platform/PlatformManager.h"
+#include <lib/dnssd/minimal_mdns/AddressPolicy.h>
+#include <lib/dnssd/minimal_mdns/AddressPolicy_DefaultImpl.h>
+#include <lib/dnssd/minimal_mdns/Parser.h>
+#include <lib/dnssd/minimal_mdns/RecordData.h>
+#include <lib/dnssd/minimal_mdns/ResponseSender.h>
+#include <lib/dnssd/minimal_mdns/Server.h>
+#include <lib/dnssd/minimal_mdns/responders/IP.h>
+#include <lib/dnssd/minimal_mdns/responders/Ptr.h>
+#include <lib/dnssd/minimal_mdns/responders/Srv.h>
+#include <lib/dnssd/minimal_mdns/responders/Txt.h>
 #include <lib/support/CHIPMem.h>
 #include <lib/support/UnitTestRegistration.h>
 
@@ -44,6 +55,35 @@
     bool mEndOfInput                    = false;
 };
 
+class TestDnssdResolveServerDelegate : public mdns::Minimal::ServerDelegate, public mdns::Minimal::ParserDelegate
+{
+public:
+    TestDnssdResolveServerDelegate(mdns::Minimal::ResponseSender * responder) : mResponder(responder) {}
+    virtual ~TestDnssdResolveServerDelegate() = default;
+
+    // Implementation of mdns::Minimal::ServerDelegate
+
+    void OnResponse(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override {}
+    void OnQuery(const mdns::Minimal::BytesRange & data, const chip::Inet::IPPacketInfo * info) override
+    {
+        mCurSrc = info;
+        mdns::Minimal::ParsePacket(data, this);
+        mCurSrc = nullptr;
+    }
+
+    // Implementation of mdns::Minimal::ParserDelegate
+
+    void OnHeader(mdns::Minimal::ConstHeaderRef & header) override { mMsgId = header.GetMessageId(); }
+    void OnQuery(const mdns::Minimal::QueryData & data) override { mResponder->Respond(mMsgId, data, mCurSrc, mRespConfig); }
+    void OnResource(mdns::Minimal::ResourceType type, const mdns::Minimal::ResourceData & data) override {}
+
+private:
+    mdns::Minimal::ResponseSender * mResponder;
+    mdns::Minimal::ResponseConfiguration mRespConfig;
+    const chip::Inet::IPPacketInfo * mCurSrc = nullptr;
+    uint16_t mMsgId                          = 0;
+};
+
 } // namespace
 
 static void Timeout(chip::System::Layer * systemLayer, void * context)
@@ -64,6 +104,10 @@
     NL_TEST_ASSERT(suite, result != nullptr);
     NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR);
 
+    // The NL_TEST_ASSERT above will not abort the test, so we need to
+    // explicitly abort it here to avoid dereferencing a null pointer.
+    VerifyOrReturn(result != nullptr, );
+
     if (!addresses.empty())
     {
         addresses.data()[0].ToString(addrBuf, sizeof(addrBuf));
@@ -122,13 +166,96 @@
     NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
 }
 
+void TestDnssdBrowse_DnssdInitCallback(void * context, CHIP_ERROR error)
+{
+    auto * ctx = static_cast<DnssdContext *>(context);
+    NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
+    auto * suite = ctx->mTestSuite;
+
+    NL_TEST_ASSERT(suite,
+                   ChipDnssdBrowse("_mock", DnssdServiceProtocol::kDnssdProtocolUdp, chip::Inet::IPAddressType::kAny,
+                                   chip::Inet::InterfaceId::Null(), HandleBrowse, context,
+                                   &ctx->mBrowseIdentifier) == CHIP_NO_ERROR);
+}
+
+// Verify that platform DNS-SD implementation can browse and resolve services.
+//
+// This test case uses platform-independent mDNS server implementation based on
+// minimal mdns library. The server is configured to respond to PTR, SRV, TXT,
+// A and AAAA queries without additional records. In order to pass this test,
+// the platform DNS-SD client implementation must be able to browse and resolve
+// services by querying for all of these records separately.
+void TestDnssdBrowse(nlTestSuite * inSuite, void * inContext)
+{
+    DnssdContext context;
+    context.mTestSuite = inSuite;
+
+    mdns::Minimal::SetDefaultAddressPolicy();
+
+    mdns::Minimal::Server<10> server;
+    mdns::Minimal::QNamePart serverName[] = { "resolve-tester", "_mock", chip::Dnssd::kCommissionProtocol,
+                                              chip::Dnssd::kLocalDomain };
+    mdns::Minimal::ResponseSender responseSender(&server);
+
+    mdns::Minimal::QueryResponder<16> queryResponder;
+    responseSender.AddQueryResponder(&queryResponder);
+
+    // Respond to PTR queries for _mock._udp.local
+    mdns::Minimal::QNamePart serviceName[]       = { "_mock", chip::Dnssd::kCommissionProtocol, chip::Dnssd::kLocalDomain };
+    mdns::Minimal::QNamePart serverServiceName[] = { "INSTANCE", chip::Dnssd::kCommissionableServiceName,
+                                                     chip::Dnssd::kCommissionProtocol, chip::Dnssd::kLocalDomain };
+    mdns::Minimal::PtrResponder ptrUdpResponder(serviceName, serverServiceName);
+    queryResponder.AddResponder(&ptrUdpResponder);
+
+    // Respond to SRV queries for INSTANCE._matterc._udp.local
+    mdns::Minimal::SrvResponder srvResponder(mdns::Minimal::SrvResourceRecord(serverServiceName, serverName, CHIP_PORT));
+    queryResponder.AddResponder(&srvResponder);
+
+    // Respond to TXT queries for INSTANCE._matterc._udp.local
+    const char * txtEntries[] = { "key=val" };
+    mdns::Minimal::TxtResponder txtResponder(mdns::Minimal::TxtResourceRecord(serverServiceName, txtEntries));
+    queryResponder.AddResponder(&txtResponder);
+
+    // Respond to A queries
+    mdns::Minimal::IPv4Responder ipv4Responder(serverName);
+    queryResponder.AddResponder(&ipv4Responder);
+
+    // Respond to AAAA queries
+    mdns::Minimal::IPv6Responder ipv6Responder(serverName);
+    queryResponder.AddResponder(&ipv6Responder);
+
+    TestDnssdResolveServerDelegate delegate(&responseSender);
+    server.SetDelegate(&delegate);
+
+    auto endpoints = mdns::Minimal::GetAddressPolicy()->GetListenEndpoints();
+    NL_TEST_ASSERT(inSuite, server.Listen(chip::DeviceLayer::UDPEndPointManager(), endpoints.get(), 5353) == CHIP_NO_ERROR);
+
+    NL_TEST_ASSERT(inSuite,
+                   chip::Dnssd::ChipDnssdInit(TestDnssdBrowse_DnssdInitCallback, DnssdErrorCallback, &context) == CHIP_NO_ERROR);
+    NL_TEST_ASSERT(inSuite,
+                   chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds32(5), Timeout, &context) ==
+                       CHIP_NO_ERROR);
+
+    ChipLogProgress(DeviceLayer, "Start EventLoop");
+    chip::DeviceLayer::PlatformMgr().RunEventLoop();
+    ChipLogProgress(DeviceLayer, "End EventLoop");
+
+    NL_TEST_ASSERT(inSuite, context.mResolvedServicesCount > 0);
+    NL_TEST_ASSERT(inSuite, !context.mTimeoutExpired);
+
+    // Stop browsing so we can safely shutdown DNS-SD
+    chip::Dnssd::ChipDnssdStopBrowse(context.mBrowseIdentifier);
+
+    chip::Dnssd::ChipDnssdShutdown();
+}
+
 static void HandlePublish(void * context, const char * type, const char * instanceName, CHIP_ERROR error)
 {
     auto * ctx = static_cast<DnssdContext *>(context);
     NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
 }
 
-static void TestDnssdPubSub_DnssdInitCallback(void * context, CHIP_ERROR error)
+static void TestDnssdPublishService_DnssdInitCallback(void * context, CHIP_ERROR error)
 {
     auto * ctx = static_cast<DnssdContext *>(context);
     NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
@@ -157,13 +284,19 @@
                                    &ctx->mBrowseIdentifier) == CHIP_NO_ERROR);
 }
 
-void TestDnssdPubSub(nlTestSuite * inSuite, void * inContext)
+// Verify that the platform DNS-SD implementation can publish services.
+//
+// This test uses platform implementation of DNS-SD server and client. Since
+// client implementation should be verified by the TestDnssdBrowse test case,
+// here we only verify that the server implementation can publish services.
+void TestDnssdPublishService(nlTestSuite * inSuite, void * inContext)
 {
     DnssdContext context;
     context.mTestSuite = inSuite;
 
     NL_TEST_ASSERT(inSuite,
-                   chip::Dnssd::ChipDnssdInit(TestDnssdPubSub_DnssdInitCallback, DnssdErrorCallback, &context) == CHIP_NO_ERROR);
+                   chip::Dnssd::ChipDnssdInit(TestDnssdPublishService_DnssdInitCallback, DnssdErrorCallback, &context) ==
+                       CHIP_NO_ERROR);
     NL_TEST_ASSERT(inSuite,
                    chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds32(5), Timeout, &context) ==
                        CHIP_NO_ERROR);
@@ -172,6 +305,7 @@
     chip::DeviceLayer::PlatformMgr().RunEventLoop();
     ChipLogProgress(DeviceLayer, "End EventLoop");
 
+    NL_TEST_ASSERT(inSuite, context.mResolvedServicesCount > 0);
     NL_TEST_ASSERT(inSuite, !context.mTimeoutExpired);
 
     // Stop browsing so we can safely shutdown DNS-SD
@@ -180,7 +314,11 @@
     chip::Dnssd::ChipDnssdShutdown();
 }
 
-static const nlTest sTests[] = { NL_TEST_DEF("Test Dnssd::PubSub", TestDnssdPubSub), NL_TEST_SENTINEL() };
+static const nlTest sTests[] = {
+    NL_TEST_DEF("Test ChipDnssdBrowse", TestDnssdBrowse),
+    NL_TEST_DEF("Test ChipDnssdPublishService", TestDnssdPublishService),
+    NL_TEST_SENTINEL(),
+};
 
 int TestDnssd_Setup(void * inContext)
 {