PEL: Add UserHeader class

The second section in a PEL is always the 'User Header' section.  This
commit adds a class to represent that.  Right now, the only constructor
available is filling in its data fields from a PEL stream.

Several of the fields in this section have predefined values that are
defined by the PEL specification.  Defining any constants or enums for
those will be left to future commits where they will actually be used.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I8b5f856a4284d44c31b04e98a664f20cd8fa0cb6
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 8a852d4..cc933f9 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -2,4 +2,5 @@
 	extensions/openpower-pels/bcd_time.cpp \
 	extensions/openpower-pels/entry_points.cpp \
 	extensions/openpower-pels/manager.cpp \
-	extensions/openpower-pels/private_header.cpp
+	extensions/openpower-pels/private_header.cpp \
+	extensions/openpower-pels/user_header.cpp
diff --git a/extensions/openpower-pels/user_header.cpp b/extensions/openpower-pels/user_header.cpp
new file mode 100644
index 0000000..16d6212
--- /dev/null
+++ b/extensions/openpower-pels/user_header.cpp
@@ -0,0 +1,67 @@
+#include "user_header.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+
+Stream& operator>>(Stream& s, UserHeader& uh)
+{
+    s >> uh._header >> uh._eventSubsystem >> uh._eventScope >>
+        uh._eventSeverity >> uh._eventType >> uh._reserved4Byte1 >>
+        uh._problemDomain >> uh._problemVector >> uh._actionFlags >>
+        uh._reserved4Byte2;
+    return s;
+}
+
+Stream& operator<<(Stream& s, UserHeader& uh)
+{
+    s << uh._header << uh._eventSubsystem << uh._eventScope << uh._eventSeverity
+      << uh._eventType << uh._reserved4Byte1 << uh._problemDomain
+      << uh._problemVector << uh._actionFlags << uh._reserved4Byte2;
+    return s;
+}
+
+UserHeader::UserHeader(Stream& pel)
+{
+    try
+    {
+        pel >> *this;
+        validate();
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Cannot unflatten user header",
+                        entry("ERROR=%s", e.what()));
+        _valid = false;
+    }
+}
+
+void UserHeader::validate()
+{
+    bool failed = false;
+    if (header().id != userHeaderSectionID)
+    {
+        log<level::ERR>("Invalid user header section ID",
+                        entry("ID=0x%X", header().id));
+        failed = true;
+        return;
+    }
+
+    if (header().version != userHeaderVersion)
+    {
+        log<level::ERR>("Invalid user header version",
+                        entry("VERSION=0x%X", header().version));
+        failed = true;
+        return;
+    }
+
+    _valid = (failed) ? false : true;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/user_header.hpp b/extensions/openpower-pels/user_header.hpp
new file mode 100644
index 0000000..d2c6ffc
--- /dev/null
+++ b/extensions/openpower-pels/user_header.hpp
@@ -0,0 +1,203 @@
+#pragma once
+
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+static constexpr uint16_t userHeaderSectionID = 0x5548; // 'UH'
+static constexpr uint16_t userHeaderVersion = 0x01;
+
+/**
+ * @class UserHeader
+ *
+ * This represents the User Header section in a PEL.  It is required,
+ * and it is always the second section.
+ *
+ * The Section base class handles the section header structure that every
+ * PEL section has at offset zero.
+ *
+ * The fields in this class directly correspond to the order and sizes of
+ * the fields in the section.
+ */
+class UserHeader : public Section
+{
+  public:
+    UserHeader() = delete;
+    ~UserHeader() = default;
+    UserHeader(const UserHeader&) = default;
+    UserHeader& operator=(const UserHeader&) = default;
+    UserHeader(UserHeader&&) = default;
+    UserHeader& operator=(UserHeader&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * Fills in this class's data fields from the stream.
+     *
+     * @param[in] pel - the PEL data stream
+     */
+    explicit UserHeader(Stream& pel);
+
+    /**
+     * @brief Returns the subsystem field.
+     *
+     * @return uint8_t& - the subsystem
+     */
+    uint8_t& subsystem()
+    {
+        return _eventSubsystem;
+    }
+
+    /**
+     * @brief Returns the event scope field.
+     *
+     * @return uint8_t& - the event scope
+     */
+    uint8_t& scope()
+    {
+        return _eventScope;
+    }
+
+    /**
+     * @brief Returns the severity field.
+     *
+     * @return uint8_t& - the severity
+     */
+    uint8_t& severity()
+    {
+        return _eventSeverity;
+    }
+
+    /**
+     * @brief Returns the event type field.
+     *
+     * @return uint8_t& - the event type
+     */
+    uint8_t& eventType()
+    {
+        return _eventType;
+    }
+
+    /**
+     * @brief Returns the problem domain field.
+     *
+     * @return uint8_t& - the problem domain
+     */
+    uint8_t& problemDomain()
+    {
+        return _problemDomain;
+    }
+
+    /**
+     * @brief Returns the problem vector field.
+     *
+     * @return uint8_t& - the problem vector
+     */
+    uint8_t& problemVector()
+    {
+        return _problemVector;
+    }
+
+    /**
+     * @brief Returns the action flags field.
+     *
+     * @return uint16_t& - the action flags
+     */
+    uint16_t& actionFlags()
+    {
+        return _actionFlags;
+    }
+
+    /**
+     * @brief Returns the size of this section when flattened into a PEL
+     *
+     * @return size_t - the size of the section
+     */
+    static constexpr size_t flattenedSize()
+    {
+        return Section::flattenedSize() + sizeof(_eventSubsystem) +
+               sizeof(_eventScope) + sizeof(_eventSeverity) +
+               sizeof(_eventType) + sizeof(_reserved4Byte1) +
+               sizeof(_problemDomain) + sizeof(_problemVector) +
+               sizeof(_actionFlags) + sizeof(_reserved4Byte2);
+    }
+
+    friend Stream& operator>>(Stream& s, UserHeader& ph);
+    friend Stream& operator<<(Stream& s, UserHeader& ph);
+
+  private:
+    /**
+     * @brief Validates the section contents
+     *
+     * Updates _valid (in Section) with the results.
+     */
+    void validate() override;
+
+    /**
+     * @brief The subsystem associated with the event.
+     */
+    uint8_t _eventSubsystem;
+
+    /**
+     * @brief The event scope field.
+     */
+    uint8_t _eventScope;
+
+    /**
+     * @brief The event severity.
+     */
+    uint8_t _eventSeverity;
+
+    /**
+     * @brief The event type.
+     */
+    uint8_t _eventType;
+
+    /**
+     * @brief A reserved word placeholder
+     */
+    uint32_t _reserved4Byte1;
+
+    /**
+     * @brief The problem domain field.
+     */
+    uint8_t _problemDomain;
+
+    /**
+     * @brief The problem vector field.
+     */
+    uint8_t _problemVector;
+
+    /**
+     * @brief The action flags field.
+     */
+    uint16_t _actionFlags;
+
+    /**
+     * @brief The second reserved word placeholder.
+     */
+    uint32_t _reserved4Byte2;
+};
+
+/**
+ * @brief Stream extraction operator for the UserHeader
+ *
+ * @param[in] s - the stream
+ * @param[out] uh - the UserHeader object
+ */
+Stream& operator>>(Stream& s, UserHeader& uh);
+
+/**
+ * @brief Stream insertion operator for the UserHeader
+ *
+ * @param[out] s - the stream
+ * @param[in] uh - the UserHeader object
+ */
+Stream& operator<<(Stream& s, UserHeader& uh);
+
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 288a5db..f860db2 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -5,11 +5,13 @@
 	bcd_time_test \
 	private_header_test \
 	section_header_test \
-	stream_test
+	stream_test \
+	user_header_test
 
 pel_objects = \
 	$(top_builddir)/extensions/openpower-pels/bcd_time.o \
-	$(top_builddir)/extensions/openpower-pels/private_header.o
+	$(top_builddir)/extensions/openpower-pels/private_header.o \
+	$(top_builddir)/extensions/openpower-pels/user_header.o
 
 additional_data_test_SOURCES = %reldir%/additional_data_test.cpp
 additional_data_test_CPPFLAGS = $(test_cppflags)
@@ -47,3 +49,12 @@
 	$(test_ldadd) \
 	$(pel_objects)
 private_header_test_LDFLAGS = $(test_ldflags)
+
+user_header_test_SOURCES = \
+	%reldir%/user_header_test.cpp %reldir%/pel_utils.cpp
+user_header_test_CPPFLAGS = $(test_cppflags)
+user_header_test_CXXFLAGS = $(test_cxxflags)
+user_header_test_LDADD = \
+	$(test_ldadd) \
+	$(pel_objects)
+user_header_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp
index 4e4c32a..b6714eb 100644
--- a/test/openpower-pels/pel_utils.cpp
+++ b/test/openpower-pels/pel_utils.cpp
@@ -1,6 +1,7 @@
 #include "pel_utils.hpp"
 
 #include "extensions/openpower-pels/private_header.hpp"
+#include "extensions/openpower-pels/user_header.hpp"
 
 #include <fstream>
 
@@ -26,8 +27,23 @@
     0x90, 0x91, 0x92, 0x93,                         // OpenBMC log ID
     0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0,    // creator version
     0x50, 0x51, 0x52, 0x53,                         // plid
-    0x80, 0x81, 0x82, 0x83                          // id
+    0x80, 0x81, 0x82, 0x83,                         // id
 
+    // user header section header
+    0x55, 0x48, // ID 'UH'
+    0x00, 0x18, // Size
+    0x01, 0x0A, // version, subtype
+    0x0B, 0x0C, // comp ID
+
+    // user header
+    0x10, 0x04,             // subsystem, scope
+    0x20, 0x00,             // severity, type
+    0x00, 0x00, 0x00, 0x00, // reserved
+    0x03, 0x04,             // problem domain, vector
+    0x80, 0xC0,             // action flags
+    0x00, 0x00, 0x00, 0x00  // reserved
+
+    // Add more as the code supports more
 };
 
 std::unique_ptr<std::vector<uint8_t>> pelDataFactory(TestPelType type)
@@ -43,6 +59,12 @@
             data = std::make_unique<std::vector<uint8_t>>(
                 simplePEL, simplePEL + PrivateHeader::flattenedSize());
             break;
+        case TestPelType::userHeaderSimple:
+            data = std::make_unique<std::vector<uint8_t>>(
+                simplePEL + PrivateHeader::flattenedSize(),
+                simplePEL + PrivateHeader::flattenedSize() +
+                    UserHeader::flattenedSize());
+            break;
     }
     return data;
 }
diff --git a/test/openpower-pels/pel_utils.hpp b/test/openpower-pels/pel_utils.hpp
index bc4e05d..65fa196 100644
--- a/test/openpower-pels/pel_utils.hpp
+++ b/test/openpower-pels/pel_utils.hpp
@@ -10,7 +10,8 @@
 enum class TestPelType
 {
     pelSimple,
-    privateHeaderSimple
+    privateHeaderSimple,
+    userHeaderSimple
 };
 
 /**
diff --git a/test/openpower-pels/user_header_test.cpp b/test/openpower-pels/user_header_test.cpp
new file mode 100644
index 0000000..52087b6
--- /dev/null
+++ b/test/openpower-pels/user_header_test.cpp
@@ -0,0 +1,93 @@
+#include "extensions/openpower-pels/private_header.hpp"
+#include "extensions/openpower-pels/user_header.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(UserHeaderTest, SizeTest)
+{
+    EXPECT_EQ(UserHeader::flattenedSize(), 24);
+}
+
+TEST(UserHeaderTest, UnflattenFlattenTest)
+{
+    auto data = pelDataFactory(TestPelType::userHeaderSimple);
+
+    Stream stream(*data);
+    UserHeader uh(stream);
+    EXPECT_EQ(uh.valid(), true);
+
+    EXPECT_EQ(uh.header().id, 0x5548);
+    EXPECT_EQ(uh.header().size, UserHeader::flattenedSize());
+    EXPECT_EQ(uh.header().version, 0x01);
+    EXPECT_EQ(uh.header().subType, 0x0A);
+    EXPECT_EQ(uh.header().componentID, 0x0B0C);
+
+    EXPECT_EQ(uh.subsystem(), 0x10);
+    EXPECT_EQ(uh.scope(), 0x04);
+    EXPECT_EQ(uh.severity(), 0x20);
+    EXPECT_EQ(uh.eventType(), 0x00);
+    EXPECT_EQ(uh.problemDomain(), 0x03);
+    EXPECT_EQ(uh.problemVector(), 0x04);
+    EXPECT_EQ(uh.actionFlags(), 0x80C0);
+
+    // Now flatten into a vector and check that this vector
+    // matches the original one.
+    std::vector<uint8_t> newData;
+    Stream newStream(newData);
+
+    newStream << uh;
+    EXPECT_EQ(*data, newData);
+
+    // Change a field, then flatten and unflatten again
+    uh.subsystem() = 0x44;
+
+    newStream.offset(0);
+    newData.clear();
+    newStream << uh;
+    EXPECT_NE(*data, newData);
+
+    newStream.offset(0);
+    UserHeader newUH(newStream);
+
+    EXPECT_TRUE(newUH.valid());
+    EXPECT_EQ(newUH.subsystem(), 0x44);
+}
+
+TEST(UserHeaderTest, ShortDataTest)
+{
+    auto data = pelDataFactory(TestPelType::userHeaderSimple);
+    data->resize(data->size() - 1);
+
+    Stream stream(*data);
+    UserHeader uh(stream);
+
+    EXPECT_EQ(uh.valid(), false);
+}
+
+TEST(UserHeaderTest, CorruptDataTest1)
+{
+    auto data = pelDataFactory(TestPelType::userHeaderSimple);
+    data->resize(data->size() - 1);
+
+    data->at(0) = 0; // corrupt the section ID
+
+    Stream stream(*data);
+    UserHeader uh(stream);
+
+    EXPECT_EQ(uh.valid(), false);
+}
+
+TEST(UserHeaderTest, CorruptDataTest2)
+{
+    auto data = pelDataFactory(TestPelType::userHeaderSimple);
+
+    data->at(4) = 0x22; // corrupt the version
+
+    Stream stream(*data);
+    UserHeader uh(stream);
+
+    EXPECT_EQ(uh.valid(), false);
+}