FruDevice: Parsing Multirecord UUID

Extend entity-manager's FruDevice to read UUID from FRU EEPROM
and update to dbus at MULTIRECORD_UUID.
The Record Type ID is: 0x03 (Management Access Record)
Sub-record Type is: 0x07 (System Unique ID)

Tested:
1. Get the UUID value:
   busctl get-property xyz.openbmc_project.FruDevice \
   /xyz/openbmc_project/FruDevice/MB \
   xyz.openbmc_project.FruDevice \
   MULTIRECORD_UUID
   s "e74d8ad8-ed3b-11ec-ace5-34025a7001de"

Signed-off-by: Hieu Huynh <hieuh@os.amperecomputing.com>
Change-Id: I61fbea9a5d90b8d857d49444775f80761386845b
diff --git a/src/fru_utils.cpp b/src/fru_utils.cpp
index 808179f..ab3283b 100644
--- a/src/fru_utils.cpp
+++ b/src/fru_utils.cpp
@@ -21,9 +21,11 @@
 #include <cstddef>
 #include <cstdint>
 #include <filesystem>
+#include <iomanip>
 #include <iostream>
 #include <numeric>
 #include <set>
+#include <sstream>
 #include <string>
 #include <vector>
 
@@ -63,6 +65,32 @@
     languageDependent = 0x3,
 };
 
+enum MultiRecordType : uint8_t
+{
+    powerSupplyInfo = 0x00,
+    dcOutput = 0x01,
+    dcLoad = 0x02,
+    managementAccessRecord = 0x03,
+    baseCompatibilityRecord = 0x04,
+    extendedCompatibilityRecord = 0x05,
+    resvASFSMBusDeviceRecord = 0x06,
+    resvASFLegacyDeviceAlerts = 0x07,
+    resvASFRemoteControl = 0x08,
+    extendedDCOutput = 0x09,
+    extendedDCLoad = 0x0A
+};
+
+enum SubManagementAccessRecord : uint8_t
+{
+    systemManagementURL = 0x01,
+    systemName = 0x02,
+    systemPingAddress = 0x03,
+    componentManagementURL = 0x04,
+    componentName = 0x05,
+    componentPingAddress = 0x06,
+    systemUniqueID = 0x07
+};
+
 /* Decode FRU data into a std::string, given an input iterator and end. If the
  * state returned is fruDataOk, then the resulting string is the decoded FRU
  * data. The input iterator is advanced past the data consumed.
@@ -256,6 +284,86 @@
     return true;
 }
 
+static void parseMultirecordUUID(
+    const std::vector<uint8_t>& device,
+    boost::container::flat_map<std::string, std::string>& result)
+{
+    constexpr size_t uuidDataLen = 16;
+    constexpr size_t multiRecordHeaderLen = 5;
+    /* UUID record data, plus one to skip past the sub-record type byte */
+    constexpr size_t uuidRecordData = multiRecordHeaderLen + 1;
+    constexpr size_t multiRecordEndOfListMask = 0x80;
+    /* The UUID {00112233-4455-6677-8899-AABBCCDDEEFF} would thus be represented
+     * as: 0x33 0x22 0x11 0x00 0x55 0x44 0x77 0x66 0x88 0x99 0xAA 0xBB 0xCC 0xDD
+     * 0xEE 0xFF
+     */
+    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));
+
+    if (areaOffset == 0)
+    {
+        return;
+    }
+
+    areaOffset *= fruBlockSize;
+    std::vector<uint8_t>::const_iterator fruBytesIter = device.begin() +
+                                                        areaOffset;
+
+    /* Verify area offset */
+    if (!verifyOffset(device, fruAreas::fruAreaMultirecord, *fruBytesIter))
+    {
+        return;
+    }
+    while (areaOffset + uuidRecordData + uuidDataLen <= device.size())
+    {
+        if ((areaOffset < device.size()) &&
+            (device[areaOffset] ==
+             (uint8_t)MultiRecordType::managementAccessRecord))
+        {
+            if ((areaOffset + multiRecordHeaderLen < device.size()) &&
+                (device[areaOffset + multiRecordHeaderLen] ==
+                 (uint8_t)SubManagementAccessRecord::systemUniqueID))
+            {
+                /* Layout of UUID:
+                 * source: https://www.ietf.org/rfc/rfc4122.txt
+                 *
+                 * UUID binary format (16 bytes):
+                 * 4B-2B-2B-2B-6B (big endian)
+                 *
+                 * UUID string is 36 length of characters (36 bytes):
+                 * 0        9    14   19   24
+                 * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+                 *    be     be   be   be       be
+                 * be means it should be converted to big endian.
+                 */
+                /* Get UUID bytes to UUID string */
+                std::stringstream tmp;
+                tmp << std::hex << std::setfill('0');
+                for (size_t i = 0; i < uuidDataLen; i++)
+                {
+                    tmp << std::setw(2)
+                        << static_cast<uint16_t>(
+                               device[areaOffset + uuidRecordData +
+                                      uuidCharOrder[i]]);
+                }
+                std::string uuidStr = tmp.str();
+                result["MULTIRECORD_UUID"] =
+                    uuidStr.substr(0, 8) + '-' + uuidStr.substr(8, 4) + '-' +
+                    uuidStr.substr(12, 4) + '-' + uuidStr.substr(16, 4) + '-' +
+                    uuidStr.substr(20, 12);
+                break;
+            }
+        }
+        if ((device[areaOffset + 1] & multiRecordEndOfListMask) != 0)
+        {
+            break;
+        }
+        areaOffset = areaOffset + device[areaOffset + 2] + multiRecordHeaderLen;
+    }
+}
+
 resCodes
     formatIPMIFRU(const std::vector<uint8_t>& fruBytes,
                   boost::container::flat_map<std::string, std::string>& result)
@@ -457,6 +565,9 @@
         }
     }
 
+    /* Parsing the Multirecord UUID */
+    parseMultirecordUUID(fruBytes, result);
+
     return ret;
 }