Add APIs for parsing BIOS configuration JSON

1) Add API to get the BIOS strings from the JSON configuration files.
2) Add API to parse the config file and setup the lookup data structures
   for the BIOS attribute table and BIOS attribute value table.
3) Add API to get the possible values and the default values for the BIOS
   enumeration type.
4) Add API to get the current value of the BIOS enumeration attribute.
5) BIOS attributes can be configured by JSON configuration files which have
   information to build the BIOS string table, attribute table and attribute
   value table.

Change-Id: I747dd3cfc0801f8262ffafe2d516ae7f4ddeb7a2
Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
diff --git a/libpldmresponder/Makefile.am b/libpldmresponder/Makefile.am
index a285b8f..2f060df 100644
--- a/libpldmresponder/Makefile.am
+++ b/libpldmresponder/Makefile.am
@@ -6,7 +6,9 @@
 	bios.cpp \
 	effecters.cpp \
 	pdr.cpp \
-	bios_table.cpp
+	bios_table.cpp \
+	bios_parser.cpp
+
 libpldmresponder_la_LIBADD = \
 	../libpldm/libpldm.la \
 	$(CODE_COVERAGE_LIBS)
diff --git a/libpldmresponder/bios_parser.cpp b/libpldmresponder/bios_parser.cpp
new file mode 100644
index 0000000..fa704e6
--- /dev/null
+++ b/libpldmresponder/bios_parser.cpp
@@ -0,0 +1,305 @@
+#include "bios_parser.hpp"
+
+#include "libpldmresponder/utils.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <nlohmann/json.hpp>
+#include <optional>
+#include <phosphor-logging/log.hpp>
+
+namespace bios_parser
+{
+
+using Json = nlohmann::json;
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+constexpr auto bIOSEnumJson = "enum_attrs.json";
+
+namespace bios_enum
+{
+
+namespace internal
+{
+
+using PropertyValue =
+    std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t,
+                 uint64_t, double, std::string>;
+using Value = std::string;
+
+/** @struct DBusMapping
+ *
+ *  Data structure for storing information regarding BIOS enumeration attribute
+ *  and the D-Bus object for the attribute.
+ */
+struct DBusMapping
+{
+    std::string objectPath;   //!< D-Bus object path
+    std::string interface;    //!< D-Bus interface
+    std::string propertyName; //!< D-Bus property name
+    std::map<PropertyValue, Value>
+        dBusValToValMap; //!< Map of D-Bus property
+                         //!< value to attribute value
+};
+
+/** @brief Map containing the possible and the default values for the BIOS
+ *         enumeration type attributes.
+ */
+AttrValuesMap valueMap;
+
+/** @brief Map containing the optional D-Bus property information about the
+ *         BIOS enumeration type attributes.
+ */
+std::map<AttrName, std::optional<DBusMapping>> attrLookup;
+
+/** @brief Populate the mapping between D-Bus property value and attribute value
+ *         for the BIOS enumeration attribute.
+ *
+ *  @param[in] type - type of the D-Bus property
+ *  @param[in] dBusValues - json array of D-Bus property values
+ *  @param[in] pv - Possible values for the BIOS enumeration attribute
+ *  @param[out] mapping - D-Bus mapping object for the attribute
+ *
+ */
+void populateMapping(const std::string& type, const Json& dBusValues,
+                     const PossibleValues& pv, DBusMapping& mapping)
+{
+    size_t pos = 0;
+    PropertyValue value;
+    for (auto it = dBusValues.begin(); it != dBusValues.end(); ++it, ++pos)
+    {
+        if (type == "uint8_t")
+        {
+            value = static_cast<uint8_t>(it.value());
+        }
+        else if (type == "uint16_t")
+        {
+            value = static_cast<uint16_t>(it.value());
+        }
+        else if (type == "uint32_t")
+        {
+            value = static_cast<uint32_t>(it.value());
+        }
+        else if (type == "uint64_t")
+        {
+            value = static_cast<uint64_t>(it.value());
+        }
+        else if (type == "int16_t")
+        {
+            value = static_cast<int16_t>(it.value());
+        }
+        else if (type == "int32_t")
+        {
+            value = static_cast<int32_t>(it.value());
+        }
+        else if (type == "int64_t")
+        {
+            value = static_cast<int64_t>(it.value());
+        }
+        else if (type == "bool")
+        {
+            value = static_cast<bool>(it.value());
+        }
+        else if (type == "double")
+        {
+            value = static_cast<double>(it.value());
+        }
+        else if (type == "string")
+        {
+            value = static_cast<std::string>(it.value());
+        }
+        else
+        {
+            log<level::ERR>("Unknown D-Bus property type",
+                            entry("TYPE=%s", type.c_str()));
+        }
+
+        mapping.dBusValToValMap.emplace(value, pv[pos]);
+    }
+}
+
+/** @brief Read the possible values for the BIOS enumeration type
+ *
+ *  @param[in] possibleValues - json array of BIOS enumeration possible values
+ */
+PossibleValues readPossibleValues(Json& possibleValues)
+{
+    Strings biosStrings{};
+
+    for (auto& val : possibleValues)
+    {
+        biosStrings.emplace_back(std::move(val));
+    }
+
+    return biosStrings;
+}
+
+} // namespace internal
+
+int setupValueLookup(const char* dirPath)
+{
+    int rc = 0;
+
+    if (!internal::valueMap.empty() && !internal::attrLookup.empty())
+    {
+        return rc;
+    }
+
+    // Parse the BIOS enumeration config file
+    fs::path filePath(dirPath);
+    filePath /= bIOSEnumJson;
+
+    std::ifstream jsonFile(filePath);
+    if (!jsonFile.is_open())
+    {
+        log<level::ERR>("BIOS enum config file does not exist",
+                        entry("FILE=%s", filePath.c_str()));
+        rc = -1;
+        return rc;
+    }
+
+    auto fileData = Json::parse(jsonFile, nullptr, false);
+    if (fileData.is_discarded())
+    {
+        log<level::ERR>("Parsing config file failed");
+        rc = -1;
+        return rc;
+    }
+
+    static const std::vector<Json> emptyList{};
+    auto entries = fileData.value("entries", emptyList);
+    // Iterate through each JSON object in the config file
+    for (const auto& entry : entries)
+    {
+        std::string attr = entry.value("attribute_name", "");
+        PossibleValues possibleValues;
+        DefaultValues defaultValues;
+
+        Json pv = entry["possible_values"];
+        for (auto& val : pv)
+        {
+            possibleValues.emplace_back(std::move(val));
+        }
+
+        Json dv = entry["default_values"];
+        for (auto& val : dv)
+        {
+            defaultValues.emplace_back(std::move(val));
+        }
+
+        std::optional<internal::DBusMapping> dBusMap = std::nullopt;
+        static const Json empty{};
+        if (entry.count("dbus") != 0)
+        {
+            auto dBusEntry = entry.value("dbus", empty);
+            dBusMap = std::make_optional<internal::DBusMapping>();
+            dBusMap.value().objectPath = dBusEntry.value("object_path", "");
+            dBusMap.value().interface = dBusEntry.value("interface", "");
+            dBusMap.value().propertyName = dBusEntry.value("property_name", "");
+            std::string propType = dBusEntry.value("property_type", "");
+            Json propValues = dBusEntry["property_values"];
+            internal::populateMapping(propType, propValues, possibleValues,
+                                      dBusMap.value());
+        }
+
+        internal::attrLookup.emplace(attr, std::move(dBusMap));
+
+        // Defaulting all the types of attributes to BIOSEnumeration
+        internal::valueMap.emplace(
+            std::move(attr), std::make_tuple(false, std::move(possibleValues),
+                                             std::move(defaultValues)));
+    }
+
+    return rc;
+}
+
+const AttrValuesMap& getValues()
+{
+    return internal::valueMap;
+}
+
+CurrentValues getAttrValue(const AttrName& attrName)
+{
+    const auto& dBusMap = internal::attrLookup.at(attrName);
+    CurrentValues currentValues;
+    internal::PropertyValue propValue;
+
+    if (dBusMap == std::nullopt)
+    {
+        const auto& valueEntry = internal::valueMap.at(attrName);
+        const auto& [readOnly, possibleValues, currentValues] = valueEntry;
+        return currentValues;
+    }
+
+    auto bus = sdbusplus::bus::new_default();
+    auto service = pldm::responder::getService(bus, dBusMap.value().objectPath,
+                                               dBusMap.value().interface);
+    auto method =
+        bus.new_method_call(service.c_str(), dBusMap.value().objectPath.c_str(),
+                            "org.freedesktop.DBus.Properties", "Get");
+    method.append(dBusMap.value().interface, dBusMap.value().propertyName);
+    auto reply = bus.call(method);
+    reply.read(propValue);
+
+    auto iter = dBusMap.value().dBusValToValMap.find(propValue);
+    if (iter != dBusMap.value().dBusValToValMap.end())
+    {
+        currentValues.push_back(iter->second);
+    }
+
+    return currentValues;
+}
+
+} // namespace bios_enum
+
+Strings getStrings(const char* dirPath)
+{
+    Strings biosStrings{};
+    fs::path dir(dirPath);
+
+    if (!fs::exists(dir) || fs::is_empty(dir))
+    {
+        return biosStrings;
+    }
+
+    for (const auto& file : fs::directory_iterator(dir))
+    {
+        std::ifstream jsonFile(file.path().c_str());
+        if (!jsonFile.is_open())
+        {
+            log<level::ERR>("JSON BIOS config file does not exist",
+                            entry("FILE=%s", file.path().filename().c_str()));
+            continue;
+        }
+
+        auto fileData = Json::parse(jsonFile, nullptr, false);
+        if (fileData.is_discarded())
+        {
+            log<level::ERR>("Parsing config file failed",
+                            entry("FILE=%s", file.path().filename().c_str()));
+            continue;
+        }
+
+        static const std::vector<Json> emptyList{};
+        auto entries = fileData.value("entries", emptyList);
+
+        // Iterate through each entry in the config file
+        for (auto& entry : entries)
+        {
+            biosStrings.emplace_back(entry.value("attribute_name", ""));
+
+            // For BIOS enumeration attributes the possible values are strings
+            if (file.path().filename() == bIOSEnumJson)
+            {
+                auto possibleValues = bios_enum::internal::readPossibleValues(
+                    entry["possible_values"]);
+                std::move(possibleValues.begin(), possibleValues.end(),
+                          std::back_inserter(biosStrings));
+            }
+        }
+    }
+
+    return biosStrings;
+}
+
+} // namespace bios_parser
diff --git a/libpldmresponder/bios_parser.hpp b/libpldmresponder/bios_parser.hpp
new file mode 100644
index 0000000..0e21991
--- /dev/null
+++ b/libpldmresponder/bios_parser.hpp
@@ -0,0 +1,90 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <tuple>
+#include <variant>
+#include <vector>
+
+/*
+ * BIOS Parser API usage:
+ *
+ * 1) bios_parser::getStrings gets all the attribute names and the preconfigured
+ *    strings used in representing the values of the attributes. This will be
+ *    used to populate the BIOS String table.
+ *
+ * 2) bios_enum::setupValueLookup (similar API for other supported BIOS
+ *    attribute types) has to be invoked to setup the lookup data structure for
+ *    all the attributes of that type. This API needs to be invoked before
+ *    invoking bios_enum::getValues and bios_enum::getAttrValue.
+ *
+ * 3) bios_enum::getValues is invoked to populate the BIOS attribute table for
+ *    BIOSEnumeration and BIOSEnumerationReadonly types.(similar API for other
+ *    BIOS attribute types)
+ *
+ * 4) bios_enum::getAttrValue will return the current values for the BIOS
+ *    enumeration attribute. If there is no D-Bus mapping for the attribute then
+ *    default value is returned.(similar API for other BIOS attribute types).
+ *
+ */
+
+namespace bios_parser
+{
+
+using Strings = std::vector<std::string>;
+
+/** @brief Parse every BIOS configuration JSON files in the directory path
+ *         and populate all the attribute names and all the preconfigured
+ *         strings used in representing the values of attributes.
+ *
+ *  @param[in] dirPath - directory path where all the BIOS configuration JSON
+ *                      files exist.
+ *
+ *  @return all the strings that should be populated in the BIOS string table
+ */
+Strings getStrings(const char* dirPath);
+
+namespace bios_enum
+{
+
+/** @brief Parse the JSON file specific to BIOSEnumeration and
+ *         BIOSEnumerationReadOnly types and populate the data structure for
+ *         the corresponding possible values and the default value. Setup the
+ *         data structure to lookup the current value of the BIOS enumeration
+ *         attribute. JSON is parsed once and the information is cached.
+ *
+ *  @param[in] dirPath - directory path where all the BIOS configuration JSON
+ *                      exist
+ *
+ *  @return 0 for success and negative return code for failure
+ */
+int setupValueLookup(const char* dirPath);
+
+using AttrName = std::string;
+using IsReadOnly = bool;
+using PossibleValues = std::vector<std::string>;
+using DefaultValues = std::vector<std::string>;
+using AttrValuesMap =
+    std::map<AttrName, std::tuple<IsReadOnly, PossibleValues, DefaultValues>>;
+
+/** @brief Get the possible values and the default values for the
+ *         BIOSEnumeration and BIOSEnumerationReadOnly types
+ *
+ *  @return information needed to build the BIOS attribute table specific to
+ *         BIOSEnumeration and BIOSEnumerationReadOnly types
+ */
+const AttrValuesMap& getValues();
+
+using CurrentValues = std::vector<std::string>;
+
+/** @brief Get the current values for the BIOS Attribute
+ *
+ *  @param[in] attrName - BIOS attribute name
+ *
+ *  @return BIOS attribute value
+ */
+CurrentValues getAttrValue(const AttrName& attrName);
+
+} // namespace bios_enum
+
+} // namespace bios_parser
diff --git a/libpldmresponder/examples/bios/enum_attrs.json b/libpldmresponder/examples/bios/enum_attrs.json
new file mode 100644
index 0000000..01f8526
--- /dev/null
+++ b/libpldmresponder/examples/bios/enum_attrs.json
@@ -0,0 +1,53 @@
+# This is a sample JSON configuration file for BIOS enumeration type
+{
+   "entries":[
+      {
+         "attribute_name" : "HMCManagedState",
+         "possible_values" : [ "On", "Off" ],
+         "default_values" : [ "On" ],
+         "object_path" : "/xyz/abc/def",
+         // This BIOS attribute has a D-Bus property as backend. 
+         "dbus":
+            {
+               "interface" : "xyz.openbmc_project.HMCManaged.State",
+               "property_name" : "State",
+               "property_type" : "string",
+               # Number of property_values should match to the number of possible values.
+               # Each is entry in the possible_values is mapped to entry in property_values. 
+               "property_values" : ["xyz.openbmc_project.State.On", "xyz.openbmc_project.State.Off"]
+            }
+      },
+      {
+         "attribute_name" : "FWBootSide",
+         "possible_values" : [ "Perm", "Temp" ],
+         "default_values" : [ "Perm" ],
+         "dbus":
+            {
+               "object_path" : "/xyz/abc/def",
+               "interface" : "xyz.openbmc.FWBoot.Side",
+               "property_name" : "Side",
+               "property_type" : "bool",
+               "property_values" : [true, false]
+            }
+      },
+      {
+         "attribute_name" : "InbandCodeUpdate",
+         "possible_values" : [ "Allowed", "NotAllowed" ],
+         "default_values" : [ "Allowed" ],
+         "dbus":
+            {
+               "object_path" : "/xyz/abc/def",
+               "interface" : "xyz.openbmc.InBandCodeUpdate",
+               "property_name" : "Policy",
+               "property_type" : "uint8_t",
+               "property_values" : [0, 1]
+            }
+      },
+      {
+         # This is an example of BIOS Enumeration Read only attribute
+         "attribute_name" : "CodeUpdatePolicy",
+         "possible_values" : [ "Concurrent", "Disruptive" ],
+         "default_values" : [ "Concurrent" ]
+      }
+    ]
+}