PEL: Let Callout object ctor take a MRU list

A list of MRUs (Manufacturing Replaceable Units) can now be passed into
the Callout object constructor.  There is also a new MRU object
constructor that will build that object with a MRU list as well.

This will be used in the future when someone wants to add MRUs to a
callout.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ic0e93e081a91ffc47dfd6642a34f02fd9a6edb8e
diff --git a/extensions/openpower-pels/callout.cpp b/extensions/openpower-pels/callout.cpp
index 4ddb897..f54d77e 100644
--- a/extensions/openpower-pels/callout.cpp
+++ b/extensions/openpower-pels/callout.cpp
@@ -80,7 +80,16 @@
 
 Callout::Callout(CalloutPriority priority, const std::string& locationCode,
                  const std::string& partNumber, const std::string& ccin,
-                 const std::string& serialNumber)
+                 const std::string& serialNumber) :
+    Callout(priority, locationCode, partNumber, ccin, serialNumber,
+            std::vector<MRU::MRUCallout>{})
+{
+}
+
+Callout::Callout(CalloutPriority priority, const std::string& locationCode,
+                 const std::string& partNumber, const std::string& ccin,
+                 const std::string& serialNumber,
+                 const std::vector<MRU::MRUCallout>& mrus)
 {
     _flags = calloutType | fruIdentIncluded;
 
@@ -91,6 +100,12 @@
     _fruIdentity =
         std::make_unique<FRUIdentity>(partNumber, ccin, serialNumber);
 
+    if (!mrus.empty())
+    {
+        _flags |= mruIncluded;
+        _mru = std::make_unique<MRU>(mrus);
+    }
+
     _size = flattenedSize();
 }
 
diff --git a/extensions/openpower-pels/callout.hpp b/extensions/openpower-pels/callout.hpp
index 1bfe9c8..66dcde0 100644
--- a/extensions/openpower-pels/callout.hpp
+++ b/extensions/openpower-pels/callout.hpp
@@ -84,6 +84,25 @@
      * @brief Constructor
      *
      * Creates the objects with a FRUIdentity substructure that calls
+     * out a normal hardware FRU, and takes a list of MRUs that will
+     * be added to the callout.
+     *
+     * @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
+     * @param[in] mrus - The MRUs, if any, to add to the callout
+     */
+    Callout(CalloutPriority priority, const std::string& locationCode,
+            const std::string& partNumber, const std::string& ccin,
+            const std::string& serialNumber,
+            const std::vector<MRU::MRUCallout>& mrus);
+
+    /**
+     * @brief Constructor
+     *
+     * Creates the objects with a FRUIdentity substructure that calls
      * out a maintenance procedure.
      *
      * @param[in] priority - The priority of the callout
diff --git a/extensions/openpower-pels/mru.cpp b/extensions/openpower-pels/mru.cpp
index a79c026..1a9ce00 100644
--- a/extensions/openpower-pels/mru.cpp
+++ b/extensions/openpower-pels/mru.cpp
@@ -26,6 +26,9 @@
 
 using namespace phosphor::logging;
 
+// The MRU substructure supports up to 15 MRUs.
+static constexpr size_t maxMRUs = 15;
+
 MRU::MRU(Stream& pel)
 {
     pel >> _type >> _size >> _flags >> _reserved4B;
@@ -53,6 +56,27 @@
     }
 }
 
+MRU::MRU(const std::vector<MRUCallout>& mrus)
+{
+    if (mrus.empty())
+    {
+        log<level::ERR>("Trying to create a MRU section with no MRUs");
+        throw std::runtime_error{"Trying to create a MRU section with no MRUs"};
+    }
+
+    _mrus = mrus;
+    if (_mrus.size() > maxMRUs)
+    {
+        _mrus.resize(maxMRUs);
+    }
+
+    _type = substructureType;
+    _size = sizeof(_type) + sizeof(_size) + sizeof(_flags) +
+            sizeof(_reserved4B) + (sizeof(MRUCallout) * _mrus.size());
+    _flags = _mrus.size();
+    _reserved4B = 0;
+}
+
 void MRU::flatten(Stream& pel) const
 {
     pel << _type << _size << _flags << _reserved4B;
diff --git a/extensions/openpower-pels/mru.hpp b/extensions/openpower-pels/mru.hpp
index e2293f1..db66e3b 100644
--- a/extensions/openpower-pels/mru.hpp
+++ b/extensions/openpower-pels/mru.hpp
@@ -57,6 +57,15 @@
     explicit MRU(Stream& pel);
 
     /**
+     * @brief Constructor
+     *
+     * Creates the object using the passed in MRUs
+     *
+     * @param[in] mrus - The  MRUs
+     */
+    explicit MRU(const std::vector<MRUCallout>& mrus);
+
+    /**
      * @brief Flatten the object into the stream
      *
      * @param[in] stream - The stream to write to
@@ -84,6 +93,26 @@
     }
 
     /**
+     * @brief Returns the size field
+     *
+     * @return size_t - The size of the MRU substructure
+     */
+    size_t size() const
+    {
+        return _size;
+    }
+
+    /**
+     * @brief Returns the flags field
+     *
+     * @return uint8_t - The flags
+     */
+    uint8_t flags() const
+    {
+        return _flags;
+    }
+
+    /**
      * @brief The type identifier value of this structure.
      */
     static const uint16_t substructureType = 0x4D52; // "MR"
diff --git a/test/openpower-pels/mru_test.cpp b/test/openpower-pels/mru_test.cpp
index e73c69a..d6b3eb6 100644
--- a/test/openpower-pels/mru_test.cpp
+++ b/test/openpower-pels/mru_test.cpp
@@ -41,6 +41,7 @@
 
     EXPECT_EQ(mru.flattenedSize(), data.size());
     EXPECT_EQ(mru.mrus().size(), 4);
+    EXPECT_EQ(mru.flags(), 4);
 
     EXPECT_EQ(mru.mrus().at(0).priority, 'H');
     EXPECT_EQ(mru.mrus().at(0).id, 0x01010101);
@@ -72,3 +73,64 @@
     Stream stream{data};
     EXPECT_THROW(MRU mru{stream}, std::out_of_range);
 }
+
+TEST(MRUTest, TestVectorConstructor)
+{
+    {
+        std::vector<MRU::MRUCallout> mrus{{'H', 1}, {'M', 2}, {'L', 3}};
+
+        MRU mru{mrus};
+
+        EXPECT_EQ(mru.mrus().size(), 3);
+        EXPECT_EQ(mru.flags(), 3);
+
+        EXPECT_EQ(mru.mrus().at(0).priority, 'H');
+        EXPECT_EQ(mru.mrus().at(0).id, 1);
+        EXPECT_EQ(mru.mrus().at(1).priority, 'M');
+        EXPECT_EQ(mru.mrus().at(1).id, 2);
+        EXPECT_EQ(mru.mrus().at(2).priority, 'L');
+        EXPECT_EQ(mru.mrus().at(2).id, 3);
+
+        // Flatten and unflatten
+        std::vector<uint8_t> data;
+        Stream stream{data};
+
+        mru.flatten(stream);
+        EXPECT_EQ(mru.size(), data.size());
+
+        stream.offset(0);
+        MRU newMRU{stream};
+
+        EXPECT_EQ(newMRU.flattenedSize(), data.size());
+        EXPECT_EQ(newMRU.size(), data.size());
+        EXPECT_EQ(newMRU.mrus().size(), 3);
+
+        EXPECT_EQ(newMRU.mrus().at(0).priority, 'H');
+        EXPECT_EQ(newMRU.mrus().at(0).id, 1);
+        EXPECT_EQ(newMRU.mrus().at(1).priority, 'M');
+        EXPECT_EQ(newMRU.mrus().at(1).id, 2);
+        EXPECT_EQ(newMRU.mrus().at(2).priority, 'L');
+        EXPECT_EQ(newMRU.mrus().at(2).id, 3);
+    }
+
+    {
+        // Too many MRUs
+        std::vector<MRU::MRUCallout> mrus;
+        for (uint32_t i = 0; i < 20; i++)
+        {
+            MRU::MRUCallout mru = {'H', i};
+            mrus.push_back(mru);
+        }
+
+        MRU mru{mrus};
+
+        EXPECT_EQ(mru.mrus().size(), 15);
+        EXPECT_EQ(mru.flags(), 15);
+    }
+
+    {
+        // Too few MRUs
+        std::vector<MRU::MRUCallout> mrus;
+        EXPECT_THROW(MRU mru{mrus}, std::runtime_error);
+    }
+}
diff --git a/test/openpower-pels/src_callout_test.cpp b/test/openpower-pels/src_callout_test.cpp
index fa93111..12f8c2b 100644
--- a/test/openpower-pels/src_callout_test.cpp
+++ b/test/openpower-pels/src_callout_test.cpp
@@ -276,6 +276,34 @@
             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
         }
     }
+
+    {
+        // With MRUs
+        std::vector<MRU::MRUCallout> mruList{{'H', 1}, {'H', 2}};
+
+        Callout callout{CalloutPriority::high, "U99-P5", "1234567", "ABCD",
+                        "123456789ABC",        mruList};
+
+        EXPECT_EQ(callout.flags(), Callout::calloutType |
+                                       Callout::fruIdentIncluded |
+                                       Callout::mruIncluded);
+
+        EXPECT_EQ(callout.priority(), 'H');
+        EXPECT_EQ(callout.locationCode(), "U99-P5");
+        EXPECT_EQ(callout.locationCodeSize(), 8);
+
+        auto& fru = callout.fruIdentity();
+        EXPECT_EQ(fru->getPN().value(), "1234567");
+        EXPECT_EQ(fru->getCCIN().value(), "ABCD");
+        EXPECT_EQ(fru->getSN().value(), "123456789ABC");
+
+        auto& mruSection = callout.mru();
+        EXPECT_EQ(mruSection->mrus().size(), 2);
+        EXPECT_EQ(mruSection->mrus().at(0).priority, 'H');
+        EXPECT_EQ(mruSection->mrus().at(0).id, 1);
+        EXPECT_EQ(mruSection->mrus().at(1).priority, 'H');
+        EXPECT_EQ(mruSection->mrus().at(1).id, 2);
+    }
 }
 
 // Create a callout object by passing in the maintenance procedure to add.