Listener:Implement Present property callback

This commit implements Present property callback in Listener. For
hotPluggable FRUs whose present property is toggled by external
application based on their actual presence detection, vpd-manager needs
to monitor "Present" property and collect/delete FRU VPD accordingly.

Test:
```
- On Everest system, toggle Present property of fan0 from busctl
- Observer callback getting triggered
```

Change-Id: Ia558306417adf18155b5b363fc9caa797bda250f
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-manager/include/listener.hpp b/vpd-manager/include/listener.hpp
index 719c21d..f387d5e 100644
--- a/vpd-manager/include/listener.hpp
+++ b/vpd-manager/include/listener.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "types.hpp"
 #include "worker.hpp"
 
 #include <sdbusplus/asio/object_server.hpp>
@@ -80,5 +81,8 @@
 
     // Shared pointer to bus connection.
     const std::shared_ptr<sdbusplus::asio::connection>& m_asioConnection;
+
+    // Map of inventory path to Present property match object
+    types::FruPresenceMatchObjectMap m_fruPresenceMatchObjectMap;
 };
 } // namespace vpd
diff --git a/vpd-manager/include/types.hpp b/vpd-manager/include/types.hpp
index 1ea8f09..31d0085 100644
--- a/vpd-manager/include/types.hpp
+++ b/vpd-manager/include/types.hpp
@@ -204,5 +204,7 @@
 using InvalidRecordEntry = std::pair<Record,ErrorType>;
 /* List of invalid record entries*/
 using InvalidRecordList = std::vector<InvalidRecordEntry>;
+/* Map of inventory path -> Present property match object */
+using FruPresenceMatchObjectMap = std::map<Path, std::shared_ptr<sdbusplus::bus::match::match>>;
 } // namespace types
 } // namespace vpd
diff --git a/vpd-manager/include/utility/json_utility.hpp b/vpd-manager/include/utility/json_utility.hpp
index 9a78b68..4bf3846 100644
--- a/vpd-manager/include/utility/json_utility.hpp
+++ b/vpd-manager/include/utility/json_utility.hpp
@@ -1158,5 +1158,45 @@
         return nlohmann::json{};
     }
 }
+
+/**
+ * @brief API to get list of FRUs for which "monitorPresence" is true.
+ *
+ * @param[in] i_sysCfgJsonObj - System config JSON object.
+ *
+ * @return On success, returns list of FRUs for which "monitorPresence" is true,
+ * empty list on error.
+ */
+inline std::vector<types::Path> getFrusWithPresenceMonitoring(
+    const nlohmann::json& i_sysCfgJsonObj) noexcept
+{
+    std::vector<types::Path> l_frusWithPresenceMonitoring;
+    try
+    {
+        if (!i_sysCfgJsonObj.contains("frus"))
+        {
+            throw JsonException("Missing frus tag in system config JSON.");
+        }
+
+        const nlohmann::json& l_listOfFrus =
+            i_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>();
+
+        for (const auto& l_aFru : l_listOfFrus)
+        {
+            if (l_aFru.at(0).value("monitorPresence", false))
+            {
+                l_frusWithPresenceMonitoring.emplace_back(
+                    l_aFru.at(0).value("inventoryPath", ""));
+            }
+        }
+    }
+    catch (const std::exception& l_ex)
+    {
+        logging::logMessage(
+            "Failed to get list of FRUs with presence monitoring, error: " +
+            std::string(l_ex.what()));
+    }
+    return l_frusWithPresenceMonitoring;
+}
 } // namespace jsonUtility
 } // namespace vpd
diff --git a/vpd-manager/src/listener.cpp b/vpd-manager/src/listener.cpp
index a95be64..49825fd 100644
--- a/vpd-manager/src/listener.cpp
+++ b/vpd-manager/src/listener.cpp
@@ -1,7 +1,9 @@
 #include "listener.hpp"
 
+#include "constants.hpp"
 #include "event_logger.hpp"
 #include "utility/dbus_utility.hpp"
+#include "utility/json_utility.hpp"
 
 namespace vpd
 {
@@ -167,11 +169,24 @@
 {
     try
     {
-        /* TODO:
-            - iterate through all FRUs.
-            - if FRU is runtime replaceable and we do not handle presence for
-           the FRU, register a Present property change callback.
-        */
+        // get list of FRUs for which presence monitoring is required
+        const auto& l_listOfFrus = jsonUtility::getFrusWithPresenceMonitoring(
+            m_worker->getSysCfgJsonObj());
+
+        for (const auto& l_inventoryPath : l_listOfFrus)
+        {
+            std::shared_ptr<sdbusplus::bus::match_t> l_fruPresenceMatch =
+                std::make_shared<sdbusplus::bus::match_t>(
+                    *m_asioConnection,
+                    sdbusplus::bus::match::rules::propertiesChanged(
+                        l_inventoryPath, constants::inventoryItemInf),
+                    [this](sdbusplus::message_t& i_msg) {
+                        presentPropertyChangeCallback(i_msg);
+                    });
+
+            // save the match object to map
+            m_fruPresenceMatchObjectMap[l_inventoryPath] = l_fruPresenceMatch;
+        }
     }
     catch (const std::exception& l_ex)
     {
@@ -195,14 +210,29 @@
                 "Error reading callback message for Present property change");
         }
 
-        const auto& l_objectPath = i_msg.get_path();
-        (void)l_objectPath;
-        /*TODO:
-         - read "Present" property
-         - if "Present" property = true, trigger "collectSingleFruVpd" for the
-         FRU
-         - if "Present" property = false, trigger "deleteFruVpd" for the FRU
-        */
+        std::string l_interface;
+        types::PropertyMap l_propMap;
+        i_msg.read(l_interface, l_propMap);
+
+        const std::string l_objectPath{i_msg.get_path()};
+
+        const auto l_itr = l_propMap.find("Present");
+        if (l_itr == l_propMap.end())
+        {
+            // Present is not found in the callback message
+            return;
+        }
+
+        if (auto l_present = std::get_if<bool>(&(l_itr->second)))
+        {
+            *l_present ? m_worker->collectSingleFruVpd(l_objectPath)
+                       : m_worker->deleteFruVpd(l_objectPath);
+        }
+        else
+        {
+            throw DbusException(
+                "Invalid type recieved in variant for present property");
+        }
     }
     catch (const std::exception& l_ex)
     {