PEL: Add Callout object constructors

Add constructors to the PEL Callout object to create a FRU callout,
either a hardware FRU with PN/SN/CCIN, or a maintenance procedure.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I015370fbfa4e5522eada316d12c10b130e1de4be
diff --git a/extensions/openpower-pels/callout.cpp b/extensions/openpower-pels/callout.cpp
index 95c6408..905c212 100644
--- a/extensions/openpower-pels/callout.cpp
+++ b/extensions/openpower-pels/callout.cpp
@@ -26,6 +26,8 @@
 
 using namespace phosphor::logging;
 
+constexpr size_t locationCodeMaxSize = 80;
+
 Callout::Callout(Stream& pel)
 {
     pel >> _size >> _flags >> _priority >> _locationCodeSize;
@@ -76,7 +78,67 @@
     }
 }
 
-size_t Callout::flattenedSize()
+Callout::Callout(CalloutPriority priority, const std::string& locationCode,
+                 const std::string& partNumber, const std::string& ccin,
+                 const std::string& serialNumber)
+{
+    _flags = calloutType | fruIdentIncluded;
+
+    _priority = static_cast<uint8_t>(priority);
+
+    setLocationCode(locationCode);
+
+    _fruIdentity =
+        std::make_unique<FRUIdentity>(partNumber, ccin, serialNumber);
+
+    _size = flattenedSize();
+}
+
+Callout::Callout(CalloutPriority priority, MaintProcedure procedure)
+{
+    _flags = calloutType | fruIdentIncluded;
+
+    _priority = static_cast<uint8_t>(priority);
+
+    _locationCodeSize = 0;
+
+    _fruIdentity = std::make_unique<FRUIdentity>(procedure);
+
+    _size = flattenedSize();
+}
+
+void Callout::setLocationCode(const std::string& locationCode)
+{
+    if (locationCode.empty())
+    {
+        _locationCodeSize = 0;
+        return;
+    }
+
+    std::copy(locationCode.begin(), locationCode.end(),
+              std::back_inserter(_locationCode));
+
+    if (_locationCode.size() < locationCodeMaxSize)
+    {
+        // Add a NULL, and then pad to a 4B boundary
+        _locationCode.push_back('\0');
+
+        while (_locationCode.size() % 4)
+        {
+            _locationCode.push_back('\0');
+        }
+    }
+    else
+    {
+        // Too big - truncate it and ensure it ends in a NULL.
+        _locationCode.resize(locationCodeMaxSize);
+        _locationCode.back() = '\0';
+    }
+
+    _locationCodeSize = _locationCode.size();
+}
+
+size_t Callout::flattenedSize() const
 {
     size_t size = sizeof(_size) + sizeof(_flags) + sizeof(_priority) +
                   sizeof(_locationCodeSize) + _locationCodeSize;
diff --git a/extensions/openpower-pels/callout.hpp b/extensions/openpower-pels/callout.hpp
index caf3aec..c8bb0a3 100644
--- a/extensions/openpower-pels/callout.hpp
+++ b/extensions/openpower-pels/callout.hpp
@@ -3,6 +3,7 @@
 #include "fru_identity.hpp"
 #include "mru.hpp"
 #include "pce_identity.hpp"
+#include "pel_types.hpp"
 #include "stream.hpp"
 
 namespace openpower
@@ -34,6 +35,19 @@
 class Callout
 {
   public:
+    /**
+     * @brief Which callout substructures are included.
+     */
+    enum calloutFlags
+    {
+        calloutType = 0b0010'0000,
+        fruIdentIncluded = 0b0000'1000,
+        mruIncluded = 0b0000'0100
+
+        // Leaving out the various PCE identity ones since
+        // we don't use them.
+    };
+
     Callout() = delete;
     ~Callout() = default;
     Callout(const Callout&) = delete;
@@ -51,11 +65,38 @@
     explicit Callout(Stream& pel);
 
     /**
+     * @brief Constructor
+     *
+     * Creates the objects with a FRUIdentity substructure that calls
+     * out a normal hardware FRU.
+     *
+     * @param[in] priority - The priority of the callout
+     * @param[in] locationCode - The location code of the callout
+     * @param[in] partNumber - The part number of the callout
+     * @param[in] ccin - The CCIN of the callout
+     * @param[in] serialNumber - The serial number of the callout
+     */
+    Callout(CalloutPriority priority, const std::string& locationCode,
+            const std::string& partNumber, const std::string& ccin,
+            const std::string& serialNumber);
+
+    /**
+     * @brief Constructor
+     *
+     * Creates the objects with a FRUIdentity substructure that calls
+     * out maintenance procedure.
+     *
+     * @param[in] priority - The priority of the callout
+     * @param[in] procedure - The maintenance procedure
+     */
+    Callout(CalloutPriority priority, MaintProcedure procedure);
+
+    /**
      * @brief Returns the size of this object when flattened into a PEL
      *
      * @return size_t - The size of the section
      */
-    size_t flattenedSize();
+    size_t flattenedSize() const;
 
     /**
      * @brief Flatten the object into the stream
@@ -101,6 +142,16 @@
     }
 
     /**
+     * @brief Returns the location code size
+     *
+     * @return size_t - The size, including the terminating null.
+     */
+    size_t locationCodeSize() const
+    {
+        return _locationCodeSize;
+    }
+
+    /**
      * @brief Returns the FRU identity substructure
      *
      * @return const std::unique_ptr<FRUIdentity>&
@@ -123,7 +174,7 @@
     /**
      * @brief Returns the MRU identity substructure
      *
-     * @return const std::unique_ptr<FRUIdentity>&
+     * @return const std::unique_ptr<MRU>&
      */
     const std::unique_ptr<MRU>& mru() const
     {
@@ -132,6 +183,13 @@
 
   private:
     /**
+     * @brief Sets the location code field
+     *
+     * @param[in] locationCode - The location code string
+     */
+    void setLocationCode(const std::string& locationCode);
+
+    /**
      * @brief The size of this structure in the PEL
      */
     uint8_t _size;
@@ -152,7 +210,7 @@
      * Includes the NULL termination, and must be a
      * multiple of 4 (padded with zeros)
      */
-    uint8_t _locationCodeSize;
+    uint8_t _locationCodeSize = 0;
 
     /**
      * @brief NULL terminated location code
diff --git a/test/openpower-pels/src_callout_test.cpp b/test/openpower-pels/src_callout_test.cpp
index 3fd2a5c..36e9e38 100644
--- a/test/openpower-pels/src_callout_test.cpp
+++ b/test/openpower-pels/src_callout_test.cpp
@@ -159,3 +159,162 @@
 
     EXPECT_TRUE(callout.locationCode().empty());
 }
+
+// Create a callout object by passing in the hardware fields to add
+TEST(CalloutTest, TestHardwareCallout)
+{
+    constexpr size_t fruIdentitySize = 28;
+
+    {
+        Callout callout{CalloutPriority::high, "U99-42.5-P1-C2-E1", "1234567",
+                        "ABCD", "123456789ABC"};
+
+        // size/flags/pri/locsize fields +
+        // rounded up location code length +
+        // FRUIdentity size
+        size_t size = 4 + 20 + fruIdentitySize;
+
+        EXPECT_EQ(callout.flags(),
+                  Callout::calloutType | Callout::fruIdentIncluded);
+
+        EXPECT_EQ(callout.flattenedSize(), size);
+        EXPECT_EQ(callout.priority(), 'H');
+        EXPECT_EQ(callout.locationCode(), "U99-42.5-P1-C2-E1");
+        EXPECT_EQ(callout.locationCodeSize(), 20);
+
+        auto& fru = callout.fruIdentity();
+        EXPECT_EQ(fru->getPN().value(), "1234567");
+        EXPECT_EQ(fru->getCCIN().value(), "ABCD");
+        EXPECT_EQ(fru->getSN().value(), "123456789ABC");
+    }
+
+    {
+        // A 3B location code, plus null = 4
+        Callout callout{CalloutPriority::high, "123", "1234567", "ABCD",
+                        "123456789ABC"};
+
+        size_t size = 4 + 4 + fruIdentitySize;
+        EXPECT_EQ(callout.locationCodeSize(), 4);
+        EXPECT_EQ(callout.flattenedSize(), size);
+        EXPECT_EQ(callout.locationCode(), "123");
+    }
+
+    {
+        // A 4B location code, plus null = 5, then pad to 8
+        Callout callout{CalloutPriority::high, "1234", "1234567", "ABCD",
+                        "123456789ABC"};
+
+        size_t size = 4 + 8 + fruIdentitySize;
+        EXPECT_EQ(callout.locationCodeSize(), 8);
+        EXPECT_EQ(callout.flattenedSize(), size);
+        EXPECT_EQ(callout.locationCode(), "1234");
+    }
+
+    {
+        // A truncated location code (80 is max size, including null)
+        std::string locCode(81, 'L');
+        Callout callout{CalloutPriority::high, locCode, "1234567", "ABCD",
+                        "123456789ABC"};
+
+        size_t size = 4 + 80 + fruIdentitySize;
+        EXPECT_EQ(callout.locationCodeSize(), 80);
+        EXPECT_EQ(callout.flattenedSize(), size);
+
+        // take off 1 to get to 80, and another for the null
+        locCode = locCode.substr(0, locCode.size() - 2);
+        EXPECT_EQ(callout.locationCode(), locCode);
+    }
+
+    {
+        // A truncated location code by 1 because of the null
+        std::string locCode(80, 'L');
+        Callout callout{CalloutPriority::high, locCode, "1234567", "ABCD",
+                        "123456789ABC"};
+
+        size_t size = 4 + 80 + fruIdentitySize;
+        EXPECT_EQ(callout.locationCodeSize(), 80);
+        EXPECT_EQ(callout.flattenedSize(), size);
+
+        locCode.pop_back();
+        EXPECT_EQ(callout.locationCode(), locCode);
+    }
+
+    {
+        // Max size location code
+        std::string locCode(79, 'L');
+        Callout callout{CalloutPriority::low, locCode, "1234567", "ABCD",
+                        "123456789ABC"};
+
+        size_t size = 4 + 80 + fruIdentitySize;
+        EXPECT_EQ(callout.locationCodeSize(), 80);
+        EXPECT_EQ(callout.flattenedSize(), size);
+
+        EXPECT_EQ(callout.locationCode(), locCode);
+
+        // How about we flatten/unflatten this last one
+        std::vector<uint8_t> data;
+        Stream stream{data};
+
+        callout.flatten(stream);
+
+        {
+            Stream newStream{data};
+            Callout newCallout{newStream};
+
+            EXPECT_EQ(newCallout.flags(),
+                      Callout::calloutType | Callout::fruIdentIncluded);
+
+            EXPECT_EQ(newCallout.flattenedSize(), callout.flattenedSize());
+            EXPECT_EQ(newCallout.priority(), callout.priority());
+            EXPECT_EQ(newCallout.locationCode(), callout.locationCode());
+            EXPECT_EQ(newCallout.locationCodeSize(),
+                      callout.locationCodeSize());
+
+            auto& fru = newCallout.fruIdentity();
+            EXPECT_EQ(fru->getPN().value(), "1234567");
+            EXPECT_EQ(fru->getCCIN().value(), "ABCD");
+            EXPECT_EQ(fru->getSN().value(), "123456789ABC");
+        }
+    }
+}
+
+// Create a callout object by passing in the maintenance procedure to add.
+TEST(CalloutTest, TestProcedureCallout)
+{
+    Callout callout{CalloutPriority::medium, MaintProcedure::noVPDforFRU};
+
+    // size/flags/pri/locsize fields + FRUIdentity size
+    // No location code.
+    size_t size = 4 + 12;
+
+    EXPECT_EQ(callout.flags(),
+              Callout::calloutType | Callout::fruIdentIncluded);
+
+    EXPECT_EQ(callout.flattenedSize(), size);
+    EXPECT_EQ(callout.priority(), 'M');
+    EXPECT_EQ(callout.locationCode(), "");
+    EXPECT_EQ(callout.locationCodeSize(), 0);
+
+    auto& fru = callout.fruIdentity();
+    EXPECT_EQ(fru->getMaintProc().value(), "BMCSP01");
+
+    // flatten/unflatten
+    std::vector<uint8_t> data;
+    Stream stream{data};
+
+    callout.flatten(stream);
+
+    Stream newStream{data};
+    Callout newCallout{newStream};
+
+    EXPECT_EQ(newCallout.flags(),
+              Callout::calloutType | Callout::fruIdentIncluded);
+
+    EXPECT_EQ(newCallout.flattenedSize(), callout.flattenedSize());
+    EXPECT_EQ(newCallout.priority(), callout.priority());
+    EXPECT_EQ(newCallout.locationCode(), callout.locationCode());
+    EXPECT_EQ(newCallout.locationCodeSize(), callout.locationCodeSize());
+
+    auto& newFRU = newCallout.fruIdentity();
+    EXPECT_EQ(newFRU->getMaintProc().value(), fru->getMaintProc().value());
+}