PEL: Save AdditionalData in a UserData section

Save the contents of the AdditionalData OpenBMC event log property as
JSON in a UserData PEL section.

For example, if the AdditionalData property, which is an array of
strings, looks like:
    ["KEY1=VALUE1", "KEY=VALUE2"]

Then the data stored as ASCII text in the UserData section is:
    {"KEY1":"VALUE1","KEY2":"VALUE2"}

If one of the keys is "ESEL", then that entry is removed from the
UserData output as that contains a full raw PEL from the host sent down
as ASCII text.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ia6ffabb39fdb4315ec2152744414e44f7d2ec4aa
diff --git a/extensions/openpower-pels/additional_data.hpp b/extensions/openpower-pels/additional_data.hpp
index b855054..f133e0f 100644
--- a/extensions/openpower-pels/additional_data.hpp
+++ b/extensions/openpower-pels/additional_data.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include <map>
+#include <nlohmann/json.hpp>
 #include <optional>
 #include <string>
 #include <vector>
@@ -67,6 +68,39 @@
         return std::nullopt;
     }
 
+    /**
+     * @brief Remove a key/value pair from the contained data
+     *
+     * @param[in] key - The key of the entry to remove
+     */
+    void remove(const std::string& key)
+    {
+        _data.erase(key);
+    }
+
+    /**
+     * @brief Says if the object has no data
+     *
+     * @return bool true if the object is empty
+     */
+    inline bool empty() const
+    {
+        return _data.empty();
+    }
+
+    /**
+     * @brief Returns the contained data as a JSON object
+     *
+     * Looks like: {"key1":"value1","key2":"value2"}
+     *
+     * @return json - The JSON object
+     */
+    nlohmann::json toJSON() const
+    {
+        nlohmann::json j = _data;
+        return j;
+    }
+
   private:
     /**
      * @brief a map of keys to values
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index ffd4974..af904ff 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -6,6 +6,7 @@
 #include "section_factory.hpp"
 #include "src.hpp"
 #include "stream.hpp"
+#include "user_data_formats.hpp"
 
 #include <phosphor-logging/log.hpp>
 
@@ -30,6 +31,12 @@
     auto mtms = std::make_unique<FailingMTMS>(dataIface);
     _optionalSections.push_back(std::move(mtms));
 
+    if (!additionalData.empty())
+    {
+        auto ud = util::makeADUserDataSection(additionalData);
+        _optionalSections.push_back(std::move(ud));
+    }
+
     _ph->sectionCount() = 2 + _optionalSections.size();
 }
 
@@ -134,5 +141,35 @@
     return std::nullopt;
 }
 
+namespace util
+{
+
+std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
+{
+    assert(!ad.empty());
+    nlohmann::json json;
+
+    // Remove the 'ESEL' entry, as it contains a full PEL in the value.
+    if (ad.getValue("ESEL"))
+    {
+        auto newAD = ad;
+        newAD.remove("ESEL");
+        json = newAD.toJSON();
+    }
+    else
+    {
+        json = ad.toJSON();
+    }
+
+    auto jsonString = json.dump();
+    std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
+
+    return std::make_unique<UserData>(
+        static_cast<uint16_t>(ComponentID::phosphorLogging),
+        static_cast<uint8_t>(UserDataFormat::json),
+        static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
+}
+
+} // namespace util
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index bc857e2..f63cfdd 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -5,6 +5,7 @@
 #include "private_header.hpp"
 #include "registry.hpp"
 #include "src.hpp"
+#include "user_data.hpp"
 #include "user_header.hpp"
 
 #include <memory>
@@ -249,5 +250,20 @@
     std::vector<std::unique_ptr<Section>> _optionalSections;
 };
 
+namespace util
+{
+
+/**
+ * @brief Create a UserData section containing the AdditionalData
+ *        contents as a JSON string.
+ *
+ * @param[in] ad - The AdditionalData contents
+ *
+ * @return std::unique_ptr<UserData> - The section
+ */
+std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad);
+
+} // namespace util
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/user_data_formats.hpp b/extensions/openpower-pels/user_data_formats.hpp
new file mode 100644
index 0000000..53cb5ee
--- /dev/null
+++ b/extensions/openpower-pels/user_data_formats.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace openpower
+{
+namespace pels
+{
+
+enum class UserDataFormat
+{
+    json = 1
+};
+
+enum class UserDataFormatVersion
+{
+    json = 1
+};
+
+} // namespace pels
+} // namespace openpower