VPD tool : dumpInventory & dumpObject

VPD tool has four options.

a) Dump Complete Inventory - no additional arguments are needed.
b) Dump Specific Object - by providing the object name.
c) Read the keyword - by providing the object name, record name and keyword to be read.
d) Write/Update the keyword - by providing the object name,  record name, keyword and the value to be updated.

{value - in ascii or hex & all the other arguments in string}

"--help" option provides details on how to use the above mentioned options.

This commit has implementation of dump inventory and dump specific object.

Output:

---------Dump Inventory---------
Not displaying the complete output.

root@rainier:/tmp# ./vpd-tool -i
[
    {
        "/system": {
            "LocationCode": "U9105.22A.SIMP10R",
            "Model": "9105-22A",
            "SerialNumber": "SIMP10R",
            "type": "xyz.openbmc_project.Inventory.Item.System"
        },
        "/system/chassis": {
            "LocationCode": "U78DA.ND1.1234567",
            "type": "xyz.openbmc_project.Inventory.Item.Chassis"
        },
        .
        .
        .
        .
       and so on..
]

---------Dump Object----------

root@rainier:/tmp# ./vpd-tool -o -O /system/chassis/motherboard/ebmc_card_bmc
[
    {
        "/system/chassis/motherboard/ebmc_card_bmc": {
            "CC": "6B58",
            "DR": "EBMC            ",
            "FN": "F191014",
            "LocationCode": "U78DA.ND1.1234567-P0-C5",
            "PN": "PN12345",
            "SN": "YL6B58010000",
            "type": "xyz.openbmc_project.Inventory.Item.Bmc"
        }
    }
]

Flag to enable VPD tool:
There is no seperate flag for VPD tool.
ibm-parser flag will generate the vpd-tool binary.

Steps to build and generate the executable using meson:
meson -Denabled=ibm-parser build
ninja -C build

Test:
Tested on simics.

Signed-off-by: PriyangaRamasamy <priyanga24@in.ibm.com>
Change-Id: I712f7ad4303eefa68f232685b6b0e53646f859f5
diff --git a/Makefile.am b/Makefile.am
index 56a3b57..683ec0f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,7 +9,8 @@
 	args.hpp \
 	utils.hpp \
         keyword_vpd_parser.hpp \
-	ibm_vpd_type_check.hpp
+	ibm_vpd_type_check.hpp \
+	vpd_tool_impl.hpp
 
 if IBM_PARSER
 noinst_HEADERS += \
@@ -30,6 +31,14 @@
 ibm_read_vpd_LDFLAGS = $(SDBUSPLUS_LIBS) $(PHOSPHOR_LOGGING_LIBS)
 ibm_read_vpd_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
 
+bin_PROGRAMS += vpd-tool
+vpd_tool_SOURCES = \
+        vpd_tool.cpp \
+        vpd_tool_impl.cpp
+
+vpd_tool_LDFLAGS = $(SDBUSPLUS_LIBS)
+vpd_tool_CXXFLAGS = $(SDBUSPLUS_CFLAGS)
+
 else
 # Be sure to build these before compiling
 BUILT_SOURCES = \
diff --git a/configure.ac b/configure.ac
index 6657b52..739ec37 100644
--- a/configure.ac
+++ b/configure.ac
@@ -63,7 +63,7 @@
         [CLI/CLI.hpp],
         [],
         [AC_MSG_ERROR([Could not find CLI11 CLI.hpp])]
-    )
+)
     AC_CHECK_HEADER(
         [nlohmann/json.hpp],
         [],
@@ -72,6 +72,8 @@
     AX_APPEND_COMPILE_FLAGS([-DIPZ_PARSER], [CXXFLAGS])
     AC_DEFINE(INVENTORY_JSON, "/usr/share/vpd/vpd_inventory.json", [JSON file that defines inventory blueprint])
             ])
+    AC_DEFINE(INVENTORY_PATH, "/xyz/openbmc_project/inventory", [Prefix for inventory D-Bus objects])
+    AC_DEFINE(INVENTORY_MANAGER_SERVICE, "xyz.openbmc_project.Inventory.Manager", [Inventory manager service])
 
 # Check/set gtest specific functions.
 AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
diff --git a/meson.build b/meson.build
index 8d95281..ac6ba29 100644
--- a/meson.build
+++ b/meson.build
@@ -3,7 +3,7 @@
      'c',
      'cpp',
      default_options: [
-	'cpp_std=c++17'
+     'cpp_std=c++17'
      ],
      version: '1.0'
 )
@@ -22,8 +22,10 @@
 configure_file(output: 'config.h',
                        configuration :{
                        'INVENTORY_JSON': '"'+get_option('INVENTORY_JSON')+'"',
+                       'INVENTORY_PATH': '"'+get_option('INVENTORY_PATH')+'"',
+		       'INVENTORY_MANAGER_SERVICE': '"'+get_option('INVENTORY_MANAGER_SERVICE')+'"'
                        }
-	)
+  )
         ibm_read_vpd_SOURCES = ['ibm_vpd_app.cpp',
                                 'ibm_vpd_type_check.cpp',
                                 'parser.cpp',
@@ -44,6 +46,19 @@
                                 install: true,
                                 cpp_args : '-DIPZ_PARSER'
                             )
+
+        vpd_tool_SOURCES = ['vpd_tool.cpp',
+                            'vpd_tool_impl.cpp'
+                           ]
+
+        vpd_tool_exe = executable(
+                                 'vpd-tool',
+                                 vpd_tool_SOURCES,
+                                 dependencies: [
+                                   sdbusplus
+                                   ],
+                                 install: true,
+                                 )
 else
         FRUGEN = '$srcdir/extra-properties.py -e' + get_option('FRU_YAML')
         PROPGEN = '$srcdir/extra-properties.py -e' + get_option('PROP_YAML')
diff --git a/meson_options.txt b/meson_options.txt
index f198832..9c094c7 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -4,3 +4,5 @@
 option('PROP_YAML',type: 'string', value: 'extra-properties-example.yaml',  description: 'YAML PROPERTY')
 option('ibm-parser', type: 'feature', description: 'ENABLE IBM PARSER')
 option('INVENTORY_JSON',type: 'string', value: '/usr/share/vpd/vpd_inventory.json',  description: 'JSON file that defines inventory blueprint')
+option('INVENTORY_PATH',type: 'string', value: '/xyz/openbmc_project/inventory', description: 'Prefix for inventory D-Bus objects')
+option('INVENTORY_MANAGER_SERVICE',type: 'string', value: 'xyz.openbmc_project.Inventory.Manager', description: 'Inventory manager service')
diff --git a/vpd_tool.cpp b/vpd_tool.cpp
new file mode 100644
index 0000000..0498761
--- /dev/null
+++ b/vpd_tool.cpp
@@ -0,0 +1,62 @@
+#include "vpd_tool_impl.hpp"
+
+#include <CLI/CLI.hpp>
+#include <fstream>
+#include <iostream>
+
+using namespace CLI;
+using namespace std;
+
+int main(int argc, char** argv)
+{
+    App app{"VPD Command line tool to dump the inventory and to read and "
+            "update the keywords"};
+
+    string objectPath{};
+
+    auto object =
+        app.add_option("--object, -O", objectPath, "Enter the Object Path");
+
+    auto dumpObjFlag =
+        app.add_flag("--dumpObject, -o",
+                     "Dump the given object from the inventory. { "
+                     "vpd-tool-exe --dumpObject/-o --object/-O object-name }")
+            ->needs(object);
+
+    auto dumpInvFlag = app.add_flag(
+        "--dumpInventory, -i", "Dump all the inventory objects. { vpd-tool-exe "
+                               "--dumpInventory/-i }");
+
+    CLI11_PARSE(app, argc, argv);
+
+    ifstream inventoryJson(INVENTORY_JSON);
+    auto jsObject = json::parse(inventoryJson);
+
+    try
+    {
+        if (*dumpObjFlag)
+        {
+            VpdTool vpdToolObj(move(objectPath));
+            vpdToolObj.dumpObject(jsObject);
+        }
+
+        else if (*dumpInvFlag)
+        {
+            VpdTool vpdToolObj;
+            vpdToolObj.dumpInventory(jsObject);
+        }
+
+        else
+        {
+            throw runtime_error("One of the valid options is required. Refer "
+                                "--help for list of options.");
+        }
+    }
+
+    catch (exception& e)
+    {
+        cerr << e.what();
+    }
+
+    return 0;
+}
diff --git a/vpd_tool_impl.cpp b/vpd_tool_impl.cpp
new file mode 100644
index 0000000..ccc2765
--- /dev/null
+++ b/vpd_tool_impl.cpp
@@ -0,0 +1,234 @@
+#include "vpd_tool_impl.hpp"
+
+#include <iostream>
+#include <sdbusplus/bus.hpp>
+#include <sstream>
+#include <variant>
+#include <vector>
+
+using namespace std;
+using sdbusplus::exception::SdBusError;
+using namespace openpower::vpd;
+
+void VpdTool::debugger(json output)
+{
+    cout << output.dump(4) << '\n';
+}
+
+auto VpdTool::makeDBusCall(const string& objectName, const string& interface,
+                           const string& kw)
+{
+    auto bus = sdbusplus::bus::new_default();
+    auto properties =
+        bus.new_method_call(INVENTORY_MANAGER_SERVICE, objectName.c_str(),
+                            "org.freedesktop.DBus.Properties", "Get");
+    properties.append(interface);
+    properties.append(kw);
+    auto result = bus.call(properties);
+
+    if (result.is_method_error())
+    {
+        throw runtime_error("Get api failed");
+    }
+    return result;
+}
+
+void VpdTool::addFruTypeAndLocation(json exIntf, const string& object,
+                                    json& kwVal)
+{
+    for (const auto& intf : exIntf.items())
+    {
+        if ((intf.key().find("Item") != string::npos) &&
+            (intf.value().is_null()))
+        {
+            kwVal.emplace("type", intf.key());
+            break;
+        }
+    }
+
+    // Add location code.
+    constexpr auto LOCATION_CODE_IF = "com.ibm.ipzvpd.Location";
+    constexpr auto LOCATION_CODE_PROP = "LocationCode";
+
+    try
+    {
+        variant<string> response;
+        makeDBusCall(object, LOCATION_CODE_IF, LOCATION_CODE_PROP)
+            .read(response);
+
+        if (auto prop = get_if<string>(&response))
+        {
+            kwVal.emplace(LOCATION_CODE_PROP, *prop);
+        }
+    }
+    catch (const SdBusError& e)
+    {
+        kwVal.emplace(LOCATION_CODE_PROP, "");
+    }
+}
+
+json VpdTool::getVINIProperties(string invPath, json exIntf)
+{
+    variant<Binary> response;
+    json output = json::object({});
+    json kwVal = json::object({});
+
+    vector<string> keyword{"CC", "SN", "PN", "FN", "DR"};
+    string interface = "com.ibm.ipzvpd.VINI";
+    string objectName = INVENTORY_PATH + invPath;
+
+    for (string kw : keyword)
+    {
+        try
+        {
+            makeDBusCall(objectName, interface, kw).read(response);
+
+            if (auto vec = get_if<Binary>(&response))
+            {
+                kwVal.emplace(kw, string(vec->begin(), vec->end()));
+            }
+        }
+        catch (const SdBusError& e)
+        {
+            output.emplace(invPath, json::object({}));
+        }
+    }
+
+    addFruTypeAndLocation(exIntf, objectName, kwVal);
+    output.emplace(invPath, kwVal);
+    return output;
+}
+
+void VpdTool::getExtraInterfaceProperties(string invPath, string extraInterface,
+                                          json prop, json exIntf, json& output)
+{
+    variant<string> response;
+
+    string objectName = INVENTORY_PATH + invPath;
+
+    for (const auto& itProp : prop.items())
+    {
+        string kw = itProp.key();
+        try
+        {
+            makeDBusCall(objectName, extraInterface, kw).read(response);
+
+            if (auto str = get_if<string>(&response))
+            {
+                output.emplace(kw, *str);
+            }
+        }
+        catch (const SdBusError& e)
+        {
+            output.emplace(invPath, json::object({}));
+        }
+    }
+    addFruTypeAndLocation(exIntf, objectName, output);
+}
+
+json VpdTool::interfaceDecider(json& itemEEPROM)
+{
+    if (itemEEPROM.find("inventoryPath") == itemEEPROM.end())
+    {
+        throw runtime_error("Inventory Path not found");
+    }
+
+    if (itemEEPROM.find("extraInterfaces") == itemEEPROM.end())
+    {
+        throw runtime_error("Extra Interfaces not found");
+    }
+
+    bool exIntfCheck = false;
+    json output = json::object({});
+
+    if (itemEEPROM.value("inherit", true))
+    {
+        json j = getVINIProperties(itemEEPROM.at("inventoryPath"),
+                                   itemEEPROM["extraInterfaces"]);
+        output.insert(j.begin(), j.end());
+    }
+    else
+    {
+        json js;
+        for (const auto& ex : itemEEPROM["extraInterfaces"].items())
+        {
+            if (!(ex.value().is_null()))
+            {
+                exIntfCheck = true;
+                getExtraInterfaceProperties(itemEEPROM.at("inventoryPath"),
+                                            ex.key(), ex.value(),
+                                            itemEEPROM["extraInterfaces"], js);
+            }
+        }
+        output.emplace(itemEEPROM.at("inventoryPath"), js);
+    }
+    return output;
+}
+
+json VpdTool::parseInvJson(const json& jsObject, char flag, string fruPath)
+{
+    json output = json::object({});
+    bool validObject = false;
+
+    if (jsObject.find("frus") == jsObject.end())
+    {
+        throw runtime_error("Frus missing in Inventory json");
+    }
+    else
+    {
+        for (const auto& itemFRUS : jsObject["frus"].items())
+        {
+            for (auto itemEEPROM : itemFRUS.value())
+            {
+                try
+                {
+                    if (flag == 'O')
+                    {
+                        if (itemEEPROM.find("inventoryPath") ==
+                            itemEEPROM.end())
+                        {
+                            throw runtime_error("Inventory Path not found");
+                        }
+
+                        else if (itemEEPROM.at("inventoryPath") == fruPath)
+                        {
+                            validObject = true;
+                            json j = interfaceDecider(itemEEPROM);
+                            output.insert(j.begin(), j.end());
+                            return output;
+                        }
+                    }
+                    else
+                    {
+                        json j = interfaceDecider(itemEEPROM);
+                        output.insert(j.begin(), j.end());
+                    }
+                }
+                catch (exception& e)
+                {
+                    cerr << e.what();
+                }
+            }
+        }
+        if ((flag == 'O') && (!validObject))
+        {
+            throw runtime_error(
+                "Invalid object path. Refer --dumpInventory/-i option.");
+        }
+    }
+    return output;
+}
+
+void VpdTool::dumpInventory(const nlohmann::basic_json<>& jsObject)
+{
+    char flag = 'I';
+    json output = parseInvJson(jsObject, flag, "");
+    debugger(output);
+}
+
+void VpdTool::dumpObject(const nlohmann::basic_json<>& jsObject)
+{
+    char flag = 'O';
+    json output = parseInvJson(jsObject, flag, fruPath);
+    debugger(output);
+}
diff --git a/vpd_tool_impl.hpp b/vpd_tool_impl.hpp
new file mode 100644
index 0000000..378ece2
--- /dev/null
+++ b/vpd_tool_impl.hpp
@@ -0,0 +1,136 @@
+#include "config.h"
+
+#include "types.hpp"
+
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+class VpdTool
+{
+  private:
+    const std::string fruPath;
+
+    /**
+     * @brief Debugger
+     * Displays the output in JSON.
+     *
+     * @param[in] output - json output to be displayed
+     */
+    void debugger(json output);
+
+    /**
+     * @brief make Dbus Call
+     *
+     * @param[in] objectName - dbus Object
+     * @param[in] interface - dbus Interface
+     * @param[in] kw - keyword under the interface
+     *
+     * @return dbus call response
+     */
+    auto makeDBusCall(const std::string& objectName,
+                      const std::string& interface, const std::string& kw);
+
+    /**
+     * @brief Adds FRU type and Location Code
+     * Appends the type of the FRU and location code to the output
+     *
+     * @param[in] exIntf - extraInterfaces json from INVENTORY_JSON
+     * @param[in] object - The D-Bus object to read the location code from
+     * @param[out] kwVal - JSON object into which the FRU type and location code
+     * are placed
+     */
+    void addFruTypeAndLocation(json exIntf, const std::string& object,
+                               json& kwVal);
+
+    /**
+     * @brief Get VINI properties
+     * Making a dbus call for properties [SN, PN, CC, FN, DR]
+     * under VINI interface.
+     *
+     * @param[in] invPath - Value of inventory Path
+     * @param[in] exIntf - extraInterfaces json from INVENTORY_JSON
+     *
+     * @return json output which gives the properties under invPath's VINI
+     * interface
+     */
+    json getVINIProperties(std::string invPath, json exIntf);
+
+    /**
+     * @brief Get ExtraInterface Properties
+     * Making a dbus call for those properties under extraInterfaces.
+     *
+     * @param[in] invPath - Value of inventory path
+     * @param[in] extraInterface - One of the invPath's extraInterfaces whose
+     * value is not null
+     * @param[in] prop - All properties of the extraInterface.
+     *
+     * @return json output which gives the properties under invPath's
+     *         extraInterface.
+     */
+    void getExtraInterfaceProperties(std::string invPath,
+                                     std::string extraInterface, json prop,
+                                     json exIntf, json& output);
+
+    /**
+     * @brief Interface Decider
+     * Decides whether to make the dbus call for
+     * getting properites from extraInterface or from
+     * VINI interface, depending on the value of
+     * extraInterfaces object in the inventory json.
+     *
+     * @param[in] itemEEPROM - holds the reference of one of the EEPROM objects.
+     *
+     * @return json output for one of the EEPROM objects.
+     */
+    json interfaceDecider(json& itemEEPROM);
+
+    /**
+     * @brief Parse Inventory JSON
+     * Parses the complete inventory json and depending upon
+     * the user option makes the dbuscall for the frus
+     * via interfaceDecider function.
+     *
+     * @param[in] jsObject - Inventory json object
+     * @param[in] flag - flag which tells about the user option(either
+     * dumpInventory or dumpObject)
+     * @param[in] fruPath - fruPath is empty for dumpInventory option and holds
+     *                      valid fruPath for dumpObject option.
+     *
+     * @return output json
+     */
+    json parseInvJson(const json& jsObject, char flag, std::string fruPath);
+
+  public:
+    /**
+     * @brief Dump the complete inventory in JSON format
+     *
+     * @param[in] jsObject - Inventory JSON specified in configure file.
+     */
+    void dumpInventory(const nlohmann::basic_json<>& jsObject);
+
+    /**
+     * @brief Dump the given inventory object in JSON format
+     *
+     * @param[in] jsObject - Inventory JSON specified in configure file.
+     */
+    void dumpObject(const nlohmann::basic_json<>& jsObject);
+
+    /**
+     * @brief A Constructor
+     * Constructor is called during the
+     * object instantiation for dumpInventory option.
+     */
+    VpdTool()
+    {
+    }
+
+    /**
+     * @brief Constructor
+     * Constructor is called during the
+     * object instantiation for dumpObject option.
+     */
+    VpdTool(const std::string&& fru) : fruPath(std::move(fru))
+    {
+    }
+};