pldm_events: Implement support for state sensorEvents

PLDM supports platform event message by which events can be generated
by a PLDM entity and can be processed by event receiver like BMC. This
patch adds support for remote state sensors which sends PlatformEventMessage
command with sensorEvent type. The sensor is mapped to a D-Bus property
and the eventState is mapped to a D-Bus property value.

Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
Change-Id: I50dea23eccbdf29ad1b4cf2c9ab5700d74c12c68
diff --git a/libpldmresponder/event_parser.cpp b/libpldmresponder/event_parser.cpp
new file mode 100644
index 0000000..0a0000c
--- /dev/null
+++ b/libpldmresponder/event_parser.cpp
@@ -0,0 +1,162 @@
+#include "event_parser.hpp"

+

+#include <filesystem>

+#include <fstream>

+#include <iostream>

+#include <set>

+#include <xyz/openbmc_project/Common/error.hpp>

+

+namespace pldm::responder::events

+{

+

+namespace fs = std::filesystem;

+using InternalFailure =

+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;

+

+const Json emptyJson{};

+const std::vector<Json> emptyJsonList{};

+const std::vector<std::string> emptyStringVec{};

+

+constexpr auto eventStateSensorJson = "event_state_sensor.json";

+

+const std::set<std::string_view> supportedDbusPropertyTypes = {

+    "bool",     "uint8_t", "int16_t",  "uint16_t", "int32_t",

+    "uint32_t", "int64_t", "uint64_t", "double",   "string"};

+

+StateSensorHandler::StateSensorHandler(const std::string& dirPath)

+{

+    fs::path dir(dirPath);

+    if (!fs::exists(dir) || fs::is_empty(dir))

+    {

+        std::cerr << "Event config directory does not exist or empty, DIR="

+                  << dirPath << "\n";

+        return;

+    }

+

+    fs::path filePath = dir / eventStateSensorJson;

+    if (!fs::exists(filePath))

+    {

+        std::cerr << "Event state sensor JSON does not exist, PATH=" << filePath

+                  << "\n";

+        throw InternalFailure();

+    }

+

+    std::ifstream jsonFile(filePath);

+

+    auto data = Json::parse(jsonFile, nullptr, false);

+    if (data.is_discarded())

+    {

+        std::cerr << "Parsing Event state sensor JSON file failed, FILE="

+                  << filePath;

+        throw InternalFailure();

+    }

+

+    auto entries = data.value("entries", emptyJsonList);

+    for (const auto& entry : entries)

+    {

+        StateSensorEntry stateSensorEntry{};

+        stateSensorEntry.containerId =

+            static_cast<uint16_t>(entry.value("containerID", 0));

+        stateSensorEntry.entityType =

+            static_cast<uint16_t>(entry.value("entityType", 0));

+        stateSensorEntry.entityInstance =

+            static_cast<uint16_t>(entry.value("entityInstance", 0));

+        stateSensorEntry.sensorOffset =

+            static_cast<uint8_t>(entry.value("sensorOffset", 0));

+

+        pldm::utils::DBusMapping dbusInfo{};

+

+        auto dbus = entry.value("dbus", emptyJson);

+        dbusInfo.objectPath = dbus.value("object_path", "");

+        dbusInfo.interface = dbus.value("interface", "");

+        dbusInfo.propertyName = dbus.value("property_name", "");

+        dbusInfo.propertyType = dbus.value("property_type", "");

+        if (dbusInfo.objectPath.empty() || dbusInfo.interface.empty() ||

+            dbusInfo.propertyName.empty() ||

+            (supportedDbusPropertyTypes.find(dbusInfo.propertyType) ==

+             supportedDbusPropertyTypes.end()))

+        {

+            std::cerr << "Invalid dbus config,"

+                      << " OBJPATH=" << dbusInfo.objectPath << " INTERFACE="

+                      << dbusInfo.interface << " PROPERTY_NAME="

+                      << dbusInfo.propertyName

+                      << " PROPERTY_TYPE=" << dbusInfo.propertyType << "\n";

+            continue;

+        }

+

+        auto eventStates = entry.value("event_states", emptyJsonList);

+        auto propertyValues = dbus.value("property_values", emptyJsonList);

+        if ((eventStates.size() == 0) || (propertyValues.size() == 0) ||

+            (eventStates.size() != propertyValues.size()))

+        {

+            std::cerr << "Invalid event state JSON config,"

+                      << " EVENT_STATE_SIZE=" << eventStates.size()

+                      << " PROPERTY_VALUE_SIZE=" << propertyValues.size()

+                      << "\n";

+            continue;

+        }

+

+        auto eventStateMap = mapStateToDBusVal(eventStates, propertyValues,

+                                               dbusInfo.propertyType);

+        eventMap.emplace(

+            stateSensorEntry,

+            std::make_tuple(std::move(dbusInfo), std::move(eventStateMap)));

+    }

+}

+

+StateToDBusValue StateSensorHandler::mapStateToDBusVal(

+    const Json& eventStates, const Json& propertyValues, std::string_view type)

+{

+    StateToDBusValue eventStateMap{};

+    auto stateIt = eventStates.begin();

+    auto propIt = propertyValues.begin();

+

+    for (; stateIt != eventStates.end(); ++stateIt, ++propIt)

+    {

+        auto propValue = utils::jsonEntryToDbusVal(type, propIt.value());

+        eventStateMap.emplace((*stateIt).get<uint8_t>(), std::move(propValue));

+    }

+

+    return eventStateMap;

+}

+

+int StateSensorHandler::eventAction(const StateSensorEntry& entry,

+                                    pdr::EventState state)

+{

+    try

+    {

+        const auto& [dbusMapping, eventStateMap] = eventMap.at(entry);

+        utils::PropertyValue propValue{};

+        try

+        {

+            propValue = eventStateMap.at(state);

+        }

+        catch (const std::out_of_range& e)

+        {

+            std::cerr << "Invalid event state" << static_cast<unsigned>(state)

+                      << '\n';

+            return PLDM_ERROR_INVALID_DATA;

+        }

+

+        try

+        {

+            pldm::utils::DBusHandler().setDbusProperty(dbusMapping, propValue);

+        }

+        catch (const std::exception& e)

+        {

+            std::cerr << "Error setting property, ERROR=" << e.what()

+                      << " PROPERTY=" << dbusMapping.propertyName

+                      << " INTERFACE=" << dbusMapping.interface << " PATH="

+                      << dbusMapping.objectPath << "\n";

+            return PLDM_ERROR;

+        }

+    }

+    catch (const std::out_of_range& e)

+    {

+        // There is no BMC action for this PLDM event

+        return PLDM_SUCCESS;

+    }

+    return PLDM_SUCCESS;

+}

+

+} // namespace pldm::responder::events
\ No newline at end of file
diff --git a/libpldmresponder/event_parser.hpp b/libpldmresponder/event_parser.hpp
new file mode 100644
index 0000000..1139c71
--- /dev/null
+++ b/libpldmresponder/event_parser.hpp
@@ -0,0 +1,120 @@
+#pragma once

+

+#include "types.hpp"

+#include "utils.hpp"

+

+#include <filesystem>

+#include <map>

+#include <nlohmann/json.hpp>

+#include <string>

+#include <tuple>

+#include <vector>

+

+namespace pldm::responder::events

+{

+

+/** @struct StateSensorEntry

+ *

+ *  StateSensorEntry is a key to uniquely identify a state sensor, so that a

+ *  D-Bus action can be defined for PlatformEventMessage command with

+ *  sensorEvent type. This struct is used as a key in a std::map so implemented

+ *  operator== and operator<.

+ */

+struct StateSensorEntry

+{

+    pdr::ContainerID containerId;

+    pdr::EntityType entityType;

+    pdr::EntityInstance entityInstance;

+    pdr::SensorOffset sensorOffset;

+

+    bool operator==(const StateSensorEntry& e) const

+    {

+        return ((containerId == e.containerId) &&

+                (entityType == e.entityType) &&

+                (entityInstance == e.entityInstance) &&

+                (sensorOffset == e.sensorOffset));

+    }

+

+    bool operator<(const StateSensorEntry& e) const

+    {

+        return (

+            (containerId < e.containerId) ||

+            ((containerId == e.containerId) && (entityType < e.entityType)) ||

+            ((containerId == e.containerId) && (entityType == e.entityType) &&

+             (entityInstance < e.entityInstance)) ||

+            ((containerId == e.containerId) && (entityType == e.entityType) &&

+             (entityInstance == e.entityInstance) &&

+             (sensorOffset < e.sensorOffset)));

+    }

+};

+

+using StateToDBusValue = std::map<pdr::EventState, pldm::utils::PropertyValue>;

+using EventDBusInfo = std::tuple<pldm::utils::DBusMapping, StateToDBusValue>;

+using EventMap = std::map<StateSensorEntry, EventDBusInfo>;

+using Json = nlohmann::json;

+

+/** @class StateSensorHandler

+ *

+ *  @brief Parses the event state sensor configuration JSON file and build

+ *         the lookup data structure, which can map the event state for a

+ *         sensor in the PlatformEventMessage command to a D-Bus property and

+ *         the property value.

+ */

+class StateSensorHandler

+{

+  public:

+    StateSensorHandler() = delete;

+

+    /** @brief Parse the event state sensor configuration JSON file and build

+     *         the lookup data stucture.

+     *

+     *  @param[in] dirPath - directory path which has the config JSONs

+     */

+    explicit StateSensorHandler(const std::string& dirPath);

+    virtual ~StateSensorHandler() = default;

+    StateSensorHandler(const StateSensorHandler&) = default;

+    StateSensorHandler& operator=(const StateSensorHandler&) = default;

+    StateSensorHandler(StateSensorHandler&&) = default;

+    StateSensorHandler& operator=(StateSensorHandler&&) = default;

+

+    /** @brief If the StateSensorEntry and EventState is valid, the D-Bus

+     *         property corresponding to the StateSensorEntry is set based on

+     *         the EventState

+     *

+     *  @param[in] entry - state sensor entry

+     *  @param[in] state - event state

+     *

+     *  @return PLDM completion code

+     */

+    int eventAction(const StateSensorEntry& entry, pdr::EventState state);

+

+    /** @brief Helper API to get D-Bus information for a StateSensorEntry

+     *

+     *  @param[in] entry - state sensor entry

+     *

+     *  @return D-Bus information corresponding to the SensorEntry

+     */

+    const EventDBusInfo& getEventInfo(const StateSensorEntry& entry) const

+    {

+        return eventMap.at(entry);

+    }

+

+  private:

+    EventMap eventMap; //!< a map of StateSensorEntry to D-Bus information

+

+    /** @brief Create a map of EventState to D-Bus property values from

+     *         the information provided in the event state configuration

+     *         JSON

+     *

+     *  @param[in] eventStates - a JSON array of event states

+     *  @param[in] propertyValues - a JSON array of D-Bus property values

+     *  @param[in] type - the type of D-Bus property

+     *

+     *  @return a map of EventState to D-Bus property values

+     */

+    StateToDBusValue mapStateToDBusVal(const Json& eventStates,

+                                       const Json& propertyValues,

+                                       std::string_view type);

+};

+

+} // namespace pldm::responder::events
\ No newline at end of file
diff --git a/libpldmresponder/examples/events/event_state_sensor.json b/libpldmresponder/examples/events/event_state_sensor.json
new file mode 100644
index 0000000..47713b8
--- /dev/null
+++ b/libpldmresponder/examples/events/event_state_sensor.json
@@ -0,0 +1,69 @@
+# Each entry is to uniquely identify a remote state sensor(there are separate

+# entry for each sensor in a composite sensor) and the supported event states.

+# The "dbus" section contains information about the corresponding D-Bus

+# property for the sensor and "property_values" are the D-Bus property values

+# for each corresponding entry in the "event_states"

+{

+    "entries": [

+        {

+            "containerID": 1,

+            "entityType": 64,

+            "entityInstance": 1,

+            "sensorOffset": 0,

+            "event_states": [

+                0,

+                1

+            ],

+            "dbus": {

+                "object_path": "/xyz/abc/def",

+                "interface": "xyz.openbmc_project.example1.value",

+                "property_name": "value1",

+                "property_type": "string",

+                "property_values": [

+                    "xyz.openbmc_project.State.On",

+                    "xyz.openbmc_project.State.Off"

+                ]

+            }

+        },

+        {

+            "containerID": 1,

+            "entityType": 64,

+            "entityInstance": 1,

+            "sensorOffset": 1,

+            "event_states": [

+                2,

+                3

+            ],

+            "dbus": {

+                "object_path": "/xyz/abc/def",

+                "interface": "xyz.openbmc_project.example2.value",

+                "property_name": "value2",

+                "property_type": "uint8_t",

+                "property_values": [

+                    9,

+                    10

+                ]

+            }

+        },

+        {

+            "containerID": 1,

+            "entityType": 67,

+            "entityInstance": 1,

+            "sensorOffset": 0,

+            "event_states": [

+                0,

+                1

+            ],

+            "dbus": {

+                "object_path": "/xyz/abc/ghi",

+                "interface": "xyz.openbmc_project.example3.value",

+                "property_name": "value3",

+                "property_type": "bool",

+                "property_values": [

+                    false,

+                    true

+                ]

+            }

+        }

+    ]

+}

diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index 0275e94..8231630 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -20,7 +20,8 @@
   'platform.cpp',
   'fru_parser.cpp',
   'fru.cpp',
-  '../host_pdr_handler.cpp'
+  '../host_pdr_handler.cpp',
+  'event_parser.cpp'
 ]
 
 if get_option('oem-ibm').enabled()
diff --git a/libpldmresponder/platform.hpp b/libpldmresponder/platform.hpp
index 1982031..a21502e 100644
--- a/libpldmresponder/platform.hpp
+++ b/libpldmresponder/platform.hpp
@@ -2,6 +2,7 @@
 
 #include "config.h"
 
+#include "event_parser.hpp"
 #include "handler.hpp"
 #include "host_pdr_handler.hpp"
 #include "libpldmresponder/pdr.hpp"
@@ -53,13 +54,13 @@
 class Handler : public CmdHandler
 {
   public:
-    Handler(const std::string& dir, pldm_pdr* repo,
-            HostPDRHandler* hostPDRHandler,
+    Handler(const std::string& pdrJsonsDir, const std::string& eventsJsonsDir,
+            pldm_pdr* repo, HostPDRHandler* hostPDRHandler,
             const std::optional<EventMap>& addOnHandlersMap = std::nullopt) :
         pdrRepo(repo),
-        hostPDRHandler(hostPDRHandler)
+        hostPDRHandler(hostPDRHandler), stateSensorHandler(eventsJsonsDir)
     {
-        generate(dir, pdrRepo);
+        generate(pdrJsonsDir, pdrRepo);
 
         handlers.emplace(PLDM_GET_PDR,
                          [this](const pldm_msg* request, size_t payloadLength) {
@@ -394,6 +395,7 @@
     uint16_t nextEffecterId{};
     DbusObjMaps dbusObjMaps{};
     HostPDRHandler* hostPDRHandler;
+    events::StateSensorHandler stateSensorHandler;
 };
 
 } // namespace platform