Implement the FRU implementation class

FRU implementation class implements the logic to build the PLDM FRU table
from the inventory D-Bus objects. This class exposes APIs to read the
FRU table, number of FRU records and the checksum of the table. This will
be consumed by the GetFRURecordTableMetadata command and GetFRURecordTable
command specified in the DMTF specification DSP0257.

Signed-off-by: Tom Joseph <tomjoseph@in.ibm.com>
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
Change-Id: I3212f59e7e972dc66f57a507ba87b707281ba0b9
diff --git a/libpldmresponder/fru.cpp b/libpldmresponder/fru.cpp
new file mode 100644
index 0000000..096c4f7
--- /dev/null
+++ b/libpldmresponder/fru.cpp
@@ -0,0 +1,177 @@
+#include "fru.hpp"
+
+#include "utils.hpp"
+
+#include <systemd/sd-journal.h>
+
+#include <boost/crc.hpp>
+#include <iostream>
+#include <sdbusplus/bus.hpp>
+#include <set>
+
+namespace pldm
+{
+
+namespace responder
+{
+
+FruImpl::FruImpl(const std::string& configPath)
+{
+    fru_parser::FruParser handle(configPath);
+
+    auto dbusInfo = handle.inventoryLookup();
+
+    // Read the all the inventory D-Bus objects
+    auto& bus = pldm::utils::DBusHandler::getBus();
+    dbus::ObjectValueTree objects;
+
+    try
+    {
+        auto method = bus.new_method_call(
+            std::get<0>(dbusInfo).c_str(), std::get<1>(dbusInfo).c_str(),
+            "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+        auto reply = bus.call(method);
+        reply.read(objects);
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << "Look up of inventory objects failed and PLDM FRU table "
+                     "creation failed";
+        return;
+    }
+
+    // Populate all the interested Item types to a map for easy lookup
+    std::set<dbus::Interface> itemIntfsLookup;
+    auto itemIntfs = std::get<2>(dbusInfo);
+    std::transform(std::begin(itemIntfs), std::end(itemIntfs),
+                   std::inserter(itemIntfsLookup, itemIntfsLookup.end()),
+                   [](dbus::Interface intf) { return intf; });
+
+    for (const auto& object : objects)
+    {
+        const auto& interfaces = object.second;
+
+        for (const auto& interface : interfaces)
+        {
+            if (itemIntfsLookup.find(interface.first) != itemIntfsLookup.end())
+            {
+                // An exception will be thrown by getRecordInfo, if the item
+                // D-Bus interface name specified in FRU_Master.json does
+                // not have corresponding config jsons
+                try
+                {
+                    auto recordInfos = handle.getRecordInfo(interface.first);
+                    populateRecords(interfaces, recordInfos);
+                }
+                catch (const std::exception& e)
+                {
+                    std::cout << "Config JSONs missing for the item "
+                                 "interface type, interface = "
+                              << interface.first << "\n";
+                    break;
+                }
+            }
+        }
+    }
+
+    if (table.size())
+    {
+        padBytes = utils::getNumPadBytes(table.size());
+        table.resize(table.size() + padBytes, 0);
+
+        // Calculate the checksum
+        boost::crc_32_type result;
+        result.process_bytes(table.data(), table.size());
+        checksum = result.checksum();
+    }
+}
+
+void FruImpl::populateRecords(
+    const pldm::responder::dbus::InterfaceMap& interfaces,
+    const fru_parser::FruRecordInfos& recordInfos)
+{
+    // recordSetIdentifier for the FRU will be set when the first record gets
+    // added for the FRU
+    uint16_t recordSetIdentifier = 0;
+    auto numRecsCount = numRecs;
+
+    for (auto const& [recType, encType, fieldInfos] : recordInfos)
+    {
+        std::vector<uint8_t> tlvs;
+        uint8_t numFRUFields = 0;
+        for (auto const& [intf, prop, propType, fieldTypeNum] : fieldInfos)
+        {
+            try
+            {
+                auto propValue = interfaces.at(intf).at(prop);
+                if (propType == "bytearray")
+                {
+                    auto byteArray = std::get<std::vector<uint8_t>>(propValue);
+                    if (!byteArray.size())
+                    {
+                        continue;
+                    }
+
+                    numFRUFields++;
+                    tlvs.emplace_back(fieldTypeNum);
+                    tlvs.emplace_back(byteArray.size());
+                    std::move(std::begin(byteArray), std::end(byteArray),
+                              std::back_inserter(tlvs));
+                }
+                else if (propType == "string")
+                {
+                    auto str = std::get<std::string>(propValue);
+                    if (!str.size())
+                    {
+                        continue;
+                    }
+
+                    numFRUFields++;
+                    tlvs.emplace_back(fieldTypeNum);
+                    tlvs.emplace_back(str.size());
+                    std::move(std::begin(str), std::end(str),
+                              std::back_inserter(tlvs));
+                }
+            }
+            catch (const std::out_of_range& e)
+            {
+                std::cout << "Interface = " << intf
+                          << " , and Property = " << prop
+                          << " , in config json not present in D-Bus"
+                          << "\n";
+                continue;
+            }
+        }
+
+        if (tlvs.size())
+        {
+            if (numRecs == numRecsCount)
+            {
+                recordSetIdentifier = nextRSI();
+            }
+            auto curSize = table.size();
+            table.resize(curSize + recHeaderSize + tlvs.size());
+            encode_fru_record(table.data(), table.size(), &curSize,
+                              recordSetIdentifier, recType, numFRUFields,
+                              encType, tlvs.data(), tlvs.size());
+            numRecs++;
+        }
+    }
+}
+
+void FruImpl::getFRUTable(Response& response)
+{
+    auto hdrSize = response.size();
+
+    response.resize(hdrSize + table.size() + sizeof(checksum), 0);
+    std::copy(table.begin(), table.end(), response.begin() + hdrSize);
+
+    // Copy the checksum to response data
+    auto iter = response.begin() + hdrSize + table.size();
+    std::copy_n(reinterpret_cast<const uint8_t*>(&checksum), sizeof(checksum),
+                iter);
+}
+
+} // namespace responder
+
+} // namespace pldm
diff --git a/libpldmresponder/fru.hpp b/libpldmresponder/fru.hpp
new file mode 100644
index 0000000..3f817fd
--- /dev/null
+++ b/libpldmresponder/fru.hpp
@@ -0,0 +1,126 @@
+#pragma once
+
+#include "config.h"
+
+#include "fru_parser.hpp"
+#include "handler.hpp"
+
+#include <map>
+#include <sdbusplus/message.hpp>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "libpldm/fru.h"
+
+namespace pldm
+{
+
+namespace responder
+{
+
+namespace dbus
+{
+
+using Value =
+    std::variant<bool, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t,
+                 uint64_t, double, std::string, std::vector<uint8_t>>;
+using PropertyMap = std::map<Property, Value>;
+using InterfaceMap = std::map<Interface, PropertyMap>;
+using ObjectValueTree = std::map<sdbusplus::message::object_path, InterfaceMap>;
+
+} // namespace dbus
+
+/** @class FruImpl
+ *
+ *  @brief Builds the PLDM FRU table containing the FRU records
+ */
+class FruImpl
+{
+  public:
+    /* @brief Header size for FRU record, it includes the FRU record set
+     *        identifier, FRU record type, Number of FRU fields, Encoding type
+     *        of FRU fields
+     */
+    static constexpr size_t recHeaderSize =
+        sizeof(struct pldm_fru_record_data_format) -
+        sizeof(struct pldm_fru_record_tlv);
+
+    /** @brief The FRU table is populated by processing the D-Bus inventory
+     *         namespace, based on the config files for FRU. The configPath is
+     *         consumed to build the FruParser object.
+     *
+     *  @param[in] configPath - path to the directory containing config files
+     * for PLDM FRU
+     */
+    FruImpl(const std::string& configPath);
+
+    /** @brief Total length of the FRU table in bytes, this excludes the pad
+     *         bytes and the checksum.
+     *
+     *  @return size of the FRU table
+     */
+    uint32_t size() const
+    {
+        return table.size() - padBytes;
+    }
+
+    /** @brief The checksum of the contents of the FRU table
+     *
+     *  @return checksum
+     */
+    uint32_t checkSum() const
+    {
+        return checksum;
+    }
+
+    /** @brief Number of record set identifiers in the FRU tables
+     *
+     *  @return number of record set identifiers
+     */
+    uint16_t numRSI() const
+    {
+        return rsi;
+    }
+
+    /** @brief The number of FRU records in the table
+     *
+     *  @return number of FRU records
+     */
+    uint16_t numRecords() const
+    {
+        return numRecs;
+    }
+
+    /** @brief Get the FRU table
+     *
+     *  @param[out] - Populate response with the FRU table
+     */
+    void getFRUTable(Response& response);
+
+  private:
+    uint16_t nextRSI()
+    {
+        return ++rsi;
+    }
+
+    uint16_t rsi = 0;
+    uint16_t numRecs = 0;
+    uint8_t padBytes = 0;
+    std::vector<uint8_t> table;
+    uint32_t checksum = 0;
+
+    /** @brief populateRecord builds the FRU records for an instance of FRU and
+     *         updates the FRU table with the FRU records.
+     *
+     *  @param[in] interfaces - D-Bus interfaces and the associated property
+     *                          values for the FRU
+     *  @param[in] recordInfos - FRU record info to build the FRU records
+     */
+    void populateRecords(const pldm::responder::dbus::InterfaceMap& interfaces,
+                         const fru_parser::FruRecordInfos& recordInfos);
+};
+
+} // namespace responder
+
+} // namespace pldm
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index 92a13be..e4e6a87 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -13,7 +13,8 @@
   'pdr.cpp',
   'effecters.cpp',
   'platform.cpp',
-  'fru_parser.cpp'
+  'fru_parser.cpp',
+  'fru.cpp'
 ]
 
 if get_option('oem-ibm').enabled()