[Tizen] Wait for mDNS on-browse events until timeout (#21553)

Tizen Native API dos not deliver all-for-now event (such event is
delivered by e.g. Avahi), so it is impossible to tell when the mDNS
browsing shall be considered finished. This commit adds timeout event
which is delivered after 250ms from the last on-browse event. This
allows Tizen platform to discover more than one mDNS service on local
network.
diff --git a/src/platform/Tizen/DnssdImpl.cpp b/src/platform/Tizen/DnssdImpl.cpp
index 4568a1c..f59e48b 100644
--- a/src/platform/Tizen/DnssdImpl.cpp
+++ b/src/platform/Tizen/DnssdImpl.cpp
@@ -41,6 +41,12 @@
 
 constexpr uint8_t kDnssdKeyMaxSize = 32;
 
+// The number of miliseconds which must elapse without a new "found" event before
+// mDNS browsing is considered finished. We need this timeout because Tizen Native
+// API does not deliver all-for-now signal (such signal is delivered by e.g. Avahi)
+// and the browsing callback is called multiple times (once for each service found).
+constexpr unsigned int kDnssdBrowseTimeoutMs = 250;
+
 bool IsSupportedProtocol(DnssdServiceProtocol protocol)
 {
     return (protocol == DnssdServiceProtocol::kDnssdProtocolUdp) || (protocol == DnssdServiceProtocol::kDnssdProtocolTcp);
@@ -109,6 +115,22 @@
     return true;
 }
 
+gboolean OnBrowseTimeout(void * userData)
+{
+    ChipLogDetail(DeviceLayer, "DNSsd %s: all for now", __func__);
+
+    auto * bCtx = reinterpret_cast<BrowseContext *>(userData);
+
+    bCtx->MainLoopQuit();
+    bCtx->mCallback(bCtx->mCbContext, bCtx->mServices.data(), bCtx->mServices.size(), CHIP_NO_ERROR);
+
+    // After this point the context might be no longer valid
+    bCtx->mInstance->RemoveContext(bCtx);
+
+    // This is a one-shot timer
+    return FALSE;
+}
+
 void OnBrowseAdd(BrowseContext * context, const char * type, const char * name, uint32_t interfaceId)
 {
     ChipLogDetail(DeviceLayer, "DNSsd %s: name: %s, type: %s, interfaceId: %u", __func__, name, type, interfaceId);
@@ -143,8 +165,20 @@
     auto bCtx = reinterpret_cast<BrowseContext *>(data);
     int ret;
 
-    // Always stop browsing
-    bCtx->MainLoopQuit();
+    // If there is already a timeout source, so we need to cancel it.
+    if (bCtx->mTimeoutSource != nullptr)
+    {
+        g_source_destroy(bCtx->mTimeoutSource);
+        g_source_unref(bCtx->mTimeoutSource);
+    }
+
+    // Start a timer, so we could detect when there is no more on-browse events.
+    // The timeout callback function will be called in the same event loop as the
+    // browse callback (this one), so locking is not required.
+    auto * source = g_timeout_source_new(kDnssdBrowseTimeoutMs);
+    g_source_set_callback(source, OnBrowseTimeout, bCtx, nullptr);
+    g_source_attach(source, g_main_context_get_thread_default());
+    bCtx->mTimeoutSource = source;
 
     char * type          = nullptr;
     char * name          = nullptr;
@@ -173,23 +207,17 @@
         OnBrowseRemove(bCtx, type, name, interfaceId);
     }
 
-    // For now, there is no way to wait for multiple services to be found.
-    // Darwin implementation just checks if kDNSServiceFlagsMoreComing is set or not,
-    // but it doesn't ensure that multiple services can be found.
-    bCtx->mCallback(bCtx->mCbContext, bCtx->mServices.data(), bCtx->mServices.size(), CHIP_NO_ERROR);
-
 exit:
 
+    dnssd_destroy_remote_service(service);
+
     if (ret != DNSSD_ERROR_NONE)
     {
         bCtx->mCallback(bCtx->mCbContext, nullptr, 0, GetChipError(ret));
+        // After this point the context might be no longer valid
+        bCtx->mInstance->RemoveContext(bCtx);
     }
 
-    // After this point, the context might be no longer valid
-    bCtx->mInstance->RemoveContext(bCtx);
-
-    dnssd_destroy_remote_service(service);
-
     g_free(type);
     g_free(name);
     g_free(ifaceName);
@@ -412,6 +440,11 @@
 
 BrowseContext::~BrowseContext()
 {
+    if (mTimeoutSource != nullptr)
+    {
+        g_source_destroy(mTimeoutSource);
+        g_source_unref(mTimeoutSource);
+    }
     if (mIsBrowsing)
     {
         dnssd_cancel_browse_service(mBrowserHandle);
diff --git a/src/platform/Tizen/DnssdImpl.h b/src/platform/Tizen/DnssdImpl.h
index 28bcf31..9163a21 100755
--- a/src/platform/Tizen/DnssdImpl.h
+++ b/src/platform/Tizen/DnssdImpl.h
@@ -80,6 +80,9 @@
     void * mCbContext;
 
     dnssd_browser_h mBrowserHandle = 0;
+    // The timeout source used to stop browsing
+    GSource * mTimeoutSource = nullptr;
+
     std::vector<DnssdService> mServices;
     bool mIsBrowsing = false;