libpldmresponder: implement setStateEffecterStates

This commit implements the handler for setStateEffecterStates response.
Apart from that it actually sets the effecter for PLDM_BOOT_PROGRESS
state. This is used when host sends setStateEffecterStates to mark
any change in hypervisor state. The currently supported states are
"StandBy" and "BootComplete" as per
xyz.openbmc_project.State.OperatingSystem.Status

Change-Id: I205627b2b8a796a0dd4a200ac3d59c1d19d71f01
Signed-off-by: Sampa Misra <sampmisr@in.ibm.com>
diff --git a/libpldmresponder/platform.hpp b/libpldmresponder/platform.hpp
index 253d786..9db93ce 100644
--- a/libpldmresponder/platform.hpp
+++ b/libpldmresponder/platform.hpp
@@ -1,10 +1,16 @@
 #pragma once
 
+#include "config.h"
+
+#include "libpldmresponder/pdr.hpp"
+#include "libpldmresponder/utils.hpp"
+
 #include <stdint.h>
 
-#include <vector>
+#include <map>
 
 #include "libpldm/platform.h"
+#include "libpldm/states.h"
 
 namespace pldm
 {
@@ -14,6 +20,15 @@
 namespace responder
 {
 
+namespace platform
+{
+
+/** @brief Register handlers for commands from the platform spec
+ */
+void registerHandlers();
+
+} // namespace platform
+
 /** @brief Handler for GetPDR
  *
  *  @param[in] request - Request message payload
@@ -22,5 +37,185 @@
  */
 Response getPDR(const pldm_msg* request, size_t payloadLength);
 
+/** @brief Handler for setStateEffecterStates
+ *
+ *  @param[in] request - Request message
+ *  @param[in] payloadLength - Request payload length
+ *  @return Response - PLDM Response message
+ */
+Response setStateEffecterStates(const pldm_msg* request, size_t payloadLength);
+
+/** @brief Function to set the effecter requested by pldm requester
+ *  @param[in] dBusIntf - The interface object
+ *  @param[in] effecterId - Effecter ID sent by the requester to act on
+ *  @param[in] stateField - The state field data for each of the states, equal
+ *        to composite effecter count in number
+ *  @return - Success or failure in setting the states. Returns failure in terms
+ *        of PLDM completion codes if atleast one state fails to be set
+ */
+template <class DBusInterface>
+int setStateEffecterStatesHandler(
+    const DBusInterface& dBusIntf, effecter::Id effecterId,
+    const std::vector<set_effecter_state_field>& stateField)
+{
+    using namespace std::string_literals;
+    using DBusProperty = std::variant<std::string, bool>;
+    using StateSetId = uint16_t;
+    using StateSetNum = uint8_t;
+    using PropertyMap =
+        std::map<StateSetId, std::map<StateSetNum, DBusProperty>>;
+    static const PropertyMap stateNumToDbusProp = {
+        {PLDM_BOOT_PROGRESS_STATE,
+         {{PLDM_BOOT_NOT_ACTIVE,
+           "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus."
+           "Standby"s},
+          {PLDM_BOOT_COMPLETED,
+           "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus."
+           "BootComplete"s}}},
+    };
+    using namespace phosphor::logging;
+    using namespace pldm::responder::pdr;
+    using namespace pldm::responder::effecter::dbus_mapping;
+
+    state_effecter_possible_states* states = nullptr;
+    pldm_state_effecter_pdr* pdr = nullptr;
+    uint8_t compEffecterCnt = stateField.size();
+    uint32_t recordHndl{};
+    Repo& pdrRepo = get(PDR_JSONS_DIR);
+    pdr::Entry pdrEntry{};
+
+    while (!pdr)
+    {
+        pdrEntry = pdrRepo.at(recordHndl);
+        pldm_pdr_hdr* header = reinterpret_cast<pldm_pdr_hdr*>(pdrEntry.data());
+        if (header->type != PLDM_STATE_EFFECTER_PDR)
+        {
+            recordHndl = pdrRepo.getNextRecordHandle(recordHndl);
+            if (recordHndl)
+            {
+                continue;
+            }
+            return PLDM_PLATFORM_INVALID_EFFECTER_ID;
+        }
+        pdr = reinterpret_cast<pldm_state_effecter_pdr*>(pdrEntry.data());
+        recordHndl = pdr->hdr.record_handle;
+        if (pdr->effecter_id == effecterId)
+        {
+            states = reinterpret_cast<state_effecter_possible_states*>(
+                pdr->possible_states);
+            if (compEffecterCnt > pdr->composite_effecter_count)
+            {
+                log<level::ERR>("The requester sent wrong composite effecter "
+                                "count for the effecter",
+                                entry("EFFECTER_ID=%d", effecterId),
+                                entry("COMP_EFF_CNT=%d", compEffecterCnt));
+                return PLDM_ERROR_INVALID_DATA;
+            }
+            break;
+        }
+        recordHndl = pdrRepo.getNextRecordHandle(recordHndl);
+        if (!recordHndl)
+        {
+            return PLDM_PLATFORM_INVALID_EFFECTER_ID;
+        }
+        pdr = nullptr;
+    }
+
+    std::map<StateSetId, std::function<int(const std::string& objPath,
+                                           const uint8_t currState)>>
+        effecterToDbusEntries = {
+            {PLDM_BOOT_PROGRESS_STATE,
+             [&](const std::string& objPath, const uint8_t currState) {
+                 auto stateSet =
+                     stateNumToDbusProp.find(PLDM_BOOT_PROGRESS_STATE);
+                 if (stateSet == stateNumToDbusProp.end())
+                 {
+                     log<level::ERR>("Couldn't find D-Bus mapping for "
+                                     "PLDM_BOOT_PROGRESS_STATE",
+                                     entry("EFFECTER_ID=%d", effecterId));
+                     return PLDM_ERROR;
+                 }
+                 auto iter = stateSet->second.find(
+                     stateField[currState].effecter_state);
+                 if (iter == stateSet->second.end())
+                 {
+                     log<level::ERR>(
+                         "Invalid state field passed or field not "
+                         "found for PLDM_BOOT_PROGRESS_STATE",
+                         entry("EFFECTER_ID=%d", effecterId),
+                         entry("FIELD=%d",
+                               stateField[currState].effecter_state),
+                         entry("OBJECT_PATH=%s", objPath.c_str()));
+                     return PLDM_ERROR_INVALID_DATA;
+                 }
+                 auto dbusProp = "OperatingSystemState";
+                 std::variant<std::string> value{
+                     std::get<std::string>(iter->second)};
+                 auto dbusInterface =
+                     "xyz.openbmc_project.State.OperatingSystem.Status";
+                 try
+                 {
+                     dBusIntf.setDbusProperty(objPath.c_str(), dbusProp,
+                                              dbusInterface, value);
+                 }
+                 catch (const std::exception& e)
+                 {
+                     log<level::ERR>("Error setting property",
+                                     entry("ERROR=%s", e.what()),
+                                     entry("PROPERTY=%s", dbusProp),
+                                     entry("INTERFACE=%s", dbusInterface),
+                                     entry("PATH=%s", objPath.c_str()));
+                     return PLDM_ERROR;
+                 }
+                 return PLDM_SUCCESS;
+             }}};
+
+    int rc = PLDM_SUCCESS;
+    auto paths = get(effecterId);
+    for (uint8_t currState = 0; currState < compEffecterCnt; ++currState)
+    {
+        std::vector<StateSetNum> allowed{};
+        // computation is based on table 79 from DSP0248 v1.1.1
+        uint8_t bitfieldIndex = stateField[currState].effecter_state / 8;
+        uint8_t bit =
+            stateField[currState].effecter_state - (8 * bitfieldIndex);
+        if (states->possible_states_size < bitfieldIndex ||
+            !(states->states[bitfieldIndex].byte & (1 << bit)))
+        {
+            log<level::ERR>(
+                "Invalid state set value", entry("EFFECTER_ID=%d", effecterId),
+                entry("VALUE=%d", stateField[currState].effecter_state),
+                entry("COMPOSITE_EFFECTER_ID=%d", currState),
+                entry("DBUS_PATH=%c", paths[currState].c_str()));
+            rc = PLDM_PLATFORM_SET_EFFECTER_UNSUPPORTED_SENSORSTATE;
+            break;
+        }
+        auto iter = effecterToDbusEntries.find(states->state_set_id);
+        if (iter == effecterToDbusEntries.end())
+        {
+            uint16_t setId = states->state_set_id;
+            log<level::ERR>(
+                "Did not find the state set for the state effecter pdr  ",
+                entry("STATE=%d", setId), entry("EFFECTER_ID=%d", effecterId));
+            rc = PLDM_PLATFORM_INVALID_STATE_VALUE;
+            break;
+        }
+        if (stateField[currState].set_request == PLDM_REQUEST_SET)
+        {
+            rc = iter->second(paths[currState], currState);
+            if (rc != PLDM_SUCCESS)
+            {
+                break;
+            }
+        }
+        uint8_t* nextState =
+            reinterpret_cast<uint8_t*>(states) +
+            sizeof(state_effecter_possible_states) - sizeof(states->states) +
+            (states->possible_states_size * sizeof(states->states));
+        states = reinterpret_cast<state_effecter_possible_states*>(nextState);
+    }
+    return rc;
+}
+
 } // namespace responder
 } // namespace pldm