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