|  | #pragma once | 
|  |  | 
|  | #include "bmcweb_config.h" | 
|  |  | 
|  | #include "aggregation_utils.hpp" | 
|  | #include "dbus_utility.hpp" | 
|  | #include "error_messages.hpp" | 
|  | #include "http_client.hpp" | 
|  | #include "http_connection.hpp" | 
|  | #include "managed_store.hpp" | 
|  | #include "request_stats.hpp" | 
|  |  | 
|  | #include <boost/algorithm/string/predicate.hpp> | 
|  |  | 
|  | #include <array> | 
|  | #include <cstdint> | 
|  | #include <random> | 
|  |  | 
|  | #ifdef UNIT_TEST_BUILD | 
|  | #include "test/g3/mock_managed_store.hpp" // NOLINT | 
|  | #endif | 
|  |  | 
|  | namespace redfish | 
|  | { | 
|  |  | 
|  | constexpr unsigned int aggregatorReadBodyLimit = 50 * 1024 * 1024; // 50MB | 
|  |  | 
|  | enum class Result : std::uint8_t | 
|  | { | 
|  | LocalHandle, | 
|  | NoLocalHandle | 
|  | }; | 
|  |  | 
|  | enum class SearchType : std::uint8_t | 
|  | { | 
|  | Collection, | 
|  | CollOrCon, | 
|  | ContainsSubordinate, | 
|  | Resource | 
|  | }; | 
|  |  | 
|  | struct RdeSatelliteConfig | 
|  | { | 
|  | std::string name; | 
|  | std::string vid; | 
|  | std::string udevid; | 
|  | std::string usbport; | 
|  | std::string objectpath; | 
|  | }; | 
|  |  | 
|  | // clang-format off | 
|  | // These are all of the properties as of version 2022.2 of the Redfish Resource | 
|  | // and Schema Guide whose Type is "string (URI)" and the name does not end in a | 
|  | // case-insensitive form of "uri".  That version of the schema is associated | 
|  | // with version 1.16.0 of the Redfish Specification.  Going forward, new URI | 
|  | // properties should end in URI so this list should not need to be maintained as | 
|  | // the spec is updated.  NOTE: These have been pre-sorted in order to be | 
|  | // compatible with binary search | 
|  | constexpr std::array nonUriProperties{ | 
|  | "@Redfish.ActionInfo", | 
|  | // "@odata.context", // We can't fix /redfish/v1/$metadata URIs | 
|  | "@odata.id", | 
|  | // "Destination", // Only used by EventService and won't be a Redfish URI | 
|  | // "HostName", // Isn't actually a Redfish URI | 
|  | "Image", | 
|  | "MetricProperty", | 
|  | // "OriginOfCondition", // Is URI when in request, but is object in response | 
|  | "TaskMonitor", | 
|  | "target", // normal string, but target URI for POST to invoke an action | 
|  | }; | 
|  | // clang-format on | 
|  |  | 
|  | // Search the top collection array to determine if the passed URI is of a | 
|  | // desired type | 
|  | inline bool searchCollectionsArray(std::string_view uri, | 
|  | const SearchType searchType) | 
|  | { | 
|  | boost::system::result<boost::urls::url> parsedUrl = | 
|  | boost::urls::parse_relative_ref(uri); | 
|  |  | 
|  | if (!parsedUrl) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Failed to get target URI from " << uri; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | parsedUrl->normalize(); | 
|  | boost::urls::segments_ref segments = parsedUrl->segments(); | 
|  | if (!segments.is_absolute()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // The passed URI must begin with "/redfish/v1", but we have to strip it | 
|  | // from the URI since topCollections does not include it in its URIs. | 
|  | if (segments.size() < 2) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | if (segments.front() != "redfish") | 
|  | { | 
|  | return false; | 
|  | } | 
|  | segments.erase(segments.begin()); | 
|  | if (segments.front() != "v1") | 
|  | { | 
|  | return false; | 
|  | } | 
|  | segments.erase(segments.begin()); | 
|  |  | 
|  | // Exclude the trailing "/" if it exists such as in "/redfish/v1/". | 
|  | if (!segments.empty() && segments.back().empty()) | 
|  | { | 
|  | segments.pop_back(); | 
|  | } | 
|  |  | 
|  | // If no segments then the passed URI was either "/redfish/v1" or | 
|  | // "/redfish/v1/". | 
|  | if (segments.empty()) | 
|  | { | 
|  | return (searchType == SearchType::ContainsSubordinate) || | 
|  | (searchType == SearchType::CollOrCon); | 
|  | } | 
|  | std::string_view url = segments.buffer(); | 
|  | const auto* it = std::ranges::lower_bound(topCollections, url); | 
|  | if (it == topCollections.end()) | 
|  | { | 
|  | // parsedUrl is alphabetically after the last entry in the array so it | 
|  | // can't be a top collection or up tree from a top collection | 
|  | return false; | 
|  | } | 
|  |  | 
|  | boost::urls::url collectionUrl(*it); | 
|  | boost::urls::segments_view collectionSegments = collectionUrl.segments(); | 
|  | boost::urls::segments_view::iterator itCollection = | 
|  | collectionSegments.begin(); | 
|  | const boost::urls::segments_view::const_iterator endCollection = | 
|  | collectionSegments.end(); | 
|  |  | 
|  | // Each segment in the passed URI should match the found collection | 
|  | for (const auto& segment : segments) | 
|  | { | 
|  | if (itCollection == endCollection) | 
|  | { | 
|  | // Leftover segments means the target is for an aggregation | 
|  | // supported resource | 
|  | return searchType == SearchType::Resource; | 
|  | } | 
|  |  | 
|  | if (segment != (*itCollection)) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | itCollection++; | 
|  | } | 
|  |  | 
|  | // No remaining segments means the passed URI was a top level collection | 
|  | if (searchType == SearchType::Collection) | 
|  | { | 
|  | return itCollection == endCollection; | 
|  | } | 
|  | if (searchType == SearchType::ContainsSubordinate) | 
|  | { | 
|  | return itCollection != endCollection; | 
|  | } | 
|  |  | 
|  | // Return this check instead of "true" in case other SearchTypes get added | 
|  | return searchType == SearchType::CollOrCon; | 
|  | } | 
|  |  | 
|  | // Strip query params which are incompatible with aggregation. | 
|  | // Note, this still doesn't work for collections that might return less than the | 
|  | // complete collection by default, but hopefully those are rare/nonexistent in | 
|  | // top collections.  bmcweb doesn't implement any of these. | 
|  | inline void parameterRemove(crow::Request& localReq) | 
|  | { | 
|  | boost::urls::url& urlNew = localReq.mutableUrl(); | 
|  | auto paramsIt = urlNew.params().begin(); | 
|  | bool foundSkip = false; | 
|  | while (paramsIt != urlNew.params().end()) | 
|  | { | 
|  | const boost::urls::param& param = *paramsIt; | 
|  |  | 
|  | // Plugins will not apply correctly to resources pulled in by offloading | 
|  | // $expand to a satellite BMC.  Don't forward $expand in that case. | 
|  | if constexpr (enablePlatform22) | 
|  | { | 
|  | if (param.key == "$expand") | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Erasing $expand from forwarded request"; | 
|  | paramsIt = urlNew.params().erase(paramsIt); | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | // By the spec, no other params should be provided with only. Don't | 
|  | // forward any params and let the aggregating BMC handle them. | 
|  | if (param.key == "only") | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << | 
|  | "Erasing all params from request to top level collection"; | 
|  | urlNew.params().clear(); | 
|  | foundSkip = false; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (param.key == "$skip") | 
|  | { | 
|  | foundSkip = true; | 
|  | } | 
|  | paramsIt++; | 
|  | } | 
|  |  | 
|  | if (foundSkip) | 
|  | { | 
|  | // Remove $skip and any other param that is supposed to processed after | 
|  | // it.  Applying $skip twice would produce different results. | 
|  | // Forwarding just the non-$skip params would result in partially | 
|  | // processing the params in the wrong order. | 
|  | paramsIt = urlNew.params().begin(); | 
|  | while (paramsIt != urlNew.params().end()) | 
|  | { | 
|  | const boost::urls::param& param = *paramsIt; | 
|  | // According to the Redfish spec, $filter is the only param that is | 
|  | // processed before $skip and is thus safe to be forwarded. All | 
|  | // others need to be removed | 
|  | if (param.key != "$filter") | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Erasing \"" << param.key | 
|  | << "\" param from request to top level collection"; | 
|  | paramsIt = urlNew.params().erase(paramsIt); | 
|  | continue; | 
|  | } | 
|  | // Pass $filter parameter | 
|  | paramsIt++; | 
|  | } | 
|  | } | 
|  | localReq.target(urlNew.buffer()); | 
|  | } | 
|  |  | 
|  | // Determines if the passed property contains a URI.  Those property names | 
|  | // either end with a case-insensitive version of "uri" or are specifically | 
|  | // defined in the above array. | 
|  | inline bool isPropertyUri(std::string_view propertyName) | 
|  | { | 
|  | return boost::iends_with(propertyName, "uri") || | 
|  | std::binary_search(nonUriProperties.begin(), nonUriProperties.end(), | 
|  | propertyName); | 
|  | } | 
|  |  | 
|  | static inline void addPrefixToStringItem(std::string& strValue, | 
|  | std::string_view prefix) | 
|  | { | 
|  | // Make sure the value is a properly formatted URI | 
|  | auto parsed = boost::urls::parse_relative_ref(strValue); | 
|  | if (!parsed) | 
|  | { | 
|  | BMCWEB_LOG_CRITICAL << "Couldn't parse URI from resource " << strValue; | 
|  | return; | 
|  | } | 
|  |  | 
|  | boost::urls::url_view thisUrl = *parsed; | 
|  |  | 
|  | // We don't need to aggregate JsonSchemas due to potential issues such as | 
|  | // version mismatches between aggregator and satellite BMCs.  For now | 
|  | // assume that the aggregator has all the schemas and versions that the | 
|  | // aggregated server has. | 
|  | if (crow::utility::readUrlSegments(thisUrl, "redfish", "v1", "JsonSchemas", | 
|  | crow::utility::OrMorePaths())) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Skipping JsonSchemas URI prefix fixing"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The first two segments should be "/redfish/v1".  We need to check that | 
|  | // before we can search topCollections | 
|  | if (!crow::utility::readUrlSegments(thisUrl, "redfish", "v1", | 
|  | crow::utility::OrMorePaths())) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Check array adding a segment each time until collection is identified | 
|  | // Add prefix to segment after the collection | 
|  | const boost::urls::segments_view urlSegments = thisUrl.segments(); | 
|  | bool addedPrefix = false; | 
|  | boost::urls::url url("/"); | 
|  | boost::urls::segments_view::iterator it = urlSegments.begin(); | 
|  | const boost::urls::segments_view::const_iterator end = urlSegments.end(); | 
|  |  | 
|  | // Skip past the leading "/redfish/v1" | 
|  | it++; | 
|  | it++; | 
|  | for (; it != end; it++) | 
|  | { | 
|  | // Trailing "/" will result in an empty segment.  In that case we need | 
|  | // to return so we don't apply a prefix to top level collections such | 
|  | // as "/redfish/v1/Chassis/" | 
|  | if ((*it).empty()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (std::binary_search(topCollections.begin(), topCollections.end(), | 
|  | url.buffer())) | 
|  | { | 
|  | std::string collectionItem(prefix); | 
|  | collectionItem += "_" + (*it); | 
|  | url.segments().push_back(collectionItem); | 
|  | it++; | 
|  | addedPrefix = true; | 
|  | break; | 
|  | } | 
|  |  | 
|  | url.segments().push_back(*it); | 
|  | } | 
|  |  | 
|  | // Finish constructing the URL here (if needed) to avoid additional checks | 
|  | for (; it != end; it++) | 
|  | { | 
|  | url.segments().push_back(*it); | 
|  | } | 
|  |  | 
|  | if (addedPrefix) | 
|  | { | 
|  | url.segments().insert(url.segments().begin(), {"redfish", "v1"}); | 
|  | // Preserve the fragment from the original URL | 
|  | if (thisUrl.has_fragment()) | 
|  | { | 
|  | url.set_fragment(thisUrl.fragment()); | 
|  | } | 
|  | strValue = url.buffer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline void addPrefixToItem(nlohmann::json& item, | 
|  | std::string_view prefix) | 
|  | { | 
|  | std::string* strValue = item.get_ptr<std::string*>(); | 
|  | if (strValue == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; | 
|  | return; | 
|  | } | 
|  | addPrefixToStringItem(*strValue, prefix); | 
|  | item = *strValue; | 
|  | } | 
|  |  | 
|  | static inline int generateRandomInt() | 
|  | { | 
|  | std::random_device randomDevice; | 
|  | std::mt19937 generator(randomDevice()); | 
|  | std::uniform_int_distribution<> distribution(1, 9999); | 
|  | int randomNumber = distribution(generator); | 
|  | return randomNumber; | 
|  | } | 
|  |  | 
|  | static inline void addAggregatedHeaders(crow::Response& asyncResp, | 
|  | const crow::Response& resp, | 
|  | std::string_view prefix) | 
|  | { | 
|  | if (!resp.getHeaderValue("Content-Type").empty()) | 
|  | { | 
|  | asyncResp.addHeader(boost::beast::http::field::content_type, | 
|  | resp.getHeaderValue("Content-Type")); | 
|  | } | 
|  | if (!resp.getHeaderValue("Allow").empty()) | 
|  | { | 
|  | asyncResp.addHeader(boost::beast::http::field::allow, | 
|  | resp.getHeaderValue("Allow")); | 
|  | } | 
|  | std::string_view header = resp.getHeaderValue("Location"); | 
|  | if (!header.empty()) | 
|  | { | 
|  | std::string location(header); | 
|  | addPrefixToStringItem(location, prefix); | 
|  | asyncResp.addHeader(boost::beast::http::field::location, location); | 
|  | } | 
|  | if (!resp.getHeaderValue("Retry-After").empty()) | 
|  | { | 
|  | asyncResp.addHeader(boost::beast::http::field::retry_after, | 
|  | resp.getHeaderValue("Retry-After")); | 
|  | } | 
|  | // TODO: we need special handling for Link Header Value | 
|  | } | 
|  |  | 
|  | // Fix HTTP headers which appear in responses from Task resources among others | 
|  | static inline void addPrefixToHeadersInResp(nlohmann::json& json, | 
|  | std::string_view prefix) | 
|  | { | 
|  | // The passed in "HttpHeaders" should be an array of headers | 
|  | nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); | 
|  | if (array == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Field wasn't an array_t????"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (nlohmann::json& item : *array) | 
|  | { | 
|  | // Each header is a single string with the form "<Field>: <Value>" | 
|  | std::string* strHeader = item.get_ptr<std::string*>(); | 
|  | if (strHeader == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | constexpr std::string_view location = "Location: "; | 
|  | if (strHeader->starts_with(location)) | 
|  | { | 
|  | std::string header = strHeader->substr(location.size()); | 
|  | addPrefixToStringItem(header, prefix); | 
|  | *strHeader = std::string(location) + header; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Search the json for all URIs and add the supplied prefix if the URI is for | 
|  | // an aggregated resource. | 
|  | static inline void addPrefixes(nlohmann::json& json, std::string_view prefix) | 
|  | { | 
|  | nlohmann::json::object_t* object = | 
|  | json.get_ptr<nlohmann::json::object_t*>(); | 
|  | if (object != nullptr) | 
|  | { | 
|  | for (std::pair<const std::string, nlohmann::json>& item : *object) | 
|  | { | 
|  | if (isPropertyUri(item.first)) | 
|  | { | 
|  | addPrefixToItem(item.second, prefix); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // "HttpHeaders" contains HTTP headers.  Among those we need to | 
|  | // attempt to fix the "Location" header | 
|  | if (item.first == "HttpHeaders") | 
|  | { | 
|  | addPrefixToHeadersInResp(item.second, prefix); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Recusively parse the rest of the json | 
|  | addPrefixes(item.second, prefix); | 
|  | } | 
|  | return; | 
|  | } | 
|  | nlohmann::json::array_t* array = json.get_ptr<nlohmann::json::array_t*>(); | 
|  | if (array != nullptr) | 
|  | { | 
|  | for (nlohmann::json& item : *array) | 
|  | { | 
|  | addPrefixes(item, prefix); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | inline std::unordered_map<std::string, std::string> | 
|  | rdePrefixMap({{"/xyz/openbmc_project/rde_devices/1_1_1_1", "BCX5VT"}, // NOLINT | 
|  | {"/xyz/openbmc_project/rde_devices/1_1_2_1", "A61MJ0"}, | 
|  | {"/xyz/openbmc_project/rde_devices/1_1_3_1", "K298LC"}, | 
|  | {"/xyz/openbmc_project/rde_devices/1_1_4_1", "2HMMCS"}}); | 
|  |  | 
|  | inline std::unordered_map<std::string, std::string> | 
|  | rdeServiceLabelMap({{"BCX5VT", "DOWNLINK"}, // NOLINT | 
|  | {"A61MJ0", "DOWNLINK"}, | 
|  | {"K298LC", "DOWNLINK"}, | 
|  | {"2HMMCS", "PCIE0"}}); | 
|  |  | 
|  | inline static bool generatePrefix(const std::string& objPath, std::string& prefix) | 
|  | { | 
|  | const auto& it = rdePrefixMap.find(objPath); | 
|  | if (it == rdePrefixMap.end()) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "objPath does not exist in prefix map " << objPath | 
|  | << "\n"; | 
|  | return false; | 
|  | } | 
|  | prefix = it->second; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static inline bool isMemberStartsWithKnownPrefix(const std::string& memberName) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "isMemberStartsWithKnownPrefix"; | 
|  | for (auto const& [path, prefix] : rdePrefixMap) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "path " << path << " " << prefix; | 
|  | if (memberName.starts_with(prefix)) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << " Known prefix " << memberName; | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | inline boost::system::error_code aggregationRetryHandler(unsigned int respCode) | 
|  | { | 
|  | // Allow all response codes because we want to surface any satellite | 
|  | // issue to the client | 
|  | BMCWEB_LOG_DEBUG << "Received " << respCode << " response from satellite"; | 
|  | return boost::system::errc::make_error_code(boost::system::errc::success); | 
|  | } | 
|  |  | 
|  | inline crow::ConnectionPolicy getAggregationPolicy() | 
|  | { | 
|  | return {.maxRetryAttempts = 1, | 
|  | .requestByteLimit = aggregatorReadBodyLimit, | 
|  | .maxConnections = 20, | 
|  | .retryPolicyAction = "TerminateAfterRetries", | 
|  | .retryIntervalSecs = std::chrono::seconds(0), | 
|  | .invalidResp = aggregationRetryHandler}; | 
|  | } | 
|  |  | 
|  | class RedfishAggregator | 
|  | { | 
|  | private: | 
|  | crow::HttpClient client; | 
|  |  | 
|  | RedfishAggregator() : | 
|  | client(std::make_shared<crow::ConnectionPolicy>(getAggregationPolicy())) | 
|  | { | 
|  | getSatelliteConfigs(constructorCallback); | 
|  | } | 
|  |  | 
|  | // Dummy callback used by the Constructor so that it can report the number | 
|  | // of satellite configs when the class is first created | 
|  | static void constructorCallback( | 
|  | const boost::system::error_code& ec, | 
|  | const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, | 
|  | const std::unordered_map<std::string, RdeSatelliteConfig>& | 
|  | rdeSatelliteInfo) | 
|  | { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Something went wrong while querying dbus!"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "There were " | 
|  | << std::to_string(satelliteInfo.size()) | 
|  | << " satellite configs found at startup"; | 
|  | BMCWEB_LOG_DEBUG << "There were " | 
|  | << std::to_string(rdeSatelliteInfo.size()) | 
|  | << " RDE Device configs found at startup"; | 
|  | } | 
|  |  | 
|  | // Search D-Bus objects for satellite config objects and add their | 
|  | // information if valid | 
|  | static void findSatelliteConfigs( | 
|  | const dbus::utility::ManagedObjectType& objects, | 
|  | std::unordered_map<std::string, boost::urls::url>& satelliteInfo, | 
|  | std::unordered_map<std::string, RdeSatelliteConfig>& rdeSatelliteInfo) | 
|  | { | 
|  | for (const auto& objectPath : objects) | 
|  | { | 
|  | for (const auto& interface : objectPath.second) | 
|  | { | 
|  | if (interface.first == | 
|  | "xyz.openbmc_project.Configuration.SatelliteController") | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Found Satellite Controller at " | 
|  | << objectPath.first.str; | 
|  |  | 
|  | if (!satelliteInfo.empty()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR | 
|  | << "Redfish Aggregation only supports one satellite!"; | 
|  | BMCWEB_LOG_DEBUG << "Clearing all satellite data"; | 
|  | satelliteInfo.clear(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // For now assume there will only be one satellite config. | 
|  | // Assign it the name/prefix "5B247A" | 
|  | addSatelliteConfig("5B247A", interface.second, | 
|  | satelliteInfo); | 
|  | } | 
|  | else if ( | 
|  | interface.first == | 
|  | "xyz.openbmc_project.Configuration.RdeSatelliteController") | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Found RDE Satellite Controller at " | 
|  | << objectPath.first.str; | 
|  | addRdeSatelliteConfig(interface.second, rdeSatelliteInfo); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Parse the properties of a satellite config object and add the | 
|  | // configuration if the properties are valid | 
|  | static void addSatelliteConfig( | 
|  | const std::string& name, | 
|  | const dbus::utility::DBusPropertiesMap& properties, | 
|  | std::unordered_map<std::string, boost::urls::url>& satelliteInfo) | 
|  | { | 
|  | boost::urls::url url; | 
|  |  | 
|  | for (const auto& prop : properties) | 
|  | { | 
|  | if (prop.first == "Hostname") | 
|  | { | 
|  | const std::string* propVal = | 
|  | std::get_if<std::string>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid Hostname value"; | 
|  | return; | 
|  | } | 
|  | url.set_host(*propVal); | 
|  | } | 
|  |  | 
|  | else if (prop.first == "Port") | 
|  | { | 
|  | const uint64_t* propVal = std::get_if<uint64_t>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid Port value"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (*propVal > std::numeric_limits<uint16_t>::max()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Port value out of range"; | 
|  | return; | 
|  | } | 
|  | url.set_port(std::to_string(static_cast<uint16_t>(*propVal))); | 
|  | } | 
|  |  | 
|  | else if (prop.first == "AuthType") | 
|  | { | 
|  | const std::string* propVal = | 
|  | std::get_if<std::string>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid AuthType value"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // For now assume authentication not required to communicate | 
|  | // with the satellite BMC | 
|  | if (*propVal != "None") | 
|  | { | 
|  | BMCWEB_LOG_ERROR | 
|  | << "Unsupported AuthType value: " << *propVal | 
|  | << ", only \"none\" is supported"; | 
|  | return; | 
|  | } | 
|  | url.set_scheme("http"); | 
|  | } | 
|  | } // Finished reading properties | 
|  |  | 
|  | // Make sure all required config information was made available | 
|  | if (url.host().empty()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Host"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!url.has_port()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Satellite config " << name << " missing Port"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!url.has_scheme()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Satellite config " << name | 
|  | << " missing AuthType"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string resultString; | 
|  | auto result = satelliteInfo.insert_or_assign(name, std::move(url)); | 
|  | if (result.second) | 
|  | { | 
|  | resultString = "Added new satellite config "; | 
|  | } | 
|  | else | 
|  | { | 
|  | resultString = "Updated existing satellite config "; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << resultString << name << " at " | 
|  | << result.first->second.scheme() << "://" | 
|  | << result.first->second.encoded_host_and_port(); | 
|  | } | 
|  |  | 
|  | // Parse the properties of a RDE Device config object and add the | 
|  | // configuration if the properties are valid | 
|  | static void addRdeSatelliteConfig( | 
|  | const dbus::utility::DBusPropertiesMap& properties, | 
|  | std::unordered_map<std::string, RdeSatelliteConfig>& rdeSatelliteInfo) | 
|  | { | 
|  | RdeSatelliteConfig rdeConfig; | 
|  | std::string name; | 
|  | for (const auto& prop : properties) | 
|  | { | 
|  | if (prop.first == "Name") | 
|  | { | 
|  | const std::string* propVal = | 
|  | std::get_if<std::string>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid Name value"; | 
|  | return; | 
|  | } | 
|  | rdeConfig.name = *propVal; | 
|  | } | 
|  | else if (prop.first == "VID") | 
|  | { | 
|  | const std::string* propVal = | 
|  | std::get_if<std::string>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid VID value"; | 
|  | return; | 
|  | } | 
|  | rdeConfig.vid = *propVal; | 
|  | } | 
|  | else if (prop.first == "UDEVID") | 
|  | { | 
|  | const std::string* propVal = | 
|  | std::get_if<std::string>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid UDEVID value"; | 
|  | return; | 
|  | } | 
|  | rdeConfig.udevid = *propVal; | 
|  | } | 
|  | else if (prop.first == "USBPORT") | 
|  | { | 
|  | const std::string* propVal = | 
|  | std::get_if<std::string>(&prop.second); | 
|  | if (propVal == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Invalid USBPORT value"; | 
|  | return; | 
|  | } | 
|  | rdeConfig.usbport = *propVal; | 
|  | } | 
|  | } // Finished reading properties | 
|  |  | 
|  | if (rdeConfig.udevid.empty()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Empty udevid"; | 
|  | return; | 
|  | } | 
|  | rdeConfig.objectpath = | 
|  | "/xyz/openbmc_project/rde_devices/" + rdeConfig.udevid; | 
|  |  | 
|  | // Set the prefix to a random string 'E0SB8D' | 
|  | // (TODO) Generate a unique random prefix for each RDE Device | 
|  | if constexpr (bmcwebEnableRdeDevice) | 
|  | { | 
|  | if (!generatePrefix(rdeConfig.objectpath, name)) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << " Failed to get a prefix for " | 
|  | << rdeConfig.objectpath; | 
|  | return; | 
|  | } | 
|  | BMCWEB_LOG_DEBUG << " Got prefix " << name; | 
|  | } | 
|  | std::string resultString; | 
|  | auto result = rdeSatelliteInfo.emplace(name, std::move(rdeConfig)); | 
|  | if (result.second) | 
|  | { | 
|  | resultString = "Added new RDE Device config "; | 
|  | } | 
|  | else | 
|  | { | 
|  | resultString = "Updated existing RDE Device config "; | 
|  | } | 
|  | } | 
|  |  | 
|  | enum AggregationType : std::uint8_t | 
|  | { | 
|  | Collection, | 
|  | ContainsSubordinate, | 
|  | Resource, | 
|  | }; | 
|  |  | 
|  | static void | 
|  | startAggregation(AggregationType aggType, const crow::Request& thisReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
|  | { | 
|  | if (thisReq.method() != boost::beast::http::verb::get) | 
|  | { | 
|  | if (aggType == AggregationType::Collection) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Only aggregate GET requests to top level collections"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (aggType == AggregationType::ContainsSubordinate) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Only aggregate GET requests when uptree of" | 
|  | << " a top level collection"; | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Create a copy of thisReq so we we can still locally process the req | 
|  | std::error_code ec; | 
|  | auto localReq = std::make_shared<crow::Request>(thisReq.req, ec); | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Failed to create copy of request"; | 
|  | if (aggType == AggregationType::Resource) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Strip query params that don't work correctly if forwarded | 
|  | parameterRemove(*localReq); | 
|  |  | 
|  | getSatelliteConfigs( | 
|  | std::bind_front(aggregateAndHandle, aggType, localReq, asyncResp)); | 
|  | } | 
|  |  | 
|  | static void findSatellite( | 
|  | const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, | 
|  | const std::unordered_map<std::string, RdeSatelliteConfig>& | 
|  | rdeSatelliteInfo, | 
|  | std::string_view memberName) | 
|  | { | 
|  | bool validPrefix = false; | 
|  | // Determine if the resource ID begins with a known prefix | 
|  | for (const auto& satellite : satelliteInfo) | 
|  | { | 
|  | std::string targetPrefix = satellite.first; | 
|  | targetPrefix += "_"; | 
|  | if (memberName.starts_with(targetPrefix)) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "\"" << satellite.first | 
|  | << "\" is a known prefix"; | 
|  |  | 
|  | // Remove the known prefix from the request's URI and | 
|  | // then forward to the associated satellite BMC | 
|  | getInstance().forwardRequest(req, asyncResp, satellite.first, | 
|  | satelliteInfo); | 
|  | validPrefix = true; | 
|  | } | 
|  | } | 
|  | // Determine if the resource ID begins with a known prefix | 
|  | for (const auto& rdeSatellite : rdeSatelliteInfo) | 
|  | { | 
|  | std::string targetPrefix = rdeSatellite.first; | 
|  | targetPrefix += "_"; | 
|  | if (memberName.starts_with(targetPrefix)) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "\"" << rdeSatellite.first | 
|  | << "\" is a known prefix"; | 
|  | // Remove the known prefix from the request's URI and | 
|  | // then forward to RDE Daemon | 
|  | forwardRdeRequest(req, asyncResp, rdeSatellite.first, | 
|  | rdeSatelliteInfo); | 
|  | validPrefix = true; | 
|  | } | 
|  | } | 
|  | if (validPrefix) | 
|  | { | 
|  | return; | 
|  | } | 
|  | // We didn't recognize the prefix and need to return a 404 | 
|  | std::string nameStr = req.url().segments().back(); | 
|  | messages::resourceNotFound(asyncResp->res, "", nameStr); | 
|  | } | 
|  |  | 
|  | // Intended to handle an incoming request based on if Redfish Aggregation | 
|  | // is enabled.  Forwards request to satellite BMC if it exists. | 
|  | static void aggregateAndHandle( | 
|  | AggregationType aggType, | 
|  | const std::shared_ptr<crow::Request>& sharedReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const boost::system::error_code& ec, | 
|  | const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, | 
|  | const std::unordered_map<std::string, RdeSatelliteConfig>& | 
|  | rdeSatelliteInfo) | 
|  | { | 
|  | if (sharedReq == nullptr) | 
|  | { | 
|  | return; | 
|  | } | 
|  | // Something went wrong while querying dbus | 
|  | if (ec) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // No satellite or RDE configs means we don't need to keep attempting to | 
|  | // aggregate | 
|  | if (satelliteInfo.empty() && rdeSatelliteInfo.empty()) | 
|  | { | 
|  | // For collections or resources that can contain a subordinate | 
|  | // top level collection we'll also handle the request locally so we | 
|  | // don't need to write an error code | 
|  | if (aggType == AggregationType::Resource) | 
|  | { | 
|  | std::string nameStr = sharedReq->url().segments().back(); | 
|  | messages::resourceNotFound(asyncResp->res, "", nameStr); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | const crow::Request& thisReq = *sharedReq; | 
|  | if constexpr (enablePlatform9) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " | 
|  | << thisReq.url(); | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Aggregation is enabled, begin processing of " | 
|  | << thisReq.target(); | 
|  | } | 
|  |  | 
|  | // We previously determined the request is for a collection.  No need to | 
|  | // check again | 
|  | if (aggType == AggregationType::Collection) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Aggregating a collection"; | 
|  | // We need to use a specific response handler and send the | 
|  | // request to all known satellites | 
|  | getInstance().forwardCollectionRequests( | 
|  | thisReq, asyncResp, satelliteInfo, rdeSatelliteInfo); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We previously determined the request may contain a subordinate | 
|  | // collection.  No need to check again | 
|  | if (aggType == AggregationType::ContainsSubordinate) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Aggregating what may have a subordinate collection"; | 
|  | // We need to use a specific response handler and send the | 
|  | // request to all known satellites | 
|  | getInstance().forwardContainsSubordinateRequests(thisReq, asyncResp, | 
|  | satelliteInfo); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const boost::urls::segments_view urlSegments = thisReq.url().segments(); | 
|  | boost::urls::url currentUrl("/"); | 
|  | boost::urls::segments_view::iterator it = urlSegments.begin(); | 
|  | const boost::urls::segments_view::const_iterator end = | 
|  | urlSegments.end(); | 
|  |  | 
|  | // Skip past the leading "/redfish/v1" | 
|  | it++; | 
|  | it++; | 
|  | for (; it != end; it++) | 
|  | { | 
|  | if (std::binary_search(topCollections.begin(), topCollections.end(), | 
|  | currentUrl.buffer())) | 
|  | { | 
|  | // We've matched a resource collection so this current segment | 
|  | // must contain an aggregation prefix | 
|  | findSatellite(thisReq, asyncResp, satelliteInfo, | 
|  | rdeSatelliteInfo, *it); | 
|  | return; | 
|  | } | 
|  |  | 
|  | currentUrl.segments().push_back(*it); | 
|  | } | 
|  |  | 
|  | // We shouldn't reach this point since we should've hit one of the | 
|  | // previous exits | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  |  | 
|  | // Attempt to forward a request to the satellite BMC associated with the | 
|  | // prefix. | 
|  | void forwardRequest( | 
|  | const crow::Request& thisReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& prefix, | 
|  | const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) | 
|  | { | 
|  | const auto& sat = satelliteInfo.find(prefix); | 
|  | if (sat == satelliteInfo.end()) | 
|  | { | 
|  | // Realistically this shouldn't get called since we perform an | 
|  | // earlier check to make sure the prefix exists | 
|  | BMCWEB_LOG_ERROR << "Unrecognized satellite prefix \"" << prefix | 
|  | << "\""; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We need to strip the prefix from the request's path | 
|  | std::string targetURI; | 
|  | if constexpr (enablePlatform9) | 
|  | { | 
|  | targetURI = std::string(thisReq.url().path()); | 
|  | } | 
|  | else | 
|  | { | 
|  | targetURI = std::string(thisReq.target()); | 
|  | } | 
|  |  | 
|  | size_t pos = targetURI.find(prefix + "_"); | 
|  | if (pos == std::string::npos) | 
|  | { | 
|  | // If this fails then something went wrong | 
|  | BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix | 
|  | << "_\" from request URI"; | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | targetURI.erase(pos, prefix.size() + 1); | 
|  |  | 
|  | std::function<void(crow::Response&)> cb = | 
|  | std::bind_front(processResponse, prefix, asyncResp); | 
|  |  | 
|  | std::string data = thisReq.req.body(); | 
|  | client.sendDataWithCallback(data, std::string(sat->second.host()), | 
|  | sat->second.port_number(), targetURI, | 
|  | false /*useSSL*/, thisReq.fields(), | 
|  | thisReq.method(), cb); | 
|  | } | 
|  |  | 
|  | // Forward a request for a collection URI to each known satellite BMC | 
|  | void forwardCollectionRequests( | 
|  | const crow::Request& thisReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::unordered_map<std::string, boost::urls::url>& satelliteInfo, | 
|  | const std::unordered_map<std::string, RdeSatelliteConfig>& | 
|  | rdeSatelliteInfo) | 
|  | { | 
|  | for (const auto& sat : satelliteInfo) | 
|  | { | 
|  | std::function<void(crow::Response&)> cb = std::bind_front( | 
|  | processCollectionResponse, sat.first, asyncResp); | 
|  |  | 
|  | std::string targetURI; | 
|  | if constexpr (enablePlatform9) | 
|  | { | 
|  | targetURI = std::string(thisReq.url().path()); | 
|  | } | 
|  | else | 
|  | { | 
|  | targetURI = std::string(thisReq.target()); | 
|  | } | 
|  | std::string data = thisReq.req.body(); | 
|  | client.sendDataWithCallback(data, std::string(sat.second.host()), | 
|  | sat.second.port_number(), targetURI, | 
|  | false /*useSSL*/, thisReq.fields(), | 
|  | thisReq.method(), cb); | 
|  | } | 
|  | for (const auto& rsat : rdeSatelliteInfo) | 
|  | { | 
|  |  | 
|  | uint8_t operationId = 1; | 
|  | std::string requestPayload; | 
|  | BMCWEB_LOG_DEBUG << " Collection Request: dbus call to RDE Daemon " | 
|  | << " operationId " << operationId; | 
|  | std::string targetURI(thisReq.target()); | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled() && | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .getCurrentRdeRequestSkips() > 0) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << " Skipping the request " << targetURI; | 
|  | asyncResp->requestStatsContext->updateRdeSkips(); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .updateCurrentRdeRequestSkips(); | 
|  | return; | 
|  | } | 
|  | BMCWEB_LOG_DEBUG << "Collections objpath " << rsat.second.objectpath | 
|  | << " targetURI " << targetURI << " udevid " | 
|  | << rsat.second.udevid; | 
|  | asyncResp->res.startTimetrace(rsat.first); | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled()) | 
|  | { | 
|  | asyncResp->requestStatsContext->updateRdeReqs(); | 
|  | } | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [&operationId, rsat, | 
|  | asyncResp](const boost::system::error_code ec, | 
|  | const std::string& jsonString) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "DBUS response error operationId " | 
|  | << operationId << " , " << ec.value() | 
|  | << ", " << ec.message(); | 
|  | if (managedStore::RequestStatsStore:: | 
|  | isRdeRateLimitEnabled()) | 
|  | { | 
|  | asyncResp->requestStatsContext->updateRdeDbusErrors(); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .updateCurrentRdeDbusErrors(); | 
|  | if (managedStore::RequestStatsStore::instance() | 
|  | .getCurrentRdeDbusErrors() > | 
|  | managedStore::RequestStatsStore:: | 
|  | rdeErrorThreshold() && | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .getCurrentRdeRequestSkips() <= 0) | 
|  | { | 
|  |  | 
|  | BMCWEB_LOG_ERROR << "RDE Rate Limit enabled"; | 
|  | asyncResp->requestStatsContext | 
|  | ->updateRdeRateLimitToggles(); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .setCurrentRdeRequestSkips( | 
|  | managedStore::RequestStatsStore:: | 
|  | rdeMaxSkips()); | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled()) | 
|  | { | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .setCurrentRdeDbusErrors(0); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .setCurrentRdeRequestSkips(0); | 
|  | } | 
|  | processRdeCollectionResponse(rsat.first, asyncResp, jsonString); | 
|  | }, | 
|  | "xyz.openbmc_project.rdeoperation", rsat.second.objectpath, | 
|  | "xyz.openbmc_project.RdeDevice", "execute_rde", | 
|  | generateRandomInt(), operationId, targetURI, rsat.second.udevid, | 
|  | requestPayload); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Forward request for a URI that is uptree of a top level collection to | 
|  | // each known satellite BMC | 
|  | void forwardContainsSubordinateRequests( | 
|  | const crow::Request& thisReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::unordered_map<std::string, boost::urls::url>& satelliteInfo) | 
|  | { | 
|  | for (const auto& sat : satelliteInfo) | 
|  | { | 
|  | std::function<void(crow::Response&)> cb = std::bind_front( | 
|  | processContainsSubordinateResponse, sat.first, asyncResp); | 
|  |  | 
|  | std::string targetURI(thisReq.target()); | 
|  | std::string data = thisReq.req.body(); | 
|  | client.sendDataWithCallback(data, std::string(sat.second.host()), | 
|  | sat.second.port_number(), targetURI, | 
|  | false /*useSSL*/, thisReq.fields(), | 
|  | thisReq.method(), cb); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Attempt to forward a request to the RDE Daemon associated with the | 
|  | // prefix. | 
|  | static void forwardRdeRequest( | 
|  | const crow::Request& thisReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& prefix, | 
|  | const std::unordered_map<std::string, RdeSatelliteConfig>& | 
|  | rdeSatelliteInfo) | 
|  | { | 
|  | const auto& sat = rdeSatelliteInfo.find(prefix); | 
|  | if (sat == rdeSatelliteInfo.end()) | 
|  | { | 
|  | // Realistically this shouldn't get called since we perform an | 
|  | // earlier check to make sure the prefix exists | 
|  | BMCWEB_LOG_ERROR << "Unrecognized RDE Device prefix \"" << prefix | 
|  | << "\""; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We need to strip the prefix from the request's path | 
|  | std::string targetURI(thisReq.target()); | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled() && | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .getCurrentRdeRequestSkips() > 0) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << " Skipping the request " << targetURI; | 
|  | asyncResp->requestStatsContext->updateRdeSkips(); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .updateCurrentRdeRequestSkips(); | 
|  | return; | 
|  | } | 
|  | size_t pos = targetURI.find(prefix + "_"); | 
|  | if (pos == std::string::npos) | 
|  | { | 
|  | // If this fails then something went wrong | 
|  | BMCWEB_LOG_ERROR << "Error removing prefix \"" << prefix | 
|  | << "_\" from request URI"; | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | targetURI.erase(pos, prefix.size() + 1); | 
|  |  | 
|  | uint8_t operationId = 1; | 
|  | std::string requestPayload; | 
|  | boost::beast::http::verb method = thisReq.method(); | 
|  | if (thisReq.method() == boost::beast::http::verb::post) | 
|  | { | 
|  | operationId = 8; | 
|  | requestPayload = thisReq.body(); | 
|  | } | 
|  | else if (thisReq.method() == boost::beast::http::verb::patch) | 
|  | { | 
|  | operationId = 4; | 
|  | requestPayload = thisReq.body(); | 
|  | } | 
|  | BMCWEB_LOG_DEBUG | 
|  | << " Resource Request: dbus call to RDE Daemon operationId " | 
|  | << operationId; | 
|  | BMCWEB_LOG_DEBUG << "objpath " << sat->second.objectpath | 
|  | << " targetURI " << targetURI << " udevid " | 
|  | << sat->second.udevid << " method " << method; | 
|  | asyncResp->res.startTimetrace(prefix); | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled()) | 
|  | { | 
|  | asyncResp->requestStatsContext->updateRdeReqs(); | 
|  | } | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [&operationId, method, prefix, | 
|  | asyncResp](const boost::system::error_code ec, | 
|  | const std::string& jsonString) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "DBUS response error operationId " | 
|  | << operationId << " , " << ec.value() << ", " | 
|  | << ec.message(); | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled()) | 
|  |  | 
|  | { | 
|  | asyncResp->requestStatsContext->updateRdeDbusErrors(); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .updateCurrentRdeDbusErrors(); | 
|  |  | 
|  | if (managedStore::RequestStatsStore::instance() | 
|  | .getCurrentRdeDbusErrors() > | 
|  | managedStore::RequestStatsStore:: | 
|  | rdeErrorThreshold() && | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .getCurrentRdeRequestSkips() <= 0) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "RDE Rate limiting enabled"; | 
|  | asyncResp->requestStatsContext | 
|  | ->updateRdeRateLimitToggles(); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .setCurrentRdeRequestSkips( | 
|  | managedStore::RequestStatsStore::rdeMaxSkips()); | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  | if (managedStore::RequestStatsStore::isRdeRateLimitEnabled()) | 
|  | { | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .setCurrentRdeDbusErrors(0); | 
|  | managedStore::RequestStatsStore::instance() | 
|  | .setCurrentRdeRequestSkips(0); | 
|  | } | 
|  | processRdeResponse(prefix, method, asyncResp, jsonString); | 
|  | }, | 
|  | "xyz.openbmc_project.rdeoperation", sat->second.objectpath, | 
|  | "xyz.openbmc_project.RdeDevice", "execute_rde", generateRandomInt(), | 
|  | operationId, targetURI, sat->second.udevid, requestPayload); | 
|  | } | 
|  | // Processes the response returned by a RDE Device and loads its | 
|  | // contents into asyncResp | 
|  | static void | 
|  | processRdeResponse(std::string_view prefix, | 
|  | boost::beast::http::verb method, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& respString) | 
|  | { | 
|  |  | 
|  | nlohmann::json jsonVal = | 
|  | nlohmann::json::parse(respString, nullptr, false); | 
|  | if (jsonVal.is_discarded()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Error parsing RDE Device response as JSON " | 
|  | << respString; | 
|  | messages::operationFailed(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Successfully parsed RDE Device response"; | 
|  |  | 
|  | if (method == boost::beast::http::verb::post) | 
|  | { | 
|  | if (jsonVal["Status"] == "Completed") | 
|  | { | 
|  | asyncResp->res.result(200); | 
|  | } | 
|  | else | 
|  | { | 
|  | messages::operationFailed(asyncResp->res); | 
|  | } | 
|  | return; | 
|  | } | 
|  | // TODO: For collections we  want to add the satellite responses to | 
|  | // our response rather than just straight overwriting them if our | 
|  | // local handling was successful (i.e. would return a 200). | 
|  | addPrefixes(jsonVal, prefix); | 
|  |  | 
|  | if constexpr (bmcwebEnableRdeDevice) | 
|  | { | 
|  | if (jsonVal.contains( | 
|  | "/Location/PartLocation/ServiceLabel"_json_pointer)) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << " @odata.type " << jsonVal["@odata.type"]; | 
|  | if (jsonVal["Model"] == platform6Chassis1 || | 
|  | jsonVal["Model"] == platform6Chassis2) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Setting up service label " | 
|  | << rdeServiceLabelMap[std::string{prefix}] | 
|  | << " for prefix " << prefix; | 
|  | jsonVal["Location"]["PartLocation"]["ServiceLabel"] = | 
|  | rdeServiceLabelMap[std::string{prefix}]; | 
|  | } | 
|  | } | 
|  | auto it = jsonVal.find("Id"); | 
|  | if (it != jsonVal.end() && it->is_string()) | 
|  | { | 
|  | std::string* id_ptr = it->get_ptr<std::string*>(); | 
|  | if (id_ptr != nullptr) | 
|  | { | 
|  | id_ptr->insert(0, std::string(prefix) + "_"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | asyncResp->res.result(200); | 
|  | asyncResp->res.jsonValue = std::move(jsonVal); | 
|  | asyncResp->res.endTimetrace(std::string(prefix)); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; | 
|  | } | 
|  |  | 
|  | // Processes the collection response returned by a RDE Device and merges | 
|  | // its "@odata.id" values | 
|  | static void processRdeCollectionResponse( | 
|  | const std::string& prefix, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& respString) | 
|  | { | 
|  | nlohmann::json jsonVal = | 
|  | nlohmann::json::parse(respString, nullptr, false); | 
|  | if (jsonVal.is_discarded()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Error parsing RDEd response as JSON " | 
|  | << respString; | 
|  |  | 
|  | // Notify the user if doing so won't overwrite a valid response | 
|  | if ((asyncResp->res.resultInt() != 200) && | 
|  | (asyncResp->res.resultInt() != 502)) | 
|  | { | 
|  | messages::operationFailed(asyncResp->res); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Successfully parsed RDEd response"; | 
|  |  | 
|  | // Now we need to add the prefix to the URIs contained in the | 
|  | // response. | 
|  | addPrefixes(jsonVal, prefix); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Added prefix to parsed RDE Device response"; | 
|  |  | 
|  | // If this resource collection does not exist on the aggregating bmc | 
|  | // and has not already been added from processing the response from | 
|  | // a different satellite then we need to completely overwrite | 
|  | // asyncResp | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | // We only want to aggregate collections that contain a | 
|  | // "Members" array | 
|  | if ((!jsonVal.contains("Members")) && | 
|  | (!jsonVal["Members"].is_array())) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Skipping aggregating unsupported resource"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Collection does not exist, overwriting asyncResp"; | 
|  | asyncResp->res.jsonValue = std::move(jsonVal); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; | 
|  | } | 
|  | else | 
|  | { | 
|  | // We only want to aggregate collections that contain a | 
|  | // "Members" array | 
|  | if ((!asyncResp->res.jsonValue.contains("Members")) && | 
|  | (!asyncResp->res.jsonValue["Members"].is_array())) | 
|  |  | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Skipping aggregating unsupported resource"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" << prefix | 
|  | << "\" to collection"; | 
|  |  | 
|  | // TODO: This is a potential race condition with multiple | 
|  | // satellites and the aggregating bmc attempting to write to | 
|  | // update this array.  May need to cascade calls to the next | 
|  | // satellite at the end of this function. | 
|  | auto& members = asyncResp->res.jsonValue["Members"]; | 
|  | auto& satMembers = jsonVal["Members"]; | 
|  | for (auto& satMem : satMembers) | 
|  | { | 
|  | members.push_back(std::move(satMem)); | 
|  | } | 
|  | asyncResp->res.jsonValue["Members@odata.count"] = members.size(); | 
|  |  | 
|  | // TODO: Do we need to sort() after updating the array? | 
|  | } | 
|  | asyncResp->res.endTimetrace(prefix); | 
|  | } // End processRdeCollectionResponse() | 
|  |  | 
|  | public: | 
|  | RedfishAggregator(const RedfishAggregator&) = delete; | 
|  | RedfishAggregator& operator=(const RedfishAggregator&) = delete; | 
|  | RedfishAggregator(RedfishAggregator&&) = delete; | 
|  | RedfishAggregator& operator=(RedfishAggregator&&) = delete; | 
|  | ~RedfishAggregator() = default; | 
|  |  | 
|  | static RedfishAggregator& getInstance() | 
|  | { | 
|  | static RedfishAggregator handler; | 
|  | return handler; | 
|  | } | 
|  |  | 
|  | // Polls D-Bus to get all available satellite config information | 
|  | // Expects a handler which interacts with the returned configs | 
|  | static void getSatelliteConfigs( | 
|  | std::function< | 
|  | void(const boost::system::error_code&, | 
|  | const std::unordered_map<std::string, boost::urls::url>&, | 
|  | const std::unordered_map<std::string, RdeSatelliteConfig>&)> | 
|  | handler) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Gathering satellite configs"; | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(nullptr); | 
|  | managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
|  | "xyz.openbmc_project.EntityManager", | 
|  | {"/xyz/openbmc_project/inventory"}, | 
|  | context, | 
|  | [handler{std::move(handler)}]( | 
|  | const boost::system::error_code& ec, | 
|  | const dbus::utility::ManagedObjectType& objects) { | 
|  | std::unordered_map<std::string, boost::urls::url> satelliteInfo; | 
|  | std::unordered_map<std::string, RdeSatelliteConfig> | 
|  | rdeSatelliteInfo; | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "DBUS response error " << ec.value() << ", " | 
|  | << ec.message(); | 
|  | handler(ec, satelliteInfo, rdeSatelliteInfo); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Maps a chosen alias representing a satellite BMC to a url | 
|  | // containing the information required to create a http | 
|  | // connection to the satellite | 
|  | findSatelliteConfigs(objects, satelliteInfo, rdeSatelliteInfo); | 
|  |  | 
|  | if (!satelliteInfo.empty()) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " | 
|  | << std::to_string(satelliteInfo.size()) | 
|  | << " satellite BMCs"; | 
|  | } | 
|  | else if (!rdeSatelliteInfo.empty()) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Redfish Aggregation enabled with " | 
|  | << std::to_string(rdeSatelliteInfo.size()) | 
|  | << " RDE Device"; | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "No satellite BMCs detected.  Redfish Aggregation not enabled"; | 
|  | } | 
|  | handler(ec, satelliteInfo, rdeSatelliteInfo); | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Processes the response returned by a satellite BMC and loads its | 
|  | // contents into asyncResp | 
|  | static void | 
|  | processResponse(std::string_view prefix, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | crow::Response& resp) | 
|  | { | 
|  | // 429 and 502 mean we didn't actually send the request so don't | 
|  | // overwrite the response headers in that case | 
|  | if ((resp.result() == boost::beast::http::status::too_many_requests) || | 
|  | (resp.result() == boost::beast::http::status::bad_gateway)) | 
|  | { | 
|  | asyncResp->res.result(resp.result()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We want to attempt prefix fixing regardless of response code | 
|  | // The resp will not have a json component | 
|  | // We need to create a json from resp's stringResponse | 
|  | std::string_view contentType = resp.getHeaderValue("Content-Type"); | 
|  | if (boost::iequals(contentType, "application/json") || | 
|  | boost::iequals(contentType, "application/json; charset=utf-8")) | 
|  | { | 
|  | nlohmann::json jsonVal = | 
|  | nlohmann::json::parse(resp.body(), nullptr, false); | 
|  | if (jsonVal.is_discarded()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; | 
|  | messages::operationFailed(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; | 
|  |  | 
|  | addPrefixes(jsonVal, prefix); | 
|  |  | 
|  | if constexpr (enablePlatform9) | 
|  | { | 
|  | // Hardcode relation to aggregating BMC | 
|  | if ((jsonVal.contains("@odata.id")) && | 
|  | (jsonVal["@odata.id"].is_string())) | 
|  | { | 
|  | std::string targetURI(jsonVal["@odata.id"]); | 
|  | std::string aggRootURI(prefix); | 
|  | aggRootURI = "/redfish/v1/Chassis/" + aggRootURI + "_" + | 
|  | std::string(platform9Chassis5); | 
|  | if (targetURI == aggRootURI) | 
|  | { | 
|  | jsonVal["Links"]["ContainedBy"] = { | 
|  | {"@odata.id", "/redfish/v1/Chassis/" + | 
|  | std::string(platform9Chassis4)}}; | 
|  | } | 
|  | } | 
|  | // End hardcoded workaround | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; | 
|  |  | 
|  | asyncResp->res.result(resp.result()); | 
|  | asyncResp->res.jsonValue = std::move(jsonVal); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Finished writing asyncResp"; | 
|  | } | 
|  | else | 
|  | { | 
|  | // We allow any Content-Type that is not "application/json" now | 
|  | asyncResp->res.result(resp.result()); | 
|  | asyncResp->res.write(resp.body()); | 
|  | } | 
|  | addAggregatedHeaders(asyncResp->res, resp, prefix); | 
|  | } | 
|  |  | 
|  | // Processes the collection response returned by a satellite BMC and merges | 
|  | // its "@odata.id" values | 
|  | static void processCollectionResponse( | 
|  | const std::string& prefix, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | crow::Response& resp) | 
|  | { | 
|  | // 429 and 502 mean we didn't actually send the request so don't | 
|  | // overwrite the response headers in that case | 
|  | if ((resp.result() == boost::beast::http::status::too_many_requests) || | 
|  | (resp.result() == boost::beast::http::status::bad_gateway)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (resp.resultInt() != 200) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Collection resource does not exist in satellite BMC \"" | 
|  | << prefix << "\""; | 
|  | // Return the error if we haven't had any successes | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | asyncResp->res.result(resp.result()); | 
|  | asyncResp->res.write(resp.body()); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The resp will not have a json component | 
|  | // We need to create a json from resp's stringResponse | 
|  | std::string_view contentType = resp.getHeaderValue("Content-Type"); | 
|  | if (boost::iequals(contentType, "application/json") || | 
|  | boost::iequals(contentType, "application/json; charset=utf-8")) | 
|  | { | 
|  | nlohmann::json jsonVal = | 
|  | nlohmann::json::parse(resp.body(), nullptr, false); | 
|  | if (jsonVal.is_discarded()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; | 
|  |  | 
|  | // Notify the user if doing so won't overwrite a valid response | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | messages::operationFailed(asyncResp->res); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; | 
|  |  | 
|  | // Now we need to add the prefix to the URIs contained in the | 
|  | // response. | 
|  | addPrefixes(jsonVal, prefix); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Added prefix to parsed satellite response"; | 
|  |  | 
|  | // If this resource collection does not exist on the aggregating bmc | 
|  | // and has not already been added from processing the response from | 
|  | // a different satellite then we need to completely overwrite | 
|  | // asyncResp | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Collection does not exist, overwriting asyncResp"; | 
|  | asyncResp->res.result(resp.result()); | 
|  | asyncResp->res.jsonValue = std::move(jsonVal); | 
|  | asyncResp->res.addHeader("Content-Type", "application/json"); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Finished overwriting asyncResp"; | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Adding aggregated resources from \"" | 
|  | << prefix << "\" to collection"; | 
|  |  | 
|  | // TODO: This is a potential race condition with multiple | 
|  | // satellites and the aggregating bmc attempting to write to | 
|  | // update this array.  May need to cascade calls to the next | 
|  | // satellite at the end of this function. | 
|  | // This is presumably not a concern when there is only a single | 
|  | // satellite since the aggregating bmc should have completed | 
|  | // before the response is received from the satellite. | 
|  |  | 
|  | auto& members = asyncResp->res.jsonValue["Members"]; | 
|  | if (!members.is_array()) | 
|  | { | 
|  | // Satellite response is being processed before top level | 
|  | // collection request has been locally handled so we need | 
|  | // to create Members array | 
|  | BMCWEB_LOG_DEBUG << "Created new Members array"; | 
|  | members = nlohmann::json::array(); | 
|  | } | 
|  | auto& satMembers = jsonVal["Members"]; | 
|  | for (auto& satMem : satMembers) | 
|  | { | 
|  | members.push_back(std::move(satMem)); | 
|  | } | 
|  | asyncResp->res.jsonValue["Members@odata.count"] = | 
|  | members.size(); | 
|  |  | 
|  | // TODO: Do we need to sort() after updating the array? | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix | 
|  | << "\""; | 
|  | // We received a response that was not a json. | 
|  | // Notify the user only if we did not receive any valid responses | 
|  | // and if the resource collection does not already exist on the | 
|  | // aggregating BMC | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | messages::operationFailed(asyncResp->res); | 
|  | } | 
|  | } | 
|  | } // End processCollectionResponse() | 
|  |  | 
|  | // Processes the response returned by a satellite BMC and merges any | 
|  | // properties whose "@odata.id" value is the URI or either a top level | 
|  | // collection or is uptree from a top level collection | 
|  | static void processContainsSubordinateResponse( | 
|  | const std::string& prefix, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | crow::Response& resp) | 
|  | { | 
|  | // 429 and 502 mean we didn't actually send the request so don't | 
|  | // overwrite the response headers in that case | 
|  | if ((resp.result() == boost::beast::http::status::too_many_requests) || | 
|  | (resp.result() == boost::beast::http::status::bad_gateway)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (resp.resultInt() != 200) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Resource uptree from Collection does not exist in " | 
|  | << "satellite BMC \"" << prefix << "\""; | 
|  | // Return the error if we haven't had any successes | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | asyncResp->res.result(resp.result()); | 
|  | asyncResp->res.write(resp.body()); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The resp will not have a json component | 
|  | // We need to create a json from resp's stringResponse | 
|  | std::string_view contentType = resp.getHeaderValue("Content-Type"); | 
|  | if (boost::iequals(contentType, "application/json") || | 
|  | boost::iequals(contentType, "application/json; charset=utf-8")) | 
|  | { | 
|  | bool addedLinks = false; | 
|  | nlohmann::json jsonVal = | 
|  | nlohmann::json::parse(resp.body(), nullptr, false); | 
|  | if (jsonVal.is_discarded()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Error parsing satellite response as JSON"; | 
|  |  | 
|  | // Notify the user if doing so won't overwrite a valid response | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | messages::operationFailed(asyncResp->res); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Successfully parsed satellite response"; | 
|  |  | 
|  | // Parse response and add properties missing from the AsyncResp | 
|  | // Valid properties will be of the form <property>.@odata.id and | 
|  | // @odata.id is a <URI>.  In other words, the json should contain | 
|  | // multiple properties such that | 
|  | // {"<property>":{"@odata.id": "<URI>"}} | 
|  | nlohmann::json::object_t* object = | 
|  | jsonVal.get_ptr<nlohmann::json::object_t*>(); | 
|  | if (object == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Parsed JSON was not an object?"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (std::pair<const std::string, nlohmann::json>& prop : *object) | 
|  | { | 
|  | if (!prop.second.contains("@odata.id")) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string* strValue = | 
|  | prop.second["@odata.id"].get_ptr<std::string*>(); | 
|  | if (strValue == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_CRITICAL << "Field wasn't a string????"; | 
|  | continue; | 
|  | } | 
|  | if (!searchCollectionsArray(*strValue, SearchType::CollOrCon)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | addedLinks = true; | 
|  | if (!asyncResp->res.jsonValue.contains(prop.first)) | 
|  | { | 
|  | // Only add the property if it did not already exist | 
|  | BMCWEB_LOG_DEBUG << "Adding link for " << *strValue | 
|  | << " from BMC " << prefix; | 
|  | asyncResp->res.jsonValue[prop.first]["@odata.id"] = | 
|  | *strValue; | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | // If we added links to a previously unsuccessful (non-200) response | 
|  | // then we need to make sure the response contains the bare minimum | 
|  | // amount of additional information that we'd expect to have been | 
|  | // populated. | 
|  | if (addedLinks && (asyncResp->res.resultInt() != 200)) | 
|  | { | 
|  | // This resource didn't locally exist or an error | 
|  | // occurred while generating the response.  Remove any | 
|  | // error messages and update the error code. | 
|  | asyncResp->res.jsonValue.erase( | 
|  | asyncResp->res.jsonValue.find("error")); | 
|  | asyncResp->res.result(resp.result()); | 
|  |  | 
|  | const auto& it1 = object->find("@odata.id"); | 
|  | if (it1 != object->end()) | 
|  | { | 
|  | asyncResp->res.jsonValue["@odata.id"] = (it1->second); | 
|  | } | 
|  | const auto& it2 = object->find("@odata.type"); | 
|  | if (it2 != object->end()) | 
|  | { | 
|  | asyncResp->res.jsonValue["@odata.type"] = (it2->second); | 
|  | } | 
|  | const auto& it3 = object->find("Id"); | 
|  | if (it3 != object->end()) | 
|  | { | 
|  | asyncResp->res.jsonValue["Id"] = (it3->second); | 
|  | } | 
|  | const auto& it4 = object->find("Name"); | 
|  | if (it4 != object->end()) | 
|  | { | 
|  | asyncResp->res.jsonValue["Name"] = (it4->second); | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Received unparsable response from \"" << prefix | 
|  | << "\""; | 
|  | // We received as response that was not a json | 
|  | // Notify the user only if we did not receive any valid responses, | 
|  | // and if the resource does not already exist on the aggregating BMC | 
|  | if (asyncResp->res.resultInt() != 200) | 
|  | { | 
|  | messages::operationFailed(asyncResp->res); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Entry point to Redfish Aggregation | 
|  | // Returns Result stating whether or not we still need to locally handle the | 
|  | // request | 
|  | static Result | 
|  | beginAggregation(const crow::Request& thisReq, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
|  | { | 
|  | using crow::utility::OrMorePaths; | 
|  | using crow::utility::readUrlSegments; | 
|  | const boost::urls::url_view url = thisReq.url(); | 
|  |  | 
|  | // We don't need to aggregate JsonSchemas due to potential issues such | 
|  | // as version mismatches between aggregator and satellite BMCs.  For | 
|  | // now assume that the aggregator has all the schemas and versions that | 
|  | // the aggregated server has. | 
|  | if (crow::utility::readUrlSegments(url, "redfish", "v1", "JsonSchemas", | 
|  | crow::utility::OrMorePaths())) | 
|  | { | 
|  | return Result::LocalHandle; | 
|  | } | 
|  |  | 
|  | // The first two segments should be "/redfish/v1".  We need to check | 
|  | // that before we can search topCollections | 
|  | if (!crow::utility::readUrlSegments(url, "redfish", "v1", | 
|  | crow::utility::OrMorePaths())) | 
|  | { | 
|  | return Result::LocalHandle; | 
|  | } | 
|  |  | 
|  | // Parse the URI to see if it begins with a known top level collection | 
|  | // such as: | 
|  | // /redfish/v1/Chassis | 
|  | // /redfish/v1/UpdateService/FirmwareInventory | 
|  | const boost::urls::segments_view urlSegments = url.segments(); | 
|  | boost::urls::url currentUrl("/"); | 
|  | boost::urls::segments_view::iterator it = urlSegments.begin(); | 
|  | const boost::urls::segments_view::const_iterator end = | 
|  | urlSegments.end(); | 
|  |  | 
|  | // Skip past the leading "/redfish/v1" | 
|  | it++; | 
|  | it++; | 
|  | for (; it != end; it++) | 
|  | { | 
|  | const std::string& collectionItem = *it; | 
|  | if (std::binary_search(topCollections.begin(), topCollections.end(), | 
|  | currentUrl.buffer())) | 
|  | { | 
|  | // We've matched a resource collection so this current segment | 
|  | // might contain an aggregation prefix | 
|  | // TODO: This needs to be rethought when we can support multiple | 
|  | // satellites due to | 
|  | // /redfish/v1/AggregationService/AggregationSources/5B247A | 
|  | // being a local resource describing the satellite | 
|  | if (collectionItem.starts_with("5B247A_") || | 
|  | isMemberStartsWithKnownPrefix(collectionItem)) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Need to forward a request"; | 
|  |  | 
|  | // Extract the prefix from the request's URI, retrieve the | 
|  | // associated satellite config information, and then forward | 
|  | // the request to that satellite. | 
|  | startAggregation(AggregationType::Resource, thisReq, | 
|  | asyncResp); | 
|  | return Result::NoLocalHandle; | 
|  | } | 
|  |  | 
|  | // Handle collection URI with a trailing backslash | 
|  | // e.g. /redfish/v1/Chassis/ | 
|  | it++; | 
|  | if ((it == end) && collectionItem.empty()) | 
|  | { | 
|  | startAggregation(AggregationType::Collection, thisReq, | 
|  | asyncResp); | 
|  | } | 
|  |  | 
|  | // We didn't recognize the prefix or it's a collection with a | 
|  | // trailing "/".  In both cases we still want to locally handle | 
|  | // the request | 
|  | return Result::LocalHandle; | 
|  | } | 
|  |  | 
|  | currentUrl.segments().push_back(collectionItem); | 
|  | } | 
|  |  | 
|  | // If we made it here then currentUrl could contain a top level | 
|  | // collection URI without a trailing "/", e.g. /redfish/v1/Chassis | 
|  | if (std::binary_search(topCollections.begin(), topCollections.end(), | 
|  | currentUrl.buffer())) | 
|  | { | 
|  | startAggregation(AggregationType::Collection, thisReq, asyncResp); | 
|  | return Result::LocalHandle; | 
|  | } | 
|  |  | 
|  | // If nothing else then the request could be for a resource which has a | 
|  | // top level collection as a subordinate | 
|  | if (searchCollectionsArray(url.buffer(), | 
|  | SearchType::ContainsSubordinate)) | 
|  | { | 
|  | startAggregation(AggregationType::ContainsSubordinate, thisReq, | 
|  | asyncResp); | 
|  | return Result::LocalHandle; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Aggregation not required"; | 
|  | return Result::LocalHandle; | 
|  | } | 
|  | }; | 
|  |  | 
|  | } // namespace redfish |