Add unit test for full FRU decode

To start refactoring this, it is helpful if we have a representative
unit test that decodes a full FRU.

To simplify this, the internal interfaces have been updated to support
passing values by std::span<const uint8_t> instead of by reference to
std::vector.

Tested: Unit tests pass

Change-Id: If8a618969b99d3d8a32bc7203aa2f57d0f999bdf
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/meson.build b/meson.build
index f789a0d..cc5a78e 100644
--- a/meson.build
+++ b/meson.build
@@ -160,7 +160,7 @@
             'src/fru_utils.cpp',
             'src/fru_reader.cpp',
             cpp_args: test_boost_args,
-            dependencies: [boost, gtest, phosphor_logging_dep, sdbusplus],
+            dependencies: [boost, gtest, gmock, phosphor_logging_dep, sdbusplus],
             include_directories: 'src',
         ),
     )
diff --git a/src/fru_utils.cpp b/src/fru_utils.cpp
index 3ce9ef4..b5c3152 100644
--- a/src/fru_utils.cpp
+++ b/src/fru_utils.cpp
@@ -100,8 +100,8 @@
  * iterator is no longer usable.
  */
 std::pair<DecodeState, std::string> decodeFRUData(
-    std::vector<uint8_t>::const_iterator& iter,
-    const std::vector<uint8_t>::const_iterator& end, bool isLangEng)
+    std::span<const uint8_t>::const_iterator& iter,
+    std::span<const uint8_t>::const_iterator& end, bool isLangEng)
 {
     std::string value;
     unsigned int i = 0;
@@ -224,7 +224,7 @@
  * len:         Length of current area space and it is a multiple of 8 bytes
  *              as per specification
  */
-bool verifyOffset(const std::vector<uint8_t>& fruBytes, fruAreas currentArea,
+bool verifyOffset(std::span<const uint8_t> fruBytes, fruAreas currentArea,
                   uint8_t len)
 {
     unsigned int fruBytesSize = fruBytes.size();
@@ -290,7 +290,7 @@
 }
 
 static void parseMultirecordUUID(
-    const std::vector<uint8_t>& device,
+    std::span<const uint8_t> device,
     boost::container::flat_map<std::string, std::string>& result)
 {
     constexpr size_t uuidDataLen = 16;
@@ -304,8 +304,12 @@
      */
     const std::array<uint8_t, uuidDataLen> uuidCharOrder = {
         3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15};
-    uint32_t areaOffset =
-        device.at(getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord));
+    size_t offset = getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord);
+    if (offset >= device.size())
+    {
+        throw std::runtime_error("Multirecord UUID offset is out of range");
+    }
+    uint32_t areaOffset = device[offset];
 
     if (areaOffset == 0)
     {
@@ -313,7 +317,7 @@
     }
 
     areaOffset *= fruBlockSize;
-    std::vector<uint8_t>::const_iterator fruBytesIter =
+    std::span<const uint8_t>::const_iterator fruBytesIter =
         device.begin() + areaOffset;
 
     /* Verify area offset */
@@ -370,7 +374,7 @@
 }
 
 resCodes formatIPMIFRU(
-    const std::vector<uint8_t>& fruBytes,
+    std::span<const uint8_t> fruBytes,
     boost::container::flat_map<std::string, std::string>& result)
 {
     resCodes ret = resCodes::resOK;
@@ -394,7 +398,7 @@
             continue;
         }
         offset *= fruBlockSize;
-        std::vector<uint8_t>::const_iterator fruBytesIter =
+        std::span<const uint8_t>::const_iterator fruBytesIter =
             fruBytes.begin() + offset;
         if (fruBytesIter + fruBlockSize >= fruBytes.end())
         {
@@ -418,7 +422,7 @@
         }
 
         size_t fruAreaSize = *fruBytesIter * fruBlockSize;
-        std::vector<uint8_t>::const_iterator fruBytesIterEndArea =
+        std::span<const uint8_t>::const_iterator fruBytesIterEndArea =
             fruBytes.begin() + offset + fruAreaSize - 1;
         ++fruBytesIter;
 
@@ -579,15 +583,15 @@
 }
 
 // Calculate new checksum for fru info area
-uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
-                          std::vector<uint8_t>::const_iterator end)
+uint8_t calculateChecksum(std::span<const uint8_t>::const_iterator iter,
+                          std::span<const uint8_t>::const_iterator end)
 {
     constexpr int checksumMod = 256;
     uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
     return (checksumMod - sum) % checksumMod;
 }
 
-uint8_t calculateChecksum(std::vector<uint8_t>& fruAreaData)
+uint8_t calculateChecksum(std::span<const uint8_t> fruAreaData)
 {
     return calculateChecksum(fruAreaData.begin(), fruAreaData.end());
 }
diff --git a/src/fru_utils.hpp b/src/fru_utils.hpp
index dcd9752..b427c25 100644
--- a/src/fru_utils.hpp
+++ b/src/fru_utils.hpp
@@ -114,25 +114,25 @@
 
 char bcdPlusToChar(uint8_t val);
 
-bool verifyOffset(const std::vector<uint8_t>& fruBytes, fruAreas currentArea,
+bool verifyOffset(std::span<const uint8_t> fruBytes, fruAreas currentArea,
                   uint8_t len);
 
 std::pair<DecodeState, std::string> decodeFRUData(
-    std::vector<uint8_t>::const_iterator& iter,
-    const std::vector<uint8_t>::const_iterator& end, bool isLangEng);
+    std::span<const uint8_t>::const_iterator& iter,
+    std::span<const uint8_t>::const_iterator& end, bool isLangEng);
 
 bool checkLangEng(uint8_t lang);
 
 resCodes formatIPMIFRU(
-    const std::vector<uint8_t>& fruBytes,
+    std::span<const uint8_t> fruBytes,
     boost::container::flat_map<std::string, std::string>& result);
 
 std::vector<uint8_t>& getFRUInfo(const uint16_t& bus, const uint8_t& address);
 
-uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
-                          std::vector<uint8_t>::const_iterator end);
+uint8_t calculateChecksum(std::span<const uint8_t>::const_iterator iter,
+                          std::span<const uint8_t>::const_iterator end);
 
-uint8_t calculateChecksum(std::vector<uint8_t>& fruAreaData);
+uint8_t calculateChecksum(std::span<const uint8_t> fruAreaData);
 
 unsigned int updateFRUAreaLenAndChecksum(
     std::vector<uint8_t>& fruData, size_t fruAreaStart,
diff --git a/test/test_fru-utils.cpp b/test/test_fru-utils.cpp
index 70330a7..19aff02 100644
--- a/test/test_fru-utils.cpp
+++ b/test/test_fru-utils.cpp
@@ -4,8 +4,12 @@
 #include <array>
 #include <iterator>
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
 extern "C"
 {
 // Include for I2C_SMBUS_BLOCK_MAX
@@ -389,3 +393,45 @@
     EXPECT_TRUE(findFRUHeader(reader, "error", blockData, offset));
     EXPECT_EQ(0x6000, offset);
 }
+
+TEST(formatIPMIFRU, FullDecode)
+{
+    const std::array<uint8_t, 176> bmcFru = {
+        0x01, 0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0xf3, 0x01, 0x0a, 0x19, 0x1f,
+        0x0f, 0xe6, 0xc6, 0x4e, 0x56, 0x49, 0x44, 0x49, 0x41, 0xc5, 0x50, 0x33,
+        0x38, 0x30, 0x39, 0xcd, 0x31, 0x35, 0x38, 0x33, 0x33, 0x32, 0x34, 0x38,
+        0x30, 0x30, 0x31, 0x35, 0x30, 0xd2, 0x36, 0x39, 0x39, 0x2d, 0x31, 0x33,
+        0x38, 0x30, 0x39, 0x2d, 0x30, 0x34, 0x30, 0x34, 0x2d, 0x36, 0x30, 0x30,
+        0xc0, 0x01, 0x01, 0xd6, 0x4d, 0x41, 0x43, 0x3a, 0x20, 0x33, 0x43, 0x3a,
+        0x36, 0x44, 0x3a, 0x36, 0x36, 0x3a, 0x31, 0x34, 0x3a, 0x43, 0x38, 0x3a,
+        0x37, 0x41, 0xc1, 0x3b, 0x01, 0x09, 0x19, 0xc6, 0x4e, 0x56, 0x49, 0x44,
+        0x49, 0x41, 0xc9, 0x50, 0x33, 0x38, 0x30, 0x39, 0x2d, 0x42, 0x4d, 0x43,
+        0xd2, 0x36, 0x39, 0x39, 0x2d, 0x31, 0x33, 0x38, 0x30, 0x39, 0x2d, 0x30,
+        0x34, 0x30, 0x34, 0x2d, 0x36, 0x30, 0x30, 0xc4, 0x41, 0x45, 0x2e, 0x31,
+        0xcd, 0x31, 0x35, 0x38, 0x33, 0x33, 0x32, 0x34, 0x38, 0x30, 0x30, 0x31,
+        0x35, 0x30, 0xc0, 0xc4, 0x76, 0x30, 0x2e, 0x31, 0xc1, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0xb4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+    boost::container::flat_map<std::string, std::string> result;
+    ASSERT_EQ(formatIPMIFRU(bmcFru, result), resCodes::resOK);
+
+    EXPECT_THAT(
+        result,
+        UnorderedElementsAre(
+            Pair("BOARD_FRU_VERSION_ID", ""), Pair("BOARD_INFO_AM1", "01"),
+            Pair("BOARD_INFO_AM2", "MAC: 3C:6D:66:14:C8:7A"),
+            Pair("BOARD_LANGUAGE_CODE", "25"),
+            Pair("BOARD_MANUFACTURER", "NVIDIA"),
+            Pair("BOARD_MANUFACTURE_DATE", "20240831T055100Z"),
+            Pair("BOARD_PART_NUMBER", "699-13809-0404-600"),
+            Pair("BOARD_PRODUCT_NAME", "P3809"),
+            Pair("BOARD_SERIAL_NUMBER", "1583324800150"),
+            Pair("Common_Format_Version", "1"), Pair("PRODUCT_ASSET_TAG", ""),
+            Pair("PRODUCT_FRU_VERSION_ID", "v0.1"),
+            Pair("PRODUCT_LANGUAGE_CODE", "25"),
+            Pair("PRODUCT_MANUFACTURER", "NVIDIA"),
+            Pair("PRODUCT_PART_NUMBER", "699-13809-0404-600"),
+            Pair("PRODUCT_PRODUCT_NAME", "P3809-BMC"),
+            Pair("PRODUCT_SERIAL_NUMBER", "1583324800150"),
+            Pair("PRODUCT_VERSION", "AE.1")));
+}