Implement API to update correlated property

This commit implements API to update correlated property.
This API is needed to complete the correlated property callback
processing flow by updating the respective correlated property on Dbus.

Change-Id: Ie4d52591a532732222e9361b0e4c85cf889be52e
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-manager/include/listener.hpp b/vpd-manager/include/listener.hpp
index bd516cf..830a287 100644
--- a/vpd-manager/include/listener.hpp
+++ b/vpd-manager/include/listener.hpp
@@ -141,7 +141,11 @@
         const std::string& i_interface, const std::string& i_property) const;
 
     /**
-     * @brief API to update a given correlated property.
+     * @brief API to update a given correlated property
+     *
+     * This API updates a given correlated property on Dbus. For updates to
+     * properties on Phosphor Inventory Manager it uses Phosphor Inventory
+     * Manager's "Notify" API to update the given property.
      *
      * @param[in] i_serviceName - Service name.
      * @param[in] i_corrProperty - Details of correlated property to update
diff --git a/vpd-manager/include/utility/common_utility.hpp b/vpd-manager/include/utility/common_utility.hpp
index 79c54c8..b5ff13c 100644
--- a/vpd-manager/include/utility/common_utility.hpp
+++ b/vpd-manager/include/utility/common_utility.hpp
@@ -137,5 +137,103 @@
 
     return l_oss.str();
 }
+
+/**
+ * @brief An API to convert binary value into ascii/hex representation.
+ *
+ * If given data contains printable characters, ASCII formated string value of
+ * the input data will be returned. Otherwise if the data has any non-printable
+ * value, returns the hex represented value of the given data in string format.
+ *
+ * @param[in] i_keywordValue - Data in binary format.
+ *
+ * @throw - Throws std::bad_alloc or std::terminate in case of error.
+ *
+ * @return - Returns the converted string value.
+ */
+inline std::string getPrintableValue(const types::BinaryVector& i_keywordValue)
+{
+    bool l_allPrintable =
+        std::all_of(i_keywordValue.begin(), i_keywordValue.end(),
+                    [](const auto& l_byte) { return std::isprint(l_byte); });
+
+    std::ostringstream l_oss;
+    if (l_allPrintable)
+    {
+        l_oss << std::string(i_keywordValue.begin(), i_keywordValue.end());
+    }
+    else
+    {
+        l_oss << "0x";
+        for (const auto& l_byte : i_keywordValue)
+        {
+            l_oss << std::setfill('0') << std::setw(2) << std::hex
+                  << static_cast<int>(l_byte);
+        }
+    }
+
+    return l_oss.str();
+}
+
+/**
+ * @brief API to get data in binary format.
+ *
+ * This API converts given string value present in hexadecimal or decimal format
+ * into array of binary data.
+ *
+ * @param[in] i_value - Input data.
+ *
+ * @return - Array of binary data on success, throws as exception in case
+ * of any error.
+ *
+ * @throw std::runtime_error, std::out_of_range, std::bad_alloc,
+ * std::invalid_argument
+ */
+inline types::BinaryVector convertToBinary(const std::string& i_value)
+{
+    if (i_value.empty())
+    {
+        throw std::runtime_error("Empty input provided");
+    }
+
+    types::BinaryVector l_binaryValue{};
+
+    if (i_value.substr(0, 2).compare("0x") == constants::STR_CMP_SUCCESS)
+    {
+        if (i_value.length() % 2 != 0)
+        {
+            throw std::runtime_error(
+                "Write option accepts 2 digit hex numbers. (Ex. 0x1 "
+                "should be given as 0x01).");
+        }
+
+        auto l_value = i_value.substr(2);
+
+        if (l_value.empty())
+        {
+            throw std::runtime_error(
+                "Provide a valid hexadecimal input. (Ex. 0x30313233)");
+        }
+
+        if (l_value.find_first_not_of("0123456789abcdefABCDEF") !=
+            std::string::npos)
+        {
+            throw std::runtime_error("Provide a valid hexadecimal input.");
+        }
+
+        for (size_t l_pos = 0; l_pos < l_value.length(); l_pos += 2)
+        {
+            uint8_t l_byte = static_cast<uint8_t>(
+                std::stoi(l_value.substr(l_pos, 2), nullptr, 16));
+            l_binaryValue.push_back(l_byte);
+        }
+    }
+    else
+    {
+        l_binaryValue.assign(i_value.begin(), i_value.end());
+    }
+    return l_binaryValue;
+}
+
 } // namespace commonUtility
 } // namespace vpd
diff --git a/vpd-manager/src/listener.cpp b/vpd-manager/src/listener.cpp
index 534112b..6470a41 100644
--- a/vpd-manager/src/listener.cpp
+++ b/vpd-manager/src/listener.cpp
@@ -4,6 +4,7 @@
 #include "event_logger.hpp"
 #include "exceptions.hpp"
 #include "logger.hpp"
+#include "utility/common_utility.hpp"
 #include "utility/dbus_utility.hpp"
 #include "utility/json_utility.hpp"
 
@@ -471,16 +472,73 @@
 }
 
 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
+    const std::string& i_serviceName,
+    const types::DbusPropertyEntry& i_corrProperty,
+    const types::DbusVariantType& i_propertyValue) 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;
+    const auto& l_destinationObjectPath{std::get<0>(i_corrProperty)};
+    const auto& l_destinationInterface{std::get<1>(i_corrProperty)};
+    const auto& l_destinationPropertyName{std::get<2>(i_corrProperty)};
+
+    try
+    {
+        types::DbusVariantType l_valueToUpdate;
+
+        // destination interface is ipz vpd
+        if (l_destinationInterface.find(constants::ipzVpdInf) !=
+            std::string::npos)
+        {
+            if (const auto l_val = std::get_if<std::string>(&i_propertyValue))
+            {
+                // convert value to binary vector before updating
+                l_valueToUpdate = commonUtility::convertToBinary(*l_val);
+            }
+            else if (const auto l_val =
+                         std::get_if<types::BinaryVector>(&i_propertyValue))
+            {
+                l_valueToUpdate = *l_val;
+            }
+        }
+        else
+        {
+            // destination interface is not ipz vpd, assume target
+            // property type is of string type
+            if (const auto l_val =
+                    std::get_if<types::BinaryVector>(&i_propertyValue))
+            {
+                // convert property value to string before updating
+
+                l_valueToUpdate = commonUtility::getPrintableValue(*l_val);
+            }
+            else if (const auto l_val =
+                         std::get_if<std::string>(&i_propertyValue))
+            {
+                l_valueToUpdate = *l_val;
+            }
+        }
+
+        if (i_serviceName == constants::pimServiceName)
+        {
+            return dbusUtility::callPIM(types::ObjectMap{
+                {l_destinationObjectPath,
+                 {{l_destinationInterface,
+                   {{l_destinationPropertyName, l_valueToUpdate}}}}}});
+        }
+        else
+        {
+            return dbusUtility::writeDbusProperty(
+                i_serviceName, l_destinationObjectPath, l_destinationInterface,
+                l_destinationPropertyName, l_valueToUpdate);
+        }
+    }
+    catch (const std::exception& l_ex)
+    {
+        logging::logMessage(
+            "Failed to update correlated property: " + i_serviceName + " : " +
+            l_destinationObjectPath + " : " + l_destinationInterface + " : " +
+            l_destinationPropertyName + ". Error: " + std::string(l_ex.what()));
+    }
+    return false;
 }
 
 } // namespace vpd