PEL: Look for callouts in an FFDC JSON file

The PEL constructor takes a list of files that should be added as
UserData sections for FFDC.  If one of those files has its format set to
JSON, and its subtype set to 0xCA, this will mean it contains a JSON
array of callouts to add to the PEL.

This commit will look for that type and subtype, and then pass the
callout JSON through to the SRC class.  A future commit will add the
callouts from the JSON to the PEL.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iec0ca0ad0ae11cc957b8fda880d97116f342d72d
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 85ab741..2391e52 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -42,6 +42,7 @@
 using namespace phosphor::logging;
 
 constexpr auto unknownValue = "Unknown";
+constexpr uint8_t jsonCalloutSubtype = 0xCA;
 
 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
          phosphor::logging::Entry::Level severity,
@@ -49,12 +50,29 @@
          const DataInterfaceBase& dataIface)
 {
     std::map<std::string, std::vector<std::string>> debugData;
+    nlohmann::json callouts;
 
     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
                                           timestamp);
     _uh = std::make_unique<UserHeader>(regEntry, severity, dataIface);
 
-    auto src = std::make_unique<SRC>(regEntry, additionalData, dataIface);
+    // Extract any callouts embedded in an FFDC file.
+    if (!ffdcFiles.empty())
+    {
+        try
+        {
+            callouts = getCalloutJSON(ffdcFiles);
+        }
+        catch (const std::exception& e)
+        {
+            debugData.emplace("FFDC file JSON callouts error",
+                              std::vector<std::string>{e.what()});
+        }
+    }
+
+    auto src =
+        std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface);
+
     if (!src->getDebugData().empty())
     {
         // Something didn't go as planned
@@ -413,6 +431,32 @@
     return true;
 }
 
+nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles)
+{
+    nlohmann::json callouts;
+
+    for (const auto& file : ffdcFiles)
+    {
+        if ((file.format == UserDataFormat::json) &&
+            (file.subType == jsonCalloutSubtype))
+        {
+            auto data = util::readFD(file.fd);
+            if (data.empty())
+            {
+                throw std::runtime_error{
+                    "Could not get data from JSON callout file descriptor"};
+            }
+
+            std::string jsonString{data.begin(), data.begin() + data.size()};
+
+            callouts = nlohmann::json::parse(jsonString);
+            break;
+        }
+    }
+
+    return callouts;
+}
+
 namespace util
 {
 
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index 0e8ee1a..2cfa8f3 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -345,6 +345,18 @@
                             uint8_t creatorID = 0) const;
 
     /**
+     * @brief Returns any callout JSON found in the FFDC files.
+     *
+     * Looks for an FFDC file that is JSON format and has the
+     * sub-type value set to 0xCA and returns its data as a JSON object.
+     *
+     * @param[in] ffdcFiles - FFCD files that go into UserData sections
+     *
+     * @return json - The callout JSON, or an empty object if not found
+     */
+    nlohmann::json getCalloutJSON(const PelFFDC& ffdcFiles);
+
+    /**
      * @brief The PEL Private Header section
      */
     std::unique_ptr<PrivateHeader> _ph;
@@ -401,6 +413,15 @@
                                const DataInterfaceBase& dataIface);
 
 /**
+ * @brief Reads data from an opened file descriptor.
+ *
+ * @param[in] fd - The FD to read from
+ *
+ * @return std::vector<uint8_t> - The data read
+ */
+std::vector<uint8_t> readFD(int fd);
+
+/**
  * @brief Create a UserData section that contains the data in the file
  *        pointed to by the file descriptor passed in.
  *
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index 7252b46..c5d8ed8 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -26,6 +26,8 @@
 #include <nlohmann/json.hpp>
 #include <sstream>
 #endif
+#include <fmt/format.h>
+
 #include <phosphor-logging/log.hpp>
 
 namespace openpower
@@ -279,7 +281,7 @@
 }
 
 SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
-         const DataInterfaceBase& dataIface)
+         const nlohmann::json& jsonCallouts, const DataInterfaceBase& dataIface)
 {
     _header.id = static_cast<uint16_t>(SectionID::primarySRC);
     _header.version = srcSectionVersion;
@@ -323,7 +325,7 @@
 
     _asciiString = std::make_unique<src::AsciiString>(regEntry);
 
-    addCallouts(regEntry, additionalData, dataIface);
+    addCallouts(regEntry, additionalData, jsonCallouts, dataIface);
 
     _size = baseSRCSize;
     _size += _callouts ? _callouts->flattenedSize() : 0;
@@ -727,6 +729,7 @@
 
 void SRC::addCallouts(const message::Entry& regEntry,
                       const AdditionalData& additionalData,
+                      const nlohmann::json& jsonCallouts,
                       const DataInterfaceBase& dataIface)
 {
     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
@@ -741,6 +744,11 @@
     {
         addRegistryCallouts(regEntry, additionalData, dataIface);
     }
+
+    if (!jsonCallouts.empty())
+    {
+        addJSONCallouts(jsonCallouts, dataIface);
+    }
 }
 
 void SRC::addInventoryCallout(const std::string& inventoryPath,
@@ -1027,5 +1035,39 @@
     }
 }
 
+void SRC::addJSONCallouts(const nlohmann::json& jsonCallouts,
+                          const DataInterfaceBase& dataIface)
+{
+    if (jsonCallouts.empty())
+    {
+        return;
+    }
+
+    if (!jsonCallouts.is_array())
+    {
+        addDebugData("Callout JSON isn't an array");
+        return;
+    }
+
+    for (const auto& callout : jsonCallouts)
+    {
+        try
+        {
+            addJSONCallout(callout, dataIface);
+        }
+        catch (const std::exception& e)
+        {
+            addDebugData(fmt::format(
+                "Failed extracting callout data from JSON: {}", e.what()));
+        }
+    }
+}
+
+void SRC::addJSONCallout(const nlohmann::json& jsonCallout,
+                         const DataInterfaceBase& dataIface)
+{
+    // TODO
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
index 44540b0..063701c 100644
--- a/extensions/openpower-pels/src.hpp
+++ b/extensions/openpower-pels/src.hpp
@@ -86,7 +86,26 @@
      * @param[in] dataIface - The DataInterface object
      */
     SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
-        const DataInterfaceBase& dataIface);
+        const DataInterfaceBase& dataIface) :
+        SRC(regEntry, additionalData, nlohmann::json{}, dataIface)
+    {
+    }
+
+    /**
+     * @brief Constructor
+     *
+     * Creates the section with data from the PEL message registry entry for
+     * this error, along with the AdditionalData property contents from the
+     * corresponding event log, and a JSON array of callouts to add.
+     *
+     * @param[in] regEntry - The message registry entry for this event log
+     * @param[in] additionalData - The AdditionalData properties in this event
+     *                             log
+     * @param[in] jsonCallouts - The array of JSON callouts, or an empty object.
+     * @param[in] dataIface - The DataInterface object
+     */
+    SRC(const message::Entry& regEntry, const AdditionalData& additionalData,
+        const nlohmann::json& jsonCallouts, const DataInterfaceBase& dataIface);
 
     /**
      * @brief Flatten the section into the stream
@@ -350,10 +369,12 @@
      *
      * @param[in] regEntry - The message registry entry for the error
      * @param[in] additionalData - The AdditionalData values
+     * @param[in] jsonCallouts - The array of JSON callouts, or an empty object
      * @param[in] dataIface - The DataInterface object
      */
     void addCallouts(const message::Entry& regEntry,
                      const AdditionalData& additionalData,
+                     const nlohmann::json& jsonCallouts,
                      const DataInterfaceBase& dataIface);
 
     /**
@@ -413,6 +434,23 @@
                                const DataInterfaceBase& dataIface);
 
     /**
+     * @brief Adds any FRU callouts specified in the incoming JSON.
+     *
+     * @param[in] jsonCallouts - The JSON array of callouts
+     * @param[in] dataIface - The DataInterface object
+     */
+    void addJSONCallouts(const nlohmann::json& jsonCallouts,
+                         const DataInterfaceBase& dataIface);
+
+    /**
+     * @brief Adds a single callout based on the JSON
+     *
+     * @param[in] jsonCallouts - A single callout entry
+     * @param[in] dataIface - The DataInterface object
+     */
+    void addJSONCallout(const nlohmann::json& jsonCallout,
+                        const DataInterfaceBase& dataIface);
+    /**
      * @brief The SRC version field
      */
     uint8_t _version;