Refactor buildFRUTable and update to Association tree

The previous logic is to obtain the entity of the parent by parsing
the DBus object path. If the obtaining fails it skipped the current
entity
type and continued to obtain the entity of the next parent.

In this commit the code is optimised to not skip the current entity
type.

The intent behind this commit is to refactor the buildFRUTable
method and their pldm entities need to be created in turn according
to the object path and update the association tree.

Tested:
There is one CPU, one DIMM, two powersupplies on my system
~# pldmtool platform getpdr -d 6
{
    "nextRecordHandle": 7,
    "responseCount": 26,
    "recordHandle": 6,
    "PDRHeaderVersion": 1,
    "PDRType": "Entity Association PDR",
    "recordChangeNumber": 0,
    "dataLength": 16,
    "containerID": 1,
    "associationType": "Physical",
    "containerEntityType": "System (logical)",
    "containerEntityInstanceNumber": 1,
    "containerEntityContainerID": 0,
    "containedEntityCount": 1,
    "containedEntityType[1]": "System chassis (main enclosure)",
    "containedEntityInstanceNumber[1]": 1,
    "containedEntityContainerID[1]": 1
}

~# pldmtool platform getpdr -d 7
{
    "nextRecordHandle": 8,
    "responseCount": 26,
    "recordHandle": 7,
    "PDRHeaderVersion": 1,
    "PDRType": "Entity Association PDR",
    "recordChangeNumber": 0,
    "dataLength": 16,
    "containerID": 2,
    "associationType": "Physical",
    "containerEntityType": "System chassis (main enclosure)",
    "containerEntityInstanceNumber": 1,
    "containerEntityContainerID": 1,
    "containedEntityCount": 1,
    "containedEntityType[1]": "System Board",
    "containedEntityInstanceNumber[1]": 1,
    "containedEntityContainerID[1]": 2
}

~# pldmtool platform getpdr -d 8
{
    "nextRecordHandle": 9,
    "responseCount": 44,
    "recordHandle": 8,
    "PDRHeaderVersion": 1,
    "PDRType": "Entity Association PDR",
    "recordChangeNumber": 0,
    "dataLength": 34,
    "containerID": 3,
    "associationType": "Physical",
    "containerEntityType": "System Board",
    "containerEntityInstanceNumber": 1,
    "containerEntityContainerID": 2,
    "containedEntityCount": 4,
    "containedEntityType[1]": "Processor",
    "containedEntityInstanceNumber[1]": 1,
    "containedEntityContainerID[1]": 3,
    "containedEntityType[2]": "Memory chip",
    "containedEntityInstanceNumber[2]": 1,
    "containedEntityContainerID[2]": 3,
    "containedEntityType[3]": "Power Supply",
    "containedEntityInstanceNumber[3]": 1,
    "containedEntityContainerID[3]": 3,
    "containedEntityType[4]": "Power Supply",
    "containedEntityInstanceNumber[4]": 2,
    "containedEntityContainerID[4]": 3
}

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: Ie375ea448d850450101c85fa818498db0b67cc1f
diff --git a/libpldmresponder/fru.cpp b/libpldmresponder/fru.cpp
index 69ca3cb..b135093 100644
--- a/libpldmresponder/fru.cpp
+++ b/libpldmresponder/fru.cpp
@@ -10,7 +10,9 @@
 #include <sdbusplus/bus.hpp>
 
 #include <iostream>
+#include <optional>
 #include <set>
+#include <stack>
 
 PHOSPHOR_LOG2_USING;
 
@@ -18,6 +20,123 @@
 {
 namespace responder
 {
+
+constexpr auto root = "/xyz/openbmc_project/inventory/";
+
+std::optional<pldm_entity>
+    FruImpl::getEntityByObjectPath(const dbus::InterfaceMap& intfMaps)
+{
+    for (const auto& intfMap : intfMaps)
+    {
+        try
+        {
+            pldm_entity entity{};
+            entity.entity_type = parser.getEntityType(intfMap.first);
+            return entity;
+        }
+        catch (const std::exception& e)
+        {
+            continue;
+        }
+    }
+
+    return std::nullopt;
+}
+
+void FruImpl::updateAssociationTree(const dbus::ObjectValueTree& objects,
+                                    const std::string& path)
+{
+    if (path.find(root) == std::string::npos)
+    {
+        return;
+    }
+
+    std::stack<std::string> tmpObjPaths{};
+    tmpObjPaths.emplace(path);
+
+    auto obj = pldm::utils::findParent(path);
+    while ((obj + '/') != root)
+    {
+        tmpObjPaths.emplace(obj);
+        obj = pldm::utils::findParent(obj);
+    }
+
+    std::stack<std::string> tmpObj = tmpObjPaths;
+    while (!tmpObj.empty())
+    {
+        std::string s = tmpObj.top();
+        std::cout << s << std::endl;
+        tmpObj.pop();
+    }
+    // Update pldm entity to assocition tree
+    std::string prePath = tmpObjPaths.top();
+    while (!tmpObjPaths.empty())
+    {
+        std::string currPath = tmpObjPaths.top();
+        tmpObjPaths.pop();
+
+        do
+        {
+            if (objToEntityNode.contains(currPath))
+            {
+                pldm_entity node =
+                    pldm_entity_extract(objToEntityNode.at(currPath));
+                if (pldm_entity_association_tree_find(entityTree, &node))
+                {
+                    break;
+                }
+            }
+            else
+            {
+                if (!objects.contains(currPath))
+                {
+                    break;
+                }
+
+                auto entityPtr = getEntityByObjectPath(objects.at(currPath));
+                if (!entityPtr)
+                {
+                    break;
+                }
+
+                pldm_entity entity = *entityPtr;
+
+                for (auto& it : objToEntityNode)
+                {
+                    pldm_entity node = pldm_entity_extract(it.second);
+                    if (node.entity_type == entity.entity_type)
+                    {
+                        entity.entity_instance_num = node.entity_instance_num +
+                                                     1;
+                        break;
+                    }
+                }
+
+                if (currPath == prePath)
+                {
+                    auto node = pldm_entity_association_tree_add(
+                        entityTree, &entity, 0xFFFF, nullptr,
+                        PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+                    objToEntityNode[currPath] = node;
+                }
+                else
+                {
+                    if (objToEntityNode.contains(prePath))
+                    {
+                        auto node = pldm_entity_association_tree_add(
+                            entityTree, &entity, 0xFFFF,
+                            objToEntityNode[prePath],
+                            PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+                        objToEntityNode[currPath] = node;
+                    }
+                }
+            }
+        } while (0);
+
+        prePath = currPath;
+    }
+}
+
 void FruImpl::buildFRUTable()
 {
     if (isBuilt)
@@ -69,35 +188,15 @@
                 // not have corresponding config jsons
                 try
                 {
+                    updateAssociationTree(objects, object.first.str);
                     pldm_entity entity{};
-                    entity.entity_type = parser.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
+                    if (objToEntityNode.contains(object.first.str))
                     {
-                        auto iter = objToEntityNode.find(parentObj);
-                        if (iter != objToEntityNode.end())
-                        {
-                            parent = iter->second;
-                            break;
-                        }
-                        parentObj = pldm::utils::findParent(parentObj);
-                    } while (parentObj != "/");
+                        pldm_entity_node* node =
+                            objToEntityNode.at(object.first.str);
 
-                    auto node = pldm_entity_association_tree_add(
-                        entityTree, &entity, 0xFFFF, parent,
-                        PLDM_ENTITY_ASSOCIAION_PHYSICAL);
-                    objToEntityNode[object.first.str] = node;
+                        entity = pldm_entity_extract(node);
+                    }
 
                     auto recordInfos = parser.getRecordInfo(interface.first);
                     populateRecords(interfaces, recordInfos, entity);
diff --git a/libpldmresponder/fru.hpp b/libpldmresponder/fru.hpp
index c47d94c..b986836 100644
--- a/libpldmresponder/fru.hpp
+++ b/libpldmresponder/fru.hpp
@@ -141,6 +141,36 @@
         return associatedEntityMap;
     }
 
+    /** @brief Get pldm entity by the object path
+     *
+     *  @param[in] intfMaps - D-Bus interfaces and the associated property
+     *                        values for the FRU
+     *
+     *  @return pldm_entity
+     */
+    std::optional<pldm_entity>
+        getEntityByObjectPath(const dbus::InterfaceMap& intfMaps);
+
+    /** @brief Update pldm entity to association tree
+     *
+     *  @param[in] objects - std::map The object value tree
+     *  @param[in] path    - Object path
+     *
+     *  Ex: Input path =
+     *  "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0"
+     *
+     *  Get the parent class in turn and store it in a temporary vector
+     *
+     *  Output tmpObjPaths = {
+     *  "/xyz/openbmc_project/inventory/system",
+     *  "/xyz/openbmc_project/inventory/system/chassis/",
+     *  "/xyz/openbmc_project/inventory/system/chassis/motherboard",
+     *  "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0"}
+     *
+     */
+    void updateAssociationTree(const dbus::ObjectValueTree& objects,
+                               const std::string& path);
+
     /* @brief Method to populate the firmware version ID
      *
      * @return firmware version ID
diff --git a/libpldmresponder/test/libpldmresponder_fru_test.cpp b/libpldmresponder/test/libpldmresponder_fru_test.cpp
index 6c942da..6921ad9 100644
--- a/libpldmresponder/test/libpldmresponder_fru_test.cpp
+++ b/libpldmresponder/test/libpldmresponder_fru_test.cpp
@@ -1,6 +1,14 @@
+#include "common/utils.hpp"
+#include "libpldmresponder/fru.hpp"
 #include "libpldmresponder/fru_parser.hpp"
 
+#include <config.h>
+#include <libpldm/pdr.h>
+
+#include <sdbusplus/message.hpp>
+
 #include <gtest/gtest.h>
+
 TEST(FruParser, allScenarios)
 {
     using namespace pldm::responder::fru_parser;
@@ -69,3 +77,108 @@
         parser.getRecordInfo("xyz.openbmc_project.Inventory.Item.DIMM"),
         std::exception);
 }
+
+TEST(FruImpl, updateAssociationTreeTest)
+{
+    using namespace pldm::responder;
+    using namespace pldm::responder::fru_parser;
+    using namespace pldm::responder::dbus;
+    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);
+    std::unique_ptr<pldm_entity_association_tree,
+                    decltype(&pldm_entity_association_tree_destroy)>
+        bmcEntityTree(pldm_entity_association_tree_init(),
+                      pldm_entity_association_tree_destroy);
+
+    ObjectValueTree objects{
+        {sdbusplus::message::object_path(
+             "/xyz/openbmc_project/inventory/system"),
+         {{"xyz.openbmc_project.Inventory.Item.System", {}}}},
+        {sdbusplus::message::object_path(
+             "/xyz/openbmc_project/inventory/system/chassis"),
+         {{"xyz.openbmc_project.Inventory.Item.Chassis", {}}}},
+        {sdbusplus::message::object_path(
+             "/xyz/openbmc_project/inventory/system/chassis/motherboard"),
+         {{"xyz.openbmc_project.Inventory.Item.Board.Motherboard", {}}}}};
+
+    typedef struct test_pldm_entity_node
+    {
+        pldm_entity entity;
+        pldm_entity parent;
+        uint16_t host_container_id;
+        pldm_entity_node* first_child;
+        pldm_entity_node* next_sibling;
+        uint8_t association_type;
+    } test_pldm_entity_node;
+
+    pldm::responder::FruImpl mockedFruHandler(FRU_JSONS_DIR, FRU_MASTER_JSON,
+                                              pdrRepo.get(), entityTree.get(),
+                                              bmcEntityTree.get());
+
+    pldm_entity systemEntity{0x2d01, 1, 0};
+    pldm_entity chassisEntity{0x2d, 1, 1};
+    pldm_entity motherboardEntity{0x40, 1, 2};
+    pldm_entity panelEntity{0x45, 1, 3};
+
+    dbus::InterfaceMap systemIface{
+        {"xyz.openbmc_project.Inventory.Item.System", {}}};
+    dbus::InterfaceMap chassisIface{
+        {"xyz.openbmc_project.Inventory.Item.Chassis", {}}};
+    dbus::InterfaceMap motherboardIface{
+        {"xyz.openbmc_project.Inventory.Item.Board.Motherboard", {}}};
+
+    mockedFruHandler.updateAssociationTree(
+        objects, "/xyz/openbmc_project/inventory/system/chassis/motherboard");
+
+    pldm_entity_node* node = pldm_entity_association_tree_find(entityTree.get(),
+                                                               &systemEntity);
+    EXPECT_TRUE(node != NULL);
+
+    node = pldm_entity_association_tree_find(entityTree.get(), &chassisEntity);
+    EXPECT_TRUE(node != NULL);
+    test_pldm_entity_node* test_node = (test_pldm_entity_node*)node;
+    EXPECT_TRUE((test_node->parent).entity_type == systemEntity.entity_type);
+
+    node = pldm_entity_association_tree_find(entityTree.get(),
+                                             &motherboardEntity);
+    EXPECT_TRUE(node != NULL);
+    test_node = (test_pldm_entity_node*)node;
+    EXPECT_TRUE((test_node->parent).entity_type == chassisEntity.entity_type);
+
+    node = pldm_entity_association_tree_find(entityTree.get(), &panelEntity);
+    EXPECT_TRUE(node == NULL);
+}
+
+TEST(FruImpl, entityByObjectPath)
+{
+    using namespace pldm::responder::dbus;
+    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);
+    std::unique_ptr<pldm_entity_association_tree,
+                    decltype(&pldm_entity_association_tree_destroy)>
+        bmcEntityTree(pldm_entity_association_tree_init(),
+                      pldm_entity_association_tree_destroy);
+
+    InterfaceMap iface = {{"xyz.openbmc_project.Inventory.Item.Chassis", {}}};
+    pldm::responder::FruImpl mockedFruHandler(FRU_JSONS_DIR, FRU_MASTER_JSON,
+                                              pdrRepo.get(), entityTree.get(),
+                                              bmcEntityTree.get());
+
+    // Good path
+    auto entityPtr = mockedFruHandler.getEntityByObjectPath(iface);
+    pldm_entity entity = *entityPtr;
+    EXPECT_TRUE(entity.entity_type == 45);
+    // Bad Path
+    InterfaceMap invalidIface = {
+        {"xyz.openbmc_project.Inventory.Item.Motherboard", {}}};
+    entityPtr = mockedFruHandler.getEntityByObjectPath(invalidIface);
+    ASSERT_TRUE(!entityPtr);
+}