mapper: Add GetAssociatedSubTreePathsById and GetAssociatedSubTreeById

dbus-interface change in:
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/69999

This commit implements two new methods: GetAssociatedSubTreePathsById
and GetAssociatedSubTreeById. These methods retrieve the paths of
associated endpoints corresponding to the provided identifier, filtering
based on their association with specified endpoint interfaces.

GetAssociatedSubTreePathsById returns the D-Bus paths of associated
endpoints, while GetAssociatedSubTreeById retrieves a dictionary of
D-Bus paths of associated endpoints mapped to corresponding services
associated with the provided identifier.

Tested:
'''
busctl call -j  "xyz.openbmc_project.ObjectMapper"   "/xyz/openbmc_project/object_mapper" \
  "xyz.openbmc_project.ObjectMapper" "GetAssociatedSubTreePathsById" ssassas \
   "chassis" \
   "/xyz/openbmc_project/inventory"  \
   1 "xyz.openbmc_project.Inventory.Item.Chassis" \
    "powered_by" \
   1  "xyz.openbmc_project.Inventory.Item.PowerSupply"
{
        "type" : "as",
        "data" : [
                [
                        "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0",
                        "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1",
                        "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply2",
                        "/xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply3"
                ]
        ]
}
'''

Another example.

```
busctl call -j xyz.openbmc_project.ObjectMapper   /xyz/openbmc_project/object_mapper \
     xyz.openbmc_project.ObjectMapper GetAssociatedSubTreePathsById ssassas \
     disk_backplane0  \
     /xyz/openbmc_project/inventory \
     1 xyz.openbmc_project.Inventory.Item.FabricAdapter \
     connecting \
     1 xyz.openbmc_project.Inventory.Connector.Port
{
	"type" : "as",
	"data" : [
		[
			"/xyz/openbmc_project/inventory/system/chassis/motherboard/disk_backplane0/dp0_connector0",
			"/xyz/openbmc_project/inventory/system/chassis/motherboard/disk_backplane0/dp0_connector1",
			"/xyz/openbmc_project/inventory/system/chassis/motherboard/disk_backplane0/dp0_connector2",
			"/xyz/openbmc_project/inventory/system/chassis/motherboard/disk_backplane0/dp0_connector3",
			"/xyz/openbmc_project/inventory/system/chassis/motherboard/disk_backplane0/dp0_connector4",
			"/xyz/openbmc_project/inventory/system/chassis/motherboard/disk_backplane0/dp0_connector5"
		]
	]
}
```

Change-Id: Id55a9b41fe70f7204543d92b5396888f6914a1d4
Signed-off-by: Lakshmi Yadlapati <lakshmiy@us.ibm.com>
Signed-off-by: Myung Bae <myungbae@us.ibm.com>
diff --git a/src/handler.cpp b/src/handler.cpp
index 5983ad9..2c4b5d7 100644
--- a/src/handler.cpp
+++ b/src/handler.cpp
@@ -336,3 +336,118 @@
     }
     return output;
 }
+
+// This function works like getSubTreePaths() but only matching id with
+// the leaf-name instead of full path.
+std::vector<std::string> getSubTreePathsById(
+    const InterfaceMapType& interfaceMap, const std::string& id,
+    const std::string& objectPath, std::vector<std::string>& interfaces)
+{
+    std::sort(interfaces.begin(), interfaces.end());
+
+    std::string localObjectPath = objectPath;
+
+    if (!localObjectPath.ends_with("/"))
+    {
+        localObjectPath += "/";
+    }
+    std::string_view objectPathStripped =
+        std::string_view(localObjectPath).substr(0, localObjectPath.size() - 1);
+
+    if (!objectPathStripped.empty() &&
+        interfaceMap.find(objectPathStripped) == interfaceMap.end())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+
+    std::vector<std::string> output;
+    for (const auto& path : interfaceMap)
+    {
+        const auto& thisPath = path.first;
+
+        // Skip exact match on stripped search term or
+        // the path does not end with the id
+        if (thisPath == objectPathStripped || !thisPath.ends_with("/" + id))
+        {
+            continue;
+        }
+
+        if (thisPath.starts_with(objectPath))
+        {
+            for (const auto& interfaceMap : path.second)
+            {
+                std::vector<std::string> tempoutput(
+                    std::min(interfaces.size(), interfaceMap.second.size()));
+                if (std::set_intersection(
+                        interfaces.begin(), interfaces.end(),
+                        interfaceMap.second.begin(), interfaceMap.second.end(),
+                        tempoutput.begin()) != tempoutput.begin())
+                {
+                    output.emplace_back(thisPath);
+                    break;
+                }
+            }
+        }
+    }
+    if (output.empty())
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::
+            ResourceNotFound();
+    }
+    return output;
+}
+
+std::vector<InterfaceMapType::value_type> getAssociatedSubTreeById(
+    const InterfaceMapType& interfaceMap,
+    const AssociationMaps& associationMaps, const std::string& id,
+    const std::string& objectPath, std::vector<std::string>& subtreeInterfaces,
+    const std::string& association,
+    std::vector<std::string>& endpointInterfaces)
+{
+    std::vector<std::string> subtreePaths =
+        getSubTreePathsById(interfaceMap, id, objectPath, subtreeInterfaces);
+
+    std::vector<InterfaceMapType::value_type> output;
+    for (const auto& subtreePath : subtreePaths)
+    {
+        // Form the association path
+        std::string associationPathStr = subtreePath + "/" + association;
+        sdbusplus::message::object_path associationPath(associationPathStr);
+
+        auto associatedSubTree =
+            getAssociatedSubTree(interfaceMap, associationMaps, associationPath,
+                                 objectPath, 0, endpointInterfaces);
+
+        output.insert(output.end(), associatedSubTree.begin(),
+                      associatedSubTree.end());
+    }
+    return output;
+}
+
+std::vector<std::string> getAssociatedSubTreePathsById(
+    const InterfaceMapType& interfaceMap,
+    const AssociationMaps& associationMaps, const std::string& id,
+    const std::string& objectPath, std::vector<std::string>& subtreeInterfaces,
+    const std::string& association,
+    std::vector<std::string>& endpointInterfaces)
+{
+    std::vector<std::string> subtreePaths =
+        getSubTreePathsById(interfaceMap, id, objectPath, subtreeInterfaces);
+    std::vector<std::string> output;
+    for (const auto& subtreePath : subtreePaths)
+    {
+        // Form the association path
+        std::string associationPathStr = subtreePath + "/" + association;
+        sdbusplus::message::object_path associationPath(associationPathStr);
+
+        auto associatedSubTree = getAssociatedSubTreePaths(
+            interfaceMap, associationMaps, associationPath, objectPath, 0,
+            endpointInterfaces);
+
+        output.insert(output.end(), associatedSubTree.begin(),
+                      associatedSubTree.end());
+    }
+
+    return output;
+}
diff --git a/src/handler.hpp b/src/handler.hpp
index 515c937..f6d4d94 100644
--- a/src/handler.hpp
+++ b/src/handler.hpp
@@ -68,3 +68,51 @@
     const sdbusplus::message::object_path& associationPath,
     const sdbusplus::message::object_path& reqPath, int32_t depth,
     std::vector<std::string>& interfaces);
+
+/**
+ * @brief Get the Associated Sub Tree Paths object by id
+ *
+ * @param interfaceMap       Mapper Structure storing all associations
+ * @param associationMaps    Map of association between objects
+ * @param id                 Identifier to search for the subtree
+ * @param objectPath         Base path to search for the subtree
+ * @param subtreeInterfaces  Interface filter for the subtree
+ * @param association        The endpoint association
+ * @param endpointInterfaces Interface filter for the endpoint association
+ *
+ * Use getAssociatedSubTree and return only the dbus objects that
+ * are associated with the provided identifier, filtering based on on their
+ * endpoint association.
+ *
+ * @return std::vector<InterfaceMapType::value_type>
+ */
+std::vector<InterfaceMapType::value_type> getAssociatedSubTreeById(
+    const InterfaceMapType& interfaceMap,
+    const AssociationMaps& associationMaps, const std::string& id,
+    const std::string& objectPath, std::vector<std::string>& subtreeInterfaces,
+    const std::string& association,
+    std::vector<std::string>& endpointInterfaces);
+
+/**
+ * @brief Get the Associated Sub Tree Paths object by id
+ *
+ * @param interfaceMap       Mapper Structure storing all associations
+ * @param associationMaps    Map of association between objects
+ * @param id                 Identifier to search for the subtree
+ * @param objectPath         Base path to search for the subtree
+ * @param subtreeInterfaces  Interface filter for the subtree
+ * @param association        The endpoint association
+ * @param endpointInterfaces Interface filter for the endpoint association
+ *
+ * Use getAssociatedSubTreePaths and return only the dbus objects that
+ * are associated with the provided identifier, filtering based on on their
+ * endpoint association.
+ *
+ * @return std::vector<std::string>
+ */
+std::vector<std::string> getAssociatedSubTreePathsById(
+    const InterfaceMapType& interfaceMap,
+    const AssociationMaps& associationMaps, const std::string& id,
+    const std::string& objectPath, std::vector<std::string>& subtreeInterfaces,
+    const std::string& association,
+    std::vector<std::string>& endpointInterfaces);
diff --git a/src/main.cpp b/src/main.cpp
index 694acd1..ebaf1ab 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -616,6 +616,28 @@
                                              interfaces);
         });
 
+    iface->register_method(
+        "GetAssociatedSubTreeById",
+        [&interfaceMap](const std::string& id, const std::string& objectPath,
+                        std::vector<std::string>& subtreeInterfaces,
+                        const std::string& association,
+                        std::vector<std::string>& endpointInterfaces) {
+            return getAssociatedSubTreeById(interfaceMap, associationMaps, id,
+                                            objectPath, subtreeInterfaces,
+                                            association, endpointInterfaces);
+        });
+
+    iface->register_method(
+        "GetAssociatedSubTreePathsById",
+        [&interfaceMap](const std::string& id, const std::string& objectPath,
+                        std::vector<std::string>& subtreeInterfaces,
+                        const std::string& association,
+                        std::vector<std::string>& endpointInterfaces) {
+            return getAssociatedSubTreePathsById(
+                interfaceMap, associationMaps, id, objectPath,
+                subtreeInterfaces, association, endpointInterfaces);
+        });
+
     iface->initialize();
 
     io.post([&]() {
diff --git a/src/test/handler.cpp b/src/test/handler.cpp
index aac2d0f..7f418e5 100644
--- a/src/test/handler.cpp
+++ b/src/test/handler.cpp
@@ -32,7 +32,16 @@
         {
             "/test/object_path_0/child/grandchild/dog",
             {{"test_object_connection_3", {"test_interface_3"}}},
-        }};
+        },
+        {
+            "/test/object_path_0/child1",
+            {{"test_object_connection_4", {"test_interface_4"}}},
+        },
+        {
+            "/test/object_path_0/grandchild/child1",
+            {{"test_object_connection_5", {"test_interface_5"}}},
+        },
+    };
 
     AssociationMaps associationMap = {
         .ifaces =
@@ -56,6 +65,25 @@
                         },
                     },
                 },
+                {
+                    "/test/object_path_0/grandchild/child1/descendent",
+                    {
+                        std::shared_ptr<sdbusplus::asio::dbus_interface>(),
+                        {
+                            "/test/object_path_0/child",
+                        },
+                    },
+                },
+                {
+                    "/test/object_path_0/child1/descendent",
+                    {
+                        std::shared_ptr<sdbusplus::asio::dbus_interface>(),
+                        {
+                            "/test/object_path_0/child1",
+                            "/test/object_path_0/child1/grandchild",
+                        },
+                    },
+                },
             },
         .owners = {},
         .pending = {},
@@ -79,7 +107,6 @@
                                                            "test_interface_1",
                                                        }));
     ASSERT_EQ(interfaceMaps.size(), 1);
-
     auto entry = std::find_if(
         interfaceMaps.begin(), interfaceMaps.end(),
         [](const auto& i) { return "test_object_path" == i.first; });
@@ -411,3 +438,151 @@
     ASSERT_THAT(subtreePath,
                 ElementsAre("/test/object_path_0/child/grandchild"));
 }
+
+TEST_F(TestHandler, getAssociatedSubTreeByIdBad)
+{
+    sdbusplus::message::object_path path("/test/object_path_0");
+    std::vector<std::string> subtreeInterfaces = {"test_interface_1",
+                                                  "test_interface_3"};
+    std::vector<std::string> badsubtreeInterfaces = {"bad_interface"};
+    std::vector<std::string> endpointinvalidInterfaces = {"test_interface_3"};
+    std::vector<std::string> endpointvalidInterfaces = {"test_interface_1",
+                                                        "test_interface_2"};
+    // invalid id
+    EXPECT_THROW(
+        getAssociatedSubTreeById(interfaceMap, associationMap, "childx", path,
+                                 subtreeInterfaces, "descendent",
+                                 endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    // invalid subtreeInterfaces
+    EXPECT_THROW(
+        getAssociatedSubTreeById(interfaceMap, associationMap, "child", path,
+                                 badsubtreeInterfaces, "descendent",
+                                 endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    // invalid endpointinterface
+    ASSERT_TRUE(getAssociatedSubTreeById(interfaceMap, associationMap, "child",
+                                         path, subtreeInterfaces, "descendent",
+                                         endpointinvalidInterfaces)
+                    .empty());
+    // valid id, but doesn't have specified interface
+    EXPECT_THROW(
+        getAssociatedSubTreeById(interfaceMap, associationMap, "grandchild",
+                                 path, subtreeInterfaces, "descendent",
+                                 endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    // invalid association
+    ASSERT_TRUE(getAssociatedSubTreeById(interfaceMap, associationMap, "child",
+                                         path, subtreeInterfaces, "dog",
+                                         endpointinvalidInterfaces)
+                    .empty());
+
+    // Invalid path
+    path = sdbusplus::message::object_path("/invalid_path");
+    EXPECT_THROW(
+        getAssociatedSubTreeById(interfaceMap, associationMap, "child", path,
+                                 subtreeInterfaces, "descendent",
+                                 endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getAssociatedSubTreeByIdGood)
+{
+    sdbusplus::message::object_path path0("/test/object_path_0");
+    std::vector<std::string> interfaces = {
+        "test_interface_1", "test_interface_2", "test_interface_3"};
+
+    // Path0
+    std::vector<InterfaceMapType::value_type> subtree =
+        getAssociatedSubTreeById(interfaceMap, associationMap, "child", path0,
+                                 interfaces, "descendent", interfaces);
+    ASSERT_EQ(subtree.size(), 1);
+    ConnectionNames connection = subtree[0].second;
+    auto object = connection.find("test_object_connection_2");
+    ASSERT_NE(object, connection.end());
+    ASSERT_THAT(object->second, ElementsAre("test_interface_2"));
+
+    std::vector<std::string> interfaces1 = {
+        "test_interface_1", "test_interface_4", "test_interface_5"};
+    // Path0 with Depth path of 0
+    subtree =
+        getAssociatedSubTreeById(interfaceMap, associationMap, "child1", path0,
+                                 interfaces1, "descendent", interfaces1);
+    ASSERT_EQ(subtree.size(), 2);
+}
+
+TEST_F(TestHandler, getAssociatedSubTreePathsByIdBad)
+{
+    sdbusplus::message::object_path path("/test/object_path_0");
+    std::vector<std::string> subtreeInterfaces = {"test_interface_1",
+                                                  "test_interface_3"};
+    std::vector<std::string> badsubtreeInterfaces = {"bad_interface"};
+    std::vector<std::string> endpointinvalidInterfaces = {"test_interface_3"};
+    std::vector<std::string> endpointvalidInterfaces = {"test_interface_1",
+                                                        "test_interface_2"};
+    // invalid id
+    EXPECT_THROW(
+        getAssociatedSubTreePathsById(interfaceMap, associationMap, "childx",
+                                      path, subtreeInterfaces, "descendent",
+                                      endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    // invalid subtreeInterfaces
+    EXPECT_THROW(
+        getAssociatedSubTreePathsById(interfaceMap, associationMap, "child",
+                                      path, badsubtreeInterfaces, "descendent",
+                                      endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    // invalid endpointinterface
+    ASSERT_TRUE(getAssociatedSubTreePathsById(
+                    interfaceMap, associationMap, "child", path,
+                    subtreeInterfaces, "descendent", endpointinvalidInterfaces)
+                    .empty());
+    // valid id, but doesn't have specified interface
+    EXPECT_THROW(
+        getAssociatedSubTreePathsById(interfaceMap, associationMap,
+                                      "grandchild", path, subtreeInterfaces,
+                                      "descendent", endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+
+    // invalid association
+    ASSERT_TRUE(getAssociatedSubTreePathsById(interfaceMap, associationMap,
+                                              "child", path, subtreeInterfaces,
+                                              "dog", endpointinvalidInterfaces)
+                    .empty());
+
+    // Invalid path
+    path = sdbusplus::message::object_path("/invalid_path");
+    EXPECT_THROW(
+        getAssociatedSubTreePathsById(interfaceMap, associationMap, "child",
+                                      path, subtreeInterfaces, "descendent",
+                                      endpointvalidInterfaces),
+        sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound);
+}
+
+TEST_F(TestHandler, getAssociatedSubTreePathsByIdGood)
+{
+    sdbusplus::message::object_path path0("/test/object_path_0");
+    std::vector<std::string> interfaces = {
+        "test_interface_1", "test_interface_2", "test_interface_3"};
+
+    // Path0
+    std::vector<std::string> subtreePath = getAssociatedSubTreePathsById(
+        interfaceMap, associationMap, "child", path0, interfaces, "descendent",
+        interfaces);
+    ASSERT_THAT(subtreePath,
+                ElementsAre("/test/object_path_0/child/grandchild"));
+
+    std::vector<std::string> interfaces1 = {
+        "test_interface_1", "test_interface_4", "test_interface_5"};
+    // Path0 with Depth path of 0
+    subtreePath = getAssociatedSubTreePathsById(
+        interfaceMap, associationMap, "child1", path0, interfaces1,
+        "descendent", interfaces1);
+    ASSERT_THAT(subtreePath, ElementsAre("/test/object_path_0/child1",
+                                         "/test/object_path_0/child"));
+}