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/libpldm/platform.h b/libpldm/platform.h
index 8e186a0..6fdbf06 100644
--- a/libpldm/platform.h
+++ b/libpldm/platform.h
@@ -46,7 +46,10 @@
 /** @brief PLDM Platform M&C completion codes
  */
 enum pldm_platform_completion_codes {
+	PLDM_PLATFORM_INVALID_EFFECTER_ID = 0x80,
+	PLDM_PLATFORM_INVALID_STATE_VALUE = 0x81,
 	PLDM_PLATFORM_INVALID_RECORD_HANDLE = 0x82,
+	PLDM_PLATFORM_SET_EFFECTER_UNSUPPORTED_SENSORSTATE = 0x82,
 };
 
 /** @struct pldm_pdr_hdr
diff --git a/libpldmresponder/platform.cpp b/libpldmresponder/platform.cpp
index 5fa14c8..538c49c 100644
--- a/libpldmresponder/platform.cpp
+++ b/libpldmresponder/platform.cpp
@@ -1,11 +1,7 @@
-#include "config.h"
 
 #include "platform.hpp"
 
-#include "pdr.hpp"
-
-#include <exception>
-#include <phosphor-logging/log.hpp>
+#include "registration.hpp"
 
 namespace pldm
 {
@@ -13,7 +9,20 @@
 namespace responder
 {
 
+namespace platform
+{
+
+void registerHandlers()
+{
+    registerHandler(PLDM_PLATFORM, PLDM_GET_PDR, std::move(getPDR));
+    registerHandler(PLDM_PLATFORM, PLDM_SET_STATE_EFFECTER_STATES,
+                    std::move(setStateEffecterStates));
+}
+
+} // namespace platform
+
 using namespace phosphor::logging;
+using namespace pldm::responder::effecter::dbus_mapping;
 
 Response getPDR(const pldm_msg* request, size_t payloadLength)
 {
@@ -83,5 +92,42 @@
     return response;
 }
 
+Response setStateEffecterStates(const pldm_msg* request, size_t payloadLength)
+{
+    Response response(
+        sizeof(pldm_msg_hdr) + PLDM_SET_STATE_EFFECTER_STATES_RESP_BYTES, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    uint16_t effecterId;
+    uint8_t compEffecterCnt;
+    constexpr auto maxCompositeEffecterCnt = 8;
+    std::vector<set_effecter_state_field> stateField(maxCompositeEffecterCnt,
+                                                     {0, 0});
+
+    if ((payloadLength > PLDM_SET_STATE_EFFECTER_STATES_REQ_BYTES) ||
+        (payloadLength < sizeof(effecterId) + sizeof(compEffecterCnt) +
+                             sizeof(set_effecter_state_field)))
+    {
+        encode_set_state_effecter_states_resp(
+            request->hdr.instance_id, PLDM_ERROR_INVALID_LENGTH, responsePtr);
+        return response;
+    }
+
+    int rc = decode_set_state_effecter_states_req(request, payloadLength,
+                                                  &effecterId, &compEffecterCnt,
+                                                  stateField.data());
+
+    if (rc == PLDM_SUCCESS)
+    {
+        stateField.resize(compEffecterCnt);
+        const DBusHandler dBusIntf;
+        rc = setStateEffecterStatesHandler<DBusHandler>(dBusIntf, effecterId,
+                                                        stateField);
+    }
+
+    encode_set_state_effecter_states_resp(request->hdr.instance_id, rc,
+                                          responsePtr);
+    return response;
+}
+
 } // namespace responder
 } // namespace pldm
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
diff --git a/libpldmresponder/utils.hpp b/libpldmresponder/utils.hpp
index 6891d81..5f366be 100644
--- a/libpldmresponder/utils.hpp
+++ b/libpldmresponder/utils.hpp
@@ -4,8 +4,13 @@
 #include <systemd/sd-bus.h>
 #include <unistd.h>
 
+#include <exception>
 #include <sdbusplus/server.hpp>
 #include <string>
+#include <variant>
+#include <vector>
+
+#include "libpldm/base.h"
 
 namespace pldm
 {
@@ -88,5 +93,41 @@
     return bcd;
 }
 
+constexpr auto dbusProperties = "org.freedesktop.DBus.Properties";
+
+/**
+ *  @class DBusHandler
+ *
+ *  Wrapper class to handle the D-Bus calls
+ *
+ *  This class contains the APIs to handle the D-Bus calls
+ *  to cater the request from pldm requester.
+ *  A class is created to mock the apis in the test cases
+ */
+class DBusHandler
+{
+  public:
+    /** @brief API to set a D-Bus property
+     *
+     *  @param[in] objPath - Object path for the D-Bus object
+     *  @param[in] dbusProp - The D-Bus property
+     *  @param[in] dbusInterface - The D-Bus interface
+     *  @param[in] value - The value to be set
+     * failure
+     */
+    template <typename T>
+    void setDbusProperty(const char* objPath, const char* dbusProp,
+                         const char* dbusInterface,
+                         const std::variant<T>& value) const
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto service = getService(bus, objPath, dbusInterface);
+        auto method = bus.new_method_call(service.c_str(), objPath,
+                                          dbusProperties, "Set");
+        method.append(dbusInterface, dbusProp, value);
+        bus.call_noreply(method);
+    }
+};
+
 } // namespace responder
 } // namespace pldm
diff --git a/pldmd.cpp b/pldmd.cpp
index 0034bcb..5623975 100644
--- a/pldmd.cpp
+++ b/pldmd.cpp
@@ -1,5 +1,6 @@
 #include "libpldmresponder/base.hpp"
 #include "libpldmresponder/bios.hpp"
+#include "libpldmresponder/platform.hpp"
 #include "libpldmresponder/utils.hpp"
 #include "registration.hpp"
 
@@ -24,6 +25,7 @@
 
 #include "libpldm/base.h"
 #include "libpldm/bios.h"
+#include "libpldm/platform.h"
 
 #ifdef OEM_IBM
 #include "libpldmresponder/file_io.hpp"
@@ -130,6 +132,7 @@
 
     pldm::responder::base::registerHandlers();
     pldm::responder::bios::registerHandlers();
+    pldm::responder::platform::registerHandlers();
 
     // Outgoing message.
     struct iovec iov[2]{};
diff --git a/test/Makefile.am b/test/Makefile.am
index fbaa01f..9891a28 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -27,14 +27,18 @@
 	$(PTHREAD_CFLAGS) \
 	$(PHOSPHOR_LOGGING_CFLAGS) \
 	$(SDBUSPLUS_CFLAGS) \
-	$(CODE_COVERAGE_CXXFLAGS)
+	$(CODE_COVERAGE_CXXFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
 
 test_ldflags = \
 	-lgtest_main \
 	-lgtest \
+	-lgmock \
+	-lstdc++fs \
 	$(PTHREAD_LIBS) \
 	$(SDBUSPLUS_LIBS) \
 	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	$(OESDK_TESTCASE_FLAGS)
 
 if OEM_IBM
@@ -162,13 +166,15 @@
 
 libpldmresponder_platform_test_CPPFLAGS = $(test_cppflags)
 libpldmresponder_platform_test_CXXFLAGS = $(test_cxxflags)
-libpldmresponder_platform_test_LDFLAGS = $(test_ldflags)
+libpldmresponder_platform_test_LDFLAGS = $(test_ldflags) $(SDBUSPLUS_LIBS)
 libpldmresponder_platform_test_LDADD = \
+	$(top_builddir)/pldmd-registration.o \
 	$(top_builddir)/libpldm/libpldm_la-base.o \
 	$(top_builddir)/libpldm/libpldm_la-platform.o \
 	$(top_builddir)/libpldmresponder/libpldmresponder_la-pdr.o \
 	$(top_builddir)/libpldmresponder/libpldmresponder_la-effecters.o \
 	$(top_builddir)/libpldmresponder/libpldmresponder_la-platform.o \
+	$(top_builddir)/libpldmresponder/libpldmresponder_la-utils.o
 	$(PHOSPHOR_LOGGING_LIBS) \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	$(SDBUSPLUS_LIBS) \
diff --git a/test/libpldmresponder_pdr_state_effecter_test.cpp b/test/libpldmresponder_pdr_state_effecter_test.cpp
index f763593..c55feb0 100644
--- a/test/libpldmresponder_pdr_state_effecter_test.cpp
+++ b/test/libpldmresponder_pdr_state_effecter_test.cpp
@@ -25,7 +25,7 @@
     ASSERT_EQ(pdr->hdr.version, 1);
     ASSERT_EQ(pdr->hdr.type, PLDM_STATE_EFFECTER_PDR);
     ASSERT_EQ(pdr->hdr.record_change_num, 0);
-    ASSERT_EQ(pdr->hdr.length, 19);
+    ASSERT_EQ(pdr->hdr.length, 23);
 
     ASSERT_EQ(pdr->terminus_handle, 0);
     ASSERT_EQ(pdr->effecter_id, 1);
@@ -35,7 +35,7 @@
     ASSERT_EQ(pdr->effecter_semantic_id, 0);
     ASSERT_EQ(pdr->effecter_init, PLDM_NO_INIT);
     ASSERT_EQ(pdr->has_description_pdr, false);
-    ASSERT_EQ(pdr->composite_effecter_count, 1);
+    ASSERT_EQ(pdr->composite_effecter_count, 2);
     state_effecter_possible_states* states =
         reinterpret_cast<state_effecter_possible_states*>(pdr->possible_states);
     ASSERT_EQ(states->state_set_id, 196);
diff --git a/test/libpldmresponder_platform_test.cpp b/test/libpldmresponder_platform_test.cpp
index dd51e72..6b89b2e 100644
--- a/test/libpldmresponder_platform_test.cpp
+++ b/test/libpldmresponder_platform_test.cpp
@@ -1,11 +1,15 @@
+#include "libpldmresponder/effecters.hpp"
 #include "libpldmresponder/pdr.hpp"
 #include "libpldmresponder/platform.hpp"
 
 #include <iostream>
 
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 using namespace pldm::responder;
+using namespace pldm::responder::pdr;
 
 TEST(getPDR, testGoodPath)
 {
@@ -171,3 +175,84 @@
     }
     ASSERT_EQ(found, true);
 }
+
+namespace pldm
+{
+
+namespace responder
+{
+
+class MockdBusHandler
+{
+  public:
+    MOCK_CONST_METHOD4(setDbusProperty,
+                       int(const std::string&, const std::string&,
+                           const std::string&,
+                           const std::variant<std::string>&));
+};
+} // namespace responder
+} // namespace pldm
+
+using ::testing::_;
+using ::testing::Return;
+
+TEST(setStateEffecterStatesHandler, testGoodRequest)
+{
+    Repo& pdrRepo = get("./pdr_jsons/state_effecter/good");
+    pdr::Entry e = pdrRepo.at(1);
+    pldm_state_effecter_pdr* pdr =
+        reinterpret_cast<pldm_state_effecter_pdr*>(e.data());
+    EXPECT_EQ(pdr->hdr.type, PLDM_STATE_EFFECTER_PDR);
+
+    std::vector<set_effecter_state_field> stateField;
+    stateField.push_back({PLDM_REQUEST_SET, 1});
+    stateField.push_back({PLDM_REQUEST_SET, 1});
+
+    auto bootProgressInf = "xyz.openbmc_project.State.OperatingSystem.Status";
+    auto bootProgressProp = "OperatingSystemState";
+    std::string objPath = "/foo/bar";
+    std::variant<std::string> value{"xyz.openbmc_project.State.OperatingSystem."
+                                    "Status.OSStatus.Standby"};
+
+    MockdBusHandler handlerObj;
+    EXPECT_CALL(handlerObj, setDbusProperty(objPath, bootProgressProp,
+                                            bootProgressInf, value))
+        .Times(2);
+    auto rc = setStateEffecterStatesHandler<MockdBusHandler>(handlerObj, 0x1,
+                                                             stateField);
+    ASSERT_EQ(rc, 0);
+}
+
+TEST(setStateEffecterStatesHandler, testBadRequest)
+{
+    Repo& pdrRepo = get("./pdr_jsons/state_effecter/good");
+    pdr::Entry e = pdrRepo.at(1);
+    pldm_state_effecter_pdr* pdr =
+        reinterpret_cast<pldm_state_effecter_pdr*>(e.data());
+    EXPECT_EQ(pdr->hdr.type, PLDM_STATE_EFFECTER_PDR);
+
+    std::vector<set_effecter_state_field> stateField;
+    stateField.push_back({PLDM_REQUEST_SET, 3});
+    stateField.push_back({PLDM_REQUEST_SET, 4});
+
+    MockdBusHandler handlerObj;
+    auto rc = setStateEffecterStatesHandler<MockdBusHandler>(handlerObj, 0x1,
+                                                             stateField);
+    ASSERT_EQ(rc, PLDM_PLATFORM_SET_EFFECTER_UNSUPPORTED_SENSORSTATE);
+
+    rc = setStateEffecterStatesHandler<MockdBusHandler>(handlerObj, 0x9,
+                                                        stateField);
+    ASSERT_EQ(rc, PLDM_PLATFORM_INVALID_EFFECTER_ID);
+
+    stateField.push_back({PLDM_REQUEST_SET, 4});
+    rc = setStateEffecterStatesHandler<MockdBusHandler>(handlerObj, 0x1,
+                                                        stateField);
+    ASSERT_EQ(rc, PLDM_ERROR_INVALID_DATA);
+
+    std::vector<set_effecter_state_field> newStateField;
+    newStateField.push_back({PLDM_REQUEST_SET, 1});
+
+    rc = setStateEffecterStatesHandler<MockdBusHandler>(handlerObj, 0x2,
+                                                        newStateField);
+    ASSERT_EQ(rc, PLDM_PLATFORM_INVALID_STATE_VALUE);
+}
diff --git a/test/meson.build b/test/meson.build
index 1ddb598..5bfad27 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -38,6 +38,6 @@
                      implicit_include_directories: false,
                      link_args: dynamic_linker,
                      build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
-                     dependencies: [libpldm, libpldmresponder, gtest, gmock]),
+                     dependencies: [libpldm, libpldmresponder, gtest, gmock, dependency('sdbusplus')]),
        workdir: meson.current_source_dir())
 endforeach
diff --git a/test/pdr_jsons/state_effecter/good/11.json b/test/pdr_jsons/state_effecter/good/11.json
index a037ad4..6a9944b 100644
--- a/test/pdr_jsons/state_effecter/good/11.json
+++ b/test/pdr_jsons/state_effecter/good/11.json
@@ -10,6 +10,14 @@
                 "states" : [1]
             },
             "dbus" : "/foo/bar"
+        },
+        {
+            "set" : {
+                "id" : 196,
+                "size" : 1,
+                "states" : [1, 2]
+            },
+            "dbus" : "/foo/bar"
         }]
     },
     {