libpldmresponder: FRU: construct PDRs

Construct FRU record set and entity association PDRs for the FRUs for
which the BMC collects VPD (FRU information off of an EEPROM).

These PDRs are structured as per PLDM spec DSP0248 v1.2.0.

Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
Change-Id: I2c72d74dad449561b26c74482e00d1606546c5a2
diff --git a/libpldmresponder/examples/fru/FRU_Master.json b/libpldmresponder/examples/fru/FRU_Master.json
index 3156022..a99b476 100644
--- a/libpldmresponder/examples/fru/FRU_Master.json
+++ b/libpldmresponder/examples/fru/FRU_Master.json
@@ -12,8 +12,14 @@
 {
     "service":"xyz.openbmc_project.Inventory.Manager",
     "root_path":"/xyz/openbmc_project/inventory/system/",
-    "interfaces":[
-        "xyz.openbmc_project.Inventory.Item.Board",
-        "xyz.openbmc_project.Inventory.Item.Cpu"
+    "entities":[
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Board",
+            "entity_type" : 64
+        },
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Cpu",
+            "entity_type" : 135
+        }
     ]
 }
diff --git a/libpldmresponder/fru.cpp b/libpldmresponder/fru.cpp
index 7b73a81..42babc3 100644
--- a/libpldmresponder/fru.cpp
+++ b/libpldmresponder/fru.cpp
@@ -15,7 +15,10 @@
 namespace responder
 {
 
-FruImpl::FruImpl(const std::string& configPath)
+FruImpl::FruImpl(const std::string& configPath, pldm_pdr* pdrRepo,
+                 pldm_entity_association_tree* entityTree) :
+    pdrRepo(pdrRepo),
+    entityTree(entityTree)
 {
     fru_parser::FruParser handle(configPath);
 
@@ -40,12 +43,7 @@
         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; });
+    auto itemIntfsLookup = std::get<2>(dbusInfo);
 
     for (const auto& object : objects)
     {
@@ -60,8 +58,39 @@
                 // not have corresponding config jsons
                 try
                 {
+                    pldm_entity entity{};
+                    entity.entity_type = handle.getEntityType(interface.first);
+                    pldm_entity_node* parent = nullptr;
+                    auto parentObj = pldm::utils::findParent(object.first.str);
+                    // To add a FRU to the entity association tree, we need to
+                    // determine if the FRU has a parent (D-Bus object). For eg
+                    // /system/backplane's parent is /system. /system has no
+                    // parent. Some D-Bus pathnames might just be namespaces
+                    // (not D-Bus objects), so we need to iterate upwards until
+                    // a parent is found, or we reach the root ("/").
+                    // Parents are always added first before children in the
+                    // entity association tree. We're relying on the fact that
+                    // the std::map containing object paths from the
+                    // GetManagedObjects call will have a sorted pathname list.
+                    do
+                    {
+                        auto iter = objToEntityNode.find(parentObj);
+                        if (iter != objToEntityNode.end())
+                        {
+                            parent = iter->second;
+                            break;
+                        }
+                        parentObj = pldm::utils::findParent(parentObj);
+                    } while (parentObj != "/");
+
+                    auto node = pldm_entity_association_tree_add(
+                        entityTree, &entity, parent,
+                        PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+                    objToEntityNode[object.first.str] = node;
+
                     auto recordInfos = handle.getRecordInfo(interface.first);
-                    populateRecords(interfaces, recordInfos);
+                    populateRecords(interfaces, recordInfos, entity);
+                    break;
                 }
                 catch (const std::exception& e)
                 {
@@ -74,6 +103,8 @@
         }
     }
 
+    pldm_entity_association_pdr_add(entityTree, pdrRepo);
+
     if (table.size())
     {
         padBytes = utils::getNumPadBytes(table.size());
@@ -88,7 +119,7 @@
 
 void FruImpl::populateRecords(
     const pldm::responder::dbus::InterfaceMap& interfaces,
-    const fru_parser::FruRecordInfos& recordInfos)
+    const fru_parser::FruRecordInfos& recordInfos, const pldm_entity& entity)
 {
     // recordSetIdentifier for the FRU will be set when the first record gets
     // added for the FRU
@@ -144,6 +175,9 @@
             if (numRecs == numRecsCount)
             {
                 recordSetIdentifier = nextRSI();
+                pldm_pdr_add_fru_record_set(
+                    pdrRepo, 0, recordSetIdentifier, entity.entity_type,
+                    entity.entity_instance_num, entity.entity_container_id);
             }
             auto curSize = table.size();
             table.resize(curSize + recHeaderSize + tlvs.size());
diff --git a/libpldmresponder/fru.hpp b/libpldmresponder/fru.hpp
index ae3ef0e..d4a647a 100644
--- a/libpldmresponder/fru.hpp
+++ b/libpldmresponder/fru.hpp
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "libpldm/fru.h"
+#include "libpldm/pdr.h"
 
 namespace pldm
 {
@@ -28,6 +29,7 @@
 using PropertyMap = std::map<Property, Value>;
 using InterfaceMap = std::map<Interface, PropertyMap>;
 using ObjectValueTree = std::map<sdbusplus::message::object_path, InterfaceMap>;
+using ObjectPath = std::string;
 
 } // namespace dbus
 
@@ -53,7 +55,8 @@
      *  @param[in] configPath - path to the directory containing config files
      * for PLDM FRU
      */
-    FruImpl(const std::string& configPath);
+    FruImpl(const std::string& configPath, pldm_pdr* pdrRepo,
+            pldm_entity_association_tree* entityTree);
 
     /** @brief Total length of the FRU table in bytes, this excludes the pad
      *         bytes and the checksum.
@@ -110,15 +113,22 @@
     std::vector<uint8_t> table;
     uint32_t checksum = 0;
 
+    pldm_pdr* pdrRepo;
+    pldm_entity_association_tree* entityTree;
+
+    std::map<dbus::ObjectPath, pldm_entity_node*> objToEntityNode{};
+
     /** @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
+     *  @param[in/out] entity - PLDM entity corresponding to FRU instance
      */
     void populateRecords(const pldm::responder::dbus::InterfaceMap& interfaces,
-                         const fru_parser::FruRecordInfos& recordInfos);
+                         const fru_parser::FruRecordInfos& recordInfos,
+                         const pldm_entity& entity);
 };
 
 namespace fru
@@ -128,7 +138,9 @@
 {
 
   public:
-    Handler(const std::string& configPath) : impl(configPath)
+    Handler(const std::string& configPath, pldm_pdr* pdrRepo,
+            pldm_entity_association_tree* entityTree) :
+        impl(configPath, pdrRepo, entityTree)
     {
         handlers.emplace(PLDM_GET_FRU_RECORD_TABLE_METADATA,
                          [this](const pldm_msg* request, size_t payloadLength) {
@@ -143,8 +155,6 @@
                          });
     }
 
-    FruImpl impl;
-
     /** @brief Handler for Get FRURecordTableMetadata
      *
      *  @param[in] request - Request message payload
@@ -163,6 +173,9 @@
      *  @return PLDM response message
      */
     Response getFRURecordTable(const pldm_msg* request, size_t payloadLength);
+
+  private:
+    FruImpl impl;
 };
 
 } // namespace fru
diff --git a/libpldmresponder/fru_parser.cpp b/libpldmresponder/fru_parser.cpp
index a63dae6..e2c2317 100644
--- a/libpldmresponder/fru_parser.cpp
+++ b/libpldmresponder/fru_parser.cpp
@@ -60,7 +60,16 @@
 
     Service service = data.value("service", "");
     RootPath rootPath = data.value("root_path", "");
-    Interfaces interfaces = data.value("interfaces", emptyStringVec);
+    auto entities = data.value("entities", emptyJsonList);
+    Interfaces interfaces{};
+    EntityType entityType{};
+    for (auto& entity : entities)
+    {
+        auto intf = entity.value("interface", "");
+        intfToEntityType[intf] =
+            std::move(entity.value("entity_type", entityType));
+        interfaces.emplace(std::move(intf));
+    }
     lookupInfo.emplace(std::make_tuple(std::move(service), std::move(rootPath),
                                        std::move(interfaces)));
 }
diff --git a/libpldmresponder/fru_parser.hpp b/libpldmresponder/fru_parser.hpp
index f49c04b..a11610a 100644
--- a/libpldmresponder/fru_parser.hpp
+++ b/libpldmresponder/fru_parser.hpp
@@ -2,6 +2,7 @@
 
 #include <filesystem>
 #include <map>
+#include <set>
 #include <string>
 #include <tuple>
 #include <vector>
@@ -18,9 +19,10 @@
 using Service = std::string;
 using RootPath = std::string;
 using Interface = std::string;
-using Interfaces = std::vector<Interface>;
+using Interfaces = std::set<Interface>;
 using Property = std::string;
 using PropertyType = std::string;
+using EntityType = uint8_t;
 
 } // namespace dbus
 
@@ -95,6 +97,11 @@
         return recordMap.at(intf);
     }
 
+    EntityType getEntityType(const Interface& intf) const
+    {
+        return intfToEntityType.at(intf);
+    }
+
   private:
     /** @brief Parse the FRU_Master.json file and populate the D-Bus lookup
      *         information which provides the service, root D-Bus path and the
@@ -114,6 +121,7 @@
 
     std::optional<DBusLookupInfo> lookupInfo;
     FruRecordMap recordMap;
+    std::map<Interface, EntityType> intfToEntityType;
 };
 
 } // namespace fru_parser
diff --git a/pldmd.cpp b/pldmd.cpp
index 6ddbcde..501de5a 100644
--- a/pldmd.cpp
+++ b/pldmd.cpp
@@ -148,14 +148,19 @@
 
     std::unique_ptr<pldm_pdr, decltype(&pldm_pdr_destroy)> pdrRepo(
         pldm_pdr_init(), pldm_pdr_destroy);
+    std::unique_ptr<pldm_entity_association_tree,
+                    decltype(&pldm_entity_association_tree_destroy)>
+        entityTree(pldm_entity_association_tree_init(),
+                   pldm_entity_association_tree_destroy);
 
     Invoker invoker{};
     invoker.registerHandler(PLDM_BASE, std::make_unique<base::Handler>());
     invoker.registerHandler(PLDM_BIOS, std::make_unique<bios::Handler>());
     invoker.registerHandler(PLDM_PLATFORM, std::make_unique<platform::Handler>(
                                                PDR_JSONS_DIR, pdrRepo.get()));
-    invoker.registerHandler(PLDM_FRU,
-                            std::make_unique<fru::Handler>(FRU_JSONS_DIR));
+    invoker.registerHandler(
+        PLDM_FRU, std::make_unique<fru::Handler>(FRU_JSONS_DIR, pdrRepo.get(),
+                                                 entityTree.get()));
 
 #ifdef OEM_IBM
     invoker.registerHandler(PLDM_OEM, std::make_unique<oem_ibm::Handler>());
diff --git a/test/fru_jsons/good/FRU_Master.json b/test/fru_jsons/good/FRU_Master.json
index 9c631fb..fa2c405 100644
--- a/test/fru_jsons/good/FRU_Master.json
+++ b/test/fru_jsons/good/FRU_Master.json
@@ -1,8 +1,14 @@
 {
     "service":"xyz.openbmc_project.Inventory.Manager",
     "root_path":"/xyz/openbmc_project/inventory/system/",
-    "interfaces":[
-        "xyz.openbmc_project.Inventory.Item.Board",
-        "xyz.openbmc_project.Inventory.Item.Cpu"
+    "entities":[
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Board",
+            "entity_type" : 64
+        },
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Cpu",
+            "entity_type" : 135
+        }
     ]
 }
diff --git a/test/fru_jsons/malformed2/FRU_Master.json b/test/fru_jsons/malformed2/FRU_Master.json
index cb23da7..1cf1e4c 100644
--- a/test/fru_jsons/malformed2/FRU_Master.json
+++ b/test/fru_jsons/malformed2/FRU_Master.json
@@ -1,8 +1,14 @@
 {
     "service":"xyz.openbmc_project.Inventory.Manager"
     "root_path":"/xyz/openbmc_project/inventory/system/",
-    "interfaces":[
-        "xyz.openbmc_project.Inventory.Item.Board",
-        "xyz.openbmc_project.Inventory.Item.Cpu"
+    "entities":[
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Board",
+            "entity_type" : 64
+        },
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Cpu",
+            "entity_type" : 135
+        }
     ]
 }
diff --git a/test/fru_jsons/malformed3/FRU_Master.json b/test/fru_jsons/malformed3/FRU_Master.json
index 9c631fb..fa2c405 100644
--- a/test/fru_jsons/malformed3/FRU_Master.json
+++ b/test/fru_jsons/malformed3/FRU_Master.json
@@ -1,8 +1,14 @@
 {
     "service":"xyz.openbmc_project.Inventory.Manager",
     "root_path":"/xyz/openbmc_project/inventory/system/",
-    "interfaces":[
-        "xyz.openbmc_project.Inventory.Item.Board",
-        "xyz.openbmc_project.Inventory.Item.Cpu"
+    "entities":[
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Board",
+            "entity_type" : 64
+        },
+        {
+            "interface" : "xyz.openbmc_project.Inventory.Item.Cpu",
+            "entity_type" : 135
+        }
     ]
 }
diff --git a/utils.hpp b/utils.hpp
index bfbfb5d..6e128be 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -5,6 +5,7 @@
 #include <unistd.h>
 
 #include <exception>
+#include <filesystem>
 #include <iostream>
 #include <sdbusplus/server.hpp>
 #include <string>
@@ -21,6 +22,8 @@
 namespace utils
 {
 
+namespace fs = std::filesystem;
+
 /** @struct CustomFD
  *
  *  RAII wrapper for file descriptor.
@@ -203,5 +206,17 @@
                          const PropertyValue& value) const override;
 };
 
+/** @brief Fetch parent D-Bus object based on pathname
+ *
+ *  @param[in] dbusObj - child D-Bus object
+ *
+ *  @return std::string - the parent D-Bus object path
+ */
+inline std::string findParent(const std::string& dbusObj)
+{
+    fs::path p(dbusObj);
+    return p.parent_path().string();
+}
+
 } // namespace utils
 } // namespace pldm