IPZ VPD Parser support

This commit enables the app ibm-read-vpd to read the
IPZ format vpd. To build this app, the application
build must be configured with the "--enable-ibm-parser"
option.

The application populates the Inventory with the parsed
VPD data. The parser relies on a JSON config file
that maps the input VPD file path to the Inventory D-Bus
object that hosts the VPD data as properties.

The JSON file also supplies any additional interfaces
and properties that the D-Bus object should implement.

Argument required to run this application-
ibm-read-vpd --file vpd_file

Tested:

Also tested this on Rainier simulation model and verified that VPD
for FRUs is properly published as properties of D-Bus objects in the
Inventory.

Change-Id: Ic9305f25625adced7f64123589dd083e2679afbb
Signed-off-by: Alpana Kumari <alpankum@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 7759ee3..c7e4d9c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,6 +10,18 @@
 	types.hpp \
 	utils.hpp
 
+if IBM_PARSER
+bin_PROGRAMS = ibm-read-vpd
+ibm_read_vpd_SOURCES = \
+	ipz_app.cpp \
+	parser.cpp \
+	impl.cpp \
+	utils.cpp
+
+ibm_read_vpd_LDFLAGS = $(SDBUSPLUS_LIBS) $(PHOSPHOR_LOGGING_LIBS)
+ibm_read_vpd_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
+
+else
 # Be sure to build these before compiling
 BUILT_SOURCES = \
 	writefru.hpp \
@@ -51,5 +63,6 @@
 	utils.cpp
 openpower_read_vpd_LDFLAGS = $(SDBUSPLUS_LIBS) $(PHOSPHOR_LOGGING_LIBS)
 openpower_read_vpd_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
+endif
 
 SUBDIRS = test
diff --git a/configure.ac b/configure.ac
index 05bf92e..5e6bb8b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,6 +17,7 @@
 PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus])
 PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging])
 
+
 # Suppress the --with-libtool-sysroot error
 LT_INIT
 
@@ -50,6 +51,33 @@
 PROPGEN="$PYTHON $srcdir/extra-properties.py -e $PROP_YAML"
 AC_SUBST(PROPGEN)
 
+# Setup option for IBM VPD formats.
+AC_ARG_ENABLE([ibm-parser],
+              AS_HELP_STRING([--enable-ibm-parser ],
+                             [Enable IBM IPZ and Keyword format VPD parser support.
+            ]))
+
+AM_CONDITIONAL([IBM_PARSER], [test "x$enable_ibm_parser" = "xyes"])
+AS_IF([test "x$enable_ibm_parser" == "xyes"], [
+# Check necessary header files
+    AC_CHECK_HEADER(
+        [CLI/CLI.hpp],
+        [],
+        [AC_MSG_ERROR([Could not find CLI11 CLI.hpp])]
+    )
+    AC_CHECK_HEADER(
+        [nlohmann/json.hpp],
+        [],
+        [AC_MSG_ERROR([Could not find nlohmann/json.hpp. Require nlohmann/json package])]
+    )
+    AX_APPEND_COMPILE_FLAGS([-DIPZ_PARSER], [CXXFLAGS])
+    AC_DEFINE(INVENTORY_JSON, "/usr/share/vpd/vpd_inventory.json", [JSON file that defines inventory blueprint])
+            ])
+
+# Check/set gtest specific functions.
+AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
+AC_SUBST(GTEST_CPPFLAGS)
+
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/examples/inventory.json b/examples/inventory.json
new file mode 100644
index 0000000..b5592cc
--- /dev/null
+++ b/examples/inventory.json
@@ -0,0 +1,34 @@
+{
+  "commonInterfaces": {
+    "xyz.openbmc_project.Inventory.Decorator.Asset": {
+      "PartNumber": {
+        "recordName": "VINI",
+        "keywordName": "PN"
+      },
+      "SerialNumber": {
+        "recordName": "VINI",
+        "keywordName": "SN"
+      }
+    },
+    "xyz.openbmc_project.Inventory.Item": {
+      "PrettyName": {
+        "recordName": "VINI",
+        "keywordName": "DR"
+      }
+    }
+  },
+  "frus": {
+    "/sys/devices/path/to/motherboard/eeeprom": {
+      "inventoryPath": "/bus/path/for/motherboardfru",
+      "extraInterfaces": {
+        "xyz.openbmc_project.Inventory.Item.Board.Motherboard": null
+      }
+    },
+    "/sys/devices/path/to/bmc/eeprom": {
+      "inventoryPath": "/bus/path/for/bmcfru",
+      "extraInterfaces": {
+        "xyz.openbmc_project.Inventory.Item.Bmc": null
+      }
+    }
+  }
+}
diff --git a/impl.cpp b/impl.cpp
index 765b1ac..35a0e0f 100644
--- a/impl.cpp
+++ b/impl.cpp
@@ -23,6 +23,7 @@
 
 static constexpr auto MAC_ADDRESS_LEN_BYTES = 6;
 static constexpr auto LAST_KW = "PF";
+static constexpr auto POUND_KW = '#';
 static constexpr auto UUID_LEN_BYTES = 16;
 static constexpr auto UUID_TIME_LOW_END = 8;
 static constexpr auto UUID_TIME_MID_END = 13;
@@ -62,6 +63,7 @@
 using RecordType = uint16_t;
 using RecordLength = uint16_t;
 using KwSize = uint8_t;
+using PoundKwSize = uint16_t;
 using ECCOffset = uint16_t;
 using ECCLength = uint16_t;
 
@@ -193,16 +195,26 @@
     std::advance(iterator, nameOffset);
 
     std::string name(iterator, iterator + lengths::RECORD_NAME);
+#ifndef IPZ_PARSER
     if (supportedRecords.end() != supportedRecords.find(name))
     {
+#endif
         // If it's a record we're interested in, proceed to find
         // contained keywords and their values.
         std::advance(iterator, lengths::RECORD_NAME);
+
+#ifdef IPZ_PARSER
+        // Reverse back to RT Kw, in ipz vpd, to Read RT KW & value
+        std::advance(iterator, -(lengths::KW_NAME + sizeof(KwSize) +
+                                 lengths::RECORD_NAME));
+#endif
         auto kwMap = readKeywords(iterator);
         // Add entry for this record (and contained keyword:value pairs)
         // to the parsed vpd output.
         out.emplace(std::move(name), std::move(kwMap));
+#ifndef IPZ_PARSER
     }
+#endif
 }
 
 std::string Impl::readKwData(const internal::KeywordInfo& keyword,
@@ -312,13 +324,35 @@
             // We're done
             break;
         }
+        // Check if the Keyword is '#kw'
+        char kwNameStart = *iterator;
+
         // Jump past keyword name
         std::advance(iterator, lengths::KW_NAME);
-        // Note keyword data length
-        std::size_t length = *iterator;
-        // Jump past keyword length
-        std::advance(iterator, sizeof(KwSize));
+
+        std::size_t length;
+        std::size_t lengthHighByte;
+        if (POUND_KW == kwNameStart)
+        {
+            // Note keyword data length
+            length = *iterator;
+            lengthHighByte = *(iterator + 1);
+            length |= (lengthHighByte << 8);
+
+            // Jump past 2Byte keyword length
+            std::advance(iterator, sizeof(PoundKwSize));
+        }
+        else
+        {
+            // Note keyword data length
+            length = *iterator;
+
+            // Jump past keyword length
+            std::advance(iterator, sizeof(KwSize));
+        }
+
         // Pointing to keyword data now
+#ifndef IPZ_PARSER
         if (supportedKeywords.end() != supportedKeywords.find(kw))
         {
             // Keyword is of interest to us
@@ -326,6 +360,14 @@
                                           length, iterator);
             map.emplace(std::move(kw), std::move(data));
         }
+
+#else
+        // support all the Keywords
+        auto stop = std::next(iterator, length);
+        std::string kwdata(iterator, stop);
+        map.emplace(std::move(kw), std::move(kwdata));
+
+#endif
         // Jump past keyword data length
         std::advance(iterator, length);
     }
diff --git a/impl.hpp b/impl.hpp
index 94a958e..525ae7a 100644
--- a/impl.hpp
+++ b/impl.hpp
@@ -36,14 +36,14 @@
 } // namespace internal
 
 /** @class Impl
- *  @brief Implements parser for OpenPOWER VPD
+ *  @brief Implements parser for VPD
  *
- *  An Impl object must be constructed by passing in OpenPOWER VPD in
+ *  An Impl object must be constructed by passing in VPD in
  *  binary format. To parse the VPD, call the run() method. The run()
  *  method returns an openpower::vpd::Store object, which contains
  *  parsed VPD, and provides access methods for the VPD.
  *
- *  Following is the algorithm used to parse OpenPOWER VPD:
+ *  Following is the algorithm used to parse IPZ/OpenPower VPD:
  *  1) Validate that the first record is VHDR, the header record.
  *  2) From the VHDR record, get the offset of the VTOC record,
  *     which is the table of contents record.
@@ -66,13 +66,13 @@
 
     /** @brief Construct an Impl
      *
-     *  @param[in] vpdBuffer - Binary OpenPOWER VPD
+     *  @param[in] vpdBuffer - Binary VPD
      */
     explicit Impl(Binary&& vpdBuffer) : vpd(std::move(vpdBuffer)), out{}
     {
     }
 
-    /** @brief Run the parser on binary OpenPOWER VPD
+    /** @brief Run the parser on binary VPD
      *
      *  @returns openpower::vpd::Store object
      */
@@ -99,13 +99,13 @@
     /** @brief Read VPD information contained within a record
      *
      *  @param[in] recordOffset - offset to a record location
-     *      within the binary OpenPOWER VPD
+     *      within the binary VPD
      */
     void processRecord(std::size_t recordOffset);
 
     /** @brief Read keyword data
      *
-     *  @param[in] keyword - OpenPOWER VPD keyword
+     *  @param[in] keyword - VPD keyword
      *  @param[in] dataLength - Length of data to be read
      *  @param[in] iterator - iterator pointing to a Keyword's data in
      *      the VPD
@@ -129,7 +129,7 @@
     /** @brief Checks if the VHDR record is present in the VPD */
     void checkHeader() const;
 
-    /** @brief OpenPOWER VPD in binary format */
+    /** @brief VPD in binary format */
     Binary vpd;
 
     /** @brief parser output */
diff --git a/ipz_app.cpp b/ipz_app.cpp
new file mode 100644
index 0000000..610ac8d
--- /dev/null
+++ b/ipz_app.cpp
@@ -0,0 +1,142 @@
+#include "config.h"
+
+#include "defines.hpp"
+#include "parser.hpp"
+#include "utils.hpp"
+
+#include <CLI/CLI.hpp>
+#include <exception>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <nlohmann/json.hpp>
+
+using namespace std;
+using namespace openpower::vpd;
+
+static void populateInterfaces(const nlohmann::json& js,
+                               inventory::InterfaceMap& interfaces,
+                               const Parsed& vpdMap)
+{
+    for (const auto& ifs : js.items())
+    {
+        const string& inf = ifs.key();
+        inventory::PropertyMap props;
+
+        for (const auto& itr : ifs.value().items())
+        {
+            const string& rec = itr.value().value("recordName", "");
+            const string& kw = itr.value().value("keywordName", "");
+
+            if (!rec.empty() && !kw.empty() && vpdMap.count(rec) &&
+                vpdMap.at(rec).count(kw))
+            {
+                props.emplace(itr.key(), string(vpdMap.at(rec).at(kw).begin(),
+                                                vpdMap.at(rec).at(kw).end()));
+            }
+        }
+        interfaces.emplace(inf, move(props));
+    }
+}
+
+static void populateDbus(Store& vpdStore, nlohmann::json& js,
+                         const string& objectPath, const string& filePath)
+{
+    inventory::InterfaceMap interfaces;
+    inventory::ObjectMap objects;
+    sdbusplus::message::object_path object(objectPath);
+    const auto& vpdMap = vpdStore.getVpdMap();
+    string preIntrStr = "com.ibm.ipzvpd.";
+
+    // Each record in the VPD becomes an interface and all keywords within the
+    // record are properties under that interface.
+    for (const auto& record : vpdMap)
+    {
+        inventory::PropertyMap prop;
+        for (auto kwVal : record.second)
+        {
+            std::vector<uint8_t> vec(kwVal.second.begin(), kwVal.second.end());
+            std::string kw = kwVal.first;
+            if (kw[0] == '#')
+            {
+                kw = std::string("PD_") + kw[1];
+            }
+            prop.emplace(move(kw), move(vec));
+        }
+        interfaces.emplace(preIntrStr + record.first, move(prop));
+    }
+
+    // Populate interfaces and properties that are common to every FRU
+    // and additional interface that might be defined on a per-FRU basis.
+    if (js.find("commonInterfaces") != js.end())
+    {
+        populateInterfaces(js["commonInterfaces"], interfaces, vpdMap);
+    }
+    if (js["frus"][filePath].find("extraInterfaces") !=
+        js["frus"][filePath].end())
+    {
+        populateInterfaces(js["frus"][filePath]["extraInterfaces"], interfaces,
+                           vpdMap);
+    }
+
+    objects.emplace(move(object), move(interfaces));
+
+    // Notify PIM
+    inventory::callPIM(move(objects));
+}
+
+int main(int argc, char** argv)
+{
+    int rc = 0;
+
+    try
+    {
+        using namespace CLI;
+        using json = nlohmann::json;
+
+        App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store "
+                "in DBUS"};
+        string file{};
+
+        app.add_option("-f, --file", file, "File containing VPD in IPZ format")
+            ->required()
+            ->check(ExistingFile);
+
+        CLI11_PARSE(app, argc, argv);
+
+        // Make sure that the file path we get is for a supported EEPROM
+        ifstream inventoryJson(INVENTORY_JSON);
+        auto js = json::parse(inventoryJson);
+
+        if ((js.find("frus") == js.end()) ||
+            (js["frus"].find(file) == js["frus"].end()))
+        {
+            throw std::runtime_error("Device path missing in inventory JSON");
+        }
+
+        const string& objectPath = js["frus"][file].value("inventoryPath", "");
+
+        if (objectPath.empty())
+        {
+            throw std::runtime_error("Could not find D-Bus object path in "
+                                     "inventory JSON");
+        }
+
+        ifstream vpdFile(file, ios::binary);
+        Binary vpd((istreambuf_iterator<char>(vpdFile)),
+                   istreambuf_iterator<char>());
+
+        // Use ipz vpd Parser
+        auto vpdStore = parse(move(vpd));
+
+        // Write it to the inventory
+        populateDbus(vpdStore, js, objectPath, file);
+    }
+    catch (exception& e)
+    {
+        cerr << e.what() << "\n";
+        rc = -1;
+    }
+
+    return rc;
+}
diff --git a/parser.hpp b/parser.hpp
index e131c46..097b14a 100644
--- a/parser.hpp
+++ b/parser.hpp
@@ -9,11 +9,11 @@
 namespace vpd
 {
 
-/** @brief API to parse OpenPOWER VPD
+/** @brief API to parse VPD
  *
- *  @param [in] vpd - OpenPOWER VPD in binary format
+ *  @param [in] vpd - VPD in binary format
  *  @returns A Store object, which provides access to
- *  the parsed VPD
+ *           the parsed VPD.
  */
 Store parse(Binary&& vpd);
 
diff --git a/store.hpp b/store.hpp
index c184e37..a8326a7 100644
--- a/store.hpp
+++ b/store.hpp
@@ -42,6 +42,15 @@
     {
     }
 
+    /** @brief Retrieves VPD from Store as a Parsed object
+     *
+     *  @returns VPD as a Parsed object
+     */
+    inline const Parsed& getVpdMap() const
+    {
+        return vpd;
+    }
+
     /** @brief Retrieves VPD from Store
      *
      *  @tparam R - VPD record
diff --git a/test/Makefile.am b/test/Makefile.am
index 4f35b94..05785da 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -3,8 +3,32 @@
 TESTS = $(check_PROGRAMS)
 
 check_PROGRAMS = store_test
+
 store_test_SOURCES = store/store.cpp
 
+check_PROGRAMS += ipz_parser_test
+
+ipz_parser_test_SOURCES = ipz_parser/parser.cpp \
+                          ../impl.cpp
+test_cppflags = \
+	-Igtest \
+	$(GTEST_CPPFLAGS) \
+	$(AM_CPPFLAGS)  \
+	-pthread  \
+	$(PTHREAD_CFLAGS) \
+	$(SDBUSPLUS_CFLAGS)
+
+test_ldflags = \
+	-lgtest_main \
+	-lgtest \
+	-pthread  \
+	$(PTHREAD_LIBS) \
+	$(SDBUSPLUS_LIBS)
+
+ipz_parser_test_CPPFLAGS = $(test_cppflags) -DIPZ_PARSER
+ipz_parser_test_LDFLAGS = $(test_ldflags)
+
+if !IBM_PARSER
 noinst_PROGRAMS = parser_test
 parser_test_SOURCES = \
 	parser/parser.cpp \
@@ -14,3 +38,4 @@
 	../utils.cpp
 parser_test_LDFLAGS = $(SDBUSPLUS_LIBS) $(PHOSPHOR_LOGGING_LIBS)
 parser_test_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
+endif
diff --git a/test/ipz_parser/parser.cpp b/test/ipz_parser/parser.cpp
new file mode 100644
index 0000000..40c60e9
--- /dev/null
+++ b/test/ipz_parser/parser.cpp
@@ -0,0 +1,144 @@
+#include <cassert>
+#include <defines.hpp>
+#include <fstream>
+#include <impl.hpp>
+#include <iterator>
+#include <parser.hpp>
+#include <store.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::vpd;
+
+TEST(IpzVpdParserApp, vpdGoodPath)
+{
+    // Create a vpd
+    Binary vpd = {
+        0x00, 0x0f, 0x17, 0xba, 0x42, 0xca, 0x82, 0xd7, 0x7b, 0x77, 0x1e, 0x84,
+        0x28, 0x00, 0x52, 0x54, 0x04, 0x56, 0x48, 0x44, 0x52, 0x56, 0x44, 0x02,
+        0x30, 0x31, 0x50, 0x54, 0x0e, 0x56, 0x54, 0x4f, 0x43, 0xd5, 0x00, 0x37,
+        0x00, 0x4c, 0x00, 0x97, 0x05, 0x13, 0x00, 0x50, 0x46, 0x08, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84, 0x48, 0x00, 0x52, 0x54,
+        0x04, 0x56, 0x54, 0x4f, 0x43, 0x50, 0x54, 0x0e, 0x56, 0x49, 0x4e, 0x49,
+        0xd5, 0x00, 0x52, 0x00, 0x90, 0x00, 0x73, 0x05, 0x24, 0x00, 0x84, 0x8c,
+        0x00, 0x52, 0x54, 0x04, 0x56, 0x49, 0x4e, 0x49, 0x44, 0x52, 0x10, 0x41,
+        0x50, 0x53, 0x53, 0x20, 0x26, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x20, 0x43,
+        0x41, 0x52, 0x44, 0x43, 0x45, 0x01, 0x31, 0x56, 0x5a, 0x02, 0x30, 0x31,
+        0x46, 0x4e, 0x07, 0x30, 0x31, 0x44, 0x48, 0x32, 0x30, 0x30, 0x50, 0x4e,
+        0x07, 0x30, 0x31, 0x44, 0x48, 0x32, 0x30, 0x31, 0x53, 0x4e, 0x0c, 0x59,
+        0x4c, 0x33, 0x30, 0x42, 0x47, 0x37, 0x43, 0x46, 0x30, 0x33, 0x50, 0x43,
+        0x43, 0x04, 0x36, 0x42, 0x36, 0x36, 0x50, 0x52, 0x08, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x45, 0x04, 0x30, 0x30, 0x30, 0x31,
+        0x43, 0x54, 0x04, 0x40, 0xb8, 0x02, 0x03, 0x48, 0x57, 0x02, 0x00, 0x01,
+        0x42, 0x33, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x34, 0x01,
+        0x00, 0x42, 0x37, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02, 0x00, 0x00, 0x78, 0x84, 0xdc,
+        0x00, 0x52, 0x54, 0x04};
+
+    // call app for this vpd
+    parser::Impl p(std::move(vpd));
+    Store vpdStore = p.run();
+
+    static const std::string record = "VINI";
+    static const std::string keyword = "DR";
+
+    // TODO 2: Move this as an utility to store.hpp
+    std::string dataFound;
+    Parsed st_bin = vpdStore.getVpdMap();
+
+    auto kw = st_bin.find(record);
+    if (st_bin.end() != kw)
+    {
+        auto value = (kw->second).find(keyword);
+        if ((kw->second).end() != value)
+        {
+            dataFound = value->second;
+        }
+    }
+
+    ASSERT_EQ(dataFound, "APSS & TPM  CARD");
+}
+
+TEST(IpzVpdParserApp, vpdBadPathEmptyVPD)
+{
+    Binary vpd = {};
+
+    // VPD is empty
+    parser::Impl p(std::move(vpd));
+
+    // Expecting a throw here
+    EXPECT_THROW(p.run(), std::runtime_error);
+}
+
+TEST(IpzVpdParserApp, vpdBadPathMissingHeader)
+{
+    Binary vpd = {
+        0x00, 0x0f, 0x17, 0xba, 0x42, 0xca, 0x82, 0xd7, 0x7b, 0x77, 0x1e, 0x84,
+        0x28, 0x00, 0x52, 0x54, 0x04, 0x56, 0x48, 0x44, 0x52, 0x56, 0x44, 0x02,
+        0x30, 0x31, 0x50, 0x54, 0x0e, 0x56, 0x54, 0x4f, 0x43, 0xd5, 0x00, 0x37,
+        0x00, 0x4c, 0x00, 0x97, 0x05, 0x13, 0x00, 0x50, 0x46, 0x08, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84, 0x48, 0x00, 0x52, 0x54,
+        0x04, 0x56, 0x54, 0x4f, 0x43, 0x50, 0x54, 0x0e, 0x56, 0x49, 0x4e, 0x49,
+        0xd5, 0x00, 0x52, 0x00, 0x90, 0x00, 0x73, 0x05, 0x24, 0x00, 0x84, 0x8c,
+        0x00, 0x52, 0x54, 0x04, 0x56, 0x49, 0x4e, 0x49, 0x44, 0x52, 0x10, 0x41,
+        0x50, 0x53, 0x53, 0x20, 0x26, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x20, 0x43,
+        0x41, 0x52, 0x44, 0x43, 0x45, 0x01, 0x31, 0x56, 0x5a, 0x02, 0x30, 0x31,
+        0x46, 0x4e, 0x07, 0x30, 0x31, 0x44, 0x48, 0x32, 0x30, 0x30, 0x50, 0x4e,
+        0x07, 0x30, 0x31, 0x44, 0x48, 0x32, 0x30, 0x31, 0x53, 0x4e, 0x0c, 0x59,
+        0x4c, 0x33, 0x30, 0x42, 0x47, 0x37, 0x43, 0x46, 0x30, 0x33, 0x50, 0x43,
+        0x43, 0x04, 0x36, 0x42, 0x36, 0x36, 0x50, 0x52, 0x08, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x45, 0x04, 0x30, 0x30, 0x30, 0x31,
+        0x43, 0x54, 0x04, 0x40, 0xb8, 0x02, 0x03, 0x48, 0x57, 0x02, 0x00, 0x01,
+        0x42, 0x33, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x34, 0x01,
+        0x00, 0x42, 0x37, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02, 0x00, 0x00, 0x78, 0x84, 0xdc,
+        0x00, 0x52, 0x54, 0x04};
+
+    // corrupt the VHDR
+    vpd[17] = 0x00;
+
+    parser::Impl p(std::move(vpd));
+
+    // Expecting a throw here
+    EXPECT_THROW(p.run(), std::runtime_error);
+}
+
+TEST(IpzVpdParserApp, vpdBadPathMissingVTOC)
+{
+    Binary vpd = {
+        0x00, 0x0f, 0x17, 0xba, 0x42, 0xca, 0x82, 0xd7, 0x7b, 0x77, 0x1e, 0x84,
+        0x28, 0x00, 0x52, 0x54, 0x04, 0x56, 0x48, 0x44, 0x52, 0x56, 0x44, 0x02,
+        0x30, 0x31, 0x50, 0x54, 0x0e, 0x56, 0x54, 0x4f, 0x43, 0xd5, 0x00, 0x37,
+        0x00, 0x4c, 0x00, 0x97, 0x05, 0x13, 0x00, 0x50, 0x46, 0x08, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x84, 0x48, 0x00, 0x52, 0x54,
+        0x04, 0x56, 0x54, 0x4f, 0x43, 0x50, 0x54, 0x0e, 0x56, 0x49, 0x4e, 0x49,
+        0xd5, 0x00, 0x52, 0x00, 0x90, 0x00, 0x73, 0x05, 0x24, 0x00, 0x84, 0x8c,
+        0x00, 0x52, 0x54, 0x04, 0x56, 0x49, 0x4e, 0x49, 0x44, 0x52, 0x10, 0x41,
+        0x50, 0x53, 0x53, 0x20, 0x26, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x20, 0x43,
+        0x41, 0x52, 0x44, 0x43, 0x45, 0x01, 0x31, 0x56, 0x5a, 0x02, 0x30, 0x31,
+        0x46, 0x4e, 0x07, 0x30, 0x31, 0x44, 0x48, 0x32, 0x30, 0x30, 0x50, 0x4e,
+        0x07, 0x30, 0x31, 0x44, 0x48, 0x32, 0x30, 0x31, 0x53, 0x4e, 0x0c, 0x59,
+        0x4c, 0x33, 0x30, 0x42, 0x47, 0x37, 0x43, 0x46, 0x30, 0x33, 0x50, 0x43,
+        0x43, 0x04, 0x36, 0x42, 0x36, 0x36, 0x50, 0x52, 0x08, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x45, 0x04, 0x30, 0x30, 0x30, 0x31,
+        0x43, 0x54, 0x04, 0x40, 0xb8, 0x02, 0x03, 0x48, 0x57, 0x02, 0x00, 0x01,
+        0x42, 0x33, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x34, 0x01,
+        0x00, 0x42, 0x37, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x50, 0x46, 0x02, 0x00, 0x00, 0x78, 0x84, 0xdc,
+        0x00, 0x52, 0x54, 0x04};
+
+    // corrupt the VTOC
+    vpd[61] = 0x00;
+
+    parser::Impl p(std::move(vpd));
+
+    // Expecting a throw here
+    EXPECT_THROW(p.run(), std::runtime_error);
+}
+
+int main(int argc, char** argv)
+{
+    ::testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
diff --git a/types.hpp b/types.hpp
index 196e0fd..172d0c1 100644
--- a/types.hpp
+++ b/types.hpp
@@ -22,7 +22,7 @@
 
 using Path = std::string;
 using Property = std::string;
-using Value = sdbusplus::message::variant<bool, int64_t, std::string>;
+using Value = sdbusplus::message::variant<bool, int64_t, std::string, Binary>;
 using PropertyMap = std::map<Property, Value>;
 
 using Interface = std::string;