fru-device: add offset variant of FRUReader abstraction

The caching FRUReader class is now joined by OffsetFRUReader, both of
which now derive from a common BaseFRUReader class.  OffsetFRUReader is
constructed in terms of an existing BaseFRUReader (probably a FRUReader)
and simply applies a fixed offset to all reads performed on it.

Tested: on an ASRock Rack romed8hm3, fru-device successfully recognizes
and parses the baseboard FRU EEPROM as it did prior to this patch.

Signed-off-by: Zev Weiss <zev@bewilderbeest.net>
Change-Id: I13f3bc48110b8ffdf1d3f135c57ae9f0736a8e53
diff --git a/include/fru_reader.hpp b/include/fru_reader.hpp
index f54d900..4f8b24d 100644
--- a/include/fru_reader.hpp
+++ b/include/fru_reader.hpp
@@ -35,16 +35,25 @@
 using ReadBlockFunc =
     std::function<int64_t(off_t offset, size_t len, uint8_t* outbuf)>;
 
-// A caching wrapper around a ReadBlockFunc
-class FRUReader
+class BaseFRUReader
 {
   public:
-    explicit FRUReader(ReadBlockFunc readFunc) : readFunc(std::move(readFunc))
-    {}
     // The ::read() operation here is analogous to ReadBlockFunc (with the same
     // return value semantics), but is not subject to SMBus block size
     // limitations; it can read as much data as needed in a single call.
-    ssize_t read(off_t start, size_t len, uint8_t* outbuf);
+    virtual ssize_t read(off_t start, size_t len, uint8_t* outbuf) = 0;
+    virtual ~BaseFRUReader() = default;
+};
+
+// A caching wrapper around a ReadBlockFunc
+class FRUReader : public BaseFRUReader
+{
+  public:
+    explicit FRUReader(ReadBlockFunc readFunc) :
+        readFunc(std::move(readFunc)), eof(std::nullopt)
+    {}
+
+    ssize_t read(off_t start, size_t len, uint8_t* outbuf) override;
 
   private:
     static constexpr size_t cacheBlockSize = 32;
@@ -52,7 +61,7 @@
     using CacheBlock = std::array<uint8_t, cacheBlockSize>;
 
     // indexed by block number (byte number / block size)
-    using Cache = std::map<uint32_t, CacheBlock>;
+    using Cache = std::unordered_map<size_t, CacheBlock>;
 
     ReadBlockFunc readFunc;
     Cache cache;
@@ -60,3 +69,18 @@
     // byte offset of the end of the FRU (if readFunc has reported it)
     std::optional<size_t> eof;
 };
+
+// wraps an existing BaseFRUReader and applies a fixed offset to all reads
+class OffsetFRUReader : public BaseFRUReader
+{
+  public:
+    OffsetFRUReader(BaseFRUReader& inner, off_t offset) :
+        inner(inner), offset(offset)
+    {}
+
+    ssize_t read(off_t start, size_t len, uint8_t* outbuf) override;
+
+  private:
+    BaseFRUReader& inner;
+    off_t offset;
+};
diff --git a/include/fru_utils.hpp b/include/fru_utils.hpp
index f1d8e94..ed019ad 100644
--- a/include/fru_utils.hpp
+++ b/include/fru_utils.hpp
@@ -138,22 +138,22 @@
 ssize_t getFieldLength(uint8_t fruFieldTypeLenValue);
 
 /// \brief Find a FRU header.
-/// \param reader the FRUReader to read via
+/// \param reader the BaseFRUReader to read via
 /// \param errorHelp and a helper string for failures
 /// \param blockData buffer to return the last read block
 /// \param baseOffset the offset to start the search at;
 ///        set to 0 to perform search;
 ///        returns the offset at which a header was found
 /// \return whether a header was found
-bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
+bool findFRUHeader(BaseFRUReader& reader, const std::string& errorHelp,
                    std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
                    off_t& baseOffset);
 
 /// \brief Read and validate FRU contents.
-/// \param reader the FRUReader to read via
+/// \param reader the BaseFRUReader to read via
 /// \param errorHelp and a helper string for failures
 /// \return the FRU contents from the file
-std::vector<uint8_t> readFRUContents(FRUReader& reader,
+std::vector<uint8_t> readFRUContents(BaseFRUReader& reader,
                                      const std::string& errorHelp);
 
 /// \brief Validate an IPMI FRU common header
diff --git a/src/fru_reader.cpp b/src/fru_reader.cpp
index 61d4020..b0ee8fe 100644
--- a/src/fru_reader.cpp
+++ b/src/fru_reader.cpp
@@ -89,3 +89,8 @@
 
     return done;
 }
+
+ssize_t OffsetFRUReader::read(off_t start, size_t len, uint8_t* outbuf)
+{
+    return inner.read(start + offset, len, outbuf);
+}
diff --git a/src/fru_utils.cpp b/src/fru_utils.cpp
index e030ac8..5e8e695 100644
--- a/src/fru_utils.cpp
+++ b/src/fru_utils.cpp
@@ -590,7 +590,7 @@
     return true;
 }
 
-bool findFRUHeader(FRUReader& reader, const std::string& errorHelp,
+bool findFRUHeader(BaseFRUReader& reader, const std::string& errorHelp,
                    std::array<uint8_t, I2C_SMBUS_BLOCK_MAX>& blockData,
                    off_t& baseOffset)
 {
@@ -634,7 +634,7 @@
     return false;
 }
 
-std::vector<uint8_t> readFRUContents(FRUReader& reader,
+std::vector<uint8_t> readFRUContents(BaseFRUReader& reader,
                                      const std::string& errorHelp)
 {
     std::array<uint8_t, I2C_SMBUS_BLOCK_MAX> blockData{};