sel: Support OEM record 0xCD~0xDF

The SEL OEM record is retrieved from logging/entry, and converted to SEL
format.

Tested: When the BMC has a logging entry like below:

    {
      "data": {
        "AdditionalData": [
          "EVENT_DIR=0",
          "GENERATOR_ID=0",
          "RECORD_TYPE=205",
          "SENSOR_DATA=1112141500DDB3BACD000000",
          "SENSOR_PATH=",
          "_PID=200"
        ],
        "Associations": [],
        "Id": 1,
        "Message": "xyz.openbmc_project.Logging.SEL.Error.Created",
        "Purpose": "xyz.openbmc_project.Software.Version.VersionPurpose.BMC",
        "Resolved": false,
        "Severity": "xyz.openbmc_project.Logging.Entry.Level.Informational",
        "Timestamp": 320246,
        "UpdateTimestamp": 320246,
        "Version": "2.9.0-dev-1308-g01b7feb91-dirty"
      },
      "message": "200 OK",
      "status": "ok"
    }

Verify the SEL entry is expected:

    SEL Record ID          : 0002
     Record Type           : cd  (OEM timestamped)
     Timestamp             : 01/01/1970 01:22:47
     Manufactacturer ID    : b3dd00
     OEM Defined           : bacd00010000 [......]

Signed-off-by: Lei YU <yulei.sh@bytedance.com>
Change-Id: Ia9ee86d8cb98ddd8d64f82170e1b57a45af7bed1
diff --git a/selutility.cpp b/selutility.cpp
index 83a8bd2..528cffd 100644
--- a/selutility.cpp
+++ b/selutility.cpp
@@ -2,6 +2,7 @@
 
 #include "selutility.hpp"
 
+#include <charconv>
 #include <chrono>
 #include <filesystem>
 #include <ipmid/api.hpp>
@@ -16,6 +17,13 @@
 using InternalFailure =
     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 
+namespace
+{
+
+constexpr auto systemEventRecord = 0x02;
+
+} // namespace
+
 namespace ipmi
 {
 
@@ -25,6 +33,54 @@
 namespace internal
 {
 
+inline bool isRecordOEM(uint8_t recordType)
+{
+    return recordType != systemEventRecord;
+}
+
+/** Parse the entry with format like key=val */
+std::pair<std::string, std::string> parseEntry(const std::string& entry)
+{
+    constexpr auto equalSign = "=";
+    auto pos = entry.find(equalSign);
+    assert(pos != std::string::npos);
+    auto key = entry.substr(0, pos);
+    auto val = entry.substr(pos + 1);
+    return {key, val};
+}
+
+std::map<std::string, std::string>
+    parseAdditionalData(const AdditionalData& data)
+{
+    std::map<std::string, std::string> ret;
+
+    for (const auto& d : data)
+    {
+        ret.insert(parseEntry(d));
+    }
+    return ret;
+}
+
+uint8_t convert(const std::string_view& str, int base = 10)
+{
+    int ret;
+    std::from_chars(str.data(), str.data() + str.size(), ret, base);
+    return static_cast<uint8_t>(ret);
+}
+
+// Convert the string to a vector of uint8_t, where the str is formatted as hex
+std::vector<uint8_t> convertVec(const std::string_view& str)
+{
+    std::vector<uint8_t> ret;
+    auto len = str.size() / 2;
+    ret.reserve(len);
+    for (size_t i = 0; i < len; ++i)
+    {
+        ret.emplace_back(convert(str.substr(i * 2, 2), 16));
+    }
+    return ret;
+}
+
 GetSELEntryResponse
     prepareSELEntry(const std::string& objPath,
                     ipmi::sensor::InvObjectIDMap::const_iterator iter)
@@ -72,7 +128,49 @@
     if (iter == invSensors.end())
     {
         // It is expected to be a custom SEL entry
-        // TODO
+        record.event.oemCD.recordID =
+            static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
+        static constexpr auto propAdditionalData = "AdditionalData";
+        // static constexpr auto strEventDir = "EVENT_DIR";
+        // static constexpr auto strGenerateId = "GENERATOR_ID";
+        static constexpr auto strRecordType = "RECORD_TYPE";
+        static constexpr auto strSensorData = "SENSOR_DATA";
+        // static constexpr auto strSensorPath = "SENSOR_PATH";
+        iterId = entryData.find(propAdditionalData);
+        if (iterId == entryData.end())
+        {
+            log<level::ERR>("Error finding AdditionalData");
+            elog<InternalFailure>();
+        }
+        const auto& addData = std::get<AdditionalData>(iterId->second);
+        auto m = parseAdditionalData(addData);
+        auto recordType = convert(m[strRecordType]);
+        auto isOEM = isRecordOEM(recordType);
+        if (isOEM)
+        {
+            if (recordType >= 0xC0 && recordType < 0xE0)
+            {
+                record.event.oemCD.timeStamp = static_cast<uint32_t>(
+                    std::chrono::duration_cast<std::chrono::seconds>(
+                        chronoTimeStamp)
+                        .count());
+                record.event.oemCD.recordType = recordType;
+                // The ManufactureID and OEM Defined are packed in the sensor
+                // data
+                auto sensorData = convertVec(m[strSensorData]);
+                // Fill the 9 bytes of Manufacture ID and oemDefined
+                memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
+                       std::min(sensorData.size(), static_cast<size_t>(9)));
+            }
+            else if (recordType >= 0xE0)
+            {
+                // TODO
+            }
+        }
+        else
+        {
+            // TODO
+        }
     }
     else
     {
@@ -83,7 +181,6 @@
             std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
                 .count());
 
-        static constexpr auto systemEventRecord = 0x02;
         static constexpr auto generatorID = 0x2000;
         static constexpr auto eventMsgRevision = 0x04;