blob: 0bfa2390c43271ff20fef91f6780fc2b795d17f3 [file] [log] [blame]
#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