Rearrange features
The backends are different things compared to generic code. Today,
these are all included in the /include folder, but it's not very clear
what options control which backends, or how things map together. This
also means that we can't separate ownership between the various
companies.
This commit is a proposal to try to create a features folder,
separated by the code for the various backends, to make interacting
with this easier. It takes the form
features/<option name>/files.hpp
features/<option name>/files_test.hpp
Note, redfish-core was already at top level, and contains lots of code,
so to prevent lots of conflicts, it's simply symlinked into that folder
to make clear that it is a backend, but not to move the implementation
and cause code conflicts.
Tested: Unit tests pass. Code compiles.
Change-Id: Idcc80ffcfd99c876734ee41d53f894ca5583fed5
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/features/google/google_service_root.hpp b/features/google/google_service_root.hpp
new file mode 100644
index 0000000..3d21af7
--- /dev/null
+++ b/features/google/google_service_root.hpp
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "dbus_utility.hpp"
+#include "error_messages.hpp"
+#include "http_request.hpp"
+#include "logging.hpp"
+#include "utils/collection.hpp"
+#include "utils/hex_utils.hpp"
+#include "utils/json_utils.hpp"
+
+#include <boost/beast/http/verb.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/url/format.hpp>
+#include <boost/url/url.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <array>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace crow
+{
+namespace google_api
+{
+
+inline void handleGoogleV1Get(
+ const crow::Request& /*req*/,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+{
+ 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";
+}
+
+inline void handleRootOfTrustCollectionGet(
+ const crow::Request& /*req*/,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+{
+ 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"};
+ dbus::utility::getSubTree(
+ "/xyz/openbmc_project", 0, hothIfaces,
+ [command, asyncResp, rotId, entityHandler{std::move(entityHandler)}](
+ const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse& subtree) {
+ hothGetSubtreeCallback(command, asyncResp, rotId, entityHandler, ec,
+ subtree);
+ });
+}
+
+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"] = boost::urls::format(
+ "/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";
+}
+
+inline void handleRootOfTrustGet(
+ const crow::Request& /*req*/,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& param)
+{
+ 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;
+ }
+
+ dbus::utility::async_method_call(
+ asyncResp,
+ [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(
+ const crow::Request& request,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& rotId)
+{
+ 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)(handleGoogleV1Get);
+
+ BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection")
+ .privileges({{"ConfigureManager"}})
+ .methods(boost::beast::http::verb::get)(handleRootOfTrustCollectionGet);
+
+ BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/<str>")
+ .privileges({{"ConfigureManager"}})
+ .methods(boost::beast::http::verb::get)(handleRootOfTrustGet);
+
+ BMCWEB_ROUTE(
+ app,
+ "/google/v1/RootOfTrustCollection/<str>/Actions/RootOfTrust.SendCommand")
+ .privileges({{"ConfigureManager"}})
+ .methods(boost::beast::http::verb::post)(handleRoTSendCommandPost);
+}
+
+} // namespace google_api
+} // namespace crow
diff --git a/features/google/google_service_root_test.cpp b/features/google/google_service_root_test.cpp
new file mode 100644
index 0000000..c104494
--- /dev/null
+++ b/features/google/google_service_root_test.cpp
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#include "async_resp.hpp"
+#include "google_service_root.hpp"
+#include "http_request.hpp"
+#include "http_response.hpp"
+
+#include <boost/beast/http/verb.hpp>
+#include <nlohmann/json.hpp>
+
+#include <memory>
+#include <system_error>
+
+#include <gtest/gtest.h>
+
+namespace crow::google_api
+{
+namespace
+{
+
+void validateServiceRootGet(crow::Response& res)
+{
+ nlohmann::json& json = res.jsonValue;
+ EXPECT_EQ(json["@odata.id"], "/google/v1");
+ EXPECT_EQ(json["@odata.type"],
+ "#GoogleServiceRoot.v1_0_0.GoogleServiceRoot");
+ EXPECT_EQ(json["@odata.id"], "/google/v1");
+ EXPECT_EQ(json["Id"], "Google Rest RootService");
+ EXPECT_EQ(json["Name"], "Google Service Root");
+ EXPECT_EQ(json["Version"], "1.0.0");
+ EXPECT_EQ(json["RootOfTrustCollection"]["@odata.id"],
+ "/google/v1/RootOfTrustCollection");
+}
+
+TEST(HandleGoogleV1Get, OnSuccess)
+{
+ std::error_code ec;
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+
+ asyncResp->res.setCompleteRequestHandler(validateServiceRootGet);
+
+ crow::Request dummyRequest{{boost::beast::http::verb::get, "", 11}, ec};
+ handleGoogleV1Get(dummyRequest, asyncResp);
+}
+
+} // namespace
+} // namespace crow::google_api
diff --git a/features/google/meson.build b/features/google/meson.build
new file mode 100644
index 0000000..5df3400
--- /dev/null
+++ b/features/google/meson.build
@@ -0,0 +1,2 @@
+incdir += include_directories('.')
+test_sources += files('google_service_root_test.cpp')