PEL: FRU identity SRC substructure

This substructure is part of the callout subsection in the SRC
section of a PEL, and contains information about the FRU (Field
Replaceable Unit) being called out.

This includes:
* The specific type of FRU (see the flags field definitions)
* The FRU part number
* The FRU CCIN value (CCIN = a keyword in VPD).
* The FRU serial number

Instead of just calling out a FRU, this structure can instead be used to
call out a maintenance procedure, which is a string that is used as
a key into the service documentation that maps to a procedure to fix
the problem.

This commit only adds support for creating an object from a flattened PEL,
such as one that comes down from the host.  A future commit will handle
creating it from scratch for BMC errors.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ic2b9489abea48084116bf2f450bd293c2d655979
diff --git a/extensions/openpower-pels/fru_identity.cpp b/extensions/openpower-pels/fru_identity.cpp
new file mode 100644
index 0000000..d62bf6a
--- /dev/null
+++ b/extensions/openpower-pels/fru_identity.cpp
@@ -0,0 +1,98 @@
+#include "fru_identity.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+FRUIdentity::FRUIdentity(Stream& pel)
+{
+    pel >> _type >> _size >> _flags;
+
+    if (_flags & (pnSupplied | maintProcSupplied))
+    {
+        pel.read(_pnOrProcedureID.data(), _pnOrProcedureID.size());
+    }
+
+    if (_flags & ccinSupplied)
+    {
+        pel.read(_ccin.data(), _ccin.size());
+    }
+
+    if (_flags & snSupplied)
+    {
+        pel.read(_sn.data(), _sn.size());
+    }
+}
+
+std::optional<std::string> FRUIdentity::getPN() const
+{
+    if (hasPN())
+    {
+        // NULL terminated
+        std::string pn{_pnOrProcedureID.data()};
+        return pn;
+    }
+
+    return std::nullopt;
+}
+
+std::optional<std::string> FRUIdentity::getMaintProc() const
+{
+    if (hasMP())
+    {
+        // NULL terminated
+        std::string mp{_pnOrProcedureID.data()};
+        return mp;
+    }
+
+    return std::nullopt;
+}
+
+std::optional<std::string> FRUIdentity::getCCIN() const
+{
+    if (hasCCIN())
+    {
+        std::string ccin{_ccin.begin(), _ccin.begin() + _ccin.size()};
+        return ccin;
+    }
+
+    return std::nullopt;
+}
+
+std::optional<std::string> FRUIdentity::getSN() const
+{
+    if (hasSN())
+    {
+        std::string sn{_sn.begin(), _sn.begin() + _sn.size()};
+        return sn;
+    }
+
+    return std::nullopt;
+}
+
+void FRUIdentity::flatten(Stream& pel)
+{
+    pel << _type << _size << _flags;
+
+    if (hasPN() || hasMP())
+    {
+        pel.write(_pnOrProcedureID.data(), _pnOrProcedureID.size());
+    }
+
+    if (hasCCIN())
+    {
+        pel.write(_ccin.data(), _ccin.size());
+    }
+
+    if (hasSN())
+    {
+        pel.write(_sn.data(), _sn.size());
+    }
+}
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/fru_identity.hpp b/extensions/openpower-pels/fru_identity.hpp
new file mode 100644
index 0000000..76142a8
--- /dev/null
+++ b/extensions/openpower-pels/fru_identity.hpp
@@ -0,0 +1,222 @@
+#pragma once
+
+#include "stream.hpp"
+
+#include <optional>
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+/**
+ * @class FRUIdentity
+ *
+ * This represents the FRU Identity substructure in the
+ * callout subsection of the SRC PEL section.
+ *
+ * It provides information about the FRU being called out,
+ * such as serial number and part number.  A maintenance
+ * procedure name may be used instead of the part number,
+ * and this would be indicated in the flags field.
+ */
+class FRUIdentity
+{
+  public:
+    /**
+     * @brief The failing component type
+     *
+     * Upper nibble of the flags byte
+     */
+    enum FailingComponentType
+    {
+        hardwareFRU = 0x10,
+        codeFRU = 0x20,
+        configError = 0x30,
+        maintenanceProc = 0x40,
+        externalFRU = 0x90,
+        externalCodeFRU = 0xA0,
+        toolFRU = 0xB0,
+        symbolicFRU = 0xC0,
+        symbolicFRUTrustedLocCode = 0xE0
+    };
+
+    /**
+     * @brief The lower nibble of the flags byte
+     */
+    enum Flags
+    {
+        pnSupplied = 0x08,
+        ccinSupplied = 0x04,
+        maintProcSupplied = 0x02,
+        snSupplied = 0x01
+    };
+
+    FRUIdentity() = delete;
+    ~FRUIdentity() = default;
+    FRUIdentity(const FRUIdentity&) = default;
+    FRUIdentity& operator=(const FRUIdentity&) = default;
+    FRUIdentity(FRUIdentity&&) = default;
+    FRUIdentity& operator=(FRUIdentity&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * Fills in this class's data fields from the stream.
+     *
+     * @param[in] pel - the PEL data stream
+     */
+    explicit FRUIdentity(Stream& pel);
+
+    /**
+     * @brief Flatten the object into the stream
+     *
+     * @param[in] stream - The stream to write to
+     */
+    void flatten(Stream& pel);
+
+    /**
+     * @brief Returns the size of this structure when flattened into a PEL
+     *
+     * @return size_t - The size of the section
+     */
+    size_t flattenedSize() const
+    {
+        return _size;
+    }
+
+    /**
+     * @brief The failing component type for this FRU callout.
+     *
+     * @return FailingComponentType
+     */
+    FailingComponentType failingComponentType() const
+    {
+        return static_cast<FailingComponentType>(_flags & 0xF0);
+    }
+
+    /**
+     * @brief Returns the part number, if supplied
+     *
+     * @return std::optional<std::string>
+     */
+    std::optional<std::string> getPN() const;
+
+    /**
+     * @brief Returns the maintenance procedure, if supplied
+     *
+     * @return std::optional<std::string>
+     */
+    std::optional<std::string> getMaintProc() const;
+
+    /**
+     * @brief Returns the CCIN, if supplied
+     *
+     * @return std::optional<std::string>
+     */
+    std::optional<std::string> getCCIN() const;
+
+    /**
+     * @brief Returns the serial number, if supplied
+     *
+     * @return std::optional<std::string>
+     */
+    std::optional<std::string> getSN() const;
+
+    /**
+     * @brief The type identifier value of this structure.
+     */
+    static const uint16_t substructureType = 0x4944; // "ID"
+
+  private:
+    /**
+     * @brief If the part number is contained in this structure.
+     *
+     * It takes the place of the maintenance procedure ID.
+     *
+     * @return bool
+     */
+    bool hasPN() const
+    {
+        return _flags & pnSupplied;
+    }
+
+    /**
+     * @brief If the CCIN is contained in this structure.
+     *
+     * @return bool
+     */
+    bool hasCCIN() const
+    {
+        return _flags & ccinSupplied;
+    }
+
+    /**
+     * @brief If a maintenance procedure is contained in this structure.
+     *
+     * It takes the place of the part number.
+     *
+     * @return bool
+     */
+    bool hasMP() const
+    {
+        return _flags & maintProcSupplied;
+    }
+
+    /**
+     * @brief If the serial number is contained in this structure.
+     *
+     * @return bool
+     */
+    bool hasSN() const
+    {
+        return _flags & snSupplied;
+    }
+
+    /**
+     * @brief The callout substructure type field. Will be "ID".
+     */
+    uint16_t _type;
+
+    /**
+     * @brief The size of this callout structure.
+     *
+     * Always a multiple of 4.
+     */
+    uint8_t _size;
+
+    /**
+     * @brief The flags byte of this substructure.
+     *
+     * See the FailingComponentType and Flags enums
+     */
+    uint8_t _flags;
+
+    /**
+     * @brief The part number OR maintenance procedure ID,
+     *        depending on what the flags field specifies.
+     *
+     * A NULL terminated ASCII string.
+     */
+    std::array<char, 8> _pnOrProcedureID;
+
+    /**
+     * @brief The CCIN VPD keyword
+     *
+     * Four ASCII characters, not NULL terminated.
+     */
+    std::array<char, 4> _ccin;
+
+    /**
+     * @brief The serial number
+     *
+     * Twelve ASCII characters, not NULL terminated.
+     */
+    std::array<char, 12> _sn;
+};
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 07d7522..9a4f4fc 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -4,6 +4,7 @@
 	extensions/openpower-pels/data_interface.cpp \
 	extensions/openpower-pels/entry_points.cpp \
 	extensions/openpower-pels/failing_mtms.cpp \
+	extensions/openpower-pels/fru_identity.cpp \
 	extensions/openpower-pels/generic.cpp \
 	extensions/openpower-pels/log_id.cpp \
 	extensions/openpower-pels/manager.cpp \