Handle entity association records from host

- Get the PDRs from host, traverse the entity association structure
  and construct the D-Bus object path according to the entity type ID
  and container type ID.

- eg:
 /xyz/openbmc_project/inventory/system1/chassis1/io_board1/powersupply1
 /xyz/openbmc_project/inventory/system1/chassis1/io_board1/powersupply2
 /xyz/openbmc_project/inventory/system1/chassis2/io_board2/powersupply1
 /xyz/openbmc_project/inventory/system1/chassis2/io_board2/powersupply2

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I1c606ea943624f2601be4f5f6b5c30d1bf71cd11
diff --git a/common/utils.cpp b/common/utils.cpp
index b4bcfcb..d44ae59 100644
--- a/common/utils.cpp
+++ b/common/utils.cpp
@@ -27,6 +27,198 @@
 constexpr auto mapperPath = "/xyz/openbmc_project/object_mapper";
 constexpr auto mapperInterface = "xyz.openbmc_project.ObjectMapper";
 
+Entities getParentEntites(const EntityAssociations& entityAssoc)
+{
+    Entities parents{};
+    for (const auto& et : entityAssoc)
+    {
+        parents.push_back(et[0]);
+    }
+
+    bool found = false;
+    for (auto it = parents.begin(); it != parents.end();
+         it = found ? parents.erase(it) : std::next(it))
+    {
+        uint16_t parent_contained_id =
+            pldm_entity_node_get_remote_container_id(*it);
+        found = false;
+        for (const auto& evs : entityAssoc)
+        {
+            for (size_t i = 1; i < evs.size() && !found; i++)
+            {
+                uint16_t node_contained_id =
+                    pldm_entity_node_get_remote_container_id(evs[i]);
+
+                pldm_entity parent_entity = pldm_entity_extract(*it);
+                pldm_entity node_entity = pldm_entity_extract(evs[i]);
+
+                if (node_entity.entity_type == parent_entity.entity_type &&
+                    node_entity.entity_instance_num ==
+                        parent_entity.entity_instance_num &&
+                    node_contained_id == parent_contained_id)
+                {
+                    found = true;
+                }
+            }
+            if (found)
+            {
+                break;
+            }
+        }
+    }
+
+    return parents;
+}
+
+void addObjectPathEntityAssociations(const EntityAssociations& entityAssoc,
+                                     pldm_entity_node* entity,
+                                     const fs::path& path,
+                                     ObjectPathMaps& objPathMap)
+{
+    if (entity == nullptr)
+    {
+        return;
+    }
+
+    bool found = false;
+    pldm_entity node_entity = pldm_entity_extract(entity);
+    if (!entityMaps.contains(node_entity.entity_type))
+    {
+        lg2::info(
+            "{ENTITY_TYPE} Entity fetched from remote PLDM terminal does not exist.",
+            "ENTITY_TYPE", (int)node_entity.entity_type);
+        return;
+    }
+
+    std::string entityName = entityMaps.at(node_entity.entity_type);
+    for (const auto& ev : entityAssoc)
+    {
+        pldm_entity ev_entity = pldm_entity_extract(ev[0]);
+        if (ev_entity.entity_instance_num == node_entity.entity_instance_num &&
+            ev_entity.entity_type == node_entity.entity_type)
+        {
+            uint16_t node_contained_id =
+                pldm_entity_node_get_remote_container_id(ev[0]);
+            uint16_t entity_contained_id =
+                pldm_entity_node_get_remote_container_id(entity);
+
+            if (node_contained_id != entity_contained_id)
+            {
+                continue;
+            }
+
+            fs::path p = path / fs::path{entityName +
+                                         std::to_string(
+                                             node_entity.entity_instance_num)};
+            std::string entity_path = p.string();
+            // If the entity obtained from the remote PLDM terminal is not in
+            // the MAP, or there is no auxiliary name PDR, add it directly.
+            // Otherwise, check whether the DBus service of entity_path exists,
+            // and overwrite the entity if it does not exist.
+            if (!objPathMap.contains(entity_path))
+            {
+                objPathMap[entity_path] = entity;
+            }
+            else
+            {
+                try
+                {
+                    pldm::utils::DBusHandler().getService(entity_path.c_str(),
+                                                          nullptr);
+                }
+                catch (const std::exception& e)
+                {
+                    objPathMap[entity_path] = entity;
+                }
+            }
+
+            for (size_t i = 1; i < ev.size(); i++)
+            {
+                addObjectPathEntityAssociations(entityAssoc, ev[i], p,
+                                                objPathMap);
+            }
+            found = true;
+        }
+    }
+
+    if (!found)
+    {
+        std::string dbusPath =
+            path / fs::path{entityName +
+                            std::to_string(node_entity.entity_instance_num)};
+
+        try
+        {
+            pldm::utils::DBusHandler().getService(dbusPath.c_str(), nullptr);
+        }
+        catch (const std::exception& e)
+        {
+            objPathMap[dbusPath] = entity;
+        }
+    }
+}
+
+void updateEntityAssociation(const EntityAssociations& entityAssoc,
+                             pldm_entity_association_tree* entityTree,
+                             ObjectPathMaps& objPathMap)
+{
+    std::vector<pldm_entity_node*> parentsEntity =
+        getParentEntites(entityAssoc);
+    for (const auto& entity : parentsEntity)
+    {
+        fs::path path{"/xyz/openbmc_project/inventory"};
+        std::deque<std::string> paths{};
+        pldm_entity node_entity = pldm_entity_extract(entity);
+        auto node = pldm_entity_association_tree_find_with_locality(
+            entityTree, &node_entity, false);
+        if (!node)
+        {
+            continue;
+        }
+
+        bool found = true;
+        while (node)
+        {
+            if (!pldm_entity_is_exist_parent(node))
+            {
+                break;
+            }
+
+            pldm_entity parent = pldm_entity_get_parent(node);
+            try
+            {
+                paths.push_back(entityMaps.at(parent.entity_type) +
+                                std::to_string(parent.entity_instance_num));
+            }
+            catch (const std::exception& e)
+            {
+                lg2::error(
+                    "Parent entity not found in the entityMaps, type: {ENTITY_TYPE}, num: {NUM}, e: {ERROR}",
+                    "ENTITY_TYPE", (int)parent.entity_type, "NUM",
+                    (int)parent.entity_instance_num, "ERROR", e);
+                found = false;
+                break;
+            }
+
+            node = pldm_entity_association_tree_find_with_locality(
+                entityTree, &parent, false);
+        }
+
+        if (!found)
+        {
+            continue;
+        }
+
+        while (!paths.empty())
+        {
+            path = path / fs::path{paths.back()};
+            paths.pop_back();
+        }
+
+        addObjectPathEntityAssociations(entityAssoc, entity, path, objPathMap);
+    }
+}
+
 std::vector<std::vector<uint8_t>> findStateEffecterPDR(uint8_t /*tid*/,
                                                        uint16_t entityID,
                                                        uint16_t stateSetId,
@@ -224,7 +416,15 @@
 
     auto mapper = bus.new_method_call(mapperBusName, mapperPath,
                                       mapperInterface, "GetObject");
-    mapper.append(path, DbusInterfaceList({interface}));
+
+    if (interface)
+    {
+        mapper.append(path, DbusInterfaceList({interface}));
+    }
+    else
+    {
+        mapper.append(path, DbusInterfaceList({}));
+    }
 
     auto mapperResponseMsg = bus.call(
         mapper,
diff --git a/common/utils.hpp b/common/utils.hpp
index 2ae7b86..d78d172 100644
--- a/common/utils.hpp
+++ b/common/utils.hpp
@@ -4,6 +4,8 @@
 
 #include <libpldm/base.h>
 #include <libpldm/bios.h>
+#include <libpldm/entity.h>
+#include <libpldm/pdr.h>
 #include <libpldm/platform.h>
 #include <libpldm/utils.h>
 #include <stdint.h>
@@ -14,9 +16,11 @@
 #include <sdbusplus/server.hpp>
 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
 
+#include <deque>
 #include <exception>
 #include <filesystem>
 #include <iostream>
+#include <map>
 #include <string>
 #include <variant>
 #include <vector>
@@ -33,6 +37,40 @@
 constexpr bool Tx = true;
 constexpr bool Rx = false;
 
+using EntityName = std::string;
+using EntityType = uint16_t;
+
+using Entities = std::vector<pldm_entity_node*>;
+using EntityAssociations = std::vector<Entities>;
+using ObjectPathMaps = std::map<fs::path, pldm_entity_node*>;
+
+const std::map<EntityType, EntityName> entityMaps = {
+    {PLDM_ENTITY_SYSTEM_CHASSIS, "chassis"},
+    {PLDM_ENTITY_BOARD, "io_board"},
+    {PLDM_ENTITY_SYS_BOARD, "motherboard"},
+    {PLDM_ENTITY_POWER_SUPPLY, "powersupply"},
+    {PLDM_ENTITY_PROC, "cpu"},
+    {PLDM_ENTITY_SYSTEM_CHASSIS | 0x8000, "system"},
+    {PLDM_ENTITY_PROC_MODULE, "dcm"},
+    {PLDM_ENTITY_PROC | 0x8000, "core"},
+    {PLDM_ENTITY_IO_MODULE, "io_module"},
+    {PLDM_ENTITY_FAN, "fan"},
+    {PLDM_ENTITY_SYS_MGMT_MODULE, "system_management_module"},
+    {PLDM_ENTITY_POWER_CONVERTER, "power_converter"},
+    {PLDM_ENTITY_SLOT, "slot"},
+    {PLDM_ENTITY_CONNECTOR, "connector"}};
+
+/** @brief Vector a entity name to pldm_entity from entity association tree
+ *  @param[in]  entityAssoc    - Vector of associated pldm entities
+ *  @param[in]  entityTree     - entity association tree
+ *  @param[out] objPathMap     - maps an object path to pldm_entity from the
+ *                               BMC's entity association tree
+ *  @return
+ */
+void updateEntityAssociation(const EntityAssociations& entityAssoc,
+                             pldm_entity_association_tree* entityTree,
+                             ObjectPathMaps& objPathMap);
+
 /** @struct CustomFD
  *
  *  RAII wrapper for file descriptor.
diff --git a/host-bmc/host_pdr_handler.cpp b/host-bmc/host_pdr_handler.cpp
index 9409b30..3ceae13 100644
--- a/host-bmc/host_pdr_handler.cpp
+++ b/host-bmc/host_pdr_handler.cpp
@@ -234,15 +234,22 @@
             return;
         }
 
+        Entities entityAssoc;
+        entityAssoc.push_back(pNode);
         for (size_t i = 1; i < numEntities; ++i)
         {
-            pldm_entity_association_tree_add_entity(
+            auto node = pldm_entity_association_tree_add_entity(
                 entityTree, &entities[i], entities[i].entity_instance_num,
                 pNode, entityPdr->association_type, true, true, 0xFFFF);
             merged = true;
+            entityAssoc.push_back(node);
         }
 
         mergedHostParents = true;
+        if (merged)
+        {
+            entityAssociations.push_back(entityAssoc);
+        }
     }
 
     if (merged)
@@ -547,6 +554,8 @@
     }
     if (!nextRecordHandle)
     {
+        updateEntityAssociation(entityAssociations, entityTree, objPathMap);
+
         /*received last record*/
         this->parseStateSensorPDRs(stateSensorPDRs);
         if (isHostUp())
@@ -554,6 +563,8 @@
             this->setHostSensorState(stateSensorPDRs);
         }
         stateSensorPDRs.clear();
+        entityAssociations.clear();
+
         if (merged)
         {
             merged = false;
diff --git a/host-bmc/host_pdr_handler.hpp b/host-bmc/host_pdr_handler.hpp
index facfeb1..5dc5e9e 100644
--- a/host-bmc/host_pdr_handler.hpp
+++ b/host-bmc/host_pdr_handler.hpp
@@ -14,6 +14,7 @@
 #include <sdeventplus/source/event.hpp>
 
 #include <deque>
+#include <filesystem>
 #include <map>
 #include <memory>
 #include <vector>
@@ -267,6 +268,15 @@
 
     /** @brief request message instance id */
     uint8_t insId;
+
+    /** @brief maps an object path to pldm_entity from the BMC's entity
+     *         association tree
+     */
+    utils::ObjectPathMaps objPathMap;
+
+    /** @brief maps an entity name to map, maps to entity name to pldm_entity
+     */
+    utils::EntityAssociations entityAssociations;
 };
 
 } // namespace pldm
diff --git a/host-bmc/test/meson.build b/host-bmc/test/meson.build
index 7ccfef7..d19d4ef 100644
--- a/host-bmc/test/meson.build
+++ b/host-bmc/test/meson.build
@@ -2,12 +2,18 @@
           sources: [ '../dbus_to_host_effecters.cpp' ],
           include_directories: '../../requester')
 
+test_sources = [
+  '../../common/utils.cpp',
+]
+
 tests = [
   'dbus_to_host_effecter_test',
+  'utils_test',
 ]
 
 foreach t : tests
   test(t, executable(t.underscorify(), t + '.cpp',
+                     test_sources,
                      implicit_include_directories: false,
                      link_args: dynamic_linker,
                      build_rpath: get_option('oe-sdk').enabled() ? rpath : '',
diff --git a/host-bmc/test/utils_test.cpp b/host-bmc/test/utils_test.cpp
new file mode 100644
index 0000000..45653ec
--- /dev/null
+++ b/host-bmc/test/utils_test.cpp
@@ -0,0 +1,98 @@
+#include "libpldm/pdr.h"
+
+#include "common/utils.hpp"
+
+#include <filesystem>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+using namespace pldm;
+using namespace pldm::utils;
+
+TEST(EntityAssociation, addObjectPathEntityAssociations1)
+{
+    pldm_entity entities[41]{};
+
+    entities[0].entity_type = 45;
+    entities[0].entity_container_id = 0;
+
+    entities[1].entity_type = 64;
+    entities[1].entity_container_id = 1;
+
+    entities[2].entity_type = 67;
+    entities[2].entity_container_id = 2;
+    entities[3].entity_type = 67;
+    entities[3].entity_container_id = 2;
+
+    entities[4].entity_type = 135;
+    entities[4].entity_container_id = 3;
+    entities[5].entity_type = 135;
+    entities[5].entity_container_id = 3;
+    entities[6].entity_type = 135;
+    entities[6].entity_container_id = 3;
+    entities[7].entity_type = 135;
+    entities[7].entity_container_id = 3;
+
+    auto tree = pldm_entity_association_tree_init();
+
+    auto l1 = pldm_entity_association_tree_add(tree, &entities[0], 1, nullptr,
+                                               PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+
+    auto l2 = pldm_entity_association_tree_add(tree, &entities[1], 1, l1,
+                                               PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+
+    auto l3a = pldm_entity_association_tree_add(
+        tree, &entities[2], 0, l2, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    auto l3b = pldm_entity_association_tree_add(
+        tree, &entities[3], 1, l2, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+
+    auto l4a = pldm_entity_association_tree_add(
+        tree, &entities[4], 0, l3a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    auto l4b = pldm_entity_association_tree_add(
+        tree, &entities[5], 1, l3a, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+
+    auto l5a = pldm_entity_association_tree_add(
+        tree, &entities[6], 0, l3b, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+    auto l5b = pldm_entity_association_tree_add(
+        tree, &entities[7], 1, l3b, PLDM_ENTITY_ASSOCIAION_PHYSICAL);
+
+    EntityAssociations entityAssociations = {
+        {l1, l2}, {l2, l3a, l3b}, {l3a, l4a, l4b}, {l3b, l5a, l5b}};
+
+    ObjectPathMaps retObjectMaps = {
+        {"/xyz/openbmc_project/inventory/chassis1", l1},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1", l2},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1/dcm0", l3a},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1/dcm1", l3b},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1/dcm0/cpu0", l4a},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1/dcm0/cpu1", l4b},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1/dcm1/cpu0", l5a},
+        {"/xyz/openbmc_project/inventory/chassis1/motherboard1/dcm1/cpu1", l5b},
+    };
+
+    ObjectPathMaps objPathMap;
+    updateEntityAssociation(entityAssociations, tree, objPathMap);
+
+    EXPECT_EQ(objPathMap.size(), retObjectMaps.size());
+
+    int index = 0;
+    for (auto& obj : objPathMap)
+    {
+        if (retObjectMaps.contains(obj.first))
+        {
+            index++;
+            pldm_entity entity = pldm_entity_extract(obj.second);
+            pldm_entity retEntity =
+                pldm_entity_extract(retObjectMaps[obj.first]);
+            EXPECT_EQ(entity.entity_type, retEntity.entity_type);
+            EXPECT_EQ(entity.entity_instance_num,
+                      retEntity.entity_instance_num);
+            EXPECT_EQ(pldm_entity_node_get_remote_container_id(obj.second),
+                      pldm_entity_node_get_remote_container_id(
+                          retObjectMaps[obj.first]));
+        }
+    }
+    EXPECT_EQ(index, retObjectMaps.size());
+    pldm_entity_association_tree_destroy(tree);
+}