[DRUEL03] Implement unsolicited private DNS validation event

Implement unsolicited private DNS validation event that will
report to the listener when process a private DNS validation
and get result.

Bug: 173485754
Test: atest resolv_integration_test resolv_unit_test\
      resolv_stress_test resolv_stats_test_utils_test
Change-Id: I2b90f4e5ad298e06efbb07c8bcc56808df46fdd0
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
index 8f3b836..c8b070a 100644
--- a/PrivateDnsConfiguration.cpp
+++ b/PrivateDnsConfiguration.cpp
@@ -30,6 +30,8 @@
 #include "netdutils/BackoffSequence.h"
 #include "util.h"
 
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
+using aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel;
 using android::base::StringPrintf;
 using android::netdutils::setThreadName;
 using std::chrono::milliseconds;
@@ -222,6 +224,35 @@
     validate_thread.detach();
 }
 
+void PrivateDnsConfiguration::sendPrivateDnsValidationEvent(const DnsTlsServer& server,
+                                                            unsigned netId, bool success) {
+    LOG(DEBUG) << "Sending validation " << (success ? "success" : "failure") << " event on netId "
+               << netId << " for " << server.toIpString() << " with hostname {" << server.name
+               << "}";
+    // Send a validation event to NetdEventListenerService.
+    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
+    if (listeners.empty()) {
+        LOG(ERROR)
+                << "Validation event not sent since no INetdEventListener receiver is available.";
+    }
+    for (const auto& it : listeners) {
+        it->onPrivateDnsValidationEvent(netId, server.toIpString(), server.name, success);
+    }
+
+    // Send a validation event to unsolicited event listeners.
+    const auto& unsolEventListeners = ResolverEventReporter::getInstance().getUnsolEventListeners();
+    const PrivateDnsValidationEventParcel validationEvent = {
+            .netId = static_cast<int32_t>(netId),
+            .ipAddress = server.toIpString(),
+            .hostname = server.name,
+            .validation = success ? IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_SUCCESS
+                                  : IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_FAILURE,
+    };
+    for (const auto& it : unsolEventListeners) {
+        it->onPrivateDnsValidationEvent(validationEvent);
+    }
+}
+
 bool PrivateDnsConfiguration::recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId,
                                                          bool success) {
     constexpr bool NEEDS_REEVALUATION = true;
@@ -268,19 +299,8 @@
         reevaluationStatus = DONT_REEVALUATE;
     }
 
-    // Send a validation event to NetdEventListenerService.
-    const auto& listeners = ResolverEventReporter::getInstance().getListeners();
-    if (listeners.size() != 0) {
-        for (const auto& it : listeners) {
-            it->onPrivateDnsValidationEvent(netId, server.toIpString(), server.name, success);
-        }
-        LOG(DEBUG) << "Sent validation " << (success ? "success" : "failure") << " event on netId "
-                   << netId << " for " << server.toIpString() << " with hostname {" << server.name
-                   << "}";
-    } else {
-        LOG(ERROR)
-                << "Validation event not sent since no INetdEventListener receiver is available.";
-    }
+    // Send private dns validation result to listeners.
+    sendPrivateDnsValidationEvent(server, netId, success);
 
     if (success) {
         updateServerState(identity, Validation::success, netId);
diff --git a/PrivateDnsConfiguration.h b/PrivateDnsConfiguration.h
index 07c6f2e..04f6a47 100644
--- a/PrivateDnsConfiguration.h
+++ b/PrivateDnsConfiguration.h
@@ -103,6 +103,9 @@
     bool recordPrivateDnsValidation(const DnsTlsServer& server, unsigned netId, bool success)
             EXCLUDES(mPrivateDnsLock);
 
+    void sendPrivateDnsValidationEvent(const DnsTlsServer& server, unsigned netId, bool success)
+            REQUIRES(mPrivateDnsLock);
+
     // Decide if a validation for |server| is needed. Note that servers that have failed
     // multiple validation attempts but for which there is still a validating
     // thread running are marked as being Validation::in_process.
diff --git a/tests/dnsresolver_binder_test.cpp b/tests/dnsresolver_binder_test.cpp
index 50c7151..19d0a0c 100644
--- a/tests/dnsresolver_binder_test.cpp
+++ b/tests/dnsresolver_binder_test.cpp
@@ -56,6 +56,7 @@
 using android::base::unique_fd;
 using android::net::ResolverStats;
 using android::net::metrics::TestOnDnsEvent;
+using android::net::resolv::aidl::UnsolicitedEventListener;
 using android::netdutils::Stopwatch;
 
 // TODO: make this dynamic and stop depending on implementation details.
@@ -302,17 +303,16 @@
 }
 
 TEST_F(DnsResolverBinderTest, RegisterUnsolicitedEventListener_DuplicateSubscription) {
-    class FakeListener : public android::net::resolv::aidl::UnsolicitedEventListener {};
-
     // Expect to subscribe successfully.
-    std::shared_ptr<FakeListener> fakeListener = ndk::SharedRefBase::make<FakeListener>();
-    ::ndk::ScopedAStatus status = mDnsResolver->registerUnsolicitedEventListener(fakeListener);
+    std::shared_ptr<UnsolicitedEventListener> listener =
+            ndk::SharedRefBase::make<UnsolicitedEventListener>(TEST_NETID);
+    ::ndk::ScopedAStatus status = mDnsResolver->registerUnsolicitedEventListener(listener);
     ASSERT_TRUE(status.isOk()) << status.getMessage();
     mExpectedLogData.push_back(
             {"registerUnsolicitedEventListener()", "registerUnsolicitedEventListener.*"});
 
     // Expect to subscribe failed with registered listener instance.
-    status = mDnsResolver->registerUnsolicitedEventListener(fakeListener);
+    status = mDnsResolver->registerUnsolicitedEventListener(listener);
     ASSERT_FALSE(status.isOk());
     ASSERT_EQ(EEXIST, status.getServiceSpecificError());
     mExpectedLogData.push_back(
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index a31de04..e83fe63 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -72,6 +72,7 @@
 #include "tests/dns_responder/dns_tls_frontend.h"
 #include "tests/resolv_test_utils.h"
 #include "tests/tun_forwarder.h"
+#include "tests/unsolicited_listener/unsolicited_event_listener.h"
 
 // Valid VPN netId range is 100 ~ 65535
 constexpr int TEST_VPN_NETID = 65502;
@@ -95,6 +96,8 @@
 using aidl::android::net::INetd;
 using aidl::android::net::ResolverParamsParcel;
 using aidl::android::net::metrics::INetdEventListener;
+using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
+using aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel;
 using android::base::Error;
 using android::base::ParseInt;
 using android::base::Result;
@@ -103,6 +106,7 @@
 using android::net::ResolverStats;
 using android::net::TunForwarder;
 using android::net::metrics::DnsMetricsListener;
+using android::net::resolv::aidl::UnsolicitedEventListener;
 using android::netdutils::enableSockopt;
 using android::netdutils::makeSlice;
 using android::netdutils::ResponseCode;
@@ -205,6 +209,12 @@
                 TEST_NETID /*monitor specific network*/);
         ASSERT_TRUE(resolvService->registerEventListener(sDnsMetricsListener).isOk());
 
+        // Subscribe the unsolicited event listener for verifying unsolicited event contents.
+        sUnsolicitedEventListener = ndk::SharedRefBase::make<UnsolicitedEventListener>(
+                TEST_NETID /*monitor specific network*/);
+        ASSERT_TRUE(
+                resolvService->registerUnsolicitedEventListener(sUnsolicitedEventListener).isOk());
+
         // Start the binder thread pool for listening DNS metrics events and receiving death
         // recipient.
         ABinderProcess_startThreadPool();
@@ -215,6 +225,7 @@
     void SetUp() {
         mDnsClient.SetUp();
         sDnsMetricsListener->reset();
+        sUnsolicitedEventListener->reset();
     }
 
     void TearDown() {
@@ -252,11 +263,16 @@
     }
 
     bool WaitForPrivateDnsValidation(std::string serverAddr, bool validated) {
-        return sDnsMetricsListener->waitForPrivateDnsValidation(serverAddr, validated);
+        return sDnsMetricsListener->waitForPrivateDnsValidation(serverAddr, validated) &&
+               sUnsolicitedEventListener->waitForPrivateDnsValidation(
+                       serverAddr,
+                       validated ? IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_SUCCESS
+                                 : IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_FAILURE);
     }
 
     bool hasUncaughtPrivateDnsValidation(const std::string& serverAddr) {
-        return sDnsMetricsListener->findValidationRecord(serverAddr);
+        return sDnsMetricsListener->findValidationRecord(serverAddr) &&
+               sUnsolicitedEventListener->findValidationRecord(serverAddr);
     }
 
     void ExpectDnsEvent(int32_t eventType, int32_t returnCode, const std::string& hostname,
@@ -368,6 +384,9 @@
     static std::shared_ptr<DnsMetricsListener>
             sDnsMetricsListener;  // Initialized in SetUpTestSuite.
 
+    inline static std::shared_ptr<UnsolicitedEventListener>
+            sUnsolicitedEventListener;  // Initialized in SetUpTestSuite.
+
     // Use a shared static death recipient to monitor the service death. The static death
     // recipient could monitor the death not only during the test but also between tests.
     static AIBinder_DeathRecipient* sResolvDeathRecipient;  // Initialized in SetUpTestSuite.
diff --git a/tests/unsolicited_listener/unsolicited_event_listener.cpp b/tests/unsolicited_listener/unsolicited_event_listener.cpp
index 5200311..f5ebb0b 100644
--- a/tests/unsolicited_listener/unsolicited_event_listener.cpp
+++ b/tests/unsolicited_listener/unsolicited_event_listener.cpp
@@ -16,24 +16,65 @@
 
 #include "unsolicited_event_listener.h"
 
+#include <thread>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/format.h>
+
 namespace android::net::resolv::aidl {
 
+using ::aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel;
+using android::base::ScopedLockAssertion;
+using std::chrono::milliseconds;
+
+constexpr milliseconds kEventTimeoutMs{5000};
+
 ::ndk::ScopedAStatus UnsolicitedEventListener::onDnsHealthEvent(
         const ::aidl::android::net::resolv::aidl::DnsHealthEventParcel&) {
     // default no-op
     return ::ndk::ScopedAStatus::ok();
-};
+}
 
 ::ndk::ScopedAStatus UnsolicitedEventListener::onNat64PrefixEvent(
         const ::aidl::android::net::resolv::aidl::Nat64PrefixEventParcel&) {
     // default no-op
     return ::ndk::ScopedAStatus::ok();
-};
+}
 
 ::ndk::ScopedAStatus UnsolicitedEventListener::onPrivateDnsValidationEvent(
-        const ::aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel&) {
-    // default no-op
+        const PrivateDnsValidationEventParcel& event) {
+    {
+        std::lock_guard lock(mMutex);
+        // keep updating the server to have latest validation status.
+        mValidationRecords.insert_or_assign({event.netId, event.ipAddress}, event.validation);
+    }
+    mCv.notify_one();
     return ::ndk::ScopedAStatus::ok();
-};
+}
+
+bool UnsolicitedEventListener::waitForPrivateDnsValidation(const std::string& serverAddr,
+                                                           int validation) {
+    const auto now = std::chrono::steady_clock::now();
+
+    std::unique_lock lock(mMutex);
+    ScopedLockAssertion assume_lock(mMutex);
+
+    // onPrivateDnsValidationEvent() might already be invoked. Search for the record first.
+    do {
+        if (findAndRemoveValidationRecord({mNetId, serverAddr}, validation)) return true;
+    } while (mCv.wait_until(lock, now + kEventTimeoutMs) != std::cv_status::timeout);
+
+    // Timeout.
+    return false;
+}
+
+bool UnsolicitedEventListener::findAndRemoveValidationRecord(const ServerKey& key, int value) {
+    auto it = mValidationRecords.find(key);
+    if (it != mValidationRecords.end() && it->second == value) {
+        mValidationRecords.erase(it);
+        return true;
+    }
+    return false;
+}
 
 }  // namespace android::net::resolv::aidl
diff --git a/tests/unsolicited_listener/unsolicited_event_listener.h b/tests/unsolicited_listener/unsolicited_event_listener.h
index 170f183..5615f24 100644
--- a/tests/unsolicited_listener/unsolicited_event_listener.h
+++ b/tests/unsolicited_listener/unsolicited_event_listener.h
@@ -16,24 +16,60 @@
 
 #pragma once
 
+#include <condition_variable>
+#include <map>
+#include <queue>
 #include <string>
+#include <utility>
 
 #include <aidl/android/net/resolv/aidl/BnDnsResolverUnsolicitedEventListener.h>
+#include <android-base/thread_annotations.h>
 
 namespace android::net::resolv::aidl {
 
 class UnsolicitedEventListener
     : public ::aidl::android::net::resolv::aidl::BnDnsResolverUnsolicitedEventListener {
   public:
-    UnsolicitedEventListener() = default;
+    UnsolicitedEventListener() = delete;
+    UnsolicitedEventListener(int32_t netId) : mNetId(netId){};
     ~UnsolicitedEventListener() = default;
 
-    virtual ::ndk::ScopedAStatus onDnsHealthEvent(
+    ::ndk::ScopedAStatus onDnsHealthEvent(
             const ::aidl::android::net::resolv::aidl::DnsHealthEventParcel&) override;
-    virtual ::ndk::ScopedAStatus onNat64PrefixEvent(
+    ::ndk::ScopedAStatus onNat64PrefixEvent(
             const ::aidl::android::net::resolv::aidl::Nat64PrefixEventParcel&) override;
-    virtual ::ndk::ScopedAStatus onPrivateDnsValidationEvent(
+    ::ndk::ScopedAStatus onPrivateDnsValidationEvent(
             const ::aidl::android::net::resolv::aidl::PrivateDnsValidationEventParcel&) override;
+
+    // Wait for the expected private DNS validation result until timeout.
+    bool waitForPrivateDnsValidation(const std::string& serverAddr, int validation);
+
+    // Return true if a validation result for |serverAddr| is found; otherwise, return false.
+    bool findValidationRecord(const std::string& serverAddr) const EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        return mValidationRecords.find({mNetId, serverAddr}) != mValidationRecords.end();
+    }
+
+    void reset() EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        mValidationRecords.clear();
+    }
+
+  private:
+    typedef std::pair<int32_t, std::string> ServerKey;
+
+    // Search mValidationRecords. Return true if |key| exists and its value is equal to
+    // |value|, and then remove it; otherwise, return false.
+    bool findAndRemoveValidationRecord(const ServerKey& key, int value) REQUIRES(mMutex);
+
+    // Monitor the event which was fired on specific network id.
+    const int32_t mNetId;
+
+    // Used to store the data from onPrivateDnsValidationEvent.
+    std::map<ServerKey, int> mValidationRecords GUARDED_BY(mMutex);
+
+    mutable std::mutex mMutex;
+    std::condition_variable mCv;
 };
 
 }  // namespace android::net::resolv::aidl