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