PEL: Create FRUIdentity with FRUs/procedures

Add constructors to the FRUIdentity structure in the SRC section to take
either a hardware callout with PN/SN/CCIN, or a maintenance procedure
callout with a maintenance procedure enum.  Both of these also take
callout priority.

These will be used when creating PELs that have callouts.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I5071d98df38b63ba53224c3f3b853e57234ed74e
diff --git a/extensions/openpower-pels/fru_identity.cpp b/extensions/openpower-pels/fru_identity.cpp
index 73b4200..7e9bf1b 100644
--- a/extensions/openpower-pels/fru_identity.cpp
+++ b/extensions/openpower-pels/fru_identity.cpp
@@ -15,6 +15,8 @@
  */
 #include "fru_identity.hpp"
 
+#include "pel_values.hpp"
+
 namespace openpower
 {
 namespace pels
@@ -26,17 +28,17 @@
 {
     pel >> _type >> _size >> _flags;
 
-    if (_flags & (pnSupplied | maintProcSupplied))
+    if (hasPN() || hasMP())
     {
         pel.read(_pnOrProcedureID.data(), _pnOrProcedureID.size());
     }
 
-    if (_flags & ccinSupplied)
+    if (hasCCIN())
     {
         pel.read(_ccin.data(), _ccin.size());
     }
 
-    if (_flags & snSupplied)
+    if (hasSN())
     {
         pel.read(_sn.data(), _sn.size());
     }
@@ -64,6 +66,29 @@
     return size;
 }
 
+FRUIdentity::FRUIdentity(const std::string& partNumber, const std::string& ccin,
+                         const std::string& serialNumber)
+{
+    _type = substructureType;
+    _flags = hardwareFRU;
+
+    setPartNumber(partNumber);
+    setCCIN(ccin);
+    setSerialNumber(serialNumber);
+
+    _size = flattenedSize();
+}
+
+FRUIdentity::FRUIdentity(MaintProcedure procedure)
+{
+    _type = substructureType;
+    _flags = maintenanceProc;
+
+    setMaintenanceProcedure(procedure);
+
+    _size = flattenedSize();
+}
+
 std::optional<std::string> FRUIdentity::getPN() const
 {
     if (hasPN())
@@ -93,6 +118,12 @@
     if (hasCCIN())
     {
         std::string ccin{_ccin.begin(), _ccin.begin() + _ccin.size()};
+
+        // Don't leave any NULLs in the string (not there usually)
+        if (auto pos = ccin.find('\0'); pos != std::string::npos)
+        {
+            ccin.resize(pos);
+        }
         return ccin;
     }
 
@@ -104,6 +135,12 @@
     if (hasSN())
     {
         std::string sn{_sn.begin(), _sn.begin() + _sn.size()};
+
+        // Don't leave any NULLs in the string (not there usually)
+        if (auto pos = sn.find('\0'); pos != std::string::npos)
+        {
+            sn.resize(pos);
+        }
         return sn;
     }
 
@@ -130,6 +167,56 @@
     }
 }
 
+void FRUIdentity::setPartNumber(const std::string& partNumber)
+{
+    _flags |= pnSupplied;
+    _flags &= ~maintProcSupplied;
+
+    auto pn = partNumber;
+
+    // Strip leading whitespace on this one.
+    while (' ' == pn.front())
+    {
+        pn = pn.substr(1);
+    }
+
+    // Note: strncpy only writes NULLs if pn short
+    strncpy(_pnOrProcedureID.data(), pn.c_str(), _pnOrProcedureID.size());
+
+    // ensure null terminated
+    _pnOrProcedureID.back() = 0;
+}
+
+void FRUIdentity::setCCIN(const std::string& ccin)
+{
+    _flags |= ccinSupplied;
+
+    // Note: _ccin not null terminated, though strncpy writes NULLs if short
+    strncpy(_ccin.data(), ccin.c_str(), _ccin.size());
+}
+
+void FRUIdentity::setSerialNumber(const std::string& serialNumber)
+{
+    _flags |= snSupplied;
+
+    // Note: _sn not null terminated, though strncpy writes NULLs if short
+    strncpy(_sn.data(), serialNumber.c_str(), _sn.size());
+}
+
+void FRUIdentity::setMaintenanceProcedure(MaintProcedure procedure)
+{
+    _flags |= maintProcSupplied;
+    _flags &= ~pnSupplied;
+
+    auto proc = pel_values::getMaintProcedure(procedure);
+
+    strncpy(_pnOrProcedureID.data(), std::get<pel_values::mpNamePos>(*proc),
+            _pnOrProcedureID.size());
+
+    // ensure null terminated
+    _pnOrProcedureID.back() = 0;
+}
+
 } // namespace src
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/fru_identity.hpp b/extensions/openpower-pels/fru_identity.hpp
index bf7b2f7..016f3fd 100644
--- a/extensions/openpower-pels/fru_identity.hpp
+++ b/extensions/openpower-pels/fru_identity.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "pel_types.hpp"
 #include "stream.hpp"
 
 #include <optional>
@@ -71,6 +72,28 @@
     explicit FRUIdentity(Stream& pel);
 
     /**
+     * Constructor
+     *
+     * Creates the object as a hardware callout with the part number,
+     * CCIN, and serial number fields supplied.
+     *
+     * @param[in] partNumber - The part number of the FRU
+     * @param[in] ccin - The CCIN of the FRU
+     * @param[in] serialNumber - The serial number of the FRU
+     */
+    FRUIdentity(const std::string& partNumber, const std::string& ccin,
+                const std::string& serialNumber);
+
+    /**
+     * @brief Constructor
+     *
+     * Creates the object with a maintenance procedure callout.
+     *
+     * @param[in] procedure - The procedure to use
+     */
+    FRUIdentity(MaintProcedure procedure);
+
+    /**
      * @brief Flatten the object into the stream
      *
      * @param[in] stream - The stream to write to
@@ -85,6 +108,15 @@
     size_t flattenedSize() const;
 
     /**
+     * @brief Returns the type field
+     *
+     * @return uint16_t - The type, always 0x4944 "ID".
+     */
+    uint16_t type() const
+    {
+        return _type;
+    }
+    /**
      * @brief The failing component type for this FRU callout.
      *
      * @return FailingComponentType
@@ -173,6 +205,35 @@
     }
 
     /**
+     * @brief Sets the 8 character null terminated part
+     *        number field to the string passed in.
+     *
+     * @param[in] partNumber - The part number string.
+     */
+    void setPartNumber(const std::string& partNumber);
+
+    /**
+     * @brief Sets the 4 character CCIN field.
+     *
+     * @param[in] ccin - The CCIN string
+     */
+    void setCCIN(const std::string& ccin);
+
+    /**
+     * @brief Sets the 12 character serial number field.
+     *
+     * @param[in] serialNumber - The serial number string
+     */
+    void setSerialNumber(const std::string& serialNumber);
+
+    /**
+     * @brief Sets the 8 character null terminated procedure
+     *        field.  This is in the same field as the part
+     *        number since they are mutually exclusive.
+     */
+    void setMaintenanceProcedure(MaintProcedure procedure);
+
+    /**
      * @brief The callout substructure type field. Will be "ID".
      */
     uint16_t _type;
diff --git a/extensions/openpower-pels/pel_types.hpp b/extensions/openpower-pels/pel_types.hpp
index d23ebe8..f6f66a5 100644
--- a/extensions/openpower-pels/pel_types.hpp
+++ b/extensions/openpower-pels/pel_types.hpp
@@ -131,6 +131,19 @@
 };
 
 /**
+ * @brief Callout priority values
+ */
+enum class CalloutPriority
+{
+    high = 'H',
+    medium = 'M',
+    mediumGroupA = 'A',
+    mediumGroupB = 'B',
+    mediumGroupC = 'C',
+    low = 'L'
+};
+
+/**
  * @brief Maintenance procedure enums
  */
 enum class MaintProcedure
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 6ba7236..9afe6cf 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -233,7 +233,8 @@
 fru_identity_test_CXXFLAGS = $(test_cxxflags)
 fru_identity_test_LDADD = \
 	$(test_ldadd) \
-	$(top_builddir)/extensions/openpower-pels/fru_identity.o
+	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o
 fru_identity_test_LDFLAGS = $(test_ldflags)
 
 pce_identity_test_SOURCES = %reldir%/pce_identity_test.cpp
@@ -264,7 +265,8 @@
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
 	$(top_builddir)/extensions/openpower-pels/mru.o \
 	$(top_builddir)/extensions/openpower-pels/mtms.o \
-	$(top_builddir)/extensions/openpower-pels/pce_identity.o
+	$(top_builddir)/extensions/openpower-pels/pce_identity.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o
 src_callout_test_LDFLAGS = $(test_ldflags)
 
 src_callouts_test_SOURCES = \
@@ -279,7 +281,8 @@
 	$(top_builddir)/extensions/openpower-pels/fru_identity.o \
 	$(top_builddir)/extensions/openpower-pels/mru.o \
 	$(top_builddir)/extensions/openpower-pels/mtms.o \
-	$(top_builddir)/extensions/openpower-pels/pce_identity.o
+	$(top_builddir)/extensions/openpower-pels/pce_identity.o \
+	$(top_builddir)/extensions/openpower-pels/pel_values.o
 src_callouts_test_LDFLAGS = $(test_ldflags)
 
 src_test_SOURCES = \
diff --git a/test/openpower-pels/fru_identity_test.cpp b/test/openpower-pels/fru_identity_test.cpp
index a6839b8..5e738aa 100644
--- a/test/openpower-pels/fru_identity_test.cpp
+++ b/test/openpower-pels/fru_identity_test.cpp
@@ -36,6 +36,7 @@
 
     EXPECT_EQ(fru.failingComponentType(), FRUIdentity::hardwareFRU);
     EXPECT_EQ(fru.flattenedSize(), data.size());
+    EXPECT_EQ(fru.type(), 0x4944);
 
     EXPECT_EQ(fru.getPN().value(), "1234567");
     EXPECT_EQ(fru.getCCIN().value(), "AAAA");
@@ -87,3 +88,102 @@
 
     EXPECT_THROW(FRUIdentity fru{stream}, std::out_of_range);
 }
+
+void testHWCallout(const std::string& pn, const std::string ccin,
+                   const std::string& sn, const std::string& expectedPN,
+                   const std::string& expectedCCIN,
+                   const std::string& expectedSN)
+{
+    FRUIdentity fru{pn, ccin, sn};
+
+    EXPECT_EQ(fru.flattenedSize(), 28);
+    EXPECT_EQ(fru.type(), 0x4944);
+    EXPECT_EQ(fru.failingComponentType(), FRUIdentity::hardwareFRU);
+    EXPECT_EQ(fru.getPN().value(), expectedPN);
+    EXPECT_EQ(fru.getCCIN().value(), expectedCCIN);
+    EXPECT_EQ(fru.getSN().value(), expectedSN);
+    EXPECT_FALSE(fru.getMaintProc());
+
+    // Flatten and unflatten, then compare again
+    std::vector<uint8_t> data;
+    Stream stream{data};
+    fru.flatten(stream);
+
+    EXPECT_EQ(data.size(), fru.flattenedSize());
+
+    stream.offset(0);
+    FRUIdentity newFRU{stream};
+
+    EXPECT_EQ(newFRU.flattenedSize(), fru.flattenedSize());
+    EXPECT_EQ(newFRU.type(), fru.type());
+    EXPECT_EQ(newFRU.failingComponentType(), fru.failingComponentType());
+    EXPECT_EQ(newFRU.getPN().value(), fru.getPN().value());
+    EXPECT_EQ(newFRU.getCCIN().value(), fru.getCCIN().value());
+    EXPECT_EQ(newFRU.getSN().value(), fru.getSN().value());
+    EXPECT_FALSE(newFRU.getMaintProc());
+}
+
+// Test the constructor that takes in a PN/SN/CCIN
+TEST(FRUIdentityTest, CreateHardwareCalloutTest)
+{
+    // The right sizes
+    testHWCallout("1234567", "1234", "123456789ABC",
+                  // expected
+                  "1234567", "1234", "123456789ABC");
+
+    // Too long
+    testHWCallout("1234567long", "1234long", "123456789ABClong",
+                  // expected
+                  "1234567", "1234", "123456789ABC");
+    // Too short
+    testHWCallout("11", "22", "333",
+                  // expected
+                  "11", "22", "333");
+
+    // empty
+    testHWCallout("", "", "",
+                  // expected
+                  "", "", "");
+
+    // Leading spaces in the part number will be stripped
+    testHWCallout("    567", "1234", "123456789ABC",
+                  // expected
+                  "567", "1234", "123456789ABC");
+
+    // All spaces in the part number
+    testHWCallout("       ", "1234", "123456789ABC",
+                  // expected
+                  "", "1234", "123456789ABC");
+}
+
+// Test the constructor that takes in a maint procedure
+TEST(FRUIdentityTest, CreateProcedureCalloutTest)
+{
+    FRUIdentity fru{MaintProcedure::noVPDforFRU};
+
+    EXPECT_EQ(fru.flattenedSize(), 12);
+    EXPECT_EQ(fru.type(), 0x4944);
+    EXPECT_EQ(fru.failingComponentType(), FRUIdentity::maintenanceProc);
+    EXPECT_EQ(fru.getMaintProc().value(), "BMCSP01");
+    EXPECT_FALSE(fru.getPN());
+    EXPECT_FALSE(fru.getCCIN());
+    EXPECT_FALSE(fru.getSN());
+
+    // Flatten and unflatten, then compare again
+    std::vector<uint8_t> data;
+    Stream stream{data};
+    fru.flatten(stream);
+
+    EXPECT_EQ(data.size(), fru.flattenedSize());
+
+    stream.offset(0);
+    FRUIdentity newFRU{stream};
+
+    EXPECT_EQ(newFRU.flattenedSize(), 12);
+    EXPECT_EQ(newFRU.type(), 0x4944);
+    EXPECT_EQ(newFRU.failingComponentType(), FRUIdentity::maintenanceProc);
+    EXPECT_EQ(newFRU.getMaintProc().value(), "BMCSP01");
+    EXPECT_FALSE(newFRU.getPN());
+    EXPECT_FALSE(newFRU.getCCIN());
+    EXPECT_FALSE(newFRU.getSN());
+}