split readFRUContents() into FruUtils module

Splits the readFRUContents() method into its own module.  This utility
code can then be more easily tested without extra dependencies.

Signed-off-by: Patrick Venture <venture@google.com>
Change-Id: I1b8fa7179d7d0092e0236e8586f99c45121eca84
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 53d3118..2552242 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -15,6 +15,7 @@
 */
 /// \file FruDevice.cpp
 
+#include "FruUtils.hpp"
 #include "Utils.hpp"
 
 #include <errno.h>
@@ -61,8 +62,6 @@
 constexpr size_t MAX_FRU_SIZE = 512;
 constexpr size_t MAX_EEPROM_PAGE_INDEX = 255;
 constexpr size_t busTimeoutSeconds = 5;
-constexpr size_t fruBlockSize = 8; // FRU areas are measured in 8-byte blocks
-constexpr size_t fruVersion = 1;   // Current FRU spec version number is 1
 
 constexpr const char* blacklistPath = PACKAGE_DIR "blacklist.json";
 
@@ -78,20 +77,6 @@
     resErr
 };
 
-enum class fruAreas
-{
-    fruAreaInternal = 0,
-    fruAreaChassis,
-    fruAreaBoard,
-    fruAreaProduct,
-    fruAreaMultirecord
-};
-inline fruAreas operator++(fruAreas& x)
-{
-    return x = static_cast<fruAreas>(std::underlying_type<fruAreas>::type(x) +
-                                     1);
-}
-
 static const std::vector<std::string> FRU_AREA_NAMES = {
     "INTERNAL", "CHASSIS", "BOARD", "PRODUCT", "MULTIRECORD"};
 const static std::regex NON_ASCII_REGEX("[^\x01-\x7f]");
@@ -128,15 +113,10 @@
 
 static const std::string FRU_CUSTOM_FIELD_NAME = "INFO_AM";
 
-static inline unsigned int getHeaderAreaFieldOffset(fruAreas area)
-{
-    return static_cast<unsigned int>(area) + 1;
-}
 static inline const std::string& getFruAreaName(fruAreas area)
 {
     return FRU_AREA_NAMES[static_cast<unsigned int>(area)];
 }
-bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData);
 uint8_t calculateChecksum(std::vector<uint8_t>::const_iterator iter,
                           std::vector<uint8_t>::const_iterator end);
 bool updateFRUProperty(
@@ -146,140 +126,6 @@
         std::pair<size_t, size_t>,
         std::shared_ptr<sdbusplus::asio::dbus_interface>>& dbusInterfaceMap);
 
-using ReadBlockFunc =
-    std::function<int64_t(int flag, int file, uint16_t address, uint16_t offset,
-                          uint8_t length, uint8_t* outBuf)>;
-
-// Read and validate FRU contents, given the flag required for raw i2c, the open
-// file handle, a read method, and a helper string for failures.
-std::vector<uint8_t> readFRUContents(int flag, int file, uint16_t address,
-                                     ReadBlockFunc readBlock,
-                                     const std::string& errorHelp)
-{
-    std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData;
-
-    if (readBlock(flag, file, address, 0x0, 0x8, blockData.data()) < 0)
-    {
-        std::cerr << "failed to read " << errorHelp << "\n";
-        return {};
-    }
-
-    // check the header checksum
-    if (!validateHeader(blockData))
-    {
-        if (DEBUG)
-        {
-            std::cerr << "Illegal header " << errorHelp << "\n";
-        }
-
-        return {};
-    }
-
-    std::vector<uint8_t> device;
-    device.insert(device.end(), blockData.begin(), blockData.begin() + 8);
-
-    bool hasMultiRecords = false;
-    size_t fruLength = fruBlockSize; // At least FRU header is present
-    for (fruAreas area = fruAreas::fruAreaInternal;
-         area <= fruAreas::fruAreaMultirecord; ++area)
-    {
-        // Offset value can be 255.
-        unsigned int areaOffset = device[getHeaderAreaFieldOffset(area)];
-        if (areaOffset == 0)
-        {
-            continue;
-        }
-
-        // MultiRecords are different. area is not tracking section, it's
-        // walking the common header.
-        if (area == fruAreas::fruAreaMultirecord)
-        {
-            hasMultiRecords = true;
-            break;
-        }
-
-        areaOffset *= fruBlockSize;
-
-        if (readBlock(flag, file, address, static_cast<uint16_t>(areaOffset),
-                      0x2, blockData.data()) < 0)
-        {
-            std::cerr << "failed to read " << errorHelp << "\n";
-            return {};
-        }
-
-        // Ignore data type (blockData is already unsigned).
-        size_t length = blockData[1] * fruBlockSize;
-        areaOffset += length;
-        fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
-    }
-
-    if (hasMultiRecords)
-    {
-        // device[area count] is the index to the last area because the 0th
-        // entry is not an offset in the common header.
-        unsigned int areaOffset =
-            device[getHeaderAreaFieldOffset(fruAreas::fruAreaMultirecord)];
-        areaOffset *= fruBlockSize;
-
-        // the multi-area record header is 5 bytes long.
-        constexpr size_t multiRecordHeaderSize = 5;
-        constexpr uint8_t multiRecordEndOfListMask = 0x80;
-
-        // Sanity hard-limit to 64KB.
-        while (areaOffset < std::numeric_limits<uint16_t>::max())
-        {
-            // In multi-area, the area offset points to the 0th record, each
-            // record has 3 bytes of the header we care about.
-            if (readBlock(flag, file, address,
-                          static_cast<uint16_t>(areaOffset), 0x3,
-                          blockData.data()) < 0)
-            {
-                std::cerr << "failed to read " << errorHelp << "\n";
-                return {};
-            }
-
-            // Ok, let's check the record length, which is in bytes (unsigned,
-            // up to 255, so blockData should hold uint8_t not char)
-            size_t recordLength = blockData[2];
-            areaOffset += (recordLength + multiRecordHeaderSize);
-            fruLength = (areaOffset > fruLength) ? areaOffset : fruLength;
-
-            // If this is the end of the list bail.
-            if ((blockData[1] & multiRecordEndOfListMask))
-            {
-                break;
-            }
-        }
-    }
-
-    // You already copied these first 8 bytes (the ipmi fru header size)
-    fruLength -= std::min(fruBlockSize, fruLength);
-
-    int readOffset = fruBlockSize;
-
-    while (fruLength > 0)
-    {
-        size_t requestLength =
-            std::min(static_cast<size_t>(I2C_SMBUS_BLOCK_MAX), fruLength);
-
-        if (readBlock(flag, file, address, static_cast<uint16_t>(readOffset),
-                      static_cast<uint8_t>(requestLength),
-                      blockData.data()) < 0)
-        {
-            std::cerr << "failed to read " << errorHelp << "\n";
-            return {};
-        }
-
-        device.insert(device.end(), blockData.begin(),
-                      blockData.begin() + requestLength);
-
-        readOffset += requestLength;
-        fruLength -= std::min(requestLength, fruLength);
-    }
-
-    return device;
-}
-
 // Given a bus/address, produce the path in sysfs for an eeprom.
 static std::string getEepromPath(size_t bus, size_t address)
 {
@@ -449,67 +295,6 @@
         file, address, reinterpret_cast<uint8_t*>(&offset), 2, buf, len);
 }
 
-bool validateHeader(const std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData)
-{
-    // ipmi spec format version number is currently at 1, verify it
-    if (blockData[0] != fruVersion)
-    {
-        if (DEBUG)
-        {
-            std::cerr << "FRU spec version " << (int)(blockData[0])
-                      << " not supported. Supported version is "
-                      << (int)(fruVersion) << "\n";
-        }
-        return false;
-    }
-
-    // verify pad is set to 0
-    if (blockData[6] != 0x0)
-    {
-        if (DEBUG)
-        {
-            std::cerr << "PAD value in header is non zero, value is "
-                      << (int)(blockData[6]) << "\n";
-        }
-        return false;
-    }
-
-    // verify offsets are 0, or don't point to another offset
-    std::set<uint8_t> foundOffsets;
-    for (int ii = 1; ii < 6; ii++)
-    {
-        if (blockData[ii] == 0)
-        {
-            continue;
-        }
-        auto inserted = foundOffsets.insert(blockData[ii]);
-        if (!inserted.second)
-        {
-            return false;
-        }
-    }
-
-    // validate checksum
-    size_t sum = 0;
-    for (int jj = 0; jj < 7; jj++)
-    {
-        sum += blockData[jj];
-    }
-    sum = (256 - sum) & 0xFF;
-
-    if (sum != blockData[7])
-    {
-        if (DEBUG)
-        {
-            std::cerr << "Checksum " << (int)(blockData[7])
-                      << " is invalid. calculated checksum is " << (int)(sum)
-                      << "\n";
-        }
-        return false;
-    }
-    return true;
-}
-
 // TODO: This code is very similar to the non-eeprom version and can be merged
 // with some tweaks.
 static std::vector<uint8_t> processEeprom(int bus, int address)