PEL: ExtendedUserData section class

This class represents the Extended User Data section.  This section is
similar to the UserData class in that it stores free form FFDC data,
except that it is meant to be added to an already existing PEL by a
creator subsystem that is different than the PEL creator's subsystem.
As such, it stores the section creator's creator ID value as a field
within the section, which the regular UserData class doesn't do.

The same parsers that run on the UserData section can also run on this
section.

Change-Id: I8562a89141f0305e397703f0cb92a06eb9f10133
diff --git a/extensions/openpower-pels/extended_user_data.cpp b/extensions/openpower-pels/extended_user_data.cpp
new file mode 100644
index 0000000..0731b60
--- /dev/null
+++ b/extensions/openpower-pels/extended_user_data.cpp
@@ -0,0 +1,131 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extended_user_data.hpp"
+
+#include "pel_types.hpp"
+#ifdef PELTOOL
+#include "user_data_json.hpp"
+#endif
+#include <fmt/format.h>
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower::pels
+{
+
+using namespace phosphor::logging;
+
+void ExtendedUserData::unflatten(Stream& stream)
+{
+    stream >> _header;
+
+    if (_header.size <= SectionHeader::flattenedSize() + 4)
+    {
+        throw std::out_of_range(
+            fmt::format("ExtendedUserData::unflatten: SectionHeader::size {} "
+                        "too small",
+                        _header.size));
+    }
+
+    size_t dataLength = _header.size - 4 - SectionHeader::flattenedSize();
+    _data.resize(dataLength);
+
+    stream >> _creatorID >> _reserved1B >> _reserved2B >> _data;
+}
+
+void ExtendedUserData::flatten(Stream& stream) const
+{
+    stream << _header << _creatorID << _reserved1B << _reserved2B << _data;
+}
+
+ExtendedUserData::ExtendedUserData(Stream& pel)
+{
+    try
+    {
+        unflatten(pel);
+        validate();
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Cannot unflatten ExtendedUserData: {}", e.what())
+                .c_str());
+        _valid = false;
+    }
+}
+
+ExtendedUserData::ExtendedUserData(uint16_t componentID, uint8_t subType,
+                                   uint8_t version, uint8_t creatorID,
+                                   const std::vector<uint8_t>& data)
+{
+    _header.id = static_cast<uint16_t>(SectionID::extUserData);
+    _header.version = version;
+    _header.subType = subType;
+    _header.componentID = componentID;
+
+    _creatorID = creatorID;
+    _reserved1B = 0;
+    _reserved2B = 0;
+    _data = data;
+    _header.size = flattenedSize();
+    _valid = true;
+}
+
+void ExtendedUserData::validate()
+{
+    if (header().id != static_cast<uint16_t>(SectionID::extUserData))
+    {
+        log<level::ERR>(
+            fmt::format("Invalid ExtendedUserData section ID {}", header().id)
+                .c_str());
+        _valid = false;
+    }
+    else
+    {
+        _valid = true;
+    }
+}
+
+std::optional<std::string>
+    ExtendedUserData::getJSON(uint8_t creatorID,
+                              const std::vector<std::string>& plugins) const
+{
+    // Use the creator ID value from the section.
+#ifdef PELTOOL
+    return user_data::getJSON(_header.componentID, _header.subType,
+                              _header.version, _data, _creatorID, plugins);
+#endif
+    return std::nullopt;
+}
+
+bool ExtendedUserData::shrink(size_t newSize)
+{
+    // minimum size is 8B header + 4B of fields + 4B of data
+    if ((newSize < flattenedSize()) &&
+        (newSize >= (Section::flattenedSize() + 8)))
+    {
+        auto dataSize = newSize - Section::flattenedSize() - 4;
+
+        // Ensure it's 4B aligned
+        _data.resize((dataSize / 4) * 4);
+        _header.size = flattenedSize();
+        return true;
+    }
+
+    return false;
+}
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/extended_user_data.hpp b/extensions/openpower-pels/extended_user_data.hpp
new file mode 100644
index 0000000..b0c0e88
--- /dev/null
+++ b/extensions/openpower-pels/extended_user_data.hpp
@@ -0,0 +1,160 @@
+#pragma once
+
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower::pels
+{
+
+/**
+ * @class ExtendedUserData
+ *
+ * This represents the Extended User Data section in a PEL.  It is free form
+ * data that the creator knows the contents of.  The component ID, version, and
+ * sub-type fields in the section header are used to identify the format.
+ *
+ *  This section is used for one subsystem to add FFDC data to a PEL created
+ *  by another subsystem.  It is basically the same as a UserData section,
+ *  except it has the creator ID of the section creator stored in the section.
+ *
+ * The Section base class handles the section header structure that every
+ * PEL section has at offset zero.
+ */
+class ExtendedUserData : public Section
+{
+  public:
+    ExtendedUserData() = delete;
+    ~ExtendedUserData() = default;
+    ExtendedUserData(const ExtendedUserData&) = default;
+    ExtendedUserData& operator=(const ExtendedUserData&) = default;
+    ExtendedUserData(ExtendedUserData&&) = default;
+    ExtendedUserData& operator=(ExtendedUserData&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * Fills in this class's data fields from the stream.
+     *
+     * @param[in] pel - the PEL data stream
+     */
+    explicit ExtendedUserData(Stream& pel);
+
+    /**
+     * @brief Constructor
+     *
+     * Create a valid ExtendedUserData object with the passed in data.
+     *
+     * The component ID, subtype, and version are used to identify
+     * the data to know which parser to call.
+     *
+     * @param[in] componentID - Component ID of the creator
+     * @param[in] subType - The type of user data
+     * @param[in] version - The version of the data
+     */
+    ExtendedUserData(uint16_t componentID, uint8_t subType, uint8_t version,
+                     uint8_t creatorID, const std::vector<uint8_t>& data);
+
+    /**
+     * @brief Flatten the section into the stream
+     *
+     * @param[in] stream - The stream to write to
+     */
+    void flatten(Stream& stream) const override;
+
+    /**
+     * @brief Returns the size of this section when flattened into a PEL
+     *
+     * @return size_t - the size of the section
+     */
+    size_t flattenedSize()
+    {
+        return Section::flattenedSize() + sizeof(_creatorID) +
+               sizeof(_reserved1B) + sizeof(_reserved2B) + _data.size();
+    }
+
+    /**
+     * @brief Returns the section creator ID field.
+     *
+     * @return uint8_t - The creator ID
+     */
+    const uint8_t creatorID() const
+    {
+        return _creatorID;
+    }
+
+    /**
+     * @brief Returns the raw section data
+     *
+     * This doesn't include the creator ID.
+     *
+     * @return std::vector<uint8_t>&
+     */
+    const std::vector<uint8_t>& data() const
+    {
+        return _data;
+    }
+
+    /**
+     * @brief Get the section contents in JSON
+     *
+     * @param[in] creatorID - Creator Subsystem ID - unused (see the .cpp)
+     * @param[in] plugins - Vector of strings of plugins found in filesystem
+     *
+     * @return The JSON as a string if a parser was found,
+     *         otherwise std::nullopt.
+     */
+    std::optional<std::string>
+        getJSON(uint8_t creatorID,
+                const std::vector<std::string>& plugins) const override;
+
+    /**
+     * @brief Shrink the section
+     *
+     * The new size must be between the current size and the minimum
+     * size, which is 12 bytes.  If it isn't a 4 byte aligned value
+     * the code will do the aligning before the resize takes place.
+     *
+     * @param[in] newSize - The new size in bytes
+     *
+     * @return bool - true if successful, false else.
+     */
+    bool shrink(size_t newSize) override;
+
+  private:
+    /**
+     * @brief Fills in the object from the stream data
+     *
+     * @param[in] stream - The stream to read from
+     */
+    void unflatten(Stream& stream);
+
+    /**
+     * @brief Validates the section contents
+     *
+     * Updates _valid (in Section) with the results.
+     */
+    void validate() override;
+
+    /**
+     * @brief The subsystem creator ID of the code that
+     *        created this section.
+     */
+    uint8_t _creatorID;
+
+    /**
+     * @brief Reserved
+     */
+    uint8_t _reserved1B;
+
+    /**
+     * @brief Reserved
+     */
+    uint16_t _reserved2B;
+
+    /**
+     * @brief The section data
+     */
+    std::vector<uint8_t> _data;
+};
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 9c6ec61..39b8b0b 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -1,5 +1,6 @@
 phosphor_log_manager_SOURCES += \
 	extensions/openpower-pels/entry_points.cpp \
+	extensions/openpower-pels/extended_user_data.cpp \
 	extensions/openpower-pels/host_notifier.cpp \
 	extensions/openpower-pels/manager.cpp \
 	extensions/openpower-pels/pldm_interface.cpp \
@@ -71,6 +72,7 @@
 bin_PROGRAMS += peltool
 
 peltool_SOURCES = \
+	extensions/openpower-pels/extended_user_data.cpp \
 	extensions/openpower-pels/tools/peltool.cpp \
 	extensions/openpower-pels/src.cpp \
 	extensions/openpower-pels/user_data.cpp \
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 2391e52..2113c0f 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -16,6 +16,7 @@
 #include "pel.hpp"
 
 #include "bcd_time.hpp"
+#include "extended_user_data.hpp"
 #include "extended_user_header.hpp"
 #include "failing_mtms.hpp"
 #include "json_utils.hpp"
@@ -311,7 +312,7 @@
         {
             json = section.getJSON(registry, plugins, creatorID);
         }
-        else if (sectionID == "UD")
+        else if ((sectionID == "UD") || (sectionID == "ED"))
         {
             json = section.getJSON(creatorID, plugins);
         }
diff --git a/extensions/openpower-pels/section_factory.cpp b/extensions/openpower-pels/section_factory.cpp
index 43a7d70..6a89e1d 100644
--- a/extensions/openpower-pels/section_factory.cpp
+++ b/extensions/openpower-pels/section_factory.cpp
@@ -15,6 +15,7 @@
  */
 #include "section_factory.hpp"
 
+#include "extended_user_data.hpp"
 #include "extended_user_header.hpp"
 #include "failing_mtms.hpp"
 #include "generic.hpp"
@@ -66,6 +67,9 @@
         case static_cast<uint16_t>(SectionID::extendedUserHeader):
             section = std::make_unique<ExtendedUserHeader>(pelData);
             break;
+        case static_cast<uint16_t>(SectionID::extUserData):
+            section = std::make_unique<ExtendedUserData>(pelData);
+            break;
         default:
             // A generic object, but at least an object.
             section = std::make_unique<Generic>(pelData);
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index b4c6a01..a098ad9 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -6,6 +6,7 @@
 	bcd_time_test \
 	device_callouts_test \
 	event_logger_test \
+	extended_user_data_test \
 	extended_user_header_test \
 	failing_mtms_test \
 	fru_identity_test \
@@ -39,6 +40,7 @@
 	$(top_builddir)/extensions/openpower-pels/callout.o \
 	$(top_builddir)/extensions/openpower-pels/callouts.o \
 	$(top_builddir)/extensions/openpower-pels/device_callouts.o \
+	$(top_builddir)/extensions/openpower-pels/extended_user_data.o \
 	$(top_builddir)/extensions/openpower-pels/extended_user_header.o \
 	$(top_builddir)/extensions/openpower-pels/failing_mtms.o \
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
@@ -383,3 +385,14 @@
 	$(pel_test_utils_ldadd) \
 	$(top_builddir)/extensions/openpower-pels/service_indicators.o
 service_indicators_test_LDFLAGS = $(test_ldflags)
+
+extended_user_data_test_SOURCES = \
+	%reldir%/extended_user_data_test.cpp
+extended_user_data_test_CPPFLAGS = $(test_cppflags)
+extended_user_data_test_CXXFLAGS = $(test_cxxflags)
+extended_user_data_test_LDADD = \
+	$(test_ldadd) \
+	$(pel_test_utils_ldadd) \
+	$(FMT_LIBS) \
+	$(top_builddir)/extensions/openpower-pels/extended_user_data.o
+extended_user_data_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/extended_user_data_test.cpp b/test/openpower-pels/extended_user_data_test.cpp
new file mode 100644
index 0000000..902cee6
--- /dev/null
+++ b/test/openpower-pels/extended_user_data_test.cpp
@@ -0,0 +1,139 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/extended_user_data.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(ExtUserDataTest, UnflattenFlattenTest)
+{
+    auto eudSectionData = pelDataFactory(TestPELType::extendedUserDataSection);
+    Stream stream(eudSectionData);
+    ExtendedUserData eud(stream);
+
+    EXPECT_TRUE(eud.valid());
+    EXPECT_EQ(eud.header().id, 0x4544);
+    EXPECT_EQ(eud.header().size, eudSectionData.size());
+    EXPECT_EQ(eud.header().version, 0x01);
+    EXPECT_EQ(eud.header().subType, 0x02);
+    EXPECT_EQ(eud.header().componentID, 0x2000);
+    EXPECT_EQ(eud.creatorID(), 'O');
+
+    const auto& data = eud.data();
+
+    // The eudSectionData itself starts 4B after the 8B header
+    EXPECT_EQ(data.size(), eudSectionData.size() - 12);
+
+    for (size_t i = 0; i < data.size(); i++)
+    {
+        EXPECT_EQ(data[i], eudSectionData[i + 12]);
+    }
+
+    // Now flatten
+    std::vector<uint8_t> newData;
+    Stream newStream(newData);
+    eud.flatten(newStream);
+
+    EXPECT_EQ(eudSectionData, newData);
+}
+
+TEST(ExtUserDataTest, BadDataTest)
+{
+    auto data = pelDataFactory(TestPELType::extendedUserDataSection);
+    data.resize(8); // Too small
+
+    Stream stream(data);
+    ExtendedUserData eud(stream);
+    EXPECT_FALSE(eud.valid());
+}
+
+TEST(ExtUserDataTest, BadSizeFieldTest)
+{
+    auto data = pelDataFactory(TestPELType::extendedUserDataSection);
+
+    {
+        data[3] = 0xFF; // Set the size field too large
+        Stream stream(data);
+        ExtendedUserData eud(stream);
+        EXPECT_FALSE(eud.valid());
+    }
+    {
+        data[3] = 0x7; // Set the size field too small
+        Stream stream(data);
+        ExtendedUserData eud(stream);
+        EXPECT_FALSE(eud.valid());
+    }
+}
+
+TEST(ExtUserDataTest, ConstructorTest)
+{
+    std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
+
+    ExtendedUserData eud{0x1112, 0x42, 0x01, 'B', data};
+
+    EXPECT_TRUE(eud.valid());
+    EXPECT_EQ(eud.header().id, 0x4544);
+    EXPECT_EQ(eud.header().size, 20);
+    EXPECT_EQ(eud.header().subType, 0x42);
+    EXPECT_EQ(eud.header().version, 0x01);
+    EXPECT_EQ(eud.header().componentID, 0x1112);
+    EXPECT_EQ(eud.flattenedSize(), 20);
+    EXPECT_EQ(eud.creatorID(), 'B');
+
+    const auto& d = eud.data();
+
+    EXPECT_EQ(d, data);
+}
+
+TEST(ExtUserDataTest, ShrinkTest)
+{
+    std::vector<uint8_t> data(100, 0xFF);
+
+    ExtendedUserData eud(0x1112, 0x42, 0x01, 'O', data);
+    EXPECT_TRUE(eud.valid());
+
+    // 4B aligned
+    EXPECT_TRUE(eud.shrink(88));
+    EXPECT_EQ(eud.flattenedSize(), 88);
+    EXPECT_EQ(eud.header().size, 88);
+
+    // rounded off
+    EXPECT_TRUE(eud.shrink(87));
+    EXPECT_EQ(eud.flattenedSize(), 84);
+    EXPECT_EQ(eud.header().size, 84);
+
+    // too big
+    EXPECT_FALSE(eud.shrink(200));
+    EXPECT_EQ(eud.flattenedSize(), 84);
+    EXPECT_EQ(eud.header().size, 84);
+
+    // way too small
+    EXPECT_FALSE(eud.shrink(3));
+    EXPECT_EQ(eud.flattenedSize(), 84);
+    EXPECT_EQ(eud.header().size, 84);
+
+    // the smallest it can go
+    EXPECT_TRUE(eud.shrink(16));
+    EXPECT_EQ(eud.flattenedSize(), 16);
+    EXPECT_EQ(eud.header().size, 16);
+
+    // one too small
+    EXPECT_FALSE(eud.shrink(15));
+    EXPECT_EQ(eud.flattenedSize(), 16);
+    EXPECT_EQ(eud.header().size, 16);
+}
diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp
index a42b0b9..b596d34 100644
--- a/test/openpower-pels/pel_utils.cpp
+++ b/test/openpower-pels/pel_utils.cpp
@@ -145,6 +145,14 @@
     0x04, 0x04, 0x04, 0x04, // MRU ID 3
 };
 
+const std::vector<uint8_t> extendedUserDataSection{
+    // Header
+    0x45, 0x44, 0x00, 0x18, 0x01, 0x02, 0x20, 0x00,
+
+    // Creator ID 'O', and then data
+    0x4F, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    0x05, 0x0A, 0x0B, 0x0C};
+
 constexpr size_t sectionCountOffset = 27;
 constexpr size_t createTimestampPHOffset = 8;
 constexpr size_t commitTimestampPHOffset = 16;
@@ -174,7 +182,9 @@
                         UserDataSection.end());
             data.insert(data.end(), ExtUserHeaderSection.begin(),
                         ExtUserHeaderSection.end());
-            data.at(sectionCountOffset) = 6;
+            data.insert(data.end(), extendedUserDataSection.begin(),
+                        extendedUserDataSection.end());
+            data.at(sectionCountOffset) = 7;
             break;
         case TestPELType::privateHeaderSection:
             data.insert(data.end(), privateHeaderSection.begin(),
@@ -220,6 +230,9 @@
         case TestPELType::failingMTMSSection:
             data.insert(data.end(), failingMTMSSection.begin(),
                         failingMTMSSection.end());
+        case TestPELType::extendedUserDataSection:
+            data.insert(data.end(), extendedUserDataSection.begin(),
+                        extendedUserDataSection.end());
     }
     return data;
 }
diff --git a/test/openpower-pels/pel_utils.hpp b/test/openpower-pels/pel_utils.hpp
index 1c4eebb..da38fa9 100644
--- a/test/openpower-pels/pel_utils.hpp
+++ b/test/openpower-pels/pel_utils.hpp
@@ -59,7 +59,8 @@
     userHeaderSection,
     primarySRCSection,
     primarySRCSection2Callouts,
-    failingMTMSSection
+    failingMTMSSection,
+    extendedUserDataSection
 };
 
 /**