PEL: Read SRC fields out of the registry

The SRC (System Reference code) is a section in the PEL and several of
its fields are sourced from the message registry.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I4ca7d18a8225f2667b762015c6fd74bfb59d70ff
diff --git a/extensions/openpower-pels/pel_types.hpp b/extensions/openpower-pels/pel_types.hpp
index caa75c2..b836942 100644
--- a/extensions/openpower-pels/pel_types.hpp
+++ b/extensions/openpower-pels/pel_types.hpp
@@ -35,5 +35,11 @@
     extUserData = 0x4544         // 'ED'
 };
 
+enum class SRCType
+{
+    bmcError = 0xBD,
+    powerError = 0x11
+};
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/registry.cpp b/extensions/openpower-pels/registry.cpp
index 42ced0c..f0efb3e 100644
--- a/extensions/openpower-pels/registry.cpp
+++ b/extensions/openpower-pels/registry.cpp
@@ -104,6 +104,149 @@
     return std::get<pv::fieldValuePos>(*s);
 }
 
+uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
+{
+    std::string rc = src["ReasonCode"];
+    uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
+    if (reasonCode == 0)
+    {
+        log<phosphor::logging::level::ERR>(
+            "Invalid reason code in message registry",
+            entry("ERROR_NAME=%s", name.c_str()),
+            entry("REASON_CODE=%s", rc.c_str()));
+
+        throw std::runtime_error("Invalid reason code in message registry");
+    }
+    return reasonCode;
+}
+
+uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
+{
+    // Looks like: "22"
+    std::string srcType = src["Type"];
+    size_t type = strtoul(srcType.c_str(), nullptr, 16);
+    if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
+    {
+        log<phosphor::logging::level::ERR>(
+            "Invalid SRC Type in message registry",
+            entry("ERROR_NAME=%s", name.c_str()),
+            entry("SRC_TYPE=%s", srcType.c_str()));
+
+        throw std::runtime_error("Invalid SRC Type in message registry");
+    }
+
+    return type;
+}
+
+std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
+    getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
+{
+    std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
+
+    // Build the map of which AdditionalData fields to use for which SRC words
+
+    // Like:
+    // {
+    //   "8":
+    //   {
+    //     "AdditionalDataPropSource": "TEST"
+    //   }
+    //
+    // }
+
+    for (const auto& word : src["Words6To9"].items())
+    {
+        std::string num = word.key();
+        size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
+
+        if (wordNum == 0)
+        {
+            log<phosphor::logging::level::ERR>(
+                "Invalid SRC word number in message registry",
+                entry("ERROR_NAME=%s", name.c_str()),
+                entry("SRC_WORD_NUM=%s", num.c_str()));
+
+            throw std::runtime_error("Invalid SRC word in message registry");
+        }
+
+        auto attributes = word.value();
+        std::string adPropName = attributes["AdditionalDataPropSource"];
+        hexwordFields[wordNum] = std::move(adPropName);
+    }
+
+    if (!hexwordFields.empty())
+    {
+        return hexwordFields;
+    }
+
+    return std::nullopt;
+}
+std::optional<std::vector<SRC::WordNum>>
+    getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
+{
+    std::vector<SRC::WordNum> symptomIDFields;
+
+    // Looks like:
+    // "SymptomIDFields": ["SRCWord3", "SRCWord6"],
+
+    for (const std::string& field : src["SymptomIDFields"])
+    {
+        // Just need the last digit off the end, e.g. SRCWord6.
+        // The schema enforces the format of these.
+        auto srcWordNum = field.substr(field.size() - 1);
+        size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
+        if (num == 0)
+        {
+            log<phosphor::logging::level::ERR>(
+                "Invalid symptom ID field in message registry",
+                entry("ERROR_NAME=%s", name.c_str()),
+                entry("FIELD_NAME=%s", srcWordNum.c_str()));
+
+            throw std::runtime_error("Invalid symptom ID in message registry");
+        }
+        symptomIDFields.push_back(num);
+    }
+    if (!symptomIDFields.empty())
+    {
+        return symptomIDFields;
+    }
+
+    return std::nullopt;
+}
+
+uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
+                        const nlohmann::json& pelEntry, const std::string& name)
+{
+    uint16_t id = 0;
+
+    // If the ComponentID field is there, use that.  Otherwise, if it's a
+    // 0xBD BMC error SRC, use the reasoncode.
+    if (pelEntry.find("ComponentID") != pelEntry.end())
+    {
+        std::string componentID = pelEntry["ComponentID"];
+        id = strtoul(componentID.c_str(), nullptr, 16);
+    }
+    else
+    {
+        // On BMC error SRCs (BD), can just get the component ID from
+        // the first byte of the reason code.
+        if (srcType == static_cast<uint8_t>(SRCType::bmcError))
+        {
+            id = reasonCode & 0xFF00;
+        }
+        else
+        {
+            log<level::ERR>("Missing component ID field in message registry",
+                            entry("ERROR_NAME=%s", name.c_str()));
+
+            throw std::runtime_error(
+                "Missing component ID field in message registry");
+        }
+    }
+
+    return id;
+}
+
 } // namespace helper
 
 std::optional<Entry> Registry::lookup(const std::string& name)
@@ -176,7 +319,38 @@
                 entry.eventScope = helper::getEventScope((*e)["EventScope"]);
             }
 
-            // TODO: SRC fields
+            auto& src = (*e)["SRC"];
+            entry.src.reasonCode = helper::getSRCReasonCode(src, name);
+
+            if (src.find("Type") != src.end())
+            {
+                entry.src.type = helper::getSRCType(src, name);
+            }
+            else
+            {
+                entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
+            }
+
+            // Now that we know the SRC type and reason code,
+            // we can get the component ID.
+            entry.componentID = helper::getComponentID(
+                entry.src.type, entry.src.reasonCode, *e, name);
+
+            if (src.find("Words6To9") != src.end())
+            {
+                entry.src.hexwordADFields =
+                    helper::getSRCHexwordFields(src, name);
+            }
+
+            if (src.find("SymptomIDFields") != src.end())
+            {
+                entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
+            }
+
+            if (src.find("PowerFault") != src.end())
+            {
+                entry.src.powerFault = src["PowerFault"];
+            }
 
             return entry;
         }
diff --git a/extensions/openpower-pels/registry.hpp b/extensions/openpower-pels/registry.hpp
index e56bd42..c55c624 100644
--- a/extensions/openpower-pels/registry.hpp
+++ b/extensions/openpower-pels/registry.hpp
@@ -15,6 +15,55 @@
 constexpr auto registryFileName = "message_registry.json";
 
 /**
+ * @brief Represents the SRC related fields in the message registry.
+ *        It is part of the 'Entry' structure that will be filled in when
+ *        an error is looked up in the registry.
+ *
+ * If a field is wrapped by std::optional, it means the field is
+ * optional in the JSON and higher level code knows how to handle it.
+ */
+struct SRC
+{
+    /**
+     * @brief SRC type - The first byte of the ASCII string
+     */
+    uint8_t type;
+
+    /**
+     * @brief The SRC reason code (2nd half of 4B 'ASCII string' word)
+     */
+    uint16_t reasonCode;
+
+    /**
+     * @brief Specifies if the SRC represents a power fault.  Optional.
+     */
+    std::optional<bool> powerFault;
+
+    /**
+     * @brief An optional vector of SRC hexword numbers that should be used
+     *        along with the SRC ASCII string to build the Symptom ID, which
+     *        is a field in the Extended Header section.
+     */
+    using WordNum = size_t;
+    std::optional<std::vector<WordNum>> symptomID;
+
+    /**
+     * @brief Which AdditionalData fields to use to fill in the user defined
+     *        SRC hexwords.
+     *
+     * For example, if the AdditionalData event log property contained
+     * "CHIPNUM=42" and this map contained {6, CHIPNUM}, then the code
+     * would put 42 into SRC hexword 6.
+     */
+    using AdditionalDataField = std::string;
+    std::optional<std::map<WordNum, AdditionalDataField>> hexwordADFields;
+
+    SRC() : type(0), reasonCode(0)
+    {
+    }
+};
+
+/**
  * @brief Represents a message registry entry, which is used for creating a
  *        PEL from an OpenBMC event log.
  */
@@ -26,6 +75,11 @@
     std::string name;
 
     /**
+     * @brief The component ID of the PEL creator.
+     */
+    uint16_t componentID;
+
+    /**
      * @brief The PEL subsystem field.
      */
     uint8_t subsystem;
@@ -65,7 +119,10 @@
      */
     std::optional<uint8_t> eventScope;
 
-    // TODO: SRC related fields
+    /**
+     * The SRC related fields.
+     */
+    SRC src;
 };
 
 /**
@@ -171,6 +228,72 @@
  */
 uint8_t getEventScope(const std::string& eventScopeName);
 
+/**
+ * @brief Reads the "ReasonCode" field out of JSON and converts the string value
+ *        such as "0x5555" to a uint16 like 0x5555.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return uint16_t - The reason code
+ */
+uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "Type" field out of JSON and converts it to the SRC::Type
+ *        value.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return uint8_t - The SRC type value, like 0x11
+ */
+uint8_t getSRCType(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "Words6To9" field out of JSON and converts it to a map
+ *        of the SRC word number to the AdditionalData property field used
+ *        to fill it in with.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
+ */
+std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
+    getSRCHexwordFields(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "SymptomIDFields" field out of JSON and converts it to
+ *        a vector of SRC word numbers.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return std::optional<std::vector<SRC::WordNum>>
+ */
+std::optional<std::vector<SRC::WordNum>>
+    getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "ComponentID" field out of JSON and converts it to a
+ *        uint16_t like 0xFF00.
+ *
+ * The ComponentID JSON field is only required if the SRC type isn't a BD
+ * BMC SRC, because for those SRCs it can be inferred from the upper byte
+ * of the SRC reasoncode.
+ *
+ * @param[in] srcType - The SRC type
+ * @param[in] reasonCode - The SRC reason code
+ * @param[in] pelEntry - The PEL entry JSON
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return uin16_t - The component ID, like 0xFF00
+ */
+uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
+                        const nlohmann::json& pelEntry,
+                        const std::string& name);
+
 } // namespace helper
 
 } // namespace message