| #pragma once |
| |
| #include "app.hpp" |
| #include "async_resp.hpp" |
| #include "dbus_utility.hpp" |
| #include "error_messages.hpp" |
| #include "managed_store.hpp" |
| #include "query.hpp" |
| #include "utils/chassis_utils.hpp" |
| #include "utils/collection.hpp" |
| #include "utils/dbus_utils.hpp" |
| #include "utils/hex_utils.hpp" |
| #include "utils/json_utils.hpp" |
| |
| #include <boost/system/error_code.hpp> |
| #include <nlohmann/json.hpp> |
| |
| #include <array> |
| #include <regex> |
| #include <string_view> |
| #include <vector> |
| |
| #ifdef UNIT_TEST_BUILD |
| #include "test/g3/mock_managed_store.hpp" // NOLINT |
| #endif |
| |
| namespace crow |
| { |
| namespace google_api |
| { |
| |
| inline void |
| handleGoogleV1Get(App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#GoogleServiceRoot.v1_0_0.GoogleServiceRoot"; |
| asyncResp->res.jsonValue["@odata.id"] = "/google/v1"; |
| asyncResp->res.jsonValue["Id"] = "Google Rest RootService"; |
| asyncResp->res.jsonValue["Name"] = "Google Service Root"; |
| asyncResp->res.jsonValue["Version"] = "1.0.0"; |
| asyncResp->res.jsonValue["RootOfTrustCollection"]["@odata.id"] = |
| "/google/v1/RootOfTrustCollection"; |
| asyncResp->res.jsonValue["NVMe"]["@odata.id"] = "/google/v1/NVMe"; |
| } |
| |
| inline void handleRootOfTrustCollectionGet( |
| App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| asyncResp->res.jsonValue["@odata.id"] = "/google/v1/RootOfTrustCollection"; |
| asyncResp->res.jsonValue["@odata.type"] = |
| "#RootOfTrustCollection.RootOfTrustCollection"; |
| const std::array<std::string_view, 1> interfaces{ |
| "xyz.openbmc_project.Control.Hoth"}; |
| redfish::collection_util::getCollectionMembers( |
| asyncResp, boost::urls::url("/google/v1/RootOfTrustCollection"), |
| interfaces, "/xyz/openbmc_project"); |
| } |
| |
| // Helper struct to identify a resolved D-Bus object interface |
| struct ResolvedEntity |
| { |
| std::string id; |
| std::string service; |
| std::string object; |
| std::string interface; |
| }; |
| |
| using ResolvedEntityHandler = std::function<void( |
| const std::string&, const std::shared_ptr<bmcweb::AsyncResp>&, |
| const ResolvedEntity&)>; |
| |
| inline void hothGetSubtreeCallback( |
| const std::string& command, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& rotId, const ResolvedEntityHandler& entityHandler, |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) |
| { |
| if (ec) |
| { |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| for (const auto& [path, services] : subtree) |
| { |
| sdbusplus::message::object_path objPath(path); |
| if (objPath.filename() != rotId || services.empty()) |
| { |
| continue; |
| } |
| |
| ResolvedEntity resolvedEntity = { |
| .id = rotId, |
| .service = services[0].first, |
| .object = path, |
| .interface = "xyz.openbmc_project.Control.Hoth"}; |
| entityHandler(command, asyncResp, resolvedEntity); |
| return; |
| } |
| |
| // Couldn't find an object with that name. return an error |
| redfish::messages::resourceNotFound(asyncResp->res, "RootOfTrust", rotId); |
| } |
| |
| inline void resolveRoT(const std::string& command, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& rotId, |
| ResolvedEntityHandler&& entityHandler) |
| { |
| constexpr std::array<std::string_view, 1> hothIfaces = { |
| "xyz.openbmc_project.Control.Hoth"}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTree( |
| "/xyz/openbmc_project", 0, hothIfaces, requestContext, |
| [command, asyncResp, rotId, |
| entityHandler{std::forward<ResolvedEntityHandler>(entityHandler)}]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { |
| hothGetSubtreeCallback(command, asyncResp, rotId, entityHandler, ec, |
| subtree); |
| }); |
| } |
| |
| inline void populateRootOfTrustState( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const dbus::utility::DBusPropertiesMap& properties) |
| { |
| const uint64_t* authRecordCapabilities = nullptr; |
| const uint32_t* bootloaderUpdateFailureCode = nullptr; |
| const uint32_t* firmwareUpdateFailedMinor = nullptr; |
| const uint32_t* firmwareUpdateFailureCode = nullptr; |
| const uint32_t* payloadConfirmFailureCode = nullptr; |
| const uint32_t* payloadUpdateFailureCode = nullptr; |
| const uint32_t* resetFlags = nullptr; |
| const uint32_t* roInfoStrikes = nullptr; |
| const uint32_t* rwInfoStrikes = nullptr; |
| const uint64_t* upTime = nullptr; |
| const bool* hasPersistentPanicInfo = nullptr; |
| const bool* hasValidAuthRecord = nullptr; |
| |
| const bool success = sdbusplus::unpackPropertiesNoThrow( |
| redfish::dbus_utils::UnpackErrorPrinter(), properties, |
| "BootloaderUpdateFailureCode", bootloaderUpdateFailureCode, |
| "FirmwareUpdateFailedMinor", firmwareUpdateFailedMinor, |
| "FirmwareUpdateFailureCode", firmwareUpdateFailureCode, |
| "PayloadConfirmFailureCode", payloadConfirmFailureCode, |
| "PayloadUpdateFailureCode", payloadUpdateFailureCode, "ResetFlags", |
| resetFlags, "RoInfoStrikes", roInfoStrikes, "RwInfoStrikes", |
| rwInfoStrikes, "UpTime", upTime, "HasPersistentPanicInfo", |
| hasPersistentPanicInfo, "HasValidAuthRecord", hasValidAuthRecord, |
| "AuthRecordCapabilities", authRecordCapabilities); |
| |
| if (!success) |
| { |
| BMCWEB_LOG_ERROR << "Failed to populate RootOfTrustState"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| if (bootloaderUpdateFailureCode != nullptr) |
| { |
| asyncResp->res |
| .jsonValue["Status"]["Oem"]["BootloaderUpdateFailureCode"] = |
| *bootloaderUpdateFailureCode; |
| } |
| if (firmwareUpdateFailedMinor != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["FirmwareUpdateFailedMinor"] = |
| *firmwareUpdateFailedMinor; |
| } |
| if (firmwareUpdateFailureCode != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["FirmwareUpdateFailureCode"] = |
| *firmwareUpdateFailureCode; |
| } |
| if (resetFlags != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["ResetFlags"] = *resetFlags; |
| } |
| if (roInfoStrikes != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["RoInfoStrikes"] = |
| *roInfoStrikes; |
| } |
| if (rwInfoStrikes != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["RwInfoStrikes"] = |
| *rwInfoStrikes; |
| } |
| if (upTime != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["UpTime"] = *upTime; |
| } |
| if (hasPersistentPanicInfo != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["HasPersistentPanicInfo"] = |
| *hasPersistentPanicInfo; |
| } |
| |
| if (payloadConfirmFailureCode != nullptr) |
| { |
| asyncResp->res |
| .jsonValue["Status"]["Oem"]["Payload"]["ConfirmFailureCode"] = |
| *payloadConfirmFailureCode; |
| } |
| if (payloadUpdateFailureCode != nullptr) |
| { |
| asyncResp->res |
| .jsonValue["Status"]["Oem"]["Payload"]["UpdateFailureCode"] = |
| *payloadUpdateFailureCode; |
| } |
| if (hasValidAuthRecord != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["Authorization"]["Valid"] = |
| *hasValidAuthRecord; |
| } |
| if (authRecordCapabilities != nullptr) |
| { |
| asyncResp->res.jsonValue["Status"]["Oem"]["Authorization"]["Capabilities"] = |
| *authRecordCapabilities; |
| } |
| } |
| |
| inline void populateRootOfTrustEntity( |
| const std::string& /*unused*/, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const ResolvedEntity& resolvedEntity) |
| { |
| asyncResp->res.jsonValue["@odata.type"] = "#RootOfTrust.v1_0_0.RootOfTrust"; |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( |
| "google", "v1", "RootOfTrustCollection", resolvedEntity.id); |
| |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; |
| asyncResp->res.jsonValue["Id"] = resolvedEntity.id; |
| // Need to fix this later to a stabler property. |
| asyncResp->res.jsonValue["Name"] = resolvedEntity.id; |
| asyncResp->res.jsonValue["Description"] = "Google Root Of Trust"; |
| asyncResp->res.jsonValue["Actions"]["#RootOfTrust.SendCommand"]["target"] = |
| "/google/v1/RootOfTrustCollection/" + resolvedEntity.id + |
| "/Actions/RootOfTrust.SendCommand"; |
| |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = |
| resolvedEntity.id; |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] = |
| "Embedded"; |
| |
| // Only link to RW firmware inventory. |
| // |
| // For USB-Hoth, FirmwareInventory URI is "hoth_${hothId}_rw". |
| // For non-USB Hoth, FirmwareInventory URI is "hoth_rw". |
| std::string hothFirmware = resolvedEntity.id == "Hoth" |
| ? "hoth_rw" |
| : "hoth_" + resolvedEntity.id + "_rw"; |
| asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"]["@odata.id"] = |
| "/redfish/v1/UpdateService/FirmwareInventory/" + hothFirmware; |
| |
| // Temporary hack to get the Hoth firmware family as "Model", by stripping |
| // the USB suffix from hothId. |
| // |
| // Non-USB Hoth has resolvedEntity.id="Hoth" as model name. |
| // |
| // Once we have SoftwareInventory 1.9.0 supported everywhere, the firmware |
| // family should reside there and the model here should be the chip's part |
| // number. |
| static std::regex hothIdSuffixRe("_[0-9_]+$"); |
| asyncResp->res.jsonValue["Model"] = |
| std::regex_replace(resolvedEntity.id, hothIdSuffixRe, ""); |
| |
| // Get Hoth properties from Dbus. |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| managedStore::GetManagedObjectStore()->getAllProperties( |
| resolvedEntity.service, resolvedEntity.object, |
| "xyz.openbmc_project.Control.Hoth.State", context, |
| [asyncResp](const boost::system::error_code ec2, |
| const dbus::utility::DBusPropertiesMap& properties) { |
| if (ec2) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec2; |
| return; |
| } |
| populateRootOfTrustState(asyncResp, properties); |
| }); |
| |
| // Get Chassis Link for Devpath from DBus |
| managedStore::ManagedObjectStoreContext context2(asyncResp); |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( |
| sdbusplus::message::object_path(resolvedEntity.object) / "contained_by", |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, |
| redfish::chassis_utils::chassisInterfaces, context2, |
| [asyncResp]( |
| const boost::system::error_code& ec, |
| const dbus::utility::MapperGetSubTreePathsResponse& chassisPaths) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec; |
| return; |
| } |
| |
| if (!chassisPaths.empty()) |
| { |
| asyncResp->res.jsonValue["RelatedItem"] = nlohmann::json::array(); |
| } |
| for (const auto& str : chassisPaths) |
| { |
| std::filesystem::path chassisPath(str); |
| nlohmann::json::object_t relatedChassis; |
| relatedChassis["@odata.id"] = crow::utility::urlFromPieces( |
| "redfish", "v1", "Chassis", chassisPath.filename().c_str()); |
| asyncResp->res.jsonValue["RelatedItem"].emplace_back( |
| relatedChassis); |
| |
| // Get PartLocationContext from Chassis LocationCode interface |
| managedStore::ManagedObjectStoreContext context(asyncResp); |
| redfish::dbus_utils::getProperty<std::string>( |
| "xyz.openbmc_project.EntityManager", chassisPath.c_str(), |
| "xyz.openbmc_project.Inventory.Decorator.LocationCode", |
| "LocationCode", context, |
| [asyncResp](const boost::system::error_code ec, |
| const std::string& locationCode) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec; |
| return; |
| } |
| asyncResp->res.jsonValue["Location"]["PartLocationContext"] = |
| locationCode; |
| }); |
| } |
| } |
| |
| ); |
| } |
| |
| inline void |
| handleRootOfTrustGet(App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& param) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| std::string emptyCommand; |
| resolveRoT(emptyCommand, asyncResp, param, populateRootOfTrustEntity); |
| } |
| |
| inline void |
| invocationCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const boost::system::error_code& ec, |
| const std::vector<uint8_t>& responseBytes) |
| { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "RootOfTrust.Actions.SendCommand failed: " |
| << ec.message(); |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| asyncResp->res.jsonValue["CommandResponse"] = |
| bytesToHexString(responseBytes); |
| } |
| |
| inline void |
| invokeRoTCommand(const std::string& command, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const ResolvedEntity& resolvedEntity) |
| { |
| std::vector<uint8_t> bytes = hexStringToBytes(command); |
| if (bytes.empty()) |
| { |
| BMCWEB_LOG_DEBUG << "Invalid command: " << command; |
| redfish::messages::actionParameterValueTypeError(command, "Command", |
| "SendCommand"); |
| return; |
| } |
| |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp{asyncResp}](const boost::system::error_code& ec, |
| const std::vector<uint8_t>& responseBytes) { |
| invocationCallback(asyncResp, ec, responseBytes); |
| }, |
| resolvedEntity.service, resolvedEntity.object, resolvedEntity.interface, |
| "SendHostCommand", bytes); |
| } |
| |
| inline void handleRoTSendCommandPost( |
| App& app, const crow::Request& request, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& rotId) |
| { |
| if (!redfish::setUpRedfishRoute(app, request, asyncResp)) |
| { |
| return; |
| } |
| std::string command; |
| if (!redfish::json_util::readJsonAction(request, asyncResp->res, "Command", |
| command)) |
| { |
| BMCWEB_LOG_DEBUG << "Missing property Command."; |
| redfish::messages::actionParameterMissing(asyncResp->res, "SendCommand", |
| "Command"); |
| return; |
| } |
| |
| resolveRoT(command, asyncResp, rotId, invokeRoTCommand); |
| } |
| |
| inline void requestRoutes(App& app) |
| { |
| BMCWEB_ROUTE(app, "/google/v1/") |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleGoogleV1Get, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/") |
| .privileges({{"ConfigureManager"}}) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleRootOfTrustCollectionGet, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/<str>/") |
| .privileges({{"ConfigureManager"}}) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleRootOfTrustGet, std::ref(app))); |
| |
| BMCWEB_ROUTE( |
| app, |
| "/google/v1/RootOfTrustCollection/<str>/Actions/RootOfTrust.SendCommand/") |
| .privileges({{"ConfigureManager"}}) |
| .methods(boost::beast::http::verb::post)( |
| std::bind_front(handleRoTSendCommandPost, std::ref(app))); |
| } |
| |
| } // namespace google_api |
| } // namespace crow |