Correlated properties callback stub

This commit includes stub implementation of utility and other APIs
required to implement the correlated properties callback.

Change-Id: I6c58b6126203cbb70aab5b2a84f8f7b09d83d06e
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-manager/include/listener.hpp b/vpd-manager/include/listener.hpp
index 734bf21..bd516cf 100644
--- a/vpd-manager/include/listener.hpp
+++ b/vpd-manager/include/listener.hpp
@@ -115,6 +115,46 @@
      */
     void correlatedPropChangedCallBack(sdbusplus::message_t& i_msg) noexcept;
 
+    /**
+     * @brief API to get correlated properties for given property.
+     *
+     * For a given service name, object path, interface and property, this API
+     * uses parsed correlated properties JSON object and returns a list of
+     * correlated object path, interface and property. Correlated properties are
+     * properties which are hosted under different interfaces with same or
+     * different data type, but share the same data. Hence if the data of a
+     * property is updated, then it's respective correlated property/properties
+     * should also be updated so that they remain in sync.
+     *
+     * @param[in] i_serviceName - Service name.
+     * @param[in] i_objectPath - Object path.
+     * @param[in] i_interface - Interface name.
+     * @param[in] i_property - Property name.
+     *
+     * @return On success, returns a vector of correlated object path, interface
+     * and property. Otherwise returns an empty vector.
+     *
+     * @throw FirmwareException
+     */
+    types::DbusPropertyList getCorrelatedProps(
+        const std::string& i_serviceName, const std::string& i_objectPath,
+        const std::string& i_interface, const std::string& i_property) const;
+
+    /**
+     * @brief API to update a given correlated property.
+     *
+     * @param[in] i_serviceName - Service name.
+     * @param[in] i_corrProperty - Details of correlated property to update
+     * @param[in] i_value - Property value
+     *
+     * @return true, if correlated property was successfully updated, false
+     * otherwise.
+     */
+    bool updateCorrelatedProperty(
+        const std::string& i_serviceName,
+        const types::DbusPropertyEntry& i_corrProperty,
+        const types::DbusVariantType& i_value) const noexcept;
+
     // Shared pointer to worker class
     const std::shared_ptr<Worker>& m_worker;
 
diff --git a/vpd-manager/include/types.hpp b/vpd-manager/include/types.hpp
index 7461455..169e128 100644
--- a/vpd-manager/include/types.hpp
+++ b/vpd-manager/include/types.hpp
@@ -218,5 +218,9 @@
     std::tuple<types::ErrorType, std::optional<types::SeverityType>, uint8_t, std::optional<std::string>,
                std::optional<std::string>, std::optional<std::string>,
                std::optional<std::string>>;
+/* A tuple of Dbus object path, interface and property*/
+using DbusPropertyEntry = std::tuple<std::string, std::string, std::string>;
+/* A list of Dbus property entries */
+using DbusPropertyList = std::vector<DbusPropertyEntry>;
 } // namespace types
 } // namespace vpd
diff --git a/vpd-manager/include/utility/dbus_utility.hpp b/vpd-manager/include/utility/dbus_utility.hpp
index 3d0ba61..b0a6bc2 100644
--- a/vpd-manager/include/utility/dbus_utility.hpp
+++ b/vpd-manager/include/utility/dbus_utility.hpp
@@ -755,5 +755,38 @@
     }
     return l_objectPaths;
 }
+
+/**
+ * @brief API to get Dbus service name for given connection identifier.
+ *
+ * @param[in] i_connectionId - Dbus connection ID.
+ *
+ * @return On success, returns the DBus service associated with given connection
+ * ID, empty string otherwise.
+ */
+inline std::string getServiceNameFromConnectionId(
+    [[maybe_unused]] const std::string& i_connectionId) noexcept
+{
+    std::string l_serviceName;
+    try
+    {
+        if (i_connectionId.empty())
+        {
+            throw std::runtime_error("Empty connection ID");
+        }
+
+        /* TODO:
+        - get PID corresponding to the connection ID
+        - use PID to get corresponding encoded service name string
+        - decode service name string */
+    }
+    catch (const std::exception& l_ex)
+    {
+        logging::logMessage(
+            "Failed to get service name from connection ID: [" +
+            i_connectionId + "]. error: " + std::string(l_ex.what()));
+    }
+    return l_serviceName;
+}
 } // namespace dbusUtility
 } // namespace vpd
diff --git a/vpd-manager/src/listener.cpp b/vpd-manager/src/listener.cpp
index 5c54467..759f85d 100644
--- a/vpd-manager/src/listener.cpp
+++ b/vpd-manager/src/listener.cpp
@@ -340,17 +340,63 @@
             throw DbusException("Error in reading property change signal.");
         }
 
-        const std::string l_interface{i_msg.get_interface()};
+        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 std::string l_signature{i_msg.get_signature()};
 
-        (void)l_interface;
-        (void)l_objectPath;
-        (void)l_signature;
+        std::string l_serviceName =
+            dbusUtility::getServiceNameFromConnectionId(i_msg.get_sender());
 
-        /*TODO:
-        Use correlated JSON to find target {object path, interface,
-        property/properties} to update*/
+        if (l_serviceName.empty())
+        {
+            throw DbusException(
+                "Failed to get service name from connection ID: " +
+                std::string(i_msg.get_sender()));
+        }
+
+        // if service name contains .service suffix, strip it
+        const std::size_t l_pos = l_serviceName.find(".service");
+        if (l_pos != std::string::npos)
+        {
+            l_serviceName = l_serviceName.substr(0, l_pos);
+        }
+
+        // iterate through all properties in map
+        for (const auto& l_propertyEntry : l_propMap)
+        {
+            const std::string& l_propertyName = l_propertyEntry.first;
+            const auto& l_propertyValue = l_propertyEntry.second;
+
+            // Use correlated JSON to find target {object path,
+            // interface,property/properties} to update
+            const auto& l_correlatedPropList = getCorrelatedProps(
+                l_serviceName, l_objectPath, l_interface, l_propertyName);
+
+            // update all target correlated properties
+            std::for_each(
+                l_correlatedPropList.begin(), l_correlatedPropList.end(),
+                [this, &l_propertyValue = std::as_const(l_propertyValue),
+                 &l_serviceName = std::as_const(l_serviceName),
+                 &l_objectPath = std::as_const(l_objectPath),
+                 &l_interface = std::as_const(l_interface),
+                 &l_propertyName = std::as_const(l_propertyName)](
+                    const auto& i_corrProperty) {
+                    if (!updateCorrelatedProperty(l_serviceName, i_corrProperty,
+                                                  l_propertyValue))
+                    {
+                        logging::logMessage(
+                            "Failed to update correlated property: " +
+                            l_serviceName + " : " +
+                            std::get<0>(i_corrProperty) + " : " +
+                            std::get<1>(i_corrProperty) + " : " +
+                            std::get<2>(i_corrProperty) + " when " +
+                            l_objectPath + " : " + l_interface + " : " +
+                            l_propertyName + " got updated.");
+                    }
+                });
+        }
     }
     catch (const std::exception& l_ex)
     {
@@ -361,4 +407,36 @@
     }
 }
 
+types::DbusPropertyList Listener::getCorrelatedProps(
+    [[maybe_unused]] const std::string& i_serviceName,
+    [[maybe_unused]] const std::string& i_objectPath,
+    [[maybe_unused]] const std::string& i_interface,
+    [[maybe_unused]] const std::string& i_property) const
+{
+    types::DbusPropertyList l_result;
+    try
+    {
+        /*TODO: Use parsed correlated JSON to find target {object path(s),
+        interface(s), property/properties}*/
+    }
+    catch (const std::exception& l_ex)
+    {
+        throw FirmwareException(l_ex.what());
+    }
+    return l_result;
+}
+
+bool Listener::updateCorrelatedProperty(
+    [[maybe_unused]] const std::string& i_serviceName,
+    [[maybe_unused]] const types::DbusPropertyEntry& i_corrProperty,
+    [[maybe_unused]] const types::DbusVariantType& i_value) const noexcept
+{
+    /* TODO:
+        1. Check destination interface type
+        2. Convert value to required type
+        3. Read current property value on Dbus, and if needed update it.
+    */
+    return true;
+}
+
 } // namespace vpd