Create class for finding D-Bus interfaces

Create a class for finding instances of D-Bus interfaces.  The class
finds existing instances using the ObjectMapper.  It also finds new
instances using an InterfacesAdded listener.

Tested:
* See test plan at
  https://gist.github.com/smccarney/67d7719f0cf9cea2d134b0796aa408da

Change-Id: Ia6fba9cb69ad73607dc971dd6d63ba2efecabe0d
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/dbus_interfaces_finder.cpp b/dbus_interfaces_finder.cpp
new file mode 100644
index 0000000..31e5023
--- /dev/null
+++ b/dbus_interfaces_finder.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2024 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dbus_interfaces_finder.hpp"
+
+#include <algorithm>
+#include <exception>
+#include <map>
+#include <utility>
+
+namespace phosphor::power::util
+{
+
+DBusInterfacesFinder::DBusInterfacesFinder(
+    sdbusplus::bus_t& bus, const std::string& service,
+    const std::vector<std::string>& interfaces, Callback callback) :
+    bus{bus},
+    service{service}, interfaces{interfaces}, callback{std::move(callback)},
+    match{bus,
+          sdbusplus::bus::match::rules::interfacesAdded() +
+              sdbusplus::bus::match::rules::sender(service),
+          std::bind(&DBusInterfacesFinder::interfacesAddedCallback, this,
+                    std::placeholders::_1)}
+{
+    findInterfaces();
+}
+
+void DBusInterfacesFinder::interfacesAddedCallback(
+    sdbusplus::message_t& message)
+{
+    // Exit if message is invalid
+    if (!message)
+    {
+        return;
+    }
+
+    try
+    {
+        // Read the D-Bus message
+        sdbusplus::message::object_path path;
+        std::map<std::string, std::map<std::string, util::DbusVariant>>
+            interfaces;
+        message.read(path, interfaces);
+
+        // Call callback for interfaces that we are looking for
+        for (const auto& [interface, properties] : interfaces)
+        {
+            if (std::ranges::contains(this->interfaces, interface))
+            {
+                callback(path, interface, properties);
+            }
+        }
+    }
+    catch (const std::exception&)
+    {
+        // Error trying to read InterfacesAdded message.  One possible cause
+        // could be a property whose value is an unexpected data type.
+    }
+}
+
+void DBusInterfacesFinder::findInterfaces()
+{
+    try
+    {
+        // Use ObjectMapper to find interface instances that already exist
+        auto objects = util::getSubTree(bus, "/", interfaces, 0);
+
+        // Search for matching interfaces in returned objects
+        for (const auto& [path, services] : objects)
+        {
+            for (const auto& [service, interfaces] : services)
+            {
+                if (service == this->service)
+                {
+                    for (const auto& interface : interfaces)
+                    {
+                        if (std::ranges::contains(this->interfaces, interface))
+                        {
+                            auto properties = util::getAllProperties(
+                                bus, path, interface, service);
+                            callback(path, interface, properties);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    catch (const std::exception&)
+    {
+        // Interface instances might not be available yet
+    }
+}
+
+} // namespace phosphor::power::util
diff --git a/dbus_interfaces_finder.hpp b/dbus_interfaces_finder.hpp
new file mode 100644
index 0000000..35a80c7
--- /dev/null
+++ b/dbus_interfaces_finder.hpp
@@ -0,0 +1,128 @@
+/**
+ * Copyright © 2024 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "utility.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace phosphor::power::util
+{
+
+/**
+ * @class DBusInterfacesFinder
+ *
+ * Class that finds instances of one or more D-Bus interfaces.
+ *
+ * A D-Bus service name and one or more D-Bus interfaces are specified in the
+ * constructor.  The class finds instances of those interfaces that are owned by
+ * the service.
+ *
+ * The class finds the instances using two different methods:
+ * - Registers an InterfacesAdded listener for the specified service.  Class is
+ *   notified when a new interface instance is created on D-Bus.
+ * - Queries the ObjectMapper to find interface instances that already exist.
+ *
+ * Utilizing both methods allows this class to be used before, during, or after
+ * the service has created the interface instances.
+ *
+ * When an interface instance is found, the callback function specified in the
+ * constructor is called.  This function will be called multiple times if
+ * multiple instances are found.
+ */
+class DBusInterfacesFinder
+{
+  public:
+    // Specify which compiler-generated methods we want
+    DBusInterfacesFinder() = delete;
+    DBusInterfacesFinder(const DBusInterfacesFinder&) = delete;
+    DBusInterfacesFinder(DBusInterfacesFinder&&) = delete;
+    DBusInterfacesFinder& operator=(const DBusInterfacesFinder&) = delete;
+    DBusInterfacesFinder& operator=(DBusInterfacesFinder&&) = delete;
+    ~DBusInterfacesFinder() = default;
+
+    /**
+     * Callback function that is called when an interface instance is found.
+     *
+     * @param path D-Bus object path that implements the interface
+     * @param interface D-Bus interface that was found
+     * @param properties Properties of the D-Bus interface
+     */
+    using Callback = std::function<void(const std::string& path,
+                                        const std::string& interface,
+                                        const DbusPropertyMap& properties)>;
+
+    /**
+     * Constructor.
+     *
+     * @param bus D-Bus bus object
+     * @param service D-Bus service that owns the object paths implementing
+     *                the specified interfaces
+     * @param interfaces D-Bus interfaces to find
+     * @param callback Callback function that is called each time an interface
+     *                 instance is found
+     */
+    explicit DBusInterfacesFinder(sdbusplus::bus_t& bus,
+                                  const std::string& service,
+                                  const std::vector<std::string>& interfaces,
+                                  Callback callback);
+
+    /**
+     * Callback function to handle InterfacesAdded D-Bus signals
+     *
+     * @param message Expanded sdbusplus message data
+     */
+    void interfacesAddedCallback(sdbusplus::message_t& message);
+
+  private:
+    /**
+     * Finds any interface instances that already exist on D-Bus.
+     */
+    void findInterfaces();
+
+    /**
+     * D-Bus bus object.
+     */
+    sdbusplus::bus_t& bus;
+
+    /**
+     * D-Bus service that owns the object paths implementing the interfaces.
+     */
+    std::string service;
+
+    /**
+     * D-Bus interfaces to find.
+     */
+    std::vector<std::string> interfaces;
+
+    /**
+     * Callback function that is called each time an interface instance is
+     * found.
+     */
+    Callback callback;
+
+    /**
+     * Match object for InterfacesAdded signals.
+     */
+    sdbusplus::bus::match_t match;
+};
+
+} // namespace phosphor::power::util
diff --git a/meson.build b/meson.build
index 7d5dd11..bc0f16f 100644
--- a/meson.build
+++ b/meson.build
@@ -154,6 +154,7 @@
     'power',
     error_cpp,
     error_hpp,
+    'dbus_interfaces_finder.cpp',
     'gpio.cpp',
     'pmbus.cpp',
     'temporary_file.cpp',