platform-mc: Support CPER Event handler

Handle CPER event(0x07) which is defined in `Table 11 - PLDM Event
Type` and section `16.17 eventData format for CPEREvent` in DSP0248
v1.3.0.

The code supports:
1. Handle the PLDM event which has eventClass as CPEREvent (0x07).
2. Store the CPER data in PLDM CPER event to file at `/var/cper/`.
3. Call `CreateDump` method of `xyz.openbmc_project.Dump.Manager` D-Bus
service to create dump fault log.
4. The user can find the dump fault logs in Redfish FaultLog entries
thru URL `/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries`. Each
CPER entry includes the URL to download the created CPER data file
`/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<id>/attachment`.
5. The user can use `cper-parser` in `libcper` to parse the CPER data in
the attached file.

Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
Change-Id: I85c53933183178c6b5acdfc12c805e8a4cf1ca2a
diff --git a/platform-mc/event_manager.cpp b/platform-mc/event_manager.cpp
index 1b9c6c3..b50daf2 100644
--- a/platform-mc/event_manager.cpp
+++ b/platform-mc/event_manager.cpp
@@ -58,6 +58,12 @@
         }
     }
 
+    /* EventClass CPEREvent as `Table 11 - PLDM Event Types` DSP0248 V1.3.0 */
+    if (eventClass == PLDM_CPER_EVENT)
+    {
+        return processCperEvent(tid, eventId, eventData, eventDataSize);
+    }
+
     lg2::info("Unsupported class type {CLASSTYPE}", "CLASSTYPE", eventClass);
 
     return PLDM_ERROR;
@@ -304,5 +310,137 @@
     return PLDM_SUCCESS;
 }
 
+int EventManager::processCperEvent(pldm_tid_t tid, uint16_t eventId,
+                                   const uint8_t* eventData,
+                                   const size_t eventDataSize)
+{
+    if (eventDataSize < PLDM_PLATFORM_CPER_EVENT_MIN_LENGTH)
+    {
+        lg2::error(
+            "Error : Invalid CPER Event data length for eventId {EVENTID}.",
+            "EVENTID", eventId);
+        return PLDM_ERROR;
+    }
+    const size_t cperEventDataSize =
+        eventDataSize - PLDM_PLATFORM_CPER_EVENT_MIN_LENGTH;
+    const size_t msgDataLen =
+        sizeof(pldm_platform_cper_event) + cperEventDataSize;
+    std::string terminusName = "";
+    auto msgData = std::make_unique<unsigned char[]>(msgDataLen);
+    auto cperEvent = new (msgData.get()) pldm_platform_cper_event;
+
+    auto rc = decode_pldm_platform_cper_event(eventData, eventDataSize,
+                                              cperEvent, msgDataLen);
+
+    if (rc)
+    {
+        lg2::error(
+            "Failed to decode CPER event for eventId {EVENTID} of terminus ID {TID} error {RC}.",
+            "EVENTID", eventId, "TID", tid, "RC", rc);
+        return rc;
+    }
+
+    if (termini.contains(tid) && !termini[tid])
+    {
+        auto tmp = termini[tid]->getTerminusName();
+        if (tmp && !tmp.value().empty())
+        {
+            terminusName = static_cast<std::string>(tmp.value());
+        }
+    }
+
+    // Save event data to file
+    std::filesystem::path dirName{"/var/cper"};
+    if (!std::filesystem::exists(dirName))
+    {
+        try
+        {
+            std::filesystem::create_directory(dirName);
+        }
+        catch (const std::filesystem::filesystem_error& e)
+        {
+            lg2::error("Failed to create /var/cper directory: {ERROR}", "ERROR",
+                       e);
+            return PLDM_ERROR;
+        }
+    }
+
+    std::string fileName{dirName.string() + "/cper-XXXXXX"};
+    auto fd = mkstemp(fileName.data());
+    if (fd < 0)
+    {
+        lg2::error("Failed to generate temp file, error {ERRORNO}", "ERRORNO",
+                   std::strerror(errno));
+        return PLDM_ERROR;
+    }
+    close(fd);
+
+    std::ofstream ofs;
+    ofs.exceptions(std::ofstream::failbit | std::ofstream::badbit |
+                   std::ofstream::eofbit);
+
+    try
+    {
+        ofs.open(fileName);
+        ofs.write(reinterpret_cast<const char*>(
+                      pldm_platform_cper_event_event_data(cperEvent)),
+                  cperEvent->event_data_length);
+        if (cperEvent->format_type == PLDM_PLATFORM_CPER_EVENT_WITH_HEADER)
+        {
+            rc = createCperDumpEntry("CPER", fileName, terminusName);
+        }
+        else
+        {
+            rc = createCperDumpEntry("CPERSection", fileName, terminusName);
+        }
+        ofs.close();
+    }
+    catch (const std::ofstream::failure& e)
+    {
+        lg2::error("Failed to save CPER to '{FILENAME}', error - {ERROR}.",
+                   "FILENAME", fileName, "ERROR", e);
+        return PLDM_ERROR;
+    }
+    return rc;
+}
+
+int EventManager::createCperDumpEntry(const std::string& dataType,
+                                      const std::string& dataPath,
+                                      const std::string& typeName)
+{
+    auto createDump =
+        [](std::map<std::string, std::variant<std::string, uint64_t>>&
+               addData) {
+            static constexpr auto dumpObjPath =
+                "/xyz/openbmc_project/dump/faultlog";
+            static constexpr auto dumpInterface =
+                "xyz.openbmc_project.Dump.Create";
+            auto& bus = pldm::utils::DBusHandler::getBus();
+
+            try
+            {
+                auto service = pldm::utils::DBusHandler().getService(
+                    dumpObjPath, dumpInterface);
+                auto method = bus.new_method_call(service.c_str(), dumpObjPath,
+                                                  dumpInterface, "CreateDump");
+                method.append(addData);
+                bus.call_noreply(method);
+            }
+            catch (const std::exception& e)
+            {
+                lg2::error(
+                    "Failed to create D-Bus Dump entry, error - {ERROR}.",
+                    "ERROR", e);
+            }
+        };
+
+    std::map<std::string, std::variant<std::string, uint64_t>> addData;
+    addData["Type"] = dataType;
+    addData["PrimaryLogId"] = dataPath;
+    addData["AdditionalTypeName"] = typeName;
+    createDump(addData);
+    return PLDM_SUCCESS;
+}
+
 } // namespace platform_mc
 } // namespace pldm
diff --git a/platform-mc/event_manager.hpp b/platform-mc/event_manager.hpp
index f40db44..c654eb7 100644
--- a/platform-mc/event_manager.hpp
+++ b/platform-mc/event_manager.hpp
@@ -87,6 +87,30 @@
                                   const uint8_t* sensorData,
                                   size_t sensorDataLength);
 
+    /** @brief Helper method to process the PLDM CPER event class
+     *
+     *  @param[in] tid - tid where the event is from
+     *  @param[in] eventId - Event ID which is the source of event
+     *  @param[in] eventData - CPER event data
+     *  @param[in] eventDataSize - event data length
+     *
+     *  @return PLDM completion code
+     */
+    int processCperEvent(pldm_tid_t tid, uint16_t eventId,
+                         const uint8_t* eventData, const size_t eventDataSize);
+
+    /** @brief Helper method to create CPER dump log
+     *
+     *  @param[in] dataType - CPER event data type
+     *  @param[in] dataPath - CPER event data fault log file path
+     *  @param[in] typeName - Terminus name which creates CPER event
+     *
+     *  @return PLDM completion code
+     */
+    int createCperDumpEntry(const std::string& dataType,
+                            const std::string& dataPath,
+                            const std::string& typeName);
+
     /** @brief Reference of terminusManager */
     TerminusManager& terminusManager;
 
diff --git a/platform-mc/manager.hpp b/platform-mc/manager.hpp
index a4fd466..c499f88 100644
--- a/platform-mc/manager.hpp
+++ b/platform-mc/manager.hpp
@@ -101,7 +101,7 @@
         sensorManager.stopPolling(tid);
     }
 
-    /** @brief Sensor event handler funtion
+    /** @brief Sensor event handler function
      *
      *  @param[in] request - Event message
      *  @param[in] payloadLength - Event message payload size
@@ -118,8 +118,32 @@
         auto eventData = reinterpret_cast<const uint8_t*>(request->payload) +
                          eventDataOffset;
         auto eventDataSize = payloadLength - eventDataOffset;
-        eventManager.handlePlatformEvent(tid, 0x00, PLDM_SENSOR_EVENT,
-                                         eventData, eventDataSize);
+        eventManager.handlePlatformEvent(tid, PLDM_PLATFORM_EVENT_ID_NULL,
+                                         PLDM_SENSOR_EVENT, eventData,
+                                         eventDataSize);
+        return PLDM_SUCCESS;
+    }
+
+    /** @brief CPER event handler function
+     *
+     *  @param[in] request - Event message
+     *  @param[in] payloadLength - Event message payload size
+     *  @param[in] tid - Terminus ID
+     *  @param[in] eventDataOffset - Event data offset
+     *
+     *  @return PLDM error code: PLDM_SUCCESS when there is no error in handling
+     *          the event
+     */
+    int handleCperEvent(const pldm_msg* request, size_t payloadLength,
+                        uint8_t /* formatVersion */, uint8_t tid,
+                        size_t eventDataOffset)
+    {
+        auto eventData =
+            const_cast<const uint8_t*>(request->payload) + eventDataOffset;
+        auto eventDataSize = payloadLength - eventDataOffset;
+        eventManager.handlePlatformEvent(tid, PLDM_PLATFORM_EVENT_ID_NULL,
+                                         PLDM_CPER_EVENT, eventData,
+                                         eventDataSize);
         return PLDM_SUCCESS;
     }
 
diff --git a/platform-mc/terminus.hpp b/platform-mc/terminus.hpp
index dd766c6..464897f 100644
--- a/platform-mc/terminus.hpp
+++ b/platform-mc/terminus.hpp
@@ -122,8 +122,12 @@
     }
 
     /** @brief The getter to get terminus's mctp medium */
-    std::string_view getTerminusName()
+    std::optional<std::string_view> getTerminusName()
     {
+        if (terminusName.empty())
+        {
+            return std::nullopt;
+        }
         return terminusName;
     }
 
diff --git a/platform-mc/test/platform_manager_test.cpp b/platform-mc/test/platform_manager_test.cpp
index 0d66a0b..09f94e5 100644
--- a/platform-mc/test/platform_manager_test.cpp
+++ b/platform-mc/test/platform_manager_test.cpp
@@ -187,7 +187,7 @@
     EXPECT_EQ(true, terminus->initialized);
     EXPECT_EQ(2, terminus->pdrs.size());
     EXPECT_EQ(1, terminus->numericSensors.size());
-    EXPECT_EQ("S0", terminus->getTerminusName());
+    EXPECT_EQ("S0", terminus->getTerminusName().value());
 }
 
 TEST_F(PlatformManagerTest, parseTerminusNameTest)
@@ -345,7 +345,7 @@
     stdexec::sync_wait(platformManager.initTerminus());
     EXPECT_EQ(true, terminus->initialized);
     EXPECT_EQ(2, terminus->pdrs.size());
-    EXPECT_EQ("S0", terminus->getTerminusName());
+    EXPECT_EQ("S0", terminus->getTerminusName().value());
 }
 
 TEST_F(PlatformManagerTest, initTerminusDontSupportGetPDRTest)
diff --git a/platform-mc/test/terminus_test.cpp b/platform-mc/test/terminus_test.cpp
index 609deed..a8dcbc1 100644
--- a/platform-mc/test/terminus_test.cpp
+++ b/platform-mc/test/terminus_test.cpp
@@ -105,7 +105,7 @@
     EXPECT_EQ("en", names[0][0].first);
     EXPECT_EQ("TEMP1", names[0][0].second);
     EXPECT_EQ(2, t1.pdrs.size());
-    EXPECT_EQ("S0", t1.getTerminusName());
+    EXPECT_EQ("S0", t1.getTerminusName().value());
 }
 
 TEST(TerminusTest, parseSensorAuxiliaryMultiNamesPDRTest)
@@ -224,7 +224,7 @@
     EXPECT_EQ("fr", names[0][2].first);
     EXPECT_EQ("TEMP12", names[0][2].second);
     EXPECT_EQ(2, t1.pdrs.size());
-    EXPECT_EQ("S0", t1.getTerminusName());
+    EXPECT_EQ("S0", t1.getTerminusName().value());
 }
 
 TEST(TerminusTest, parseSensorAuxiliaryNamesMultiSensorsPDRTest)
@@ -345,7 +345,7 @@
     EXPECT_EQ("fr", names[1][1].first);
     EXPECT_EQ("TEMP12", names[1][1].second);
     EXPECT_EQ(2, t1.pdrs.size());
-    EXPECT_EQ("S0", t1.getTerminusName());
+    EXPECT_EQ("S0", t1.getTerminusName().value());
 }
 
 TEST(TerminusTest, parsePDRTestNoSensorPDR)
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index 5aae40d..fc2b08d 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -275,6 +275,13 @@
         std::make_unique<platform_mc::Manager>(event, reqHandler, instanceIdDb);
 
     pldm::responder::platform::EventMap addOnEventHandlers{
+        {PLDM_CPER_EVENT,
+         {[&platformManager](const pldm_msg* request, size_t payloadLength,
+                             uint8_t formatVersion, uint8_t tid,
+                             size_t eventDataOffset) {
+             return platformManager->handleCperEvent(
+                 request, payloadLength, formatVersion, tid, eventDataOffset);
+         }}},
         {PLDM_SENSOR_EVENT,
          {[&platformManager](const pldm_msg* request, size_t payloadLength,
                              uint8_t formatVersion, uint8_t tid,