Support multiple inventory objects per EEPROM

This commit enables the IBM VPD parser to add multiple
inventory objects based on the contents of a single EEPROM.

An example would be inventory items that share data with the
parent FRU that actually contains the VPD, such as an ethernet
port on the BMC card.

The commit also adds support for encoding keyword data in
formats other than just converting them to string before
publishing it on D-Bus. While this commit only adds MAC address
as a possible encoding, more can be added in the future by
specifying the encoding in the inventory JSON.

Tested: Verified the following:

* D-Bus objects added to the inventory for the BMC FRU and its
  associated connectors.
* The ethernet objects are populated with the MACAddress property.

Sample output from D-Bus:

root@rainier:~# busctl get-property xyz.openbmc_project.Inventory.Manager
/xyz/openbmc_project/inventory/system/chassis/motherboard/ebmc_card_bmc/ethernet0
xyz.openbmc_project.Inventory.Item.NetworkInterface MACAddress --no-pager
s "00:11:25:c1:00:00"
root@rainier:~# busctl get-property xyz.openbmc_project.Inventory.Manager
/xyz/openbmc_project/inventory/system/chassis/motherboard/ebmc_card_bmc/ethernet1
xyz.openbmc_project.Inventory.Item.NetworkInterface MACAddress --no-pager
s "00:11:25:c1:00:01"

Signed-off-by: Santosh Puranik <santosh.puranik@in.ibm.com>
Change-Id: Iee839b405c60e7e825f709e4ad0bb0f63d11a1b3
diff --git a/ipz_app.cpp b/ipz_app.cpp
index 610ac8d..cd5be8f 100644
--- a/ipz_app.cpp
+++ b/ipz_app.cpp
@@ -14,6 +14,33 @@
 using namespace std;
 using namespace openpower::vpd;
 
+/** @brief Encodes a keyword for D-Bus.
+ */
+static string encodeKeyword(const string& rec, const string& kw,
+                            const string& encoding, const Parsed& vpdMap)
+{
+    if (encoding == "MAC")
+    {
+        string res{};
+        const auto& val = vpdMap.at(rec).at(kw);
+        size_t first = val[0];
+        res += toHex(first >> 4);
+        res += toHex(first & 0x0f);
+        for (size_t i = 1; i < val.size(); ++i)
+        {
+            res += ":";
+            res += toHex(val[i] >> 4);
+            res += toHex(val[i] & 0x0f);
+        }
+        return res;
+    }
+    else // default to string encoding
+    {
+        return string(vpdMap.at(rec).at(kw).begin(),
+                      vpdMap.at(rec).at(kw).end());
+    }
+}
+
 static void populateInterfaces(const nlohmann::json& js,
                                inventory::InterfaceMap& interfaces,
                                const Parsed& vpdMap)
@@ -27,12 +54,13 @@
         {
             const string& rec = itr.value().value("recordName", "");
             const string& kw = itr.value().value("keywordName", "");
+            const string& encoding = itr.value().value("encoding", "");
 
             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()));
+                auto encoded = encodeKeyword(rec, kw, encoding, vpdMap);
+                props.emplace(itr.key(), encoded);
             }
         }
         interfaces.emplace(inf, move(props));
@@ -40,47 +68,57 @@
 }
 
 static void populateDbus(Store& vpdStore, nlohmann::json& js,
-                         const string& objectPath, const string& filePath)
+                         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)
+    for (const auto& item : js["frus"][filePath])
     {
-        inventory::PropertyMap prop;
-        for (auto kwVal : record.second)
+        const auto& objectPath = item["inventoryPath"];
+        sdbusplus::message::object_path object(objectPath);
+
+        // Populate the VPD keywords and the common interfaces only if we
+        // are asked to inherit that data from the VPD, else only add the
+        // extraInterfaces.
+        if (item.value("inherit", true))
         {
-            std::vector<uint8_t> vec(kwVal.second.begin(), kwVal.second.end());
-            std::string kw = kwVal.first;
-            if (kw[0] == '#')
+            // Each record in the VPD becomes an interface and all keywords
+            // within the record are properties under that interface.
+            for (const auto& record : vpdMap)
             {
-                kw = std::string("PD_") + kw[1];
+                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));
             }
-            prop.emplace(move(kw), move(vec));
+
+            // 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);
+            }
         }
-        interfaces.emplace(preIntrStr + record.first, move(prop));
+        if (item.find("extraInterfaces") != item.end())
+        {
+            populateInterfaces(item["extraInterfaces"], interfaces, vpdMap);
+        }
+        objects.emplace(move(object), move(interfaces));
     }
 
-    // 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));
 }
@@ -114,14 +152,6 @@
             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>());
@@ -130,7 +160,7 @@
         auto vpdStore = parse(move(vpd));
 
         // Write it to the inventory
-        populateDbus(vpdStore, js, objectPath, file);
+        populateDbus(vpdStore, js, file);
     }
     catch (exception& e)
     {