pldmd: D-Bus to host effecter translation

This commit implements a mechanism to move the Host's boot state
from 'not started' to 'boot complete' by setting the relevant
Host effecter when the associated D-Bus property is set in the BMC.

Also added an example JSON to match D-Bus to host effecters

Change-Id: I41025d99d2b4b3452d4c51b03efe3750e159328b
Signed-off-by: Sampa Misra <sampmisr@in.ibm.com>
diff --git a/dbus_to_host_effecters.cpp b/dbus_to_host_effecters.cpp
new file mode 100644
index 0000000..b2fc092
--- /dev/null
+++ b/dbus_to_host_effecters.cpp
@@ -0,0 +1,341 @@
+#include "dbus_to_host_effecters.hpp"
+
+#include "libpldm/pdr.h"
+#include "libpldm/platform.h"
+#include "libpldm/requester/pldm.h"
+
+#include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp>
+
+#include <fstream>
+#include <iostream>
+
+namespace pldm
+{
+namespace host_effecters
+{
+
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+constexpr auto hostEffecterJson = "dbus_to_host_effecter.json";
+
+void HostEffecterParser::populatePropVals(
+    const Json& dBusValues, std::vector<PropertyValue>& propertyValues,
+    const std::string& propertyType)
+
+{
+    for (const auto& elem : dBusValues)
+    {
+        auto value = jsonEntryToDbusVal(propertyType, elem);
+        propertyValues.emplace_back(value);
+    }
+}
+
+void HostEffecterParser::parseEffecterJson(const std::string& jsonPath)
+{
+    fs::path jsonDir(jsonPath);
+    if (!fs::exists(jsonDir) || fs::is_empty(jsonDir))
+    {
+        std::cerr << "Host Effecter json path does not exist, DIR=" << jsonPath
+                  << "\n";
+        return;
+    }
+
+    fs::path jsonFilePath = jsonDir / hostEffecterJson;
+    if (!fs::exists(jsonFilePath))
+    {
+        std::cerr << "json does not exist, PATH=" << jsonFilePath << "\n";
+        throw InternalFailure();
+    }
+
+    std::ifstream jsonFile(jsonFilePath);
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        std::cerr << "Parsing json file failed, FILE=" << jsonFilePath << "\n";
+        throw InternalFailure();
+    }
+    const Json empty{};
+    const std::vector<Json> emptyList{};
+
+    auto entries = data.value("entries", emptyList);
+    for (const auto& entry : entries)
+    {
+        EffecterInfo effecterInfo;
+        effecterInfo.mctpEid = entry.value("mctp_eid", 0xFF);
+        auto jsonEffecterInfo = entry.value("effecter_info", empty);
+        auto effecterId =
+            jsonEffecterInfo.value("effecterID", PLDM_INVALID_EFFECTER_ID);
+        effecterInfo.containerId = jsonEffecterInfo.value("containerID", 0);
+        effecterInfo.entityType = jsonEffecterInfo.value("entityType", 0);
+        effecterInfo.entityInstance =
+            jsonEffecterInfo.value("entityInstance", 0);
+        effecterInfo.compEffecterCnt =
+            jsonEffecterInfo.value("compositeEffecterCount", 0);
+        auto effecters = entry.value("effecters", emptyList);
+        for (const auto& effecter : effecters)
+        {
+            DBusEffecterMapping dbusInfo{};
+            auto jsonDbusInfo = effecter.value("dbus_info", empty);
+            dbusInfo.dbusMap.objectPath = jsonDbusInfo.value("object_path", "");
+            dbusInfo.dbusMap.interface = jsonDbusInfo.value("interface", "");
+            dbusInfo.dbusMap.propertyName =
+                jsonDbusInfo.value("property_name", "");
+            dbusInfo.dbusMap.propertyType =
+                jsonDbusInfo.value("property_type", "");
+            Json propertyValues = jsonDbusInfo["property_values"];
+
+            populatePropVals(propertyValues, dbusInfo.propertyValues,
+                             dbusInfo.dbusMap.propertyType);
+
+            const std::vector<uint8_t> emptyStates{};
+            auto state = effecter.value("state", empty);
+            dbusInfo.state.stateSetId = state.value("id", 0);
+            auto states = state.value("state_values", emptyStates);
+            if (dbusInfo.propertyValues.size() != states.size())
+            {
+                std::cerr << "Number of states do not match with"
+                          << " number of D-Bus property values in the json. "
+                          << "Object path " << dbusInfo.dbusMap.objectPath
+                          << " and property " << dbusInfo.dbusMap.propertyName
+                          << " will not be monitored \n";
+                continue;
+            }
+            for (const auto& s : states)
+            {
+                dbusInfo.state.states.emplace_back(s);
+            }
+
+            auto effecterInfoIndex = hostEffecterInfo.size();
+            auto dbusInfoIndex = effecterInfo.dbusInfo.size();
+            createHostEffecterMatch(
+                dbusInfo.dbusMap.objectPath, dbusInfo.dbusMap.interface,
+                effecterInfoIndex, dbusInfoIndex, effecterId);
+            effecterInfo.dbusInfo.emplace_back(std::move(dbusInfo));
+        }
+        hostEffecterInfo.emplace_back(std::move(effecterInfo));
+    }
+}
+
+void HostEffecterParser::processHostEffecterChangeNotification(
+    const DbusChgHostEffecterProps& chProperties, size_t effecterInfoIndex,
+    size_t dbusInfoIndex, uint16_t effecterId)
+{
+    const auto& propertyName = hostEffecterInfo[effecterInfoIndex]
+                                   .dbusInfo[dbusInfoIndex]
+                                   .dbusMap.propertyName;
+
+    const auto& it = chProperties.find(propertyName);
+
+    if (it == chProperties.end())
+    {
+        return;
+    }
+
+    if (effecterId == PLDM_INVALID_EFFECTER_ID)
+    {
+        effecterId = findStateEffecterId(
+            pdrRepo, hostEffecterInfo[effecterInfoIndex].entityType,
+            hostEffecterInfo[effecterInfoIndex].entityInstance,
+            hostEffecterInfo[effecterInfoIndex].containerId,
+            hostEffecterInfo[effecterInfoIndex]
+                .dbusInfo[dbusInfoIndex]
+                .state.stateSetId);
+        if (effecterId == PLDM_INVALID_EFFECTER_ID)
+        {
+            std::cerr << "Effecter id not found in pdr repo \n";
+            return;
+        }
+    }
+    constexpr auto hostStateInterface =
+        "xyz.openbmc_project.State.OperatingSystem.Status";
+    constexpr auto hostStatePath = "/xyz/openbmc_project/state/host0";
+
+    try
+    {
+        auto propVal = dbusHandler->getDbusPropertyVariant(
+            hostStatePath, "OperatingSystemState", hostStateInterface);
+        const auto& currHostState = std::get<std::string>(propVal);
+        if ((currHostState != "xyz.openbmc_project.State.OperatingSystem."
+                              "Status.OSStatus.Standby") &&
+            (currHostState != "xyz.openbmc_project.State.OperatingSystem."
+                              "Status.OSStatus.BootComplete"))
+        {
+            std::cout << "Host is not up. Current host state: "
+                      << currHostState.c_str() << "\n";
+            return;
+        }
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        std::cerr << "Error in getting current host state. Will still "
+                     "continue to set the host effecter \n";
+    }
+    uint8_t newState{};
+    try
+    {
+        newState =
+            findNewStateValue(effecterInfoIndex, dbusInfoIndex, it->second);
+    }
+    catch (const std::out_of_range& e)
+    {
+        std::cerr << "new state not found in json"
+                  << "\n";
+        return;
+    }
+
+    std::vector<set_effecter_state_field> stateField;
+    for (uint8_t i = 0; i < hostEffecterInfo[effecterInfoIndex].compEffecterCnt;
+         i++)
+    {
+        if (i == dbusInfoIndex)
+        {
+            stateField.push_back({PLDM_REQUEST_SET, newState});
+        }
+        else
+        {
+            stateField.push_back({PLDM_NO_CHANGE, 0});
+        }
+    }
+    int rc{};
+    try
+    {
+        rc = setHostStateEffecter(effecterInfoIndex, stateField, effecterId);
+    }
+    catch (const std::runtime_error& e)
+    {
+        std::cerr << "Could not set host state effecter \n";
+        return;
+    }
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr << "Could not set the host state effecter, rc= " << rc
+                  << " \n";
+    }
+}
+
+uint8_t
+    HostEffecterParser::findNewStateValue(size_t effecterInfoIndex,
+                                          size_t dbusInfoIndex,
+                                          const PropertyValue& propertyValue)
+{
+    const auto& propValues = hostEffecterInfo[effecterInfoIndex]
+                                 .dbusInfo[dbusInfoIndex]
+                                 .propertyValues;
+    auto it = std::find(propValues.begin(), propValues.end(), propertyValue);
+    uint8_t newState{};
+    if (it != propValues.end())
+    {
+        auto index = std::distance(propValues.begin(), it);
+        newState = hostEffecterInfo[effecterInfoIndex]
+                       .dbusInfo[dbusInfoIndex]
+                       .state.states[index];
+    }
+    else
+    {
+        throw std::out_of_range("new state not found in json");
+    }
+    return newState;
+}
+
+int HostEffecterParser::setHostStateEffecter(
+    size_t effecterInfoIndex, std::vector<set_effecter_state_field>& stateField,
+    uint16_t effecterId)
+{
+    uint8_t& mctpEid = hostEffecterInfo[effecterInfoIndex].mctpEid;
+    uint8_t& compEffCnt = hostEffecterInfo[effecterInfoIndex].compEffecterCnt;
+    auto instanceId = requester->getInstanceId(mctpEid);
+
+    std::vector<uint8_t> requestMsg(
+        sizeof(pldm_msg_hdr) + sizeof(effecterId) + sizeof(compEffCnt) +
+            sizeof(set_effecter_state_field) * compEffCnt,
+        0);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto rc = encode_set_state_effecter_states_req(
+        instanceId, effecterId, compEffCnt, stateField.data(), request);
+
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr << "Message encode failure. PLDM error code = " << std::hex
+                  << std::showbase << rc << "\n";
+        requester->markFree(mctpEid, instanceId);
+        return rc;
+    }
+
+    if (verbose)
+    {
+        if (requestMsg.size())
+        {
+            std::ostringstream tempStream;
+            for (int byte : requestMsg)
+            {
+                tempStream << std::setfill('0') << std::setw(2) << std::hex
+                           << byte << " ";
+            }
+            std::cout << tempStream.str() << std::endl;
+        }
+    }
+
+    uint8_t* responseMsg = nullptr;
+    size_t responseMsgSize{};
+
+    rc = pldm_send_recv(mctpEid, sockFd, requestMsg.data(), requestMsg.size(),
+                        &responseMsg, &responseMsgSize);
+    std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{responseMsg,
+                                                                  std::free};
+    requester->markFree(mctpEid, instanceId);
+
+    if (rc != PLDM_REQUESTER_SUCCESS)
+    {
+        std::cerr << "Failed to send message/receive response. RC = " << rc
+                  << ", errno = " << errno << " for setting host effecter "
+                  << effecterId << "\n";
+        pldm::utils::reportError(
+            "xyz.openbmc_project.bmc.pldm.InternalFailure");
+        return rc;
+    }
+    auto responsePtr = reinterpret_cast<struct pldm_msg*>(responseMsgPtr.get());
+    uint8_t completionCode{};
+    rc = decode_set_state_effecter_states_resp(
+        responsePtr, responseMsgSize - sizeof(pldm_msg_hdr), &completionCode);
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr << "Failed to decode setStateEffecterStates response, rc = "
+                  << rc << "\n";
+        return rc;
+    }
+    if (completionCode != PLDM_SUCCESS)
+    {
+        std::cerr << "Failed setStateEffecterStates for effecter " << effecterId
+                  << ". Response from Host " << (uint32_t)completionCode
+                  << "\n";
+        pldm::utils::reportError(
+            "xyz.openbmc_project.bmc.pldm.InternalFailure");
+    }
+    return rc;
+}
+
+void HostEffecterParser::createHostEffecterMatch(const std::string& objectPath,
+                                                 const std::string& interface,
+                                                 size_t effecterInfoIndex,
+                                                 size_t dbusInfoIndex,
+                                                 uint16_t effecterId)
+{
+    using namespace sdbusplus::bus::match::rules;
+    effecterInfoMatch.emplace_back(
+        std::make_unique<sdbusplus::bus::match::match>(
+            pldm::utils::DBusHandler::getBus(),
+            propertiesChanged(objectPath, interface),
+            [this, effecterInfoIndex, dbusInfoIndex,
+             effecterId](sdbusplus::message::message& msg) {
+                DbusChgHostEffecterProps props;
+                std::string iface;
+                msg.read(iface, props);
+                processHostEffecterChangeNotification(
+                    props, effecterInfoIndex, dbusInfoIndex, effecterId);
+            }));
+}
+
+} // namespace host_effecters
+} // namespace pldm