unit-test: Create initial associations file

Make interface more unit testable by moving to separate
file and documenting it.

Change-Id: Ia27f33d706c62a0011790ec94185dd6be3922f21
Signed-off-by: Andrew Geissler <geissonator@yahoo.com>
diff --git a/src/associations.cpp b/src/associations.cpp
new file mode 100644
index 0000000..3ae7db4
--- /dev/null
+++ b/src/associations.cpp
@@ -0,0 +1,76 @@
+#include "associations.hpp"
+
+void removeAssociation(const std::string& sourcePath, const std::string& owner,
+                       sdbusplus::asio::object_server& server,
+                       AssociationOwnersType& assocOwners,
+                       AssociationInterfaces& assocInterfaces)
+{
+    // Use associationOwners to find the association paths and endpoints
+    // that the passed in object path and service own.  Remove all of
+    // these endpoints from the actual association D-Bus objects, and if
+    // the endpoints property is then empty, the whole association object
+    // can be removed.  Note there can be multiple services that own an
+    // association, and also that sourcePath is the path of the object
+    // that contains the org.openbmc.Associations interface and not the
+    // association path itself.
+
+    // Find the services that have associations for this object path
+    auto owners = assocOwners.find(sourcePath);
+    if (owners == assocOwners.end())
+    {
+        return;
+    }
+
+    // Find the association paths and endpoints owned by this object
+    // path for this service.
+    auto assocs = owners->second.find(owner);
+    if (assocs == owners->second.end())
+    {
+        return;
+    }
+
+    for (const auto& [assocPath, endpointsToRemove] : assocs->second)
+    {
+        // Get the association D-Bus object for this assocPath
+        auto target = assocInterfaces.find(assocPath);
+        if (target == assocInterfaces.end())
+        {
+            continue;
+        }
+
+        // Remove the entries in the endpoints D-Bus property for this
+        // path/owner/association-path.
+        auto& existingEndpoints = std::get<endpointsPos>(target->second);
+        for (const auto& endpointToRemove : endpointsToRemove)
+        {
+            auto e = std::find(existingEndpoints.begin(),
+                               existingEndpoints.end(), endpointToRemove);
+
+            if (e != existingEndpoints.end())
+            {
+                existingEndpoints.erase(e);
+            }
+        }
+
+        // Remove the association from D-Bus if there are no more endpoints,
+        // otherwise just update the endpoints property.
+        if (existingEndpoints.empty())
+        {
+            server.remove_interface(std::get<ifacePos>(target->second));
+            std::get<ifacePos>(target->second) = nullptr;
+            std::get<endpointsPos>(target->second).clear();
+        }
+        else
+        {
+            std::get<ifacePos>(target->second)
+                ->set_property("endpoints", existingEndpoints);
+        }
+    }
+
+    // Remove the associationOwners entries for this owning path/service.
+    owners->second.erase(assocs);
+    if (owners->second.empty())
+    {
+        assocOwners.erase(owners);
+    }
+}