|  | #include "topology.hpp" | 
|  |  | 
|  | #include "phosphor-logging/lg2.hpp" | 
|  |  | 
|  | void Topology::addBoard(const std::string& path, const std::string& boardType, | 
|  | const std::string& boardName, | 
|  | const nlohmann::json& exposesItem) | 
|  | { | 
|  | auto findType = exposesItem.find("Type"); | 
|  | if (findType == exposesItem.end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | boardNames.try_emplace(boardName, path); | 
|  |  | 
|  | PortType exposesType = findType->get<std::string>(); | 
|  |  | 
|  | if (exposesType == "DownstreamPort") | 
|  | { | 
|  | addDownstreamPort(path, exposesItem); | 
|  | } | 
|  | else if (exposesType.ends_with("Port")) | 
|  | { | 
|  | upstreamPorts[exposesType].insert(path); | 
|  | } | 
|  | else | 
|  | { | 
|  | return; | 
|  | } | 
|  | boardTypes[path] = boardType; | 
|  | } | 
|  |  | 
|  | void Topology::addDownstreamPort(const Path& path, | 
|  | const nlohmann::json& exposesItem) | 
|  | { | 
|  | auto findConnectsTo = exposesItem.find("ConnectsToType"); | 
|  | if (findConnectsTo == exposesItem.end()) | 
|  | { | 
|  | lg2::error("Board at path {PATH} is missing ConnectsToType", "PATH", | 
|  | path); | 
|  | return; | 
|  | } | 
|  | PortType connectsTo = findConnectsTo->get<std::string>(); | 
|  |  | 
|  | downstreamPorts[connectsTo].insert(path); | 
|  | auto findPoweredBy = exposesItem.find("PowerPort"); | 
|  | if (findPoweredBy != exposesItem.end()) | 
|  | { | 
|  | powerPaths.insert(path); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::unordered_map<std::string, std::set<Association>> Topology::getAssocs( | 
|  | BoardPathsView boardPaths) | 
|  | { | 
|  | std::unordered_map<std::string, std::set<Association>> result; | 
|  |  | 
|  | // look at each upstream port type | 
|  | for (const auto& upstreamPortPair : upstreamPorts) | 
|  | { | 
|  | auto downstreamMatch = downstreamPorts.find(upstreamPortPair.first); | 
|  |  | 
|  | if (downstreamMatch == downstreamPorts.end()) | 
|  | { | 
|  | // no match | 
|  | continue; | 
|  | } | 
|  |  | 
|  | fillAssocsForPortId(result, boardPaths, upstreamPortPair.second, | 
|  | downstreamMatch->second); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void Topology::fillAssocsForPortId( | 
|  | std::unordered_map<std::string, std::set<Association>>& result, | 
|  | BoardPathsView boardPaths, const std::set<Path>& upstreamPaths, | 
|  | const std::set<Path>& downstreamPaths) | 
|  | { | 
|  | for (const Path& upstream : upstreamPaths) | 
|  | { | 
|  | for (const Path& downstream : downstreamPaths) | 
|  | { | 
|  | fillAssocForPortId(result, boardPaths, upstream, downstream); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void Topology::fillAssocForPortId( | 
|  | std::unordered_map<std::string, std::set<Association>>& result, | 
|  | BoardPathsView boardPaths, const Path& upstream, const Path& downstream) | 
|  | { | 
|  | if (boardTypes[upstream] != "Chassis" && boardTypes[upstream] != "Board") | 
|  | { | 
|  | return; | 
|  | } | 
|  | // The downstream path must be one we care about. | 
|  | if (!std::ranges::contains(boardPaths, downstream)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string assoc = "contained_by"; | 
|  | std::optional<std::string> opposite = getOppositeAssoc(assoc); | 
|  |  | 
|  | if (!opposite.has_value()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | result[downstream].insert({assoc, opposite.value(), upstream}); | 
|  |  | 
|  | if (powerPaths.contains(downstream)) | 
|  | { | 
|  | assoc = "powering"; | 
|  | opposite = getOppositeAssoc(assoc); | 
|  | if (!opposite.has_value()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | result[downstream].insert({assoc, opposite.value(), upstream}); | 
|  | } | 
|  | } | 
|  |  | 
|  | const std::set<std::pair<std::string, std::string>> assocs = { | 
|  | {"powering", "powered_by"}, {"containing", "contained_by"}, | 
|  | // ... extend as needed | 
|  | }; | 
|  |  | 
|  | std::optional<std::string> Topology::getOppositeAssoc( | 
|  | const AssocName& assocName) | 
|  | { | 
|  | for (const auto& entry : assocs) | 
|  | { | 
|  | if (entry.first == assocName) | 
|  | { | 
|  | return entry.second; | 
|  | } | 
|  | if (entry.second == assocName) | 
|  | { | 
|  | return entry.first; | 
|  | } | 
|  | } | 
|  |  | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | void Topology::remove(const std::string& boardName) | 
|  | { | 
|  | // Remove the board from boardNames, and then using the path | 
|  | // found in boardNames remove it from upstreamPorts and | 
|  | // downstreamPorts. | 
|  | auto boardFind = boardNames.find(boardName); | 
|  | if (boardFind == boardNames.end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string boardPath = boardFind->second; | 
|  |  | 
|  | boardNames.erase(boardFind); | 
|  |  | 
|  | for (auto& upstreamPort : upstreamPorts) | 
|  | { | 
|  | upstreamPort.second.erase(boardPath); | 
|  | } | 
|  |  | 
|  | for (auto& downstreamPort : downstreamPorts) | 
|  | { | 
|  | downstreamPort.second.erase(boardPath); | 
|  | } | 
|  | } |