PEL: MTMS class to handle the type-model and SN

This class represents the (M)achine (T)ype-(M)odel (S)erial number
structure for the PEL, where it is used in both the ExtendedUserHeader
and FailingMTMS sections.

It consists of an 8 byte machine type+model field of the form TTT-MMMM,
followed by a 12 byte machine serial number field.  Unused bytes are set
to 0.

Note that this is not a PEL section itself. It's just used by other PEL
sections.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I15f9858e951a913ab2353cf93b7f20cc2709c502
diff --git a/extensions/openpower-pels/mtms.cpp b/extensions/openpower-pels/mtms.cpp
new file mode 100644
index 0000000..355431f
--- /dev/null
+++ b/extensions/openpower-pels/mtms.cpp
@@ -0,0 +1,80 @@
+#include "mtms.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+MTMS::MTMS()
+{
+    memset(_machineTypeAndModel.data(), 0, mtmSize);
+    memset(_serialNumber.data(), 0, snSize);
+}
+
+MTMS::MTMS(const std::string& typeModel, const std::string& serialNumber)
+{
+    memset(_machineTypeAndModel.data(), 0, mtmSize);
+    memset(_serialNumber.data(), 0, snSize);
+
+    // Copy in as much as the fields as possible
+    for (size_t i = 0; i < mtmSize; i++)
+    {
+        if (typeModel.size() > i)
+        {
+            _machineTypeAndModel[i] = typeModel[i];
+        }
+    }
+
+    for (size_t i = 0; i < snSize; i++)
+    {
+        if (serialNumber.size() > i)
+        {
+            _serialNumber[i] = serialNumber[i];
+        }
+    }
+}
+
+MTMS::MTMS(Stream& stream)
+{
+    for (size_t i = 0; i < mtmSize; i++)
+    {
+        stream >> _machineTypeAndModel[i];
+    }
+
+    for (size_t i = 0; i < snSize; i++)
+    {
+        stream >> _serialNumber[i];
+    }
+}
+
+Stream& operator<<(Stream& s, MTMS& mtms)
+{
+    for (size_t i = 0; i < MTMS::mtmSize; i++)
+    {
+        s << mtms.machineTypeAndModel()[i];
+    }
+
+    for (size_t i = 0; i < MTMS::snSize; i++)
+    {
+        s << mtms.machineSerialNumber()[i];
+    }
+
+    return s;
+}
+
+Stream& operator>>(Stream& s, MTMS& mtms)
+{
+    for (size_t i = 0; i < MTMS::mtmSize; i++)
+    {
+        s >> mtms.machineTypeAndModel()[i];
+    }
+
+    for (size_t i = 0; i < MTMS::snSize; i++)
+    {
+        s >> mtms.machineSerialNumber()[i];
+    }
+
+    return s;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/mtms.hpp b/extensions/openpower-pels/mtms.hpp
new file mode 100644
index 0000000..fe8723d
--- /dev/null
+++ b/extensions/openpower-pels/mtms.hpp
@@ -0,0 +1,128 @@
+#pragma once
+
+#include "stream.hpp"
+
+#include <string>
+
+namespace openpower
+{
+namespace pels
+{
+
+/** @class MTMS
+ *
+ * (M)achine (T)ype-(M)odel (S)erialNumber
+ *
+ * Represents the PEL's Machine Type / Model / Serial Number
+ * structure, which shows up in multiple places in a PEL.
+ *
+ * It holds 8 bytes for the Type+Model, and 12 for the SN.
+ * Unused bytes are set to 0s.
+ *
+ * The type and model are combined into a single field.
+ */
+class MTMS
+{
+  public:
+    MTMS(const MTMS&) = default;
+    MTMS& operator=(const MTMS&) = default;
+    MTMS(MTMS&&) = default;
+    MTMS& operator=(MTMS&&) = default;
+    ~MTMS() = default;
+
+    enum
+    {
+        mtmSize = 8,
+        snSize = 12
+    };
+
+    /**
+     * @brief Constructor
+     */
+    MTMS();
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] typeModel - The machine type+model
+     * @param[in] serialNumber - The machine serial number
+     */
+    MTMS(const std::string& typeModel, const std::string& serialNumber);
+
+    /**
+     * @brief Constructor
+     *
+     * Fills in this class's data fields from the stream.
+     *
+     * @param[in] pel - the PEL data stream
+     */
+    explicit MTMS(Stream& stream);
+
+    /**
+     * @brief Returns the machine type/model value
+     *
+     * @return std::array<uint8_t, mtmSize>&  - The TM value
+     */
+    std::array<uint8_t, mtmSize>& machineTypeAndModel()
+    {
+        return _machineTypeAndModel;
+    }
+
+    /**
+     * @brief Returns the machine serial number value
+     *
+     * @return std::array<uint8_t, snSize>& - The SN value
+     */
+    std::array<uint8_t, snSize>& machineSerialNumber()
+    {
+        return _serialNumber;
+    }
+
+    /**
+     * @brief Returns the size of the data when flattened
+     *
+     * @return size_t - The size of the data
+     */
+    static constexpr size_t flattenedSize()
+    {
+        return mtmSize + snSize;
+    }
+
+  private:
+    /**
+     * @brief The type+model value
+     *
+     *     Of the form TTTT-MMM where:
+     *        TTTT = machine type
+     *        MMM = machine model
+     */
+    std::array<uint8_t, mtmSize> _machineTypeAndModel;
+
+    /**
+     * @brief Machine Serial Number
+     */
+    std::array<uint8_t, snSize> _serialNumber;
+};
+
+/**
+ * @brief Stream extraction operator for MTMS
+ *
+ * @param[in] s - the stream
+ * @param[out] mtms - the MTMS object
+ *
+ * @return Stream&
+ */
+Stream& operator>>(Stream& s, MTMS& mtms);
+
+/**
+ * @brief Stream insertion operator for MTMS
+ *
+ * @param[out] s - the stream
+ * @param[in] mtms - the MTMS object
+ *
+ * @return Stream&
+ */
+Stream& operator<<(Stream& s, MTMS& mtms);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index def28a1..6a58db8 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -3,6 +3,7 @@
 	extensions/openpower-pels/entry_points.cpp \
 	extensions/openpower-pels/log_id.cpp \
 	extensions/openpower-pels/manager.cpp \
+	extensions/openpower-pels/mtms.cpp \
 	extensions/openpower-pels/paths.cpp \
 	extensions/openpower-pels/pel.cpp \
 	extensions/openpower-pels/private_header.cpp \
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 48e1872..51f3ecc 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -4,6 +4,7 @@
 	additional_data_test \
 	bcd_time_test \
 	log_id_test \
+	mtms_test \
 	pel_test \
 	pel_manager_test \
 	private_header_test \
@@ -102,4 +103,12 @@
 	$(pel_objects) \
 	$(top_builddir)/extensions/openpower-pels/manager.o \
 	$(top_builddir)/extensions/openpower-pels/repository.o
-pel_manager_test_LDFLAGS = $(test_ldflags)
\ No newline at end of file
+pel_manager_test_LDFLAGS = $(test_ldflags)
+
+mtms_test_SOURCES = %reldir%/mtms_test.cpp
+mtms_test_CPPFLAGS = $(test_cppflags)
+mtms_test_CXXFLAGS = $(test_cxxflags)
+mtms_test_LDADD = \
+	$(test_ldadd) \
+	$(top_builddir)/extensions/openpower-pels/mtms.o
+mtms_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/mtms_test.cpp b/test/openpower-pels/mtms_test.cpp
new file mode 100644
index 0000000..999100f
--- /dev/null
+++ b/test/openpower-pels/mtms_test.cpp
@@ -0,0 +1,109 @@
+#include "extensions/openpower-pels/mtms.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(MTMSTest, SizeTest)
+{
+    EXPECT_EQ(MTMS::flattenedSize(), 20);
+}
+
+TEST(MTMSTest, ConstructorTest)
+{
+    {
+        std::string tm{"TTTT-MMM"};
+        std::string sn{"123456789ABC"};
+
+        MTMS mtms{tm, sn};
+
+        std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'};
+        EXPECT_EQ(t, mtms.machineTypeAndModel());
+
+        std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6',
+                                  '7', '8', '9', 'A', 'B', 'C'};
+        EXPECT_EQ(s, mtms.machineSerialNumber());
+    }
+
+    {
+        // too long- truncate it
+        std::string tm{"TTTT-MMME"};
+        std::string sn{"123456789ABCE"};
+
+        MTMS mtms{tm, sn};
+
+        std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'};
+        EXPECT_EQ(t, mtms.machineTypeAndModel());
+
+        std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6',
+                                  '7', '8', '9', 'A', 'B', 'C'};
+        EXPECT_EQ(s, mtms.machineSerialNumber());
+    }
+
+    {
+        // short
+        std::string tm{"TTTT"};
+        std::string sn{"1234"};
+
+        MTMS mtms{tm, sn};
+
+        std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', 0, 0, 0, 0};
+        EXPECT_EQ(t, mtms.machineTypeAndModel());
+
+        std::array<uint8_t, 12> s{'1', '2', '3', '4', 0, 0, 0, 0, 0, 0, 0, 0};
+        EXPECT_EQ(s, mtms.machineSerialNumber());
+    }
+
+    {
+        // Stream constructor
+        std::vector<uint8_t> data{'T', 'T', 'T', 'T', '-', 'M', 'M',
+                                  'M', '1', '2', '3', '4', '5', '6',
+                                  '7', '8', '9', 'A', 'B', 'C'};
+        Stream stream{data};
+
+        MTMS mtms{stream};
+
+        std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'};
+        EXPECT_EQ(t, mtms.machineTypeAndModel());
+
+        std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6',
+                                  '7', '8', '9', 'A', 'B', 'C'};
+        EXPECT_EQ(s, mtms.machineSerialNumber());
+    }
+}
+
+TEST(MTMSTest, OperatorExtractTest)
+{
+    std::string tm{"TTTT-MMM"};
+    std::string sn{"123456789ABC"};
+
+    MTMS mtms{tm, sn};
+
+    // Check that we can extract the same data
+    std::vector<uint8_t> data;
+    Stream stream{data};
+    stream << mtms;
+
+    std::vector<uint8_t> expected{'T', 'T', 'T', 'T', '-', 'M', 'M',
+                                  'M', '1', '2', '3', '4', '5', '6',
+                                  '7', '8', '9', 'A', 'B', 'C'};
+    EXPECT_EQ(expected, data);
+}
+
+TEST(MTMSTest, OperatorInsertTest)
+{
+    std::vector<uint8_t> data{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2',
+                              '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'};
+    Stream stream{data};
+
+    // Check that when we insert data it is what's expected
+    MTMS mtms;
+    stream >> mtms;
+
+    std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'};
+    EXPECT_EQ(t, mtms.machineTypeAndModel());
+
+    std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6',
+                              '7', '8', '9', 'A', 'B', 'C'};
+    EXPECT_EQ(s, mtms.machineSerialNumber());
+}