|  | #pragma once | 
|  |  | 
|  | #include "bmcweb_config.h" | 
|  |  | 
|  | #include "dbus_utility.hpp" | 
|  | #include "managed_store_types.hpp" | 
|  | #include "query.hpp" | 
|  | #include "utils/dbus_utils.hpp" | 
|  |  | 
|  | #include <app.hpp> | 
|  | #include <async_resp.hpp> | 
|  | #include <dbus_utility.hpp> | 
|  | #include <error_messages.hpp> | 
|  | #include <nlohmann/json.hpp> | 
|  | #include <sdbusplus/asio/property.hpp> | 
|  | #include <sdbusplus/message/native_types.hpp> | 
|  | #include <utils/collection.hpp> | 
|  | #include <utils/json_utils.hpp> | 
|  | #include <utils/storage_utils.hpp> | 
|  |  | 
|  | #include <functional> | 
|  | #include <vector> | 
|  |  | 
|  | #include "managed_store.hpp" | 
|  |  | 
|  | #ifdef UNIT_TEST_BUILD | 
|  | #include "test/g3/mock_managed_store.hpp" // NOLINT | 
|  | #endif | 
|  |  | 
|  | namespace crow | 
|  | { | 
|  | namespace google_api | 
|  | { | 
|  |  | 
|  | using NVMeControllerHandlerCb = std::function<void( | 
|  | const boost::system::error_code ec, const std::string& storagePath, | 
|  | const std::vector<std::string>& controllerList)>; | 
|  |  | 
|  | using boost::asio::posix::stream_descriptor; | 
|  |  | 
|  | static std::shared_ptr<boost::asio::io_context> fdIOContext; // NOLINT | 
|  |  | 
|  | static void | 
|  | fetchFile(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::shared_ptr<stream_descriptor>& fileConn, | 
|  | const std::shared_ptr<std::array<char, 1024>>& readBuffer = | 
|  | std::make_shared<std::array<char, 1024>>(), | 
|  | const std::shared_ptr<std::string>& output = | 
|  | std::make_shared<std::string>()) | 
|  | { | 
|  | if (!fileConn) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Fetch File Context is not setup properly"; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | fileConn->async_read_some(boost::asio::buffer(*readBuffer), | 
|  | [asyncResp, fileConn, readBuffer, | 
|  | output](const boost::system::error_code& ec, | 
|  | const std::size_t& bytesTransferred) { | 
|  | if (ec == boost::asio::error::eof) | 
|  | { | 
|  | asyncResp->res.stringResponse->clear(); | 
|  | asyncResp->res.addHeader(boost::beast::http::field::content_type, | 
|  | "application/octet-stream"); | 
|  | asyncResp->res.body() = std::move(*output); | 
|  | fileConn->close(); | 
|  | return; | 
|  | } | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Failed to async_read_some" << ec.message(); | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | *output += std::string(readBuffer->begin(), | 
|  | readBuffer->begin() + bytesTransferred); | 
|  | fetchFile(asyncResp, fileConn, readBuffer, output); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void populateCustomNVMeControllerLink( | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& systemName, const std::string& storageId, | 
|  | const sdbusplus::message::object_path& controllerPath) | 
|  | { | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.Inventory.Item.StorageController"}; | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | 
|  | controllerPath / customNVMeAssociation, | 
|  | sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, | 
|  | interfaces, requestContext, | 
|  | [asyncResp, systemName, storageId, | 
|  | controllerPath](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreePathsResponse& | 
|  | customNVMeList) { | 
|  | if (ec && ec.value() != EBADR) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Failed to get " << customNVMe | 
|  | << "Controllers for controller " | 
|  | << controllerPath.str; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (customNVMeList.size() > 1) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "More than 1 " << customNVMe | 
|  | << "Controller for controller " | 
|  | << controllerPath.str; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (customNVMeList.empty()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const sdbusplus::message::object_path path{customNVMeList[0]}; | 
|  |  | 
|  | redfish::storage_utils::findStorageForController( | 
|  | asyncResp, storageId, path, | 
|  | [asyncResp, systemName, | 
|  | path](const std::optional<sdbusplus::message::object_path>& | 
|  | canonStoragePath) { | 
|  | if (canonStoragePath) | 
|  | { | 
|  | const std::string canonStorageId = canonStoragePath->filename(); | 
|  | const std::string id = path.filename(); | 
|  | asyncResp->res.jsonValue[std::string(customNVMe) + "Controller"] | 
|  | ["@odata.id"] = | 
|  | crow::utility::urlFromPieces( | 
|  | "redfish", "v1", "Systems", systemName, "Storage", | 
|  | canonStorageId, "Controllers", id); | 
|  | } | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void checkPrimary(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const sdbusplus::message::object_path& controllerPath) | 
|  | { | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.Inventory.Item.StorageController"}; | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | 
|  | controllerPath / "primary", | 
|  | sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, | 
|  | interfaces, requestContext, | 
|  | [asyncResp, | 
|  | controllerPath](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreePathsResponse& | 
|  | controllerList) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "getAssociatedSubTreePaths for path " | 
|  | << controllerPath.str << " failed with code " | 
|  | << ec; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | asyncResp->res.jsonValue["ControllerType"] = | 
|  | controllerList.empty() ? "Primary" : "Secondary"; | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void populateNVMeController( | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& systemName, const std::string& nvmeId, | 
|  | const std::string& controllerId, const std::string& controllerPath) | 
|  | { | 
|  | constexpr std::array<std::string_view, 1> interfaces{ | 
|  | "xyz.openbmc_project.NVMe.NVMeAdmin"}; | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | controllerPath, interfaces, context, | 
|  | [asyncResp, systemName, nvmeId, controllerId, | 
|  | controllerPath](const boost::system::error_code ec, | 
|  | const dbus::utility::MapperGetObject& objects) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "GetObject for path " << controllerPath | 
|  | << " failed with code " << ec; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (objects.size() != 1) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | BMCWEB_LOG_ERROR << "Service supporting " << controllerPath | 
|  | << " is not equal to 1"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | asyncResp->res.jsonValue["@odata.type"] = | 
|  | "#NVMeController.v1_0_0.NVMeController"; | 
|  | asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "google", "v1", "NVMe", nvmeId, "Controllers", controllerId); | 
|  | asyncResp->res.jsonValue["Name"] = "Google NVMe Controller"; | 
|  |  | 
|  | // NVMe actions | 
|  | asyncResp->res | 
|  | .jsonValue["Actions"]["#NVMeController.AdminNonDataCmd"]["target"] = | 
|  | crow::utility::urlFromPieces("google", "v1", "NVMe", nvmeId, | 
|  | "Controllers", controllerId, "Actions", | 
|  | "NVMeController.AdminNonDataCmd"); | 
|  | asyncResp->res | 
|  | .jsonValue["Actions"]["#NVMeController.GetLogPage"]["target"] = | 
|  | crow::utility::urlFromPieces("google", "v1", "NVMe", nvmeId, | 
|  | "Controllers", controllerId, "Actions", | 
|  | "NVMeController.GetLogPage"); | 
|  | asyncResp->res | 
|  | .jsonValue["Actions"]["#NVMeController.Identify"]["target"] = | 
|  | crow::utility::urlFromPieces("google", "v1", "NVMe", nvmeId, | 
|  | "Controllers", controllerId, "Actions", | 
|  | "NVMeController.Identify"); | 
|  | asyncResp->res.jsonValue["Links"]["NVMe"]["@odata.id"] = | 
|  | crow::utility::urlFromPieces("google", "v1", "NVMe", nvmeId); | 
|  | asyncResp->res.jsonValue["StorageController"]["@odata.id"] = | 
|  | crow::utility::urlFromPieces("redfish", "v1", "Systems", systemName, | 
|  | "Storage", nvmeId, "Controllers", | 
|  | controllerId); | 
|  | if (enableCustomNVMe) | 
|  | { | 
|  | asyncResp->res.jsonValue["Actions"]["#NVMeController." + | 
|  | std::string(customNVMe) + | 
|  | "Identify"]["target"] = | 
|  | crow::utility::urlFromPieces( | 
|  | "google", "v1", "NVMe", nvmeId, "Controllers", controllerId, | 
|  | "Actions", | 
|  | "NVMeController." + std::string(customNVMe) + "Identify"); | 
|  | populateCustomNVMeControllerLink(asyncResp, systemName, nvmeId, | 
|  | controllerPath); | 
|  | checkPrimary(asyncResp, controllerPath); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void | 
|  | setupNVMeController(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& systemName, | 
|  | const std::string& nvmeId, | 
|  | const std::string& controllerId, | 
|  | const std::vector<std::string>& controllerList) | 
|  | { | 
|  | if (controllerList.empty()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (const auto& controller : controllerList) | 
|  | { | 
|  | if (sdbusplus::message::object_path(controller).filename() != | 
|  | controllerId) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | populateNVMeController(asyncResp, systemName, nvmeId, controllerId, controller); | 
|  | return; | 
|  | } | 
|  |  | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | } | 
|  |  | 
|  | inline void populateNVMeControllerCollection( | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvme, const boost::system::error_code ec, | 
|  | const nlohmann::json::json_pointer& jsonPtr, | 
|  | const std::vector<std::string>& controllerList) | 
|  | { | 
|  | if (ec == boost::system::errc::io_error || controllerList.empty()) | 
|  | { | 
|  | auto controllers = nlohmann::json::object(); | 
|  | controllers["Name"] = "Google NVMe Controller Collection"; | 
|  | controllers["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "google", "v1", "NVMe", nvme, "Controllers"); | 
|  | controllers["Members"] = nlohmann::json::array(); | 
|  | controllers["Members@odata.count"] = 0; | 
|  | asyncResp->res.jsonValue[jsonPtr] = std::move(controllers); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "DBUS response error " << ec.value(); | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto controllers = nlohmann::json::object(); | 
|  | controllers["Name"] = "Google NVMe Controller Collection"; | 
|  | controllers["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "google", "v1", "NVMe", nvme, "Controllers"); | 
|  | nlohmann::json members = nlohmann::json::array(); | 
|  | for (const std::string& controller : controllerList) | 
|  | { | 
|  | std::string name = | 
|  | sdbusplus::message::object_path(controller).filename(); | 
|  | if (name.empty()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | nlohmann::json::object_t member; | 
|  | member["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "google", "v1", "NVMe", nvme, "Controllers", name); | 
|  | members.push_back(std::move(member)); | 
|  | } | 
|  | controllers["Members@odata.count"] = members.size(); | 
|  | controllers["Members"] = std::move(members); | 
|  | asyncResp->res.jsonValue[jsonPtr] = std::move(controllers); | 
|  | } | 
|  |  | 
|  | inline void | 
|  | processDriveResource(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::optional<std::string>& nvme, | 
|  | std::function<void(const std::string&)>&& cb) | 
|  | { | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  | constexpr std::array<std::string_view, 1> interfaces{ | 
|  | "xyz.openbmc_project.Inventory.Item.Drive"}; | 
|  | managedStore::GetManagedObjectStore()->getSubTree( | 
|  | "/xyz/openbmc_project/inventory", 0, interfaces, requestContext, | 
|  | [asyncResp, nvme, requestContext, | 
|  | cb{std::forward<std::function<void(const std::string&)>>(cb)}]( | 
|  | const boost::system::error_code ec, | 
|  | const dbus::utility::MapperGetSubTreeResponse& subtree) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_INFO << "DBUS error: no matched iface for Drive: " | 
|  | << ec.message(); | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (const auto& [path, services] : subtree) | 
|  | { | 
|  | if (nvme.has_value() && | 
|  | sdbusplus::message::object_path(path).filename() != *nvme) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | for (const auto& [service, interfaces] : services) | 
|  | { | 
|  | for (const std::string& interface : interfaces) | 
|  | { | 
|  | if (interface != "xyz.openbmc_project.Inventory.Item.Drive") | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | redfish::dbus_utils::getProperty<std::string>( | 
|  | service, path, interface, "Protocol", requestContext, | 
|  | [path{path}, cb](const boost::system::error_code ec2, | 
|  | const std::string& driveProtocol) { | 
|  | if (ec2) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (driveProtocol != | 
|  | "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | cb(path); | 
|  | }); | 
|  |  | 
|  | // If expecting valid NVMe, return after finding the first | 
|  | // NVMe. | 
|  | if (nvme.has_value()) | 
|  | { | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | // Expecting valid NVMe | 
|  | if (nvme.has_value()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeController( | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvme, NVMeControllerHandlerCb&& cb) | 
|  | { | 
|  | processDriveResource( | 
|  | asyncResp, nvme, | 
|  | [asyncResp, cb{std::forward<NVMeControllerHandlerCb>(cb)}]( | 
|  | const std::string& path) mutable { | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  | redfish::dbus_utils::getProperty<std::vector<std::string>>( | 
|  | "xyz.openbmc_project.ObjectMapper", path + "/storage_controller", | 
|  | "xyz.openbmc_project.Association", "endpoints", requestContext, | 
|  | [cb{std::move(cb)}, | 
|  | path](const boost::system::error_code ec, | 
|  | const std::vector<std::string>& controllerList) { | 
|  | cb(ec, path, controllerList); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void populateNVMe(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& path, const std::string& nvmeId) | 
|  | { | 
|  | asyncResp->res.jsonValue["@odata.type"] = "#NVMe.v1_0_0.NVMe"; | 
|  | asyncResp->res.jsonValue["@odata.id"] = | 
|  | crow::utility::urlFromPieces("google", "v1", "NVMe", nvmeId); | 
|  | asyncResp->res.jsonValue["Name"] = nvmeId; | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  | redfish::dbus_utils::getProperty<std::vector<std::string>>( | 
|  | "xyz.openbmc_project.ObjectMapper", path + "/storage", | 
|  | "xyz.openbmc_project.Association", "endpoints", requestContext, | 
|  | [asyncResp, nvmeId, | 
|  | requestContext](const boost::system::error_code ec, | 
|  | const std::vector<std::string>& storageList) { | 
|  | if (ec || storageList.empty()) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG | 
|  | << "Failed to find storage that is associated with " << nvmeId; | 
|  | return; | 
|  | } | 
|  | if (storageList.size() > 1) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << nvmeId | 
|  | << " is associated with mutliple storages"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | sdbusplus::message::object_path storagePath(storageList[0]); | 
|  | std::string storageId = storagePath.filename(); | 
|  | if (storageId.empty()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "filename() is empty in " << storagePath.str; | 
|  | return; | 
|  | } | 
|  | redfish::storage_utils::getSystemPathFromStorage( | 
|  | asyncResp, storagePath, | 
|  | [asyncResp, storageId](std::optional<std::string_view> systemPath) { | 
|  | std::string systemName = | 
|  | systemPath ? std::filesystem::path(*systemPath).filename() | 
|  | : "system"; | 
|  | asyncResp->res.jsonValue["Links"]["Storage"]["@odata.id"] = | 
|  | crow::utility::urlFromPieces("redfish", "v1", "Systems", | 
|  | systemName, "Storage", storageId); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.NVMe.NVMeAdmin"}; | 
|  | managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | 
|  | path + "/storage_controller", | 
|  | sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, | 
|  | interfaces, requestContext, | 
|  | [asyncResp, nvmeId](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreePathsResponse& | 
|  | controllerList) { | 
|  | populateNVMeControllerCollection(asyncResp, nvmeId, ec, | 
|  | "/Controllers"_json_pointer, controllerList); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void | 
|  | handleGoogleNvme(App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvme) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | processDriveResource(asyncResp, nvme, | 
|  | [asyncResp, nvme](const std::string& path) { | 
|  | populateNVMe(asyncResp, path, nvme); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeControllerCollection( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvme) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | processDriveResource(asyncResp, nvme, | 
|  | [asyncResp, nvme](const std::string& path) { | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.NVMe.NVMeAdmin"}; | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | 
|  | path + "/storage_controller", | 
|  | sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), | 
|  | 0, interfaces, requestContext, | 
|  | [asyncResp, | 
|  | nvme](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreePathsResponse& | 
|  | controllerList) { | 
|  | populateNVMeControllerCollection(asyncResp, nvme, ec, | 
|  | ""_json_pointer, controllerList); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeCollection( | 
|  | 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"] = | 
|  | "#NVMeCollection.v1_0_0.NVMeCollection"; | 
|  | asyncResp->res.jsonValue["@odata.id"] = "/google/v1/NVMe"; | 
|  | asyncResp->res.jsonValue["Name"] = "Google NVMe Collection"; | 
|  |  | 
|  | asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); | 
|  | asyncResp->res.jsonValue["Members@odata.count"] = 0; | 
|  |  | 
|  | processDriveResource(asyncResp, std::nullopt, | 
|  | [asyncResp](const std::string& path) { | 
|  | std::string id = sdbusplus::message::object_path(path).filename(); | 
|  | if (id.empty()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | nlohmann::json& members = asyncResp->res.jsonValue["Members"]; | 
|  | nlohmann::json::object_t member; | 
|  | member["@odata.id"] = | 
|  | crow::utility::urlFromPieces("google", "v1", "NVMe", id); | 
|  | members.push_back(std::move(member)); | 
|  | asyncResp->res.jsonValue["Members@odata.count"] = members.size(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void getIdentity( | 
|  | uint8_t cns, uint16_t cntid, uint32_t nsid, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& controllerId, const boost::system::error_code ec, | 
|  | const std::vector<std::string>& controllerList, | 
|  | const std::string& adminInterface = "xyz.openbmc_project.NVMe.NVMeAdmin") | 
|  | { | 
|  | if (ec || controllerList.empty()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string controllerPath; | 
|  | for (const std::string& controller : controllerList) | 
|  | { | 
|  | if (sdbusplus::message::object_path(controller).filename() == | 
|  | controllerId) | 
|  | { | 
|  | controllerPath = controller; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (controllerPath.empty()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | const std::string_view adminInterfaceView = adminInterface; | 
|  | std::span<const std::string_view> interfaces(&adminInterfaceView, 1); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | controllerPath, interfaces, context, | 
|  | [asyncResp, cns, cntid, nsid, controllerPath, | 
|  | adminInterface](const boost::system::error_code ec2, | 
|  | const dbus::utility::MapperGetObject& objects) { | 
|  | if (ec2) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "GetObject for path " << controllerPath | 
|  | << " failed with code " << ec2; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (objects.size() != 1) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | BMCWEB_LOG_ERROR << "Service supporting " << controllerPath | 
|  | << " is not equal to 1"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [asyncResp, objects, | 
|  | controllerPath](const boost::system::error_code ec3, | 
|  | const sdbusplus::message::unix_fd& fd) { | 
|  | if (ec3) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Failed to call NVMe Identity: " | 
|  | << ec3.message(); | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | int dupFd = dup(fd.fd); | 
|  | fetchFile(asyncResp, | 
|  | std::make_shared<stream_descriptor>(*fdIOContext, dupFd)); | 
|  | }, | 
|  | objects[0].first, controllerPath, adminInterface, "Identify", cns, | 
|  | nsid, cntid); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeControllerCustomNVMeIdentifyAction( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvmeId, const std::string& controllerId) | 
|  | { | 
|  | uint8_t cns = 0;    // Controller or Namespace Structure | 
|  | uint16_t cntid = 0; // Controller Identifier | 
|  | uint32_t nsid = 0;  // Namespace Identifier | 
|  |  | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!redfish::json_util::readJsonAction(req, asyncResp->res, "CNS", cns, | 
|  | "CNTID", cntid, "NSID", nsid)) | 
|  | { | 
|  | redfish::messages::actionParameterMissing( | 
|  | asyncResp->res, "ControllerNamespace", "ControllerNamespaceId"); | 
|  | return; | 
|  | } | 
|  | handleGoogleNvmeController( | 
|  | asyncResp, nvmeId, | 
|  | [cns, cntid, nsid, asyncResp, | 
|  | controllerId](const boost::system::error_code ec, | 
|  | const std::string& /* storagePath */, | 
|  | const std::vector<std::string>& controllerList) { | 
|  | getIdentity( | 
|  | cns, cntid, nsid, asyncResp, controllerId, ec, controllerList, | 
|  | "xyz.openbmc_project.NVMe." + std::string(customNVMe) + "Admin"); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeControllerIdentifyAction( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvmeId, const std::string& controllerId) | 
|  | { | 
|  | uint8_t cns = 0;    // Controller or Namespace Structure | 
|  | uint16_t cntid = 0; // Controller Identifier | 
|  | uint32_t nsid = 0;  // Namespace Identifier | 
|  |  | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!redfish::json_util::readJsonAction(req, asyncResp->res, "CNS", cns, | 
|  | "CNTID", cntid, "NSID", nsid)) | 
|  | { | 
|  | redfish::messages::actionParameterMissing( | 
|  | asyncResp->res, "ControllerNamespace", "ControllerNamespaceId"); | 
|  | return; | 
|  | } | 
|  | handleGoogleNvmeController( | 
|  | asyncResp, nvmeId, | 
|  | [cns, cntid, nsid, asyncResp, | 
|  | controllerId](const boost::system::error_code ec, | 
|  | const std::string& /* storagePath */, | 
|  | const std::vector<std::string>& controllerList) { | 
|  | getIdentity(cns, cntid, nsid, asyncResp, controllerId, ec, | 
|  | controllerList); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void adminNonDataCmd([[maybe_unused]] uint8_t opcode,[[maybe_unused]] uint32_t cdw1,[[maybe_unused]] uint32_t cdw2, | 
|  | [[maybe_unused]] uint32_t cdw3,[[maybe_unused]] uint32_t cdw10,[[maybe_unused]] uint32_t cdw11, | 
|  | [[maybe_unused]] uint32_t cdw12,[[maybe_unused]] uint32_t cdw13,[[maybe_unused]] uint32_t cdw14, | 
|  | [[maybe_unused]] uint32_t cdw15, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& controllerId, | 
|  | const boost::system::error_code ec, | 
|  | const std::vector<std::string>& controllerList) | 
|  | { | 
|  | if (ec || controllerList.empty()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string controllerPath; | 
|  | for (const std::string& controller : controllerList) | 
|  | { | 
|  | if (sdbusplus::message::object_path(controller).filename() == | 
|  | controllerId) | 
|  | { | 
|  | controllerPath = controller; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (controllerPath.empty()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "failed to find DBus controller path for " | 
|  | << controllerId; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The PostDbusCallToIoContextThreadSafe is NOT unitestable. MOCK_METHOD dosent work with this many params. | 
|  | // If we want to unit test this funciton, we probably will have to change dbus. | 
|  | #ifndef UNIT_TEST_BUILD | 
|  |  | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.NVMe.Passthru"}; | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | controllerPath, interfaces, context, | 
|  | [asyncResp, opcode, cdw1, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, cdw14, | 
|  | cdw15, controllerPath](const boost::system::error_code ec2, | 
|  | const dbus::utility::MapperGetObject& objects) { | 
|  | if (ec2) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "GetObject for path " << controllerPath | 
|  | << " failed with code " << ec2; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (objects.size() != 1) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | BMCWEB_LOG_ERROR << "Service supporting " << controllerPath | 
|  | << " is not equal to 1"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | const std::string& service {objects[0].first}; | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [asyncResp]( | 
|  | const boost::system::error_code ec3, | 
|  | const sdbusplus::message_t& msg, | 
|  | const std::tuple<uint32_t, uint32_t, uint32_t>& response) { | 
|  | const ::sd_bus_error* sd_err = msg.get_error(); | 
|  | if (sd_err != nullptr) | 
|  | { | 
|  | redfish::messages::generalError(asyncResp->res); | 
|  | if (sd_err->message != nullptr) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Error: " << sd_err->name << " message " | 
|  | << sd_err->message; | 
|  | asyncResp->res.jsonValue["error"]["message"] = | 
|  | sd_err->message; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ec3) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "AdminNonDataCmd dbus error " << ec3; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Success | 
|  | asyncResp->res.jsonValue["MIStatus"] = std::get<0>(response); | 
|  | asyncResp->res.jsonValue["AdminStatus"] = std::get<1>(response); | 
|  | asyncResp->res.jsonValue["CompletionDW0"] = std::get<2>(response); | 
|  | }, | 
|  | service, controllerPath, "xyz.openbmc_project.NVMe.Passthru", | 
|  | "AdminNonDataCmd", opcode, cdw1, cdw2, cdw3, cdw10, cdw11, cdw12, | 
|  | cdw13, cdw14, cdw15); | 
|  | }); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | inline void getLogPage( | 
|  | uint8_t lid, // Log Page Identifier, Command Dword 10 bits[07:00] | 
|  | uint32_t nsid, | 
|  | uint8_t lsp,  // Log Specific Field,  Command Dword 10 bits[14:08] | 
|  | uint16_t lsi, // Log Specific Identifier, Command Dword 11 bits[31:16] | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& controllerId, const boost::system::error_code ec, | 
|  | const std::vector<std::string>& controllerList) | 
|  | { | 
|  | if (ec || controllerList.empty()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string controllerPath; | 
|  | for (const std::string& controller : controllerList) | 
|  | { | 
|  | if (sdbusplus::message::object_path(controller).filename() == | 
|  | controllerId) | 
|  | { | 
|  | controllerPath = controller; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (controllerPath.empty()) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.NVMe.NVMeAdmin"}; | 
|  | managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | controllerPath, interfaces, requestContext, | 
|  | [asyncResp, lid, nsid, lsp, lsi, | 
|  | controllerPath](const boost::system::error_code ec2, | 
|  | const dbus::utility::MapperGetObject& objects) { | 
|  | if (ec2) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "GetObject for path " << controllerPath | 
|  | << " failed with code " << ec2; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (objects.size() != 1) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Service supporting " << controllerPath | 
|  | << " is not equal to 1"; | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [asyncResp, objects, | 
|  | controllerPath](const boost::system::error_code ec3, | 
|  | const sdbusplus::message::unix_fd& fd) { | 
|  | if (ec3) | 
|  | { | 
|  | BMCWEB_LOG_INFO << "Failed to call NVMe Log " << ec3.message(); | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | int dupFd = dup(fd.fd); | 
|  | fetchFile(asyncResp, | 
|  | std::make_shared<stream_descriptor>(*fdIOContext, dupFd)); | 
|  | }, | 
|  | objects[0].first, controllerPath, | 
|  | "xyz.openbmc_project.NVMe.NVMeAdmin", "GetLogPage", lid, nsid, lsp, | 
|  | lsi); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeControllerLogAction( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvmeId, const std::string& controllerId) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint8_t lid = 0; // Log Page Identifier, Command Dword 10 bits[07:00] | 
|  | uint32_t nsid = 0; | 
|  | uint8_t lsp = 0;  // Log Specific Field,  Command Dword 10 bits[14:08] | 
|  | uint16_t lsi = 0; // Log Specific Identifier, Command Dword 11 bits[31:16] | 
|  |  | 
|  | if (!redfish::json_util::readJsonAction(req, asyncResp->res, "LID", lid, | 
|  | "NSID", nsid, "LSP", lsp, "LSI", | 
|  | lsi)) | 
|  | { | 
|  | redfish::messages::actionParameterMissing(asyncResp->res, | 
|  | "LogSpecificId", "LogId"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | handleGoogleNvmeController( | 
|  | asyncResp, nvmeId, | 
|  | [lid, nsid, lsp, lsi, asyncResp, | 
|  | controllerId](const boost::system::error_code ec, | 
|  | const std::string& /* storagePath */, | 
|  | const std::vector<std::string>& controllerList) { | 
|  | getLogPage(lid, nsid, lsp, lsi, asyncResp, controllerId, ec, | 
|  | controllerList); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNvmeControllerAdminNonDataAction( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvmeId, const std::string& controllerId) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Only opcode is mandatory | 
|  | uint8_t opcode = 0; | 
|  | std::optional<uint32_t> cdw1; | 
|  | std::optional<uint32_t> cdw2; | 
|  | std::optional<uint32_t> cdw3; | 
|  | std::optional<uint32_t> cdw10; | 
|  | std::optional<uint32_t> cdw11; | 
|  | std::optional<uint32_t> cdw12; | 
|  | std::optional<uint32_t> cdw13; | 
|  | std::optional<uint32_t> cdw14; | 
|  | std::optional<uint32_t> cdw15; | 
|  |  | 
|  | if (!redfish::json_util::readJsonAction( | 
|  | req, asyncResp->res, "opcode", opcode, "cdw1", cdw1, "cdw2", cdw2, | 
|  | "cdw3", cdw3, "cdw10", cdw10, "cdw11", cdw11, "cdw12", cdw12, | 
|  | "cdw13", cdw13, "cdw14", cdw14, "cdw15", cdw15)) | 
|  | { | 
|  | redfish::messages::actionParameterMissing( | 
|  | asyncResp->res, "NVMeController.AdminNonDataCmd", "opcode"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | handleGoogleNvmeController( | 
|  | asyncResp, nvmeId, | 
|  | [opcode, cdw1{cdw1.value_or(0)}, cdw2{cdw2.value_or(0)}, | 
|  | cdw3{cdw3.value_or(0)}, cdw10{cdw10.value_or(0)}, | 
|  | cdw11{cdw11.value_or(0)}, cdw12{cdw12.value_or(0)}, | 
|  | cdw13{cdw13.value_or(0)}, cdw14{cdw14.value_or(0)}, | 
|  | cdw15{cdw15.value_or(0)}, asyncResp, | 
|  | controllerId](const boost::system::error_code ec, | 
|  | const std::string& /* storagePath */, | 
|  | const std::vector<std::string>& controllerList) { | 
|  | adminNonDataCmd(opcode, cdw1, cdw2, cdw3, cdw10, cdw11, cdw12, cdw13, | 
|  | cdw14, cdw15, asyncResp, controllerId, ec, | 
|  | controllerList); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void setupGoogleNVMeFdFetchIOContext( | 
|  | const std::shared_ptr<boost::asio::io_context>& ioc) | 
|  | { | 
|  | fdIOContext = ioc; | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeControllerActionCustomNVMeIdentify(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE( | 
|  | app, "/google/v1/NVMe/<str>/Controllers/<str>/Actions/NVMeController." + | 
|  | std::string(customNVMe) + "Identify") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleGoogleNvmeControllerCustomNVMeIdentifyAction, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeControllerActionIdentify(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE( | 
|  | app, | 
|  | "/google/v1/NVMe/<str>/Controllers/<str>/Actions/NVMeController.Identify") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleGoogleNvmeControllerIdentifyAction, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeControllerActionLog(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE( | 
|  | app, | 
|  | "/google/v1/NVMe/<str>/Controllers/<str>/Actions/NVMeController.GetLogPage") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleGoogleNvmeControllerLogAction, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeControllerActionAdminNonData(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE( | 
|  | app, | 
|  | "/google/v1/NVMe/<str>/Controllers/<str>/Actions/NVMeController.AdminNonDataCmd") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleGoogleNvmeControllerAdminNonDataAction, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void handleGoogleNVMeControllerGet( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& nvmeId, const std::string& controllerId) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | handleGoogleNvmeController( | 
|  | asyncResp, nvmeId, | 
|  | [asyncResp, nvmeId, controllerId]( | 
|  | const boost::system::error_code ec, const std::string& storagePath, | 
|  | const std::vector<std::string>& controllerList) { | 
|  | if (ec) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | redfish::storage_utils::getSystemPathFromStorage( | 
|  | asyncResp, storagePath, | 
|  | [asyncResp, nvmeId, controllerId, | 
|  | controllerList](std::optional<std::string_view> systemPath) { | 
|  | std::string systemName = | 
|  | systemPath ? std::filesystem::path(*systemPath).filename() | 
|  | : "system"; | 
|  | setupNVMeController(asyncResp, systemName, nvmeId, controllerId, | 
|  | controllerList); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeController(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE(app, "/google/v1/NVMe/<str>/Controllers/<str>") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::get)( | 
|  | std::bind_front(handleGoogleNVMeControllerGet, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeControllerCollection(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE(app, "/google/v1/NVMe/<str>/Controllers") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::get)(std::bind_front( | 
|  | handleGoogleNvmeControllerCollection, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMe(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE(app, "/google/v1/NVMe/<str>") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::get)( | 
|  | std::bind_front(handleGoogleNvme, std::ref(app))); | 
|  | } | 
|  |  | 
|  | inline void requestGoogleNVMeCollection(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE(app, "/google/v1/NVMe") | 
|  | .privileges({{"ConfigureManager"}}) | 
|  | .methods(boost::beast::http::verb::get)( | 
|  | std::bind_front(handleGoogleNvmeCollection, std::ref(app))); | 
|  | } | 
|  |  | 
|  | } // namespace google_api | 
|  | } // namespace crow |