PEL: Add PrivateHeader class

The first section in a PEL is always the 'Private 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.

The Section base class, which will be the base class of all PEL
sections, is also being introduced here.  It contains the section header
structure, and a valid flag that derived classes can use.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ia5806017155fe1ef29ea57bf8ab202ff861bde2e
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 94c7bb8..8a852d4 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -1,5 +1,5 @@
 phosphor_log_manager_SOURCES += \
 	extensions/openpower-pels/bcd_time.cpp \
 	extensions/openpower-pels/entry_points.cpp \
-	extensions/openpower-pels/manager.cpp
-
+	extensions/openpower-pels/manager.cpp \
+	extensions/openpower-pels/private_header.cpp
diff --git a/extensions/openpower-pels/private_header.cpp b/extensions/openpower-pels/private_header.cpp
new file mode 100644
index 0000000..7611a75
--- /dev/null
+++ b/extensions/openpower-pels/private_header.cpp
@@ -0,0 +1,90 @@
+#include "private_header.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+
+PrivateHeader::PrivateHeader(Stream& pel)
+{
+    try
+    {
+        pel >> *this;
+        validate();
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Cannot unflatten private header",
+                        entry("ERROR=%s", e.what()));
+        _valid = false;
+    }
+}
+
+void PrivateHeader::validate()
+{
+    bool failed = false;
+
+    if (header().id != privateHeaderSectionID)
+    {
+        log<level::ERR>("Invalid private header section ID",
+                        entry("ID=0x%X", header().id));
+        failed = true;
+    }
+
+    if (header().version != privateHeaderVersion)
+    {
+        log<level::ERR>("Invalid private header version",
+                        entry("VERSION=0x%X", header().version));
+        failed = true;
+    }
+
+    if (_sectionCount < minSectionCount)
+    {
+        log<level::ERR>("Invalid section count in private header",
+                        entry("SECTION_COUNT=0x%X", _sectionCount));
+        failed = true;
+    }
+
+    _valid = (failed) ? false : true;
+}
+
+Stream& operator>>(Stream& s, PrivateHeader& ph)
+{
+    s >> ph._header >> ph._createTimestamp >> ph._commitTimestamp >>
+        ph._creatorID >> ph._logType >> ph._reservedByte >> ph._sectionCount >>
+        ph._obmcLogID >> ph._creatorVersion >> ph._plid >> ph._id;
+    return s;
+}
+
+Stream& operator<<(Stream& s, PrivateHeader& ph)
+{
+    s << ph._header << ph._createTimestamp << ph._commitTimestamp
+      << ph._creatorID << ph._logType << ph._reservedByte << ph._sectionCount
+      << ph._obmcLogID << ph._creatorVersion << ph._plid << ph._id;
+    return s;
+}
+
+Stream& operator>>(Stream& s, CreatorVersion& cv)
+{
+    for (size_t i = 0; i < sizeof(CreatorVersion); i++)
+    {
+        s >> cv.version[i];
+    }
+    return s;
+}
+
+Stream& operator<<(Stream& s, CreatorVersion& cv)
+{
+    for (size_t i = 0; i < sizeof(CreatorVersion); i++)
+    {
+        s << cv.version[i];
+    }
+    return s;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/private_header.hpp b/extensions/openpower-pels/private_header.hpp
new file mode 100644
index 0000000..d9353ab
--- /dev/null
+++ b/extensions/openpower-pels/private_header.hpp
@@ -0,0 +1,256 @@
+#pragma once
+
+#include "bcd_time.hpp"
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+struct CreatorVersion
+{
+    uint8_t version[8];
+};
+
+static constexpr uint16_t privateHeaderSectionID = 0x5048; // 'PH'
+static constexpr uint16_t privateHeaderVersion = 0x01;
+static constexpr uint8_t minSectionCount = 2;
+
+/**
+ * @class PrivateHeader
+ *
+ * This represents the Private Header section in a PEL.  It is required,
+ * and it is always the first 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 PrivateHeader : public Section
+{
+  public:
+    PrivateHeader() = delete;
+    ~PrivateHeader() = default;
+    PrivateHeader(const PrivateHeader&) = default;
+    PrivateHeader& operator=(const PrivateHeader&) = default;
+    PrivateHeader(PrivateHeader&&) = default;
+    PrivateHeader& operator=(PrivateHeader&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * Fills in this class's data fields from the stream.
+     *
+     * @param[in] pel - the PEL data stream
+     *
+     */
+    explicit PrivateHeader(Stream& pel);
+
+    /**
+     * @brief Returns the creation timestamp
+     *
+     * @return BCDTime& - the timestamp
+     */
+    BCDTime& createTimestamp()
+    {
+        return _createTimestamp;
+    }
+
+    /**
+     * @brief Returns the commit time timestamp
+     *
+     * @return BCDTime& - the timestamp
+     */
+    BCDTime& commitTimestamp()
+    {
+        return _commitTimestamp;
+    }
+
+    /**
+     * @brief Returns the creator ID field
+     *
+     * @return uint8_t& - the creator ID
+     */
+    uint8_t& creatorID()
+    {
+        return _creatorID;
+    }
+
+    /**
+     * @brief Returns the log type field
+     *
+     * @return uint8_t& - the log type
+     */
+    uint8_t& logType()
+    {
+        return _logType;
+    }
+
+    /**
+     * @brief Returns the section count field
+     *
+     * @return uint8_t& - the section count
+     */
+    uint8_t& sectionCount()
+    {
+        return _sectionCount;
+    }
+
+    /**
+     * @brief Returns the OpenBMC log ID field
+     *
+     * This is the ID the OpenBMC event log that corresponds
+     * to this PEL.
+     *
+     * @return uint32_t& - the OpenBMC event log ID
+     */
+    uint32_t& obmcLogID()
+    {
+        return _obmcLogID;
+    }
+
+    /**
+     * @brief Returns the Creator Version field
+     *
+     * @return CreatorVersion& - the creator version
+     */
+    CreatorVersion& creatorVersion()
+    {
+        return _creatorVersion;
+    }
+
+    /**
+     * @brief Returns the error log ID field
+     *
+     * @return uint32_t& - the error log ID
+     */
+    uint32_t& id()
+    {
+        return _id;
+    }
+
+    /**
+     * @brief Returns the platform log ID field
+     *
+     * @return uint32_t& - the platform log ID
+     */
+    uint32_t& plid()
+    {
+        return _plid;
+    }
+
+    /**
+     * @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(_createTimestamp) +
+               sizeof(_commitTimestamp) + sizeof(_creatorID) +
+               sizeof(_logType) + sizeof(_reservedByte) +
+               sizeof(_sectionCount) + sizeof(_obmcLogID) +
+               sizeof(_creatorVersion) + sizeof(_plid) + sizeof(_id);
+    }
+
+    friend Stream& operator>>(Stream& s, PrivateHeader& ph);
+    friend Stream& operator<<(Stream& s, PrivateHeader& ph);
+
+  private:
+    /**
+     * @brief Validates the section contents
+     *
+     * Updates _valid (in Section) with the results.
+     */
+    void validate() override;
+
+    /**
+     * @brief The creation time timestamp
+     */
+    BCDTime _createTimestamp;
+
+    /**
+     * @brief The commit time timestamp
+     */
+    BCDTime _commitTimestamp;
+
+    /**
+     * @brief The creator ID field
+     */
+    uint8_t _creatorID;
+
+    /**
+     * @brief The log type field
+     */
+    uint8_t _logType;
+
+    /**
+     * @brief A reserved byte.
+     */
+    uint8_t _reservedByte;
+
+    /**
+     * @brief The section count field, which is the total number
+     * of sections in the PEL.
+     */
+    uint8_t _sectionCount;
+
+    /**
+     * @brief The OpenBMC event log ID that corresponds to this PEL.
+     */
+    uint32_t _obmcLogID;
+
+    /**
+     * @brief The creator subsystem version field
+     */
+    CreatorVersion _creatorVersion;
+
+    /**
+     * @brief The platform log ID field
+     */
+    uint32_t _plid;
+
+    /**
+     * @brief The log entry ID field
+     */
+    uint32_t _id;
+};
+
+/**
+ * @brief Stream extraction operator for the PrivateHeader
+ *
+ * @param[in] s - the stream
+ * @param[out] ph - the PrivateHeader object
+ */
+Stream& operator>>(Stream& s, PrivateHeader& ph);
+
+/**
+ * @brief Stream insertion operator for the PrivateHeader
+ *
+ * @param[out] s - the stream
+ * @param[in] ph - the PrivateHeader object
+ */
+Stream& operator<<(Stream& s, PrivateHeader& ph);
+
+/**
+ * @brief Stream extraction operator for the CreatorVersion
+ *
+ * @param[in] s - the stream
+ * @param[out] cv - the CreatorVersion object
+ */
+Stream& operator>>(Stream& s, CreatorVersion& cv);
+
+/**
+ * @brief Stream insertion operator for the CreatorVersion
+ *
+ * @param[out] s - the stream
+ * @param[in] cv - the CreatorVersion object
+ */
+Stream& operator<<(Stream& s, CreatorVersion& cv);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/section.hpp b/extensions/openpower-pels/section.hpp
new file mode 100644
index 0000000..7214e17
--- /dev/null
+++ b/extensions/openpower-pels/section.hpp
@@ -0,0 +1,73 @@
+#pragma once
+#include "section_header.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class Section
+ *
+ * The base class for a PEL section.  It contains the SectionHeader
+ * as all sections start with it.
+ *
+ */
+class Section
+{
+  public:
+    Section() = default;
+    virtual ~Section() = default;
+    Section(const Section&) = default;
+    Section& operator=(const Section&) = default;
+    Section(Section&&) = default;
+    Section& operator=(Section&&) = default;
+
+    /**
+     * @brief Returns a reference to the SectionHeader
+     */
+    SectionHeader& header()
+    {
+        return _header;
+    }
+
+    /**
+     * @brief Says if the section is valid.
+     */
+    bool valid() const
+    {
+        return _valid;
+    }
+
+  protected:
+    /**
+     * @brief Returns the flattened size of the section header
+     */
+    static constexpr size_t flattenedSize()
+    {
+        return SectionHeader::flattenedSize();
+    }
+
+    /**
+     * @brief Used to validate the section.
+     *
+     * Implemented by derived classes.
+     */
+    virtual void validate() = 0;
+
+    /**
+     * @brief The section header structure.
+     *
+     * Filled in by derived classes.
+     */
+    SectionHeader _header;
+
+    /**
+     * @brief The section valid flag.
+     *
+     * This is determined by the derived class.
+     */
+    bool _valid = false;
+};
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 07e596d..288a5db 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -3,9 +3,14 @@
 check_PROGRAMS += \
 	additional_data_test \
 	bcd_time_test \
+	private_header_test \
 	section_header_test \
 	stream_test
 
+pel_objects = \
+	$(top_builddir)/extensions/openpower-pels/bcd_time.o \
+	$(top_builddir)/extensions/openpower-pels/private_header.o
+
 additional_data_test_SOURCES = %reldir%/additional_data_test.cpp
 additional_data_test_CPPFLAGS = $(test_cppflags)
 additional_data_test_CXXFLAGS = $(test_cxxflags)
@@ -32,4 +37,13 @@
 section_header_test_CPPFLAGS = $(test_cppflags)
 section_header_test_CXXFLAGS = $(test_cxxflags)
 section_header_test_LDADD = $(test_ldadd)
-section_header_test_LDFLAGS = $(test_ldflags)
\ No newline at end of file
+section_header_test_LDFLAGS = $(test_ldflags)
+
+private_header_test_SOURCES = \
+	%reldir%/private_header_test.cpp %reldir%/pel_utils.cpp
+private_header_test_CPPFLAGS = $(test_cppflags)
+private_header_test_CXXFLAGS = $(test_cxxflags)
+private_header_test_LDADD = \
+	$(test_ldadd) \
+	$(pel_objects)
+private_header_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp
new file mode 100644
index 0000000..4e4c32a
--- /dev/null
+++ b/test/openpower-pels/pel_utils.cpp
@@ -0,0 +1,57 @@
+#include "pel_utils.hpp"
+
+#include "extensions/openpower-pels/private_header.hpp"
+
+#include <fstream>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+using namespace openpower::pels;
+
+constexpr uint8_t simplePEL[] = {
+    // private header section header
+    0x50, 0x48, // ID 'PH'
+    0x00, 0x30, // Size
+    0x01, 0x02, // version, subtype
+    0x03, 0x04, // comp ID
+
+    // private header
+    0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,  // create timestamp
+    0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A, 0x00, // commit timestamp
+    0xAA,                                           // creatorID
+    0x00,                                           // logtype
+    0x00,                                           // reserved
+    0x02,                                           // section count
+    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
+
+};
+
+std::unique_ptr<std::vector<uint8_t>> pelDataFactory(TestPelType type)
+{
+    std::unique_ptr<std::vector<uint8_t>> data;
+    switch (type)
+    {
+        case TestPelType::pelSimple:
+            data = std::make_unique<std::vector<uint8_t>>(
+                simplePEL, simplePEL + sizeof(simplePEL));
+            break;
+        case TestPelType::privateHeaderSimple:
+            data = std::make_unique<std::vector<uint8_t>>(
+                simplePEL, simplePEL + PrivateHeader::flattenedSize());
+            break;
+    }
+    return data;
+}
+
+std::unique_ptr<std::vector<uint8_t>> readPELFile(const fs::path& path)
+{
+    std::ifstream file{path};
+
+    auto pel = std::make_unique<std::vector<uint8_t>>(
+        std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+    return pel;
+}
diff --git a/test/openpower-pels/pel_utils.hpp b/test/openpower-pels/pel_utils.hpp
new file mode 100644
index 0000000..bc4e05d
--- /dev/null
+++ b/test/openpower-pels/pel_utils.hpp
@@ -0,0 +1,33 @@
+#include <filesystem>
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+/**
+ * @brief Tells the factory which PEL to create
+ */
+enum class TestPelType
+{
+    pelSimple,
+    privateHeaderSimple
+};
+
+/**
+ * @brief PEL data factory, for testing
+ *
+ * @param[in] type - the type of data to create
+ *
+ * @return std::unique_ptr<std::vector<uint8_t>> - the PEL data
+ */
+std::unique_ptr<std::vector<uint8_t>> pelDataFactory(TestPelType type);
+
+/**
+ * @brief Helper function to read raw PEL data from a file
+ *
+ * @param[in] path - the path to read
+ *
+ * @return std::unique_ptr<std::vector<uint8_t>> - the data from the file
+ */
+std::unique_ptr<std::vector<uint8_t>>
+    readPELFile(const std::filesystem::path& path);
diff --git a/test/openpower-pels/private_header_test.cpp b/test/openpower-pels/private_header_test.cpp
new file mode 100644
index 0000000..40a1ad2
--- /dev/null
+++ b/test/openpower-pels/private_header_test.cpp
@@ -0,0 +1,126 @@
+#include "extensions/openpower-pels/private_header.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(PrivateHeaderTest, SizeTest)
+{
+    EXPECT_EQ(PrivateHeader::flattenedSize(), 48);
+}
+
+TEST(PrivateHeaderTest, UnflattenFlattenTest)
+{
+    auto data = pelDataFactory(TestPelType::privateHeaderSimple);
+
+    Stream stream(*data);
+    PrivateHeader ph(stream);
+    EXPECT_EQ(ph.valid(), true);
+
+    EXPECT_EQ(ph.header().id, 0x5048);
+    EXPECT_EQ(ph.header().size, PrivateHeader::flattenedSize());
+    EXPECT_EQ(ph.header().version, 0x01);
+    EXPECT_EQ(ph.header().subType, 0x02);
+    EXPECT_EQ(ph.header().componentID, 0x0304);
+
+    auto& ct = ph.createTimestamp();
+    EXPECT_EQ(ct.yearMSB, 0x20);
+    EXPECT_EQ(ct.yearLSB, 0x30);
+    EXPECT_EQ(ct.month, 0x05);
+    EXPECT_EQ(ct.day, 0x09);
+    EXPECT_EQ(ct.hour, 0x011);
+    EXPECT_EQ(ct.minutes, 0x1E);
+    EXPECT_EQ(ct.seconds, 0x01);
+    EXPECT_EQ(ct.hundredths, 0x63);
+
+    auto& mt = ph.commitTimestamp();
+    EXPECT_EQ(mt.yearMSB, 0x20);
+    EXPECT_EQ(mt.yearLSB, 0x31);
+    EXPECT_EQ(mt.month, 0x06);
+    EXPECT_EQ(mt.day, 0x0F);
+    EXPECT_EQ(mt.hour, 0x09);
+    EXPECT_EQ(mt.minutes, 0x22);
+    EXPECT_EQ(mt.seconds, 0x3A);
+    EXPECT_EQ(mt.hundredths, 0x00);
+
+    EXPECT_EQ(ph.creatorID(), 0xAA);
+    EXPECT_EQ(ph.logType(), 0x00);
+    EXPECT_EQ(ph.sectionCount(), 0x02);
+    EXPECT_EQ(ph.obmcLogID(), 0x90919293);
+
+    char expected[] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00};
+    EXPECT_TRUE(memcmp(ph.creatorVersion().version, expected, 8) == 0);
+
+    EXPECT_EQ(ph.plid(), 0x50515253);
+    EXPECT_EQ(ph.id(), 0x80818283);
+
+    // Now flatten into a vector and check that this vector
+    // matches the original one.
+    std::vector<uint8_t> newData;
+    Stream newStream(newData);
+
+    newStream << ph;
+    EXPECT_EQ(*data, newData);
+
+    // Change a field, then flatten and unflatten again
+    ph.creatorID() = 0x55;
+
+    newStream.offset(0);
+    newData.clear();
+    newStream << ph;
+    EXPECT_NE(*data, newData);
+
+    newStream.offset(0);
+    PrivateHeader newPH(newStream);
+
+    EXPECT_TRUE(newPH.valid());
+    EXPECT_EQ(newPH.creatorID(), 0x55);
+}
+
+TEST(PrivateHeaderTest, ShortDataTest)
+{
+    auto data = pelDataFactory(TestPelType::privateHeaderSimple);
+    data->resize(PrivateHeader::flattenedSize() - 1);
+    Stream stream(*data);
+
+    PrivateHeader ph(stream);
+
+    EXPECT_EQ(ph.valid(), false);
+}
+
+TEST(PrivateHeaderTest, CorruptDataTest1)
+{
+    auto data = pelDataFactory(TestPelType::privateHeaderSimple);
+    Stream stream(*data);
+
+    data->at(0) = 0; // corrupt the section ID
+
+    PrivateHeader ph(stream);
+
+    EXPECT_EQ(ph.valid(), false);
+}
+
+TEST(PrivateHeaderTest, CorruptDataTest2)
+{
+    auto data = pelDataFactory(TestPelType::privateHeaderSimple);
+    Stream stream(*data);
+
+    data->at(4) = 0x22; // corrupt the version
+
+    PrivateHeader ph(stream);
+
+    EXPECT_EQ(ph.valid(), false);
+}
+
+TEST(PrivateHeaderTest, CorruptDataTest3)
+{
+    auto data = pelDataFactory(TestPelType::privateHeaderSimple);
+    Stream stream(*data);
+
+    data->at(27) = 1; // corrupt the section count
+
+    PrivateHeader ph(stream);
+
+    EXPECT_EQ(ph.valid(), false);
+}