1 #include "associations.hpp"
2 
3 #include <iostream>
4 
5 void removeAssociation(const std::string& sourcePath, const std::string& owner,
6                        sdbusplus::asio::object_server& server,
7                        AssociationMaps& assocMaps)
8 {
9     // Use associationOwners to find the association paths and endpoints
10     // that the passed in object path and service own.  Remove all of
11     // these endpoints from the actual association D-Bus objects, and if
12     // the endpoints property is then empty, the whole association object
13     // can be removed.  Note there can be multiple services that own an
14     // association, and also that sourcePath is the path of the object
15     // that contains the org.openbmc.Associations interface and not the
16     // association path itself.
17 
18     // Find the services that have associations for this object path
19     auto owners = assocMaps.owners.find(sourcePath);
20     if (owners == assocMaps.owners.end())
21     {
22         return;
23     }
24 
25     // Find the association paths and endpoints owned by this object
26     // path for this service.
27     auto assocs = owners->second.find(owner);
28     if (assocs == owners->second.end())
29     {
30         return;
31     }
32 
33     for (const auto& [assocPath, endpointsToRemove] : assocs->second)
34     {
35         removeAssociationEndpoints(server, assocPath, endpointsToRemove,
36                                    assocMaps);
37     }
38 
39     // Remove the associationOwners entries for this owning path/service.
40     owners->second.erase(assocs);
41     if (owners->second.empty())
42     {
43         assocMaps.owners.erase(owners);
44     }
45 
46     // If we were still waiting on the other side of this association to
47     // show up, cancel that wait.
48     removeFromPendingAssociations(sourcePath, assocMaps);
49 }
50 
51 void removeAssociationEndpoints(
52     sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
53     const boost::container::flat_set<std::string>& endpointsToRemove,
54     AssociationMaps& assocMaps)
55 {
56     auto assoc = assocMaps.ifaces.find(assocPath);
57     if (assoc == assocMaps.ifaces.end())
58     {
59         return;
60     }
61 
62     auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
63 
64     for (const auto& endpointToRemove : endpointsToRemove)
65     {
66         auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
67                            endpointToRemove);
68 
69         if (e != endpointsInDBus.end())
70         {
71             endpointsInDBus.erase(e);
72         }
73     }
74 
75     if (endpointsInDBus.empty())
76     {
77         objectServer.remove_interface(std::get<ifacePos>(assoc->second));
78         std::get<ifacePos>(assoc->second) = nullptr;
79         std::get<endpointsPos>(assoc->second).clear();
80     }
81     else
82     {
83         std::get<ifacePos>(assoc->second)
84             ->set_property("endpoints", endpointsInDBus);
85     }
86 }
87 
88 void checkAssociationEndpointRemoves(
89     const std::string& sourcePath, const std::string& owner,
90     const AssociationPaths& newAssociations,
91     sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
92 {
93     // Find the services that have associations on this path.
94     auto originalOwners = assocMaps.owners.find(sourcePath);
95     if (originalOwners == assocMaps.owners.end())
96     {
97         return;
98     }
99 
100     // Find the associations for this service
101     auto originalAssociations = originalOwners->second.find(owner);
102     if (originalAssociations == originalOwners->second.end())
103     {
104         return;
105     }
106 
107     // Compare the new endpoints versus the original endpoints, and
108     // remove any of the original ones that aren't in the new list.
109     for (const auto& [originalAssocPath, originalEndpoints] :
110          originalAssociations->second)
111     {
112         // Check if this source even still has each association that
113         // was there previously, and if not, remove all of its endpoints
114         // from the D-Bus endpoints property which will cause the whole
115         // association path to be removed if no endpoints remain.
116         auto newEndpoints = newAssociations.find(originalAssocPath);
117         if (newEndpoints == newAssociations.end())
118         {
119             removeAssociationEndpoints(objectServer, originalAssocPath,
120                                        originalEndpoints, assocMaps);
121         }
122         else
123         {
124             // The association is still there.  Check if the endpoints
125             // changed.
126             boost::container::flat_set<std::string> toRemove;
127 
128             for (auto& originalEndpoint : originalEndpoints)
129             {
130                 if (std::find(newEndpoints->second.begin(),
131                               newEndpoints->second.end(),
132                               originalEndpoint) == newEndpoints->second.end())
133                 {
134                     toRemove.emplace(originalEndpoint);
135                 }
136             }
137             if (!toRemove.empty())
138             {
139                 removeAssociationEndpoints(objectServer, originalAssocPath,
140                                            toRemove, assocMaps);
141             }
142         }
143     }
144 }
145 
146 void addEndpointsToAssocIfaces(
147     sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
148     const boost::container::flat_set<std::string>& endpointPaths,
149     AssociationMaps& assocMaps)
150 {
151     auto& iface = assocMaps.ifaces[assocPath];
152     auto& i = std::get<ifacePos>(iface);
153     auto& endpoints = std::get<endpointsPos>(iface);
154 
155     // Only add new endpoints
156     for (auto& e : endpointPaths)
157     {
158         if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end())
159         {
160             endpoints.push_back(e);
161         }
162     }
163 
164     // If the interface already exists, only need to update
165     // the property value, otherwise create it
166     if (i)
167     {
168         i->set_property("endpoints", endpoints);
169     }
170     else
171     {
172         i = objectServer.add_interface(assocPath, XYZ_ASSOCIATION_INTERFACE);
173         i->register_property("endpoints", endpoints);
174         i->initialize();
175     }
176 }
177 
178 void associationChanged(sdbusplus::asio::object_server& objectServer,
179                         const std::vector<Association>& associations,
180                         const std::string& path, const std::string& owner,
181                         const interface_map_type& interfaceMap,
182                         AssociationMaps& assocMaps)
183 {
184     AssociationPaths objects;
185 
186     for (const Association& association : associations)
187     {
188         std::string forward;
189         std::string reverse;
190         std::string endpoint;
191         std::tie(forward, reverse, endpoint) = association;
192 
193         if (endpoint.empty())
194         {
195             std::cerr << "Found invalid association on path " << path << "\n";
196             continue;
197         }
198 
199         // Can't create this association if the endpoint isn't on D-Bus.
200         if (interfaceMap.find(endpoint) == interfaceMap.end())
201         {
202             addPendingAssociation(endpoint, reverse, path, forward, owner,
203                                   assocMaps);
204             continue;
205         }
206 
207         if (forward.size())
208         {
209             objects[path + "/" + forward].emplace(endpoint);
210         }
211         if (reverse.size())
212         {
213             objects[endpoint + "/" + reverse].emplace(path);
214         }
215     }
216     for (const auto& object : objects)
217     {
218         addEndpointsToAssocIfaces(objectServer, object.first, object.second,
219                                   assocMaps);
220     }
221 
222     // Check for endpoints being removed instead of added
223     checkAssociationEndpointRemoves(path, owner, objects, objectServer,
224                                     assocMaps);
225 
226     if (!objects.empty())
227     {
228         // Update associationOwners with the latest info
229         auto a = assocMaps.owners.find(path);
230         if (a != assocMaps.owners.end())
231         {
232             auto o = a->second.find(owner);
233             if (o != a->second.end())
234             {
235                 o->second = std::move(objects);
236             }
237             else
238             {
239                 a->second.emplace(owner, std::move(objects));
240             }
241         }
242         else
243         {
244             boost::container::flat_map<std::string, AssociationPaths> owners;
245             owners.emplace(owner, std::move(objects));
246             assocMaps.owners.emplace(path, owners);
247         }
248     }
249 }
250 
251 void addPendingAssociation(const std::string& objectPath,
252                            const std::string& type,
253                            const std::string& endpointPath,
254                            const std::string& endpointType,
255                            const std::string& owner, AssociationMaps& assocMaps)
256 {
257     Association assoc{type, endpointType, endpointPath};
258 
259     auto p = assocMaps.pending.find(objectPath);
260     if (p == assocMaps.pending.end())
261     {
262         ExistingEndpoints ee;
263         ee.emplace_back(owner, std::move(assoc));
264         assocMaps.pending.emplace(objectPath, std::move(ee));
265     }
266     else
267     {
268         // Already waiting on this path for another association,
269         // so just add this endpoint and owner.
270         auto& endpoints = p->second;
271         auto e =
272             std::find_if(endpoints.begin(), endpoints.end(),
273                          [&assoc, &owner](const auto& endpoint) {
274                              return (std::get<ownerPos>(endpoint) == owner) &&
275                                     (std::get<assocPos>(endpoint) == assoc);
276                          });
277         if (e == endpoints.end())
278         {
279             endpoints.emplace_back(owner, std::move(assoc));
280         }
281     }
282 }
283 
284 void removeFromPendingAssociations(const std::string& endpointPath,
285                                    AssociationMaps& assocMaps)
286 {
287     auto assoc = assocMaps.pending.begin();
288     while (assoc != assocMaps.pending.end())
289     {
290         auto endpoint = assoc->second.begin();
291         while (endpoint != assoc->second.end())
292         {
293             auto& e = std::get<assocPos>(*endpoint);
294             if (std::get<reversePathPos>(e) == endpointPath)
295             {
296                 endpoint = assoc->second.erase(endpoint);
297                 continue;
298             }
299 
300             endpoint++;
301         }
302 
303         if (assoc->second.empty())
304         {
305             assoc = assocMaps.pending.erase(assoc);
306             continue;
307         }
308 
309         assoc++;
310     }
311 }
312 
313 void addSingleAssociation(sdbusplus::asio::object_server& server,
314                           const std::string& assocPath,
315                           const std::string& endpoint, const std::string& owner,
316                           const std::string& ownerPath,
317                           AssociationMaps& assocMaps)
318 {
319     boost::container::flat_set<std::string> endpoints{endpoint};
320 
321     addEndpointsToAssocIfaces(server, assocPath, endpoints, assocMaps);
322 
323     AssociationPaths objects;
324     boost::container::flat_set e{endpoint};
325     objects.emplace(assocPath, e);
326 
327     auto a = assocMaps.owners.find(ownerPath);
328     if (a != assocMaps.owners.end())
329     {
330         auto o = a->second.find(owner);
331         if (o != a->second.end())
332         {
333             auto p = o->second.find(assocPath);
334             if (p != o->second.end())
335             {
336                 p->second.emplace(endpoint);
337             }
338             else
339             {
340                 o->second.emplace(assocPath, e);
341             }
342         }
343         else
344         {
345             a->second.emplace(owner, std::move(objects));
346         }
347     }
348     else
349     {
350         boost::container::flat_map<std::string, AssociationPaths> owners;
351         owners.emplace(owner, std::move(objects));
352         assocMaps.owners.emplace(endpoint, owners);
353     }
354 }
355 
356 void checkIfPendingAssociation(const std::string& objectPath,
357                                const interface_map_type& interfaceMap,
358                                AssociationMaps& assocMaps,
359                                sdbusplus::asio::object_server& server)
360 {
361     auto pending = assocMaps.pending.find(objectPath);
362     if (pending == assocMaps.pending.end())
363     {
364         return;
365     }
366 
367     if (interfaceMap.find(objectPath) == interfaceMap.end())
368     {
369         return;
370     }
371 
372     auto endpoint = pending->second.begin();
373 
374     while (endpoint != pending->second.end())
375     {
376         const auto& e = std::get<assocPos>(*endpoint);
377 
378         // Ensure the other side of the association still exists
379         if (interfaceMap.find(std::get<reversePathPos>(e)) ==
380             interfaceMap.end())
381         {
382             endpoint++;
383             continue;
384         }
385 
386         // Add both sides of the association:
387         //  objectPath/forwardType and reversePath/reverseType
388         //
389         // The ownerPath is the reversePath - i.e. the endpoint that
390         // is on D-Bus and owns the org.openbmc.Associations iface.
391         //
392         const auto& ownerPath = std::get<reversePathPos>(e);
393         const auto& owner = std::get<ownerPos>(*endpoint);
394 
395         auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e);
396         auto endpointPath = ownerPath;
397 
398         addSingleAssociation(server, assocPath, endpointPath, owner, ownerPath,
399                              assocMaps);
400 
401         // Now the reverse direction (still the same owner and ownerPath)
402         assocPath = endpointPath + '/' + std::get<reverseTypePos>(e);
403         endpointPath = objectPath;
404         addSingleAssociation(server, assocPath, endpointPath, owner, ownerPath,
405                              assocMaps);
406 
407         // Not pending anymore
408         endpoint = pending->second.erase(endpoint);
409     }
410 
411     if (pending->second.empty())
412     {
413         assocMaps.pending.erase(objectPath);
414     }
415 }
416