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/openbmc_rest/dbus_monitor.hpp b/features/openbmc_rest/dbus_monitor.hpp
new file mode 100644
index 0000000..050bd34
--- /dev/null
+++ b/features/openbmc_rest/dbus_monitor.hpp
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+#include "app.hpp"
+#include "dbus_singleton.hpp"
+#include "logging.hpp"
+#include "openbmc_dbus_rest.hpp"
+#include "websocket.hpp"
+
+#include <systemd/sd-bus.h>
+
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <cstddef>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <regex>
+#include <string>
+#include <vector>
+
+namespace crow
+{
+namespace dbus_monitor
+{
+
+struct DbusWebsocketSession
+{
+    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
+    boost::container::flat_set<std::string, std::less<>,
+                               std::vector<std::string>>
+        interfaces;
+};
+
+using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
+                                              DbusWebsocketSession>;
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static SessionMap sessions;
+
+inline int onPropertyUpdate(sd_bus_message* m, void* userdata,
+                            sd_bus_error* retError)
+{
+    if (retError == nullptr || (sd_bus_error_is_set(retError) != 0))
+    {
+        BMCWEB_LOG_ERROR("Got sdbus error on match");
+        return 0;
+    }
+    crow::websocket::Connection* connection =
+        static_cast<crow::websocket::Connection*>(userdata);
+    auto thisSession = sessions.find(connection);
+    if (thisSession == sessions.end())
+    {
+        BMCWEB_LOG_ERROR("Couldn't find dbus connection {}",
+                         logPtr(connection));
+        return 0;
+    }
+    sdbusplus::message_t message(m);
+    nlohmann::json json;
+    json["event"] = message.get_member();
+    json["path"] = message.get_path();
+    if (strcmp(message.get_member(), "PropertiesChanged") == 0)
+    {
+        nlohmann::json data;
+        int r = openbmc_mapper::convertDBusToJSON("sa{sv}as", message, data);
+        if (r < 0)
+        {
+            BMCWEB_LOG_ERROR("convertDBusToJSON failed with {}", r);
+            return 0;
+        }
+        if (!data.is_array())
+        {
+            BMCWEB_LOG_ERROR("No data in PropertiesChanged signal");
+            return 0;
+        }
+
+        // data is type sa{sv}as and is an array[3] of string, object, array
+        json["interface"] = data[0];
+        json["properties"] = data[1];
+    }
+    else if (strcmp(message.get_member(), "InterfacesAdded") == 0)
+    {
+        nlohmann::json data;
+        int r = openbmc_mapper::convertDBusToJSON("oa{sa{sv}}", message, data);
+        if (r < 0)
+        {
+            BMCWEB_LOG_ERROR("convertDBusToJSON failed with {}", r);
+            return 0;
+        }
+        nlohmann::json::array_t* arr = data.get_ptr<nlohmann::json::array_t*>();
+        if (arr == nullptr)
+        {
+            BMCWEB_LOG_ERROR("No data in InterfacesAdded signal");
+            return 0;
+        }
+        if (arr->size() < 2)
+        {
+            BMCWEB_LOG_ERROR("No data in InterfacesAdded signal");
+            return 0;
+        }
+
+        nlohmann::json::object_t* obj =
+            (*arr)[1].get_ptr<nlohmann::json::object_t*>();
+        if (obj == nullptr)
+        {
+            BMCWEB_LOG_ERROR("No data in InterfacesAdded signal");
+            return 0;
+        }
+        // data is type oa{sa{sv}} which is an array[2] of string, object
+        for (const auto& entry : *obj)
+        {
+            auto it = thisSession->second.interfaces.find(entry.first);
+            if (it != thisSession->second.interfaces.end())
+            {
+                json["interfaces"][entry.first] = entry.second;
+            }
+        }
+    }
+    else
+    {
+        BMCWEB_LOG_CRITICAL("message {} was unexpected", message.get_member());
+        return 0;
+    }
+
+    connection->sendText(
+        json.dump(2, ' ', true, nlohmann::json::error_handler_t::replace));
+    return 0;
+}
+
+inline void requestRoutes(App& app)
+{
+    BMCWEB_ROUTE(app, "/subscribe")
+        .privileges({{"Login"}})
+        .websocket()
+        .onopen([](crow::websocket::Connection& conn) {
+            BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
+            sessions.try_emplace(&conn);
+        })
+        .onclose([](crow::websocket::Connection& conn, const std::string&) {
+            sessions.erase(&conn);
+        })
+        .onmessage([](crow::websocket::Connection& conn,
+                      const std::string& data, bool) {
+            const auto sessionPair = sessions.find(&conn);
+            if (sessionPair == sessions.end())
+            {
+                conn.close("Internal error");
+            }
+            DbusWebsocketSession& thisSession = sessionPair->second;
+            BMCWEB_LOG_DEBUG("Connection {} received {}", logPtr(&conn), data);
+            nlohmann::json j = nlohmann::json::parse(data, nullptr, false);
+            if (j.is_discarded())
+            {
+                BMCWEB_LOG_ERROR("Unable to parse json data for monitor");
+                conn.close("Unable to parse json request");
+                return;
+            }
+            nlohmann::json::iterator interfaces = j.find("interfaces");
+            if (interfaces != j.end())
+            {
+                thisSession.interfaces.reserve(interfaces->size());
+                for (auto& interface : *interfaces)
+                {
+                    const std::string* str =
+                        interface.get_ptr<const std::string*>();
+                    if (str != nullptr)
+                    {
+                        thisSession.interfaces.insert(*str);
+                    }
+                }
+            }
+
+            nlohmann::json::iterator paths = j.find("paths");
+            if (paths == j.end())
+            {
+                BMCWEB_LOG_ERROR("Unable to find paths in json data");
+                conn.close("Unable to find paths in json data");
+                return;
+            }
+
+            size_t interfaceCount = thisSession.interfaces.size();
+            if (interfaceCount == 0)
+            {
+                interfaceCount = 1;
+            }
+
+            // These regexes derived on the rules here:
+            // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
+            static std::regex validPath("^/([A-Za-z0-9_]+/?)*$");
+            static std::regex validInterface(
+                "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$");
+
+            for (const auto& thisPath : *paths)
+            {
+                const std::string* thisPathString =
+                    thisPath.get_ptr<const std::string*>();
+                if (thisPathString == nullptr)
+                {
+                    BMCWEB_LOG_ERROR("subscribe path isn't a string?");
+                    conn.close();
+                    return;
+                }
+                if (!std::regex_match(*thisPathString, validPath))
+                {
+                    BMCWEB_LOG_ERROR("Invalid path name {}", *thisPathString);
+                    conn.close();
+                    return;
+                }
+                std::string propertiesMatchString =
+                    ("type='signal',"
+                     "interface='org.freedesktop.DBus.Properties',"
+                     "path_namespace='" +
+                     *thisPathString +
+                     "',"
+                     "member='PropertiesChanged'");
+                // If interfaces weren't specified, add a single match for all
+                // interfaces
+                if (thisSession.interfaces.empty())
+                {
+                    BMCWEB_LOG_DEBUG("Creating match {}",
+                                     propertiesMatchString);
+
+                    thisSession.matches.emplace_back(
+                        std::make_unique<sdbusplus::bus::match_t>(
+                            *crow::connections::systemBus,
+                            propertiesMatchString, onPropertyUpdate, &conn));
+                }
+                else
+                {
+                    // If interfaces were specified, add a match for each
+                    // interface
+                    for (const std::string& interface : thisSession.interfaces)
+                    {
+                        if (!std::regex_match(interface, validInterface))
+                        {
+                            BMCWEB_LOG_ERROR("Invalid interface name {}",
+                                             interface);
+                            conn.close();
+                            return;
+                        }
+                        std::string ifaceMatchString = propertiesMatchString;
+                        ifaceMatchString += ",arg0='";
+                        ifaceMatchString += interface;
+                        ifaceMatchString += "'";
+                        BMCWEB_LOG_DEBUG("Creating match {}", ifaceMatchString);
+                        thisSession.matches.emplace_back(
+                            std::make_unique<sdbusplus::bus::match_t>(
+                                *crow::connections::systemBus, ifaceMatchString,
+                                onPropertyUpdate, &conn));
+                    }
+                }
+                std::string objectManagerMatchString =
+                    ("type='signal',"
+                     "interface='org.freedesktop.DBus.ObjectManager',"
+                     "path_namespace='" +
+                     *thisPathString +
+                     "',"
+                     "member='InterfacesAdded'");
+                BMCWEB_LOG_DEBUG("Creating match {}", objectManagerMatchString);
+                thisSession.matches.emplace_back(
+                    std::make_unique<sdbusplus::bus::match_t>(
+                        *crow::connections::systemBus, objectManagerMatchString,
+                        onPropertyUpdate, &conn));
+            }
+        });
+}
+} // namespace dbus_monitor
+} // namespace crow
diff --git a/features/openbmc_rest/image_upload.hpp b/features/openbmc_rest/image_upload.hpp
new file mode 100644
index 0000000..f6a0dfc
--- /dev/null
+++ b/features/openbmc_rest/image_upload.hpp
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "dbus_singleton.hpp"
+#include "dbus_utility.hpp"
+#include "http_request.hpp"
+#include "io_context_singleton.hpp"
+#include "logging.hpp"
+#include "ossl_random.hpp"
+
+#include <boost/asio/error.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/beast/http/verb.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <algorithm>
+#include <chrono>
+#include <cstdio>
+#include <fstream>
+#include <functional>
+#include <memory>
+#include <ranges>
+#include <string>
+
+namespace crow
+{
+namespace image_upload
+{
+
+// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
+static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
+
+inline void uploadImageHandler(
+    const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+{
+    // Only allow one FW update at a time
+    if (fwUpdateMatcher != nullptr)
+    {
+        asyncResp->res.addHeader("Retry-After", "30");
+        asyncResp->res.result(boost::beast::http::status::service_unavailable);
+        return;
+    }
+    // Make this const static so it survives outside this method
+    static boost::asio::steady_timer timeout(getIoContext(),
+                                             std::chrono::seconds(5));
+
+    timeout.expires_after(std::chrono::seconds(15));
+
+    auto timeoutHandler = [asyncResp](const boost::system::error_code& ec) {
+        fwUpdateMatcher = nullptr;
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            // expected, we were canceled before the timer completed.
+            return;
+        }
+        BMCWEB_LOG_ERROR("Timed out waiting for Version interface");
+
+        if (ec)
+        {
+            BMCWEB_LOG_ERROR("Async_wait failed {}", ec);
+            return;
+        }
+
+        asyncResp->res.result(boost::beast::http::status::bad_request);
+        asyncResp->res.jsonValue["data"]["description"] =
+            "Version already exists or failed to be extracted";
+        asyncResp->res.jsonValue["message"] = "400 Bad Request";
+        asyncResp->res.jsonValue["status"] = "error";
+    };
+
+    std::function<void(sdbusplus::message_t&)> callback =
+        [asyncResp](sdbusplus::message_t& m) {
+            BMCWEB_LOG_DEBUG("Match fired");
+
+            sdbusplus::message::object_path path;
+            dbus::utility::DBusInterfacesMap interfaces;
+            m.read(path, interfaces);
+
+            if (std::ranges::find_if(interfaces, [](const auto& i) {
+                    return i.first == "xyz.openbmc_project.Software.Version";
+                }) != interfaces.end())
+            {
+                timeout.cancel();
+                std::string leaf = path.filename();
+                if (leaf.empty())
+                {
+                    leaf = path.str;
+                }
+
+                asyncResp->res.jsonValue["data"] = leaf;
+                asyncResp->res.jsonValue["message"] = "200 OK";
+                asyncResp->res.jsonValue["status"] = "ok";
+                BMCWEB_LOG_DEBUG("ending response");
+                fwUpdateMatcher = nullptr;
+            }
+        };
+    fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
+        *crow::connections::systemBus,
+        "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
+        "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
+        callback);
+
+    std::string filepath("/tmp/images/" + bmcweb::getRandomUUID());
+    BMCWEB_LOG_DEBUG("Writing file to {}", filepath);
+    std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
+                                    std::ofstream::trunc);
+    out << req.body();
+    out.close();
+    timeout.async_wait(timeoutHandler);
+}
+
+inline void requestRoutes(App& app)
+{
+    BMCWEB_ROUTE(app, "/upload/image/<str>")
+        .privileges({{"ConfigureComponents", "ConfigureManager"}})
+        .methods(boost::beast::http::verb::post, boost::beast::http::verb::put)(
+            [](const crow::Request& req,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string&) { uploadImageHandler(req, asyncResp); });
+
+    BMCWEB_ROUTE(app, "/upload/image")
+        .privileges({{"ConfigureComponents", "ConfigureManager"}})
+        .methods(boost::beast::http::verb::post, boost::beast::http::verb::put)(
+            [](const crow::Request& req,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+                uploadImageHandler(req, asyncResp);
+            });
+}
+} // namespace image_upload
+} // namespace crow
diff --git a/features/openbmc_rest/meson.build b/features/openbmc_rest/meson.build
new file mode 100644
index 0000000..cfceb79
--- /dev/null
+++ b/features/openbmc_rest/meson.build
@@ -0,0 +1,2 @@
+incdir += include_directories('.')
+test_sources += files('openbmc_dbus_rest_test.cpp')
diff --git a/features/openbmc_rest/openbmc_dbus_rest.hpp b/features/openbmc_rest/openbmc_dbus_rest.hpp
new file mode 100644
index 0000000..d2bd48f8
--- /dev/null
+++ b/features/openbmc_rest/openbmc_dbus_rest.hpp
@@ -0,0 +1,2650 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+// SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
+
+#pragma once
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "boost_formatters.hpp"
+#include "dbus_singleton.hpp"
+#include "dbus_utility.hpp"
+#include "http_request.hpp"
+#include "http_response.hpp"
+#include "json_formatters.hpp"
+#include "logging.hpp"
+#include "parsing.hpp"
+#include "str_utility.hpp"
+
+#include <systemd/sd-bus-protocol.h>
+#include <systemd/sd-bus.h>
+#include <tinyxml2.h>
+
+#include <boost/beast/http/field.hpp>
+#include <boost/beast/http/status.hpp>
+#include <boost/beast/http/verb.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/system/error_code.hpp>
+#include <nlohmann/json.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <algorithm>
+#include <array>
+#include <cerrno>
+#include <cstdint>
+#include <cstring>
+#include <filesystem>
+#include <functional>
+#include <initializer_list>
+#include <limits>
+#include <map>
+#include <memory>
+#include <ranges>
+#include <regex>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace crow
+{
+namespace openbmc_mapper
+{
+const constexpr char* notFoundMsg = "404 Not Found";
+const constexpr char* badReqMsg = "400 Bad Request";
+const constexpr char* methodNotAllowedMsg = "405 Method Not Allowed";
+const constexpr char* forbiddenMsg = "403 Forbidden";
+const constexpr char* unsupportedMediaMsg = "415 Unsupported Media Type";
+const constexpr char* methodFailedMsg = "500 Method Call Failed";
+const constexpr char* methodOutputFailedMsg = "500 Method Output Error";
+const constexpr char* notFoundDesc =
+    "org.freedesktop.DBus.Error.FileNotFound: path or object not found";
+const constexpr char* propNotFoundDesc =
+    "The specified property cannot be found";
+const constexpr char* noJsonDesc = "No JSON object could be decoded";
+const constexpr char* invalidContentType =
+    "Content-type header is missing or invalid";
+const constexpr char* methodNotFoundDesc =
+    "The specified method cannot be found";
+const constexpr char* methodNotAllowedDesc = "Method not allowed";
+const constexpr char* forbiddenPropDesc =
+    "The specified property cannot be created";
+const constexpr char* forbiddenResDesc =
+    "The specified resource cannot be created";
+
+inline bool validateFilename(const std::string& filename)
+{
+    static std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)");
+
+    return std::regex_match(filename, validFilename);
+}
+
+inline void setErrorResponse(crow::Response& res,
+                             boost::beast::http::status result,
+                             const std::string& desc, std::string_view msg)
+{
+    res.result(result);
+    res.jsonValue["data"]["description"] = desc;
+    res.jsonValue["message"] = msg;
+    res.jsonValue["status"] = "error";
+}
+
+inline void introspectObjects(
+    const std::string& processName, const std::string& objectPath,
+    const std::shared_ptr<bmcweb::AsyncResp>& transaction)
+{
+    if (transaction->res.jsonValue.is_null())
+    {
+        transaction->res.jsonValue["status"] = "ok";
+        transaction->res.jsonValue["bus_name"] = processName;
+        transaction->res.jsonValue["objects"] = nlohmann::json::array();
+    }
+
+    dbus::utility::async_method_call(
+        [transaction, processName{std::string(processName)},
+         objectPath{std::string(objectPath)}](
+            const boost::system::error_code& ec,
+            const std::string& introspectXml) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR(
+                    "Introspect call failed with error: {} on process: {} path: {}",
+                    ec.message(), processName, objectPath);
+                return;
+            }
+            nlohmann::json::object_t object;
+            object["path"] = objectPath;
+
+            transaction->res.jsonValue["objects"].emplace_back(
+                std::move(object));
+
+            tinyxml2::XMLDocument doc;
+
+            doc.Parse(introspectXml.c_str());
+            tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
+            if (pRoot == nullptr)
+            {
+                BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
+                                 processName, objectPath);
+            }
+            else
+            {
+                tinyxml2::XMLElement* node = pRoot->FirstChildElement("node");
+                while (node != nullptr)
+                {
+                    const char* childPath = node->Attribute("name");
+                    if (childPath != nullptr)
+                    {
+                        std::string newpath;
+                        if (objectPath != "/")
+                        {
+                            newpath += objectPath;
+                        }
+                        newpath += std::string("/") + childPath;
+                        // introspect the subobjects as well
+                        introspectObjects(processName, newpath, transaction);
+                    }
+
+                    node = node->NextSiblingElement("node");
+                }
+            }
+        },
+        processName, objectPath, "org.freedesktop.DBus.Introspectable",
+        "Introspect");
+}
+
+inline void getPropertiesForEnumerate(
+    const std::string& objectPath, const std::string& service,
+    const std::string& interface,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+{
+    BMCWEB_LOG_DEBUG("getPropertiesForEnumerate {} {} {}", objectPath, service,
+                     interface);
+
+    dbus::utility::getAllProperties(
+        service, objectPath, interface,
+        [asyncResp, objectPath, service,
+         interface](const boost::system::error_code& ec,
+                    const dbus::utility::DBusPropertiesMap& propertiesList) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR(
+                    "GetAll on path {} iface {} service {} failed with code {}",
+                    objectPath, interface, service, ec);
+                return;
+            }
+
+            nlohmann::json& dataJson = asyncResp->res.jsonValue["data"];
+            nlohmann::json& objectJson = dataJson[objectPath];
+            if (objectJson.is_null())
+            {
+                objectJson = nlohmann::json::object();
+            }
+
+            for (const auto& [name, value] : propertiesList)
+            {
+                nlohmann::json& propertyJson = objectJson[name];
+                std::visit([&propertyJson](auto&& val) { propertyJson = val; },
+                           value);
+            }
+        });
+}
+
+// Find any results that weren't picked up by ObjectManagers, to be
+// called after all ObjectManagers are searched for and called.
+inline void findRemainingObjectsForEnumerate(
+    const std::string& objectPath,
+    const std::shared_ptr<dbus::utility::MapperGetSubTreeResponse>& subtree,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+{
+    BMCWEB_LOG_DEBUG("findRemainingObjectsForEnumerate");
+    const nlohmann::json& dataJson = asyncResp->res.jsonValue["data"];
+
+    for (const auto& [path, interface_map] : *subtree)
+    {
+        if (path == objectPath)
+        {
+            // An enumerate does not return the target path's properties
+            continue;
+        }
+        if (!dataJson.contains(path))
+        {
+            for (const auto& [service, interfaces] : interface_map)
+            {
+                for (const auto& interface : interfaces)
+                {
+                    if (!interface.starts_with("org.freedesktop.DBus"))
+                    {
+                        getPropertiesForEnumerate(path, service, interface,
+                                                  asyncResp);
+                    }
+                }
+            }
+        }
+    }
+}
+
+struct InProgressEnumerateData
+{
+    InProgressEnumerateData(
+        const std::string& objectPathIn,
+        const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
+        objectPath(objectPathIn), asyncResp(asyncRespIn)
+    {}
+
+    ~InProgressEnumerateData()
+    {
+        try
+        {
+            findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp);
+        }
+        catch (...)
+        {
+            BMCWEB_LOG_CRITICAL(
+                "findRemainingObjectsForEnumerate threw exception");
+        }
+    }
+
+    InProgressEnumerateData(const InProgressEnumerateData&) = delete;
+    InProgressEnumerateData(InProgressEnumerateData&&) = delete;
+    InProgressEnumerateData& operator=(const InProgressEnumerateData&) = delete;
+    InProgressEnumerateData& operator=(InProgressEnumerateData&&) = delete;
+    const std::string objectPath;
+    std::shared_ptr<dbus::utility::MapperGetSubTreeResponse> subtree;
+    std::shared_ptr<bmcweb::AsyncResp> asyncResp;
+};
+
+inline void getManagedObjectsForEnumerate(
+    const std::string& objectName, const std::string& objectManagerPath,
+    const std::string& connectionName,
+    const std::shared_ptr<InProgressEnumerateData>& transaction)
+{
+    BMCWEB_LOG_DEBUG(
+        "getManagedObjectsForEnumerate {} object_manager_path {} connection_name {}",
+        objectName, objectManagerPath, connectionName);
+    sdbusplus::message::object_path path(objectManagerPath);
+    dbus::utility::getManagedObjects(
+        connectionName, path,
+        [transaction, objectName,
+         connectionName](const boost::system::error_code& ec,
+                         const dbus::utility::ManagedObjectType& objects) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR(
+                    "GetManagedObjects on path {} on connection {} failed with code {}",
+                    objectName, connectionName, ec);
+                return;
+            }
+
+            nlohmann::json& dataJson =
+                transaction->asyncResp->res.jsonValue["data"];
+
+            for (const auto& objectPath : objects)
+            {
+                if (objectPath.first.str.starts_with(objectName))
+                {
+                    BMCWEB_LOG_DEBUG("Reading object {}", objectPath.first.str);
+                    nlohmann::json& objectJson = dataJson[objectPath.first.str];
+                    if (objectJson.is_null())
+                    {
+                        objectJson = nlohmann::json::object();
+                    }
+                    for (const auto& interface : objectPath.second)
+                    {
+                        for (const auto& property : interface.second)
+                        {
+                            nlohmann::json& propertyJson =
+                                objectJson[property.first];
+                            std::visit(
+                                [&propertyJson](auto&& val) {
+                                    if constexpr (
+                                        std::is_same_v<
+                                            std::decay_t<decltype(val)>,
+                                            sdbusplus::message::unix_fd>)
+                                    {
+                                        propertyJson = val.fd;
+                                    }
+                                    else
+                                    {
+                                        propertyJson = val;
+                                    }
+                                },
+                                property.second);
+                        }
+                    }
+                }
+                for (const auto& interface : objectPath.second)
+                {
+                    if (interface.first == "org.freedesktop.DBus.ObjectManager")
+                    {
+                        getManagedObjectsForEnumerate(
+                            objectPath.first.str, objectPath.first.str,
+                            connectionName, transaction);
+                    }
+                }
+            }
+        });
+}
+
+inline void findObjectManagerPathForEnumerate(
+    const std::string& objectName, const std::string& connectionName,
+    const std::shared_ptr<InProgressEnumerateData>& transaction)
+{
+    BMCWEB_LOG_DEBUG("Finding objectmanager for path {} on connection:{}",
+                     objectName, connectionName);
+    dbus::utility::async_method_call(
+        [transaction, objectName, connectionName](
+            const boost::system::error_code& ec,
+            const dbus::utility::MapperGetAncestorsResponse& objects) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR("GetAncestors on path {} failed with code {}",
+                                 objectName, ec);
+                return;
+            }
+
+            for (const auto& pathGroup : objects)
+            {
+                for (const auto& connectionGroup : pathGroup.second)
+                {
+                    if (connectionGroup.first == connectionName)
+                    {
+                        // Found the object manager path for this resource.
+                        getManagedObjectsForEnumerate(
+                            objectName, pathGroup.first, connectionName,
+                            transaction);
+                        return;
+                    }
+                }
+            }
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetAncestors", objectName,
+        std::array<const char*, 1>{"org.freedesktop.DBus.ObjectManager"});
+}
+
+// Uses GetObject to add the object info about the target /enumerate path to
+// the results of GetSubTree, as GetSubTree will not return info for the
+// target path, and then continues on enumerating the rest of the tree.
+inline void getObjectAndEnumerate(
+    const std::shared_ptr<InProgressEnumerateData>& transaction)
+{
+    dbus::utility::getDbusObject(
+        transaction->objectPath, {},
+        [transaction](const boost::system::error_code& ec,
+                      const dbus::utility::MapperGetObject& objects) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR("GetObject for path {} failed with code {}",
+                                 transaction->objectPath, ec);
+                return;
+            }
+
+            BMCWEB_LOG_DEBUG("GetObject for {} has {} entries",
+                             transaction->objectPath, objects.size());
+            if (!objects.empty())
+            {
+                transaction->subtree->emplace_back(transaction->objectPath,
+                                                   objects);
+            }
+
+            // Map indicating connection name, and the path where the object
+            // manager exists
+            boost::container::flat_map<
+                std::string, std::string, std::less<>,
+                std::vector<std::pair<std::string, std::string>>>
+                connections;
+
+            for (const auto& object : *(transaction->subtree))
+            {
+                for (const auto& connection : object.second)
+                {
+                    for (const auto& interface : connection.second)
+                    {
+                        BMCWEB_LOG_DEBUG("{} has interface {}",
+                                         connection.first, interface);
+                        if (interface == "org.freedesktop.DBus.ObjectManager")
+                        {
+                            BMCWEB_LOG_DEBUG("found object manager path {}",
+                                             object.first);
+                            connections[connection.first] = object.first;
+                        }
+                    }
+                }
+            }
+            BMCWEB_LOG_DEBUG("Got {} connections", connections.size());
+
+            for (const auto& connection : connections)
+            {
+                // If we already know where the object manager is, we don't
+                // need to search for it, we can call directly in to
+                // getManagedObjects
+                if (!connection.second.empty())
+                {
+                    getManagedObjectsForEnumerate(
+                        transaction->objectPath, connection.second,
+                        connection.first, transaction);
+                }
+                else
+                {
+                    // otherwise we need to find the object manager path
+                    // before we can continue
+                    findObjectManagerPathForEnumerate(
+                        transaction->objectPath, connection.first, transaction);
+                }
+            }
+        });
+}
+
+// Structure for storing data on an in progress action
+struct InProgressActionData
+{
+    explicit InProgressActionData(
+        const std::shared_ptr<bmcweb::AsyncResp>& res) : asyncResp(res)
+    {}
+    ~InProgressActionData()
+    {
+        // Methods could have been called across different owners
+        // and interfaces, where some calls failed and some passed.
+        //
+        // The rules for this are:
+        // * if no method was called - error
+        // * if a method failed and none passed - error
+        //   (converse: if at least one method passed - OK)
+        // * for the method output:
+        //   * if output processing didn't fail, return the data
+
+        // Only deal with method returns if nothing failed earlier
+        if (asyncResp->res.result() == boost::beast::http::status::ok)
+        {
+            if (!methodPassed)
+            {
+                if (!methodFailed)
+                {
+                    setErrorResponse(asyncResp->res,
+                                     boost::beast::http::status::not_found,
+                                     methodNotFoundDesc, notFoundMsg);
+                }
+            }
+            else
+            {
+                if (outputFailed)
+                {
+                    setErrorResponse(
+                        asyncResp->res,
+                        boost::beast::http::status::internal_server_error,
+                        "Method output failure", methodOutputFailedMsg);
+                }
+                else
+                {
+                    asyncResp->res.jsonValue["status"] = "ok";
+                    asyncResp->res.jsonValue["message"] = "200 OK";
+                    asyncResp->res.jsonValue["data"] = methodResponse;
+                }
+            }
+        }
+    }
+    InProgressActionData(const InProgressActionData&) = delete;
+    InProgressActionData(InProgressActionData&&) = delete;
+    InProgressActionData& operator=(const InProgressActionData&) = delete;
+    InProgressActionData& operator=(InProgressActionData&&) = delete;
+
+    void setErrorStatus(const std::string& desc)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::bad_request, desc,
+                         badReqMsg);
+    }
+    std::shared_ptr<bmcweb::AsyncResp> asyncResp;
+    std::string path;
+    std::string methodName;
+    std::string interfaceName;
+    bool methodPassed = false;
+    bool methodFailed = false;
+    bool outputFailed = false;
+    bool convertedToArray = false;
+    nlohmann::json methodResponse;
+    nlohmann::json arguments;
+};
+
+inline std::vector<std::string> dbusArgSplit(const std::string& string)
+{
+    std::vector<std::string> ret;
+    if (string.empty())
+    {
+        return ret;
+    }
+    ret.emplace_back("");
+    int containerDepth = 0;
+
+    for (std::string::const_iterator character = string.begin();
+         character != string.end(); character++)
+    {
+        ret.back() += *character;
+        switch (*character)
+        {
+            case ('a'):
+                break;
+            case ('('):
+            case ('{'):
+                containerDepth++;
+                break;
+            case ('}'):
+            case (')'):
+                containerDepth--;
+                if (containerDepth == 0)
+                {
+                    if (character + 1 != string.end())
+                    {
+                        ret.emplace_back("");
+                    }
+                }
+                break;
+            default:
+                if (containerDepth == 0)
+                {
+                    if (character + 1 != string.end())
+                    {
+                        ret.emplace_back("");
+                    }
+                }
+                break;
+        }
+    }
+
+    return ret;
+}
+
+inline int convertJsonToDbus(sd_bus_message* m, const std::string& argType,
+                             const nlohmann::json& inputJson)
+{
+    int r = 0;
+    BMCWEB_LOG_DEBUG("Converting {} to type: {}", inputJson, argType);
+    const std::vector<std::string> argTypes = dbusArgSplit(argType);
+
+    // Assume a single object for now.
+    const nlohmann::json* j = &inputJson;
+    nlohmann::json::const_iterator jIt = inputJson.begin();
+
+    for (const std::string& argCode : argTypes)
+    {
+        // If we are decoding multiple objects, grab the pointer to the
+        // iterator, and increment it for the next loop
+        if (argTypes.size() > 1)
+        {
+            if (jIt == inputJson.end())
+            {
+                return -2;
+            }
+            j = &*jIt;
+            jIt++;
+        }
+        const int64_t* intValue = j->get_ptr<const int64_t*>();
+        const std::string* stringValue = j->get_ptr<const std::string*>();
+        const double* doubleValue = j->get_ptr<const double*>();
+        const bool* b = j->get_ptr<const bool*>();
+        int64_t v = 0;
+        double d = 0.0;
+
+        // Do some basic type conversions that make sense.  uint can be
+        // converted to int.  int and uint can be converted to double
+        if (intValue == nullptr)
+        {
+            const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
+            if (uintValue != nullptr)
+            {
+                v = static_cast<int64_t>(*uintValue);
+                intValue = &v;
+            }
+        }
+        if (doubleValue == nullptr)
+        {
+            const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
+            if (uintValue != nullptr)
+            {
+                d = static_cast<double>(*uintValue);
+                doubleValue = &d;
+            }
+        }
+        if (doubleValue == nullptr)
+        {
+            if (intValue != nullptr)
+            {
+                d = static_cast<double>(*intValue);
+                doubleValue = &d;
+            }
+        }
+
+        if (argCode == "s")
+        {
+            if (stringValue == nullptr)
+            {
+                return -1;
+            }
+            r = sd_bus_message_append_basic(
+                m, argCode[0], static_cast<const void*>(stringValue->data()));
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode == "i")
+        {
+            if (intValue == nullptr)
+            {
+                return -1;
+            }
+            if ((*intValue < std::numeric_limits<int32_t>::lowest()) ||
+                (*intValue > std::numeric_limits<int32_t>::max()))
+            {
+                return -ERANGE;
+            }
+            int32_t i = static_cast<int32_t>(*intValue);
+            r = sd_bus_message_append_basic(m, argCode[0], &i);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode == "b")
+        {
+            // lots of ways bool could be represented here.  Try them all
+            int boolInt = 0;
+            if (intValue != nullptr)
+            {
+                if (*intValue == 1)
+                {
+                    boolInt = 1;
+                }
+                else if (*intValue == 0)
+                {
+                    boolInt = 0;
+                }
+                else
+                {
+                    return -ERANGE;
+                }
+            }
+            else if (b != nullptr)
+            {
+                boolInt = *b ? 1 : 0;
+            }
+            else if (stringValue != nullptr)
+            {
+                if (!stringValue->empty())
+                {
+                    if (stringValue->front() == 't' ||
+                        stringValue->front() == 'T')
+                    {
+                        boolInt = 1;
+                    }
+                }
+            }
+            else
+            {
+                return -1;
+            }
+            r = sd_bus_message_append_basic(m, argCode[0], &boolInt);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode == "n")
+        {
+            if (intValue == nullptr)
+            {
+                return -1;
+            }
+            if ((*intValue < std::numeric_limits<int16_t>::lowest()) ||
+                (*intValue > std::numeric_limits<int16_t>::max()))
+            {
+                return -ERANGE;
+            }
+            int16_t n = static_cast<int16_t>(*intValue);
+            r = sd_bus_message_append_basic(m, argCode[0], &n);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode == "x")
+        {
+            if (intValue == nullptr)
+            {
+                return -1;
+            }
+            r = sd_bus_message_append_basic(m, argCode[0], intValue);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode == "y")
+        {
+            const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
+            if (uintValue == nullptr)
+            {
+                return -1;
+            }
+            if (*uintValue > std::numeric_limits<uint8_t>::max())
+            {
+                return -ERANGE;
+            }
+            uint8_t y = static_cast<uint8_t>(*uintValue);
+            r = sd_bus_message_append_basic(m, argCode[0], &y);
+        }
+        else if (argCode == "q")
+        {
+            const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
+            if (uintValue == nullptr)
+            {
+                return -1;
+            }
+            if (*uintValue > std::numeric_limits<uint16_t>::max())
+            {
+                return -ERANGE;
+            }
+            uint16_t q = static_cast<uint16_t>(*uintValue);
+            r = sd_bus_message_append_basic(m, argCode[0], &q);
+        }
+        else if (argCode == "u")
+        {
+            const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
+            if (uintValue == nullptr)
+            {
+                return -1;
+            }
+            if (*uintValue > std::numeric_limits<uint32_t>::max())
+            {
+                return -ERANGE;
+            }
+            uint32_t u = static_cast<uint32_t>(*uintValue);
+            r = sd_bus_message_append_basic(m, argCode[0], &u);
+        }
+        else if (argCode == "t")
+        {
+            const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
+            if (uintValue == nullptr)
+            {
+                return -1;
+            }
+            r = sd_bus_message_append_basic(m, argCode[0], uintValue);
+        }
+        else if (argCode == "d")
+        {
+            if (doubleValue == nullptr)
+            {
+                return -1;
+            }
+            if ((*doubleValue < std::numeric_limits<double>::lowest()) ||
+                (*doubleValue > std::numeric_limits<double>::max()))
+            {
+                return -ERANGE;
+            }
+            r = sd_bus_message_append_basic(m, argCode[0], doubleValue);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode.starts_with("a"))
+        {
+            std::string containedType = argCode.substr(1);
+            r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
+                                              containedType.c_str());
+            if (r < 0)
+            {
+                return r;
+            }
+
+            for (const auto& it : *j)
+            {
+                r = convertJsonToDbus(m, containedType, it);
+                if (r < 0)
+                {
+                    return r;
+                }
+            }
+            sd_bus_message_close_container(m);
+        }
+        else if (argCode.starts_with("v"))
+        {
+            std::string containedType = argCode.substr(1);
+            BMCWEB_LOG_DEBUG("variant type: {} appending variant of type: {}",
+                             argCode, containedType);
+            r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
+                                              containedType.c_str());
+            if (r < 0)
+            {
+                return r;
+            }
+
+            r = convertJsonToDbus(m, containedType, inputJson);
+            if (r < 0)
+            {
+                return r;
+            }
+
+            r = sd_bus_message_close_container(m);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (argCode.starts_with("(") && argCode.ends_with(")"))
+        {
+            std::string containedType = argCode.substr(1, argCode.size() - 2);
+            r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
+                                              containedType.c_str());
+            if (r < 0)
+            {
+                return r;
+            }
+
+            nlohmann::json::const_iterator it = j->begin();
+            for (const std::string& argCode2 : dbusArgSplit(containedType))
+            {
+                if (it == j->end())
+                {
+                    return -1;
+                }
+                r = convertJsonToDbus(m, argCode2, *it);
+                if (r < 0)
+                {
+                    return r;
+                }
+                it++;
+            }
+            r = sd_bus_message_close_container(m);
+        }
+        else if (argCode.starts_with("{") && argCode.ends_with("}"))
+        {
+            std::string containedType = argCode.substr(1, argCode.size() - 2);
+            r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
+                                              containedType.c_str());
+            if (r < 0)
+            {
+                return r;
+            }
+
+            std::vector<std::string> codes = dbusArgSplit(containedType);
+            if (codes.size() != 2)
+            {
+                return -1;
+            }
+            const std::string& keyType = codes[0];
+            const std::string& valueType = codes[1];
+            const nlohmann::json::object_t* arr =
+                j->get_ptr<const nlohmann::json::object_t*>();
+            if (arr == nullptr)
+            {
+                return -1;
+            }
+            for (const auto& it : *arr)
+            {
+                r = convertJsonToDbus(m, keyType, it.first);
+                if (r < 0)
+                {
+                    return r;
+                }
+
+                r = convertJsonToDbus(m, valueType, it.second);
+                if (r < 0)
+                {
+                    return r;
+                }
+            }
+            r = sd_bus_message_close_container(m);
+        }
+        else
+        {
+            return -2;
+        }
+        if (r < 0)
+        {
+            return r;
+        }
+
+        if (argTypes.size() > 1)
+        {
+            jIt++;
+        }
+    }
+
+    return r;
+}
+
+template <typename T>
+int readMessageItem(const std::string& typeCode, sdbusplus::message_t& m,
+                    nlohmann::json& data)
+{
+    T value;
+    // When T == char*, this warning fires.  Unclear how to resolve
+    // Given that sd-bus takes a void pointer to a char*, and that's
+    // Not something we can fix.
+    // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
+    int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value);
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_read_basic on type {} failed!",
+                         typeCode);
+        return r;
+    }
+
+    data = value;
+    return 0;
+}
+
+int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m,
+                      nlohmann::json& response);
+
+inline int readDictEntryFromMessage(const std::string& typeCode,
+                                    sdbusplus::message_t& m,
+                                    nlohmann::json& object)
+{
+    std::vector<std::string> types = dbusArgSplit(typeCode);
+    if (types.size() != 2)
+    {
+        BMCWEB_LOG_ERROR("wrong number contained types in dictionary: {}",
+                         types.size());
+        return -1;
+    }
+
+    int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY,
+                                           typeCode.c_str());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_enter_container with rc {}", r);
+        return r;
+    }
+
+    nlohmann::json key;
+    r = convertDBusToJSON(types[0], m, key);
+    if (r < 0)
+    {
+        return r;
+    }
+
+    const std::string* keyPtr = key.get_ptr<const std::string*>();
+    if (keyPtr == nullptr)
+    {
+        // json doesn't support non-string keys.  If we hit this condition,
+        // convert the result to a string so we can proceed
+        key = key.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
+        keyPtr = key.get_ptr<const std::string*>();
+        // in theory this can't fail now, but lets be paranoid about it
+        // anyway
+        if (keyPtr == nullptr)
+        {
+            return -1;
+        }
+    }
+    nlohmann::json& value = object[*keyPtr];
+
+    r = convertDBusToJSON(types[1], m, value);
+    if (r < 0)
+    {
+        return r;
+    }
+
+    r = sd_bus_message_exit_container(m.get());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
+        return r;
+    }
+
+    return 0;
+}
+
+inline int readArrayFromMessage(const std::string& typeCode,
+                                sdbusplus::message_t& m, nlohmann::json& data)
+{
+    if (typeCode.size() < 2)
+    {
+        BMCWEB_LOG_ERROR("Type code {} too small for an array", typeCode);
+        return -1;
+    }
+
+    std::string containedType = typeCode.substr(1);
+
+    int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY,
+                                           containedType.c_str());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
+        return r;
+    }
+
+    bool dict = containedType.starts_with("{") && containedType.ends_with("}");
+
+    if (dict)
+    {
+        // Remove the { }
+        containedType = containedType.substr(1, containedType.size() - 2);
+        data = nlohmann::json::object();
+    }
+    else
+    {
+        data = nlohmann::json::array();
+    }
+
+    while (true)
+    {
+        r = sd_bus_message_at_end(m.get(), 0);
+        if (r < 0)
+        {
+            BMCWEB_LOG_ERROR("sd_bus_message_at_end failed");
+            return r;
+        }
+
+        if (r > 0)
+        {
+            break;
+        }
+
+        // Dictionaries are only ever seen in an array
+        if (dict)
+        {
+            r = readDictEntryFromMessage(containedType, m, data);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else
+        {
+            data.push_back(nlohmann::json());
+
+            r = convertDBusToJSON(containedType, m, data.back());
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+    }
+
+    r = sd_bus_message_exit_container(m.get());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
+        return r;
+    }
+
+    return 0;
+}
+
+inline int readStructFromMessage(const std::string& typeCode,
+                                 sdbusplus::message_t& m, nlohmann::json& data)
+{
+    if (typeCode.size() < 3)
+    {
+        BMCWEB_LOG_ERROR("Type code {} too small for a struct", typeCode);
+        return -1;
+    }
+
+    std::string containedTypes = typeCode.substr(1, typeCode.size() - 2);
+    std::vector<std::string> types = dbusArgSplit(containedTypes);
+
+    int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_STRUCT,
+                                           containedTypes.c_str());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
+        return r;
+    }
+
+    for (const std::string& type : types)
+    {
+        data.push_back(nlohmann::json());
+        r = convertDBusToJSON(type, m, data.back());
+        if (r < 0)
+        {
+            return r;
+        }
+    }
+
+    r = sd_bus_message_exit_container(m.get());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
+        return r;
+    }
+    return 0;
+}
+
+inline int readVariantFromMessage(sdbusplus::message_t& m, nlohmann::json& data)
+{
+    const char* containerType = nullptr;
+    int r = sd_bus_message_peek_type(m.get(), nullptr, &containerType);
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_peek_type failed");
+        return r;
+    }
+
+    r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_VARIANT,
+                                       containerType);
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
+        return r;
+    }
+
+    r = convertDBusToJSON(containerType, m, data);
+    if (r < 0)
+    {
+        return r;
+    }
+
+    r = sd_bus_message_exit_container(m.get());
+    if (r < 0)
+    {
+        BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed");
+        return r;
+    }
+
+    return 0;
+}
+
+inline int convertDBusToJSON(const std::string& returnType,
+                             sdbusplus::message_t& m, nlohmann::json& response)
+{
+    int r = 0;
+    const std::vector<std::string> returnTypes = dbusArgSplit(returnType);
+
+    for (const std::string& typeCode : returnTypes)
+    {
+        nlohmann::json* thisElement = &response;
+        if (returnTypes.size() > 1)
+        {
+            response.push_back(nlohmann::json{});
+            thisElement = &response.back();
+        }
+
+        if (typeCode == "s" || typeCode == "g" || typeCode == "o")
+        {
+            r = readMessageItem<char*>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "b")
+        {
+            r = readMessageItem<int>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+
+            *thisElement = static_cast<bool>(thisElement->get<int>());
+        }
+        else if (typeCode == "u")
+        {
+            r = readMessageItem<uint32_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "i")
+        {
+            r = readMessageItem<int32_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "x")
+        {
+            r = readMessageItem<int64_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "t")
+        {
+            r = readMessageItem<uint64_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "n")
+        {
+            r = readMessageItem<int16_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "q")
+        {
+            r = readMessageItem<uint16_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "y")
+        {
+            r = readMessageItem<uint8_t>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "d")
+        {
+            r = readMessageItem<double>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode == "h")
+        {
+            r = readMessageItem<int>(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode.starts_with("a"))
+        {
+            r = readArrayFromMessage(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode.starts_with("(") && typeCode.ends_with(")"))
+        {
+            r = readStructFromMessage(typeCode, m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else if (typeCode.starts_with("v"))
+        {
+            r = readVariantFromMessage(m, *thisElement);
+            if (r < 0)
+            {
+                return r;
+            }
+        }
+        else
+        {
+            BMCWEB_LOG_ERROR("Invalid D-Bus signature type {}", typeCode);
+            return -2;
+        }
+    }
+
+    return 0;
+}
+
+inline void handleMethodResponse(
+    const std::shared_ptr<InProgressActionData>& transaction,
+    sdbusplus::message_t& m, const std::string& returnType)
+{
+    nlohmann::json data;
+
+    int r = convertDBusToJSON(returnType, m, data);
+    if (r < 0)
+    {
+        transaction->outputFailed = true;
+        return;
+    }
+
+    if (data.is_null())
+    {
+        return;
+    }
+
+    if (transaction->methodResponse.is_null())
+    {
+        transaction->methodResponse = std::move(data);
+        return;
+    }
+
+    // If they're both dictionaries or arrays, merge into one.
+    // Otherwise, make the results an array with every result
+    // an entry.  Could also just fail in that case, but it
+    // seems better to get the data back somehow.
+    nlohmann::json::object_t* dataobj =
+        data.get_ptr<nlohmann::json::object_t*>();
+
+    nlohmann::json::object_t* methodResponseObj =
+        transaction->methodResponse.get_ptr<nlohmann::json::object_t*>();
+    if (methodResponseObj != nullptr && dataobj != nullptr)
+    {
+        for (auto& obj : *dataobj)
+        {
+            // Note: Will overwrite the data for a duplicate key
+            methodResponseObj->emplace(obj.first, std::move(obj.second));
+        }
+        return;
+    }
+
+    nlohmann::json::array_t* dataarr = data.get_ptr<nlohmann::json::array_t*>();
+    nlohmann::json::array_t* methodResponseArr =
+        transaction->methodResponse.get_ptr<nlohmann::json::array_t*>();
+    if (methodResponseArr != nullptr && dataarr != nullptr)
+    {
+        for (auto& obj : *dataarr)
+        {
+            methodResponseArr->emplace_back(std::move(obj));
+        }
+        return;
+    }
+
+    if (!transaction->convertedToArray)
+    {
+        // They are different types. May as well turn them into an array
+        nlohmann::json j = std::move(transaction->methodResponse);
+        transaction->methodResponse = nlohmann::json::array();
+        transaction->methodResponse.emplace_back(std::move(j));
+        transaction->methodResponse.emplace_back(std::move(data));
+        transaction->convertedToArray = true;
+    }
+    else
+    {
+        transaction->methodResponse.emplace_back(std::move(data));
+    }
+}
+
+inline void findActionOnInterface(
+    const std::shared_ptr<InProgressActionData>& transaction,
+    const std::string& connectionName)
+{
+    BMCWEB_LOG_DEBUG("findActionOnInterface for connection {}", connectionName);
+    dbus::utility::async_method_call(
+        [transaction, connectionName{std::string(connectionName)}](
+            const boost::system::error_code& ec,
+            const std::string& introspectXml) {
+            BMCWEB_LOG_DEBUG("got xml:\n {}", introspectXml);
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR(
+                    "Introspect call failed with error: {} on process: {}",
+                    ec.message(), connectionName);
+                return;
+            }
+            tinyxml2::XMLDocument doc;
+
+            doc.Parse(introspectXml.data(), introspectXml.size());
+            tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
+            if (pRoot == nullptr)
+            {
+                BMCWEB_LOG_ERROR("XML document failed to parse {}",
+                                 connectionName);
+                return;
+            }
+            tinyxml2::XMLElement* interfaceNode =
+                pRoot->FirstChildElement("interface");
+            while (interfaceNode != nullptr)
+            {
+                const char* thisInterfaceName =
+                    interfaceNode->Attribute("name");
+                if (thisInterfaceName != nullptr)
+                {
+                    if (!transaction->interfaceName.empty() &&
+                        (transaction->interfaceName != thisInterfaceName))
+                    {
+                        interfaceNode =
+                            interfaceNode->NextSiblingElement("interface");
+                        continue;
+                    }
+
+                    tinyxml2::XMLElement* methodNode =
+                        interfaceNode->FirstChildElement("method");
+                    while (methodNode != nullptr)
+                    {
+                        const char* thisMethodName =
+                            methodNode->Attribute("name");
+                        BMCWEB_LOG_DEBUG("Found method: {}", thisMethodName);
+                        if (thisMethodName != nullptr &&
+                            thisMethodName == transaction->methodName)
+                        {
+                            BMCWEB_LOG_DEBUG(
+                                "Found method named {} on interface {}",
+                                thisMethodName, thisInterfaceName);
+                            sdbusplus::message_t m =
+                                crow::connections::systemBus->new_method_call(
+                                    connectionName.c_str(),
+                                    transaction->path.c_str(),
+                                    thisInterfaceName,
+                                    transaction->methodName.c_str());
+
+                            tinyxml2::XMLElement* argumentNode =
+                                methodNode->FirstChildElement("arg");
+
+                            std::string returnType;
+
+                            // Find the output type
+                            while (argumentNode != nullptr)
+                            {
+                                const char* argDirection =
+                                    argumentNode->Attribute("direction");
+                                const char* argType =
+                                    argumentNode->Attribute("type");
+                                if (argDirection != nullptr &&
+                                    argType != nullptr &&
+                                    std::string(argDirection) == "out")
+                                {
+                                    returnType = argType;
+                                    break;
+                                }
+                                argumentNode =
+                                    argumentNode->NextSiblingElement("arg");
+                            }
+
+                            auto argIt = transaction->arguments.begin();
+
+                            argumentNode = methodNode->FirstChildElement("arg");
+
+                            while (argumentNode != nullptr)
+                            {
+                                const char* argDirection =
+                                    argumentNode->Attribute("direction");
+                                const char* argType =
+                                    argumentNode->Attribute("type");
+                                if (argDirection != nullptr &&
+                                    argType != nullptr &&
+                                    std::string(argDirection) == "in")
+                                {
+                                    if (argIt == transaction->arguments.end())
+                                    {
+                                        transaction->setErrorStatus(
+                                            "Invalid method args");
+                                        return;
+                                    }
+                                    if (convertJsonToDbus(m.get(),
+                                                          std::string(argType),
+                                                          *argIt) < 0)
+                                    {
+                                        transaction->setErrorStatus(
+                                            "Invalid method arg type");
+                                        return;
+                                    }
+
+                                    argIt++;
+                                }
+                                argumentNode =
+                                    argumentNode->NextSiblingElement("arg");
+                            }
+
+                            crow::connections::systemBus->async_send(
+                                m, [transaction, returnType](
+                                       const boost::system::error_code& ec2,
+                                       sdbusplus::message_t& m2) {
+                                    if (ec2)
+                                    {
+                                        transaction->methodFailed = true;
+                                        const sd_bus_error* e = m2.get_error();
+
+                                        if (e != nullptr)
+                                        {
+                                            setErrorResponse(
+                                                transaction->asyncResp->res,
+                                                boost::beast::http::status::
+                                                    bad_request,
+                                                e->name, e->message);
+                                        }
+                                        else
+                                        {
+                                            setErrorResponse(
+                                                transaction->asyncResp->res,
+                                                boost::beast::http::status::
+                                                    bad_request,
+                                                "Method call failed",
+                                                methodFailedMsg);
+                                        }
+                                        return;
+                                    }
+                                    transaction->methodPassed = true;
+
+                                    handleMethodResponse(transaction, m2,
+                                                         returnType);
+                                });
+                            break;
+                        }
+                        methodNode = methodNode->NextSiblingElement("method");
+                    }
+                }
+                interfaceNode = interfaceNode->NextSiblingElement("interface");
+            }
+        },
+        connectionName, transaction->path,
+        "org.freedesktop.DBus.Introspectable", "Introspect");
+}
+
+inline void handleAction(const crow::Request& req,
+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const std::string& objectPath,
+                         const std::string& methodName)
+{
+    BMCWEB_LOG_DEBUG("handleAction on path: {} and method {}", objectPath,
+                     methodName);
+    nlohmann::json requestDbusData;
+
+    JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
+    if (ret == JsonParseResult::BadContentType)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::unsupported_media_type,
+                         invalidContentType, unsupportedMediaMsg);
+        return;
+    }
+    if (ret != JsonParseResult::Success)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::bad_request, noJsonDesc,
+                         badReqMsg);
+        return;
+    }
+    nlohmann::json::iterator data = requestDbusData.find("data");
+    if (data == requestDbusData.end())
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::bad_request, noJsonDesc,
+                         badReqMsg);
+        return;
+    }
+
+    if (!data->is_array())
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::bad_request, noJsonDesc,
+                         badReqMsg);
+        return;
+    }
+    auto transaction = std::make_shared<InProgressActionData>(asyncResp);
+
+    transaction->path = objectPath;
+    transaction->methodName = methodName;
+    transaction->arguments = std::move(*data);
+    dbus::utility::getDbusObject(
+        objectPath, {},
+        [transaction](
+            const boost::system::error_code& ec,
+            const std::vector<std::pair<std::string, std::vector<std::string>>>&
+                interfaceNames) {
+            if (ec || interfaceNames.empty())
+            {
+                BMCWEB_LOG_ERROR("Can't find object");
+                setErrorResponse(transaction->asyncResp->res,
+                                 boost::beast::http::status::not_found,
+                                 notFoundDesc, notFoundMsg);
+                return;
+            }
+
+            BMCWEB_LOG_DEBUG("GetObject returned {} object(s)",
+                             interfaceNames.size());
+
+            for (const std::pair<std::string, std::vector<std::string>>&
+                     object : interfaceNames)
+            {
+                findActionOnInterface(transaction, object.first);
+            }
+        });
+}
+
+inline void handleDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const std::string& objectPath)
+{
+    BMCWEB_LOG_DEBUG("handleDelete on path: {}", objectPath);
+
+    dbus::utility::getDbusObject(
+        objectPath, {},
+        [asyncResp, objectPath](
+            const boost::system::error_code& ec,
+            const std::vector<std::pair<std::string, std::vector<std::string>>>&
+                interfaceNames) {
+            if (ec || interfaceNames.empty())
+            {
+                BMCWEB_LOG_ERROR("Can't find object");
+                setErrorResponse(asyncResp->res,
+                                 boost::beast::http::status::method_not_allowed,
+                                 methodNotAllowedDesc, methodNotAllowedMsg);
+                return;
+            }
+
+            auto transaction =
+                std::make_shared<InProgressActionData>(asyncResp);
+            transaction->path = objectPath;
+            transaction->methodName = "Delete";
+            transaction->interfaceName = "xyz.openbmc_project.Object.Delete";
+
+            for (const std::pair<std::string, std::vector<std::string>>&
+                     object : interfaceNames)
+            {
+                findActionOnInterface(transaction, object.first);
+            }
+        });
+}
+
+inline void handleList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                       const std::string& objectPath, int32_t depth = 0)
+{
+    dbus::utility::getSubTreePaths(
+        objectPath, depth, {},
+        [asyncResp](
+            const boost::system::error_code& ec,
+            const dbus::utility::MapperGetSubTreePathsResponse& objectPaths) {
+            if (ec)
+            {
+                setErrorResponse(asyncResp->res,
+                                 boost::beast::http::status::not_found,
+                                 notFoundDesc, notFoundMsg);
+            }
+            else
+            {
+                asyncResp->res.jsonValue["status"] = "ok";
+                asyncResp->res.jsonValue["message"] = "200 OK";
+                asyncResp->res.jsonValue["data"] = objectPaths;
+            }
+        });
+}
+
+inline void handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                            const std::string& objectPath)
+{
+    BMCWEB_LOG_DEBUG("Doing enumerate on {}", objectPath);
+
+    asyncResp->res.jsonValue["message"] = "200 OK";
+    asyncResp->res.jsonValue["status"] = "ok";
+    asyncResp->res.jsonValue["data"] = nlohmann::json::object();
+
+    dbus::utility::getSubTree(
+        objectPath, 0, {},
+        [objectPath, asyncResp](
+            const boost::system::error_code& ec,
+            const dbus::utility::MapperGetSubTreeResponse& objectNames) {
+            auto transaction = std::make_shared<InProgressEnumerateData>(
+                objectPath, asyncResp);
+
+            transaction->subtree =
+                std::make_shared<dbus::utility::MapperGetSubTreeResponse>(
+                    objectNames);
+
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR("GetSubTree failed on {}",
+                                 transaction->objectPath);
+                setErrorResponse(transaction->asyncResp->res,
+                                 boost::beast::http::status::not_found,
+                                 notFoundDesc, notFoundMsg);
+                return;
+            }
+
+            // Add the data for the path passed in to the results
+            // as if GetSubTree returned it, and continue on enumerating
+            getObjectAndEnumerate(transaction);
+        });
+}
+
+inline void handleGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                      std::string& objectPath, std::string& destProperty)
+{
+    BMCWEB_LOG_DEBUG("handleGet: {} prop:{}", objectPath, destProperty);
+    std::shared_ptr<std::string> propertyName =
+        std::make_shared<std::string>(std::move(destProperty));
+
+    std::shared_ptr<std::string> path =
+        std::make_shared<std::string>(std::move(objectPath));
+
+    dbus::utility::getDbusObject(
+        *path, {},
+        [asyncResp, path,
+         propertyName](const boost::system::error_code& ec,
+                       const dbus::utility::MapperGetObject& objectNames) {
+            if (ec || objectNames.empty())
+            {
+                setErrorResponse(asyncResp->res,
+                                 boost::beast::http::status::not_found,
+                                 notFoundDesc, notFoundMsg);
+                return;
+            }
+            std::shared_ptr<nlohmann::json> response =
+                std::make_shared<nlohmann::json>(nlohmann::json::object());
+            for (const std::pair<std::string, std::vector<std::string>>&
+                     connection : objectNames)
+            {
+                const std::vector<std::string>& interfaceNames =
+                    connection.second;
+
+                if (interfaceNames.empty())
+                {
+                    // mapper allows empty interfaces in case an
+                    // object does not implement any interface.
+                    continue;
+                }
+
+                for (const std::string& interface : interfaceNames)
+                {
+                    sdbusplus::message_t m =
+                        crow::connections::systemBus->new_method_call(
+                            connection.first.c_str(), path->c_str(),
+                            "org.freedesktop.DBus.Properties", "GetAll");
+                    m.append(interface);
+                    crow::connections::systemBus->async_send(
+                        m, [asyncResp, response,
+                            propertyName](const boost::system::error_code& ec2,
+                                          sdbusplus::message_t& msg) {
+                            if (ec2)
+                            {
+                                BMCWEB_LOG_ERROR("Bad dbus request error: {}",
+                                                 ec2);
+                            }
+                            else
+                            {
+                                nlohmann::json properties;
+                                int r =
+                                    convertDBusToJSON("a{sv}", msg, properties);
+                                if (r < 0)
+                                {
+                                    BMCWEB_LOG_ERROR(
+                                        "convertDBusToJSON failed");
+                                }
+                                else
+                                {
+                                    nlohmann::json::object_t* obj =
+                                        properties.get_ptr<
+                                            nlohmann::json::object_t*>();
+                                    if (obj == nullptr)
+                                    {
+                                        return;
+                                    }
+                                    for (auto& prop : *obj)
+                                    {
+                                        // if property name is empty, or
+                                        // matches our search query, add it
+                                        // to the response json
+
+                                        if (propertyName->empty())
+                                        {
+                                            (*response)[prop.first] =
+                                                std::move(prop.second);
+                                        }
+                                        else if (prop.first == *propertyName)
+                                        {
+                                            *response = std::move(prop.second);
+                                        }
+                                    }
+                                }
+                            }
+                            if (response.use_count() == 1)
+                            {
+                                if (!propertyName->empty() && response->empty())
+                                {
+                                    setErrorResponse(
+                                        asyncResp->res,
+                                        boost::beast::http::status::not_found,
+                                        propNotFoundDesc, notFoundMsg);
+                                }
+                                else
+                                {
+                                    asyncResp->res.jsonValue["status"] = "ok";
+                                    asyncResp->res.jsonValue["message"] =
+                                        "200 OK";
+                                    asyncResp->res.jsonValue["data"] =
+                                        *response;
+                                }
+                            }
+                        });
+                }
+            }
+        });
+}
+
+struct AsyncPutRequest
+{
+    explicit AsyncPutRequest(const std::shared_ptr<bmcweb::AsyncResp>& resIn) :
+        asyncResp(resIn)
+    {}
+    ~AsyncPutRequest()
+    {
+        if (asyncResp->res.jsonValue.empty())
+        {
+            setErrorResponse(asyncResp->res,
+                             boost::beast::http::status::forbidden,
+                             forbiddenMsg, forbiddenPropDesc);
+        }
+    }
+
+    AsyncPutRequest(const AsyncPutRequest&) = delete;
+    AsyncPutRequest(AsyncPutRequest&&) = delete;
+    AsyncPutRequest& operator=(const AsyncPutRequest&) = delete;
+    AsyncPutRequest& operator=(AsyncPutRequest&&) = delete;
+
+    void setErrorStatus(const std::string& desc)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::internal_server_error,
+                         desc, badReqMsg);
+    }
+
+    const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
+    std::string objectPath;
+    std::string propertyName;
+    nlohmann::json propertyValue;
+};
+
+inline void handlePut(const crow::Request& req,
+                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                      const std::string& objectPath,
+                      const std::string& destProperty)
+{
+    if (destProperty.empty())
+    {
+        setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden,
+                         forbiddenResDesc, forbiddenMsg);
+        return;
+    }
+    nlohmann::json requestDbusData;
+
+    JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
+    if (ret == JsonParseResult::BadContentType)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::unsupported_media_type,
+                         invalidContentType, unsupportedMediaMsg);
+        return;
+    }
+
+    if (ret != JsonParseResult::Success)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::bad_request, noJsonDesc,
+                         badReqMsg);
+        return;
+    }
+
+    auto propertyIt = requestDbusData.find("data");
+    if (propertyIt == requestDbusData.end())
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::bad_request, noJsonDesc,
+                         badReqMsg);
+        return;
+    }
+    const nlohmann::json& propertySetValue = *propertyIt;
+    auto transaction = std::make_shared<AsyncPutRequest>(asyncResp);
+    transaction->objectPath = objectPath;
+    transaction->propertyName = destProperty;
+    transaction->propertyValue = propertySetValue;
+
+    dbus::utility::getDbusObject(
+        transaction->objectPath, {},
+        [transaction](const boost::system::error_code& ec2,
+                      const dbus::utility::MapperGetObject& objectNames) {
+            if (!ec2 && objectNames.empty())
+            {
+                setErrorResponse(transaction->asyncResp->res,
+                                 boost::beast::http::status::not_found,
+                                 propNotFoundDesc, notFoundMsg);
+                return;
+            }
+
+            for (const std::pair<std::string, std::vector<std::string>>&
+                     connection : objectNames)
+            {
+                const std::string& connectionName = connection.first;
+
+                dbus::utility::async_method_call(
+                    [connectionName{std::string(connectionName)},
+                     transaction](const boost::system::error_code& ec3,
+                                  const std::string& introspectXml) {
+                        if (ec3)
+                        {
+                            BMCWEB_LOG_ERROR(
+                                "Introspect call failed with error: {} on process: {}",
+                                ec3.message(), connectionName);
+                            transaction->setErrorStatus("Unexpected Error");
+                            return;
+                        }
+                        tinyxml2::XMLDocument doc;
+
+                        doc.Parse(introspectXml.c_str());
+                        tinyxml2::XMLNode* pRoot =
+                            doc.FirstChildElement("node");
+                        if (pRoot == nullptr)
+                        {
+                            BMCWEB_LOG_ERROR("XML document failed to parse: {}",
+                                             introspectXml);
+                            transaction->setErrorStatus("Unexpected Error");
+                            return;
+                        }
+                        tinyxml2::XMLElement* ifaceNode =
+                            pRoot->FirstChildElement("interface");
+                        while (ifaceNode != nullptr)
+                        {
+                            const char* interfaceName =
+                                ifaceNode->Attribute("name");
+                            BMCWEB_LOG_DEBUG("found interface {}",
+                                             interfaceName);
+                            tinyxml2::XMLElement* propNode =
+                                ifaceNode->FirstChildElement("property");
+                            while (propNode != nullptr)
+                            {
+                                const char* propertyName =
+                                    propNode->Attribute("name");
+                                if (propertyName == nullptr)
+                                {
+                                    BMCWEB_LOG_DEBUG(
+                                        "Couldn't find name property");
+                                    continue;
+                                }
+                                BMCWEB_LOG_DEBUG("Found property {}",
+                                                 propertyName);
+                                if (propertyName == transaction->propertyName)
+                                {
+                                    const char* argType =
+                                        propNode->Attribute("type");
+                                    if (argType != nullptr)
+                                    {
+                                        sdbusplus::message_t m =
+                                            crow::connections::systemBus
+                                                ->new_method_call(
+                                                    connectionName.c_str(),
+                                                    transaction->objectPath
+                                                        .c_str(),
+                                                    "org.freedesktop.DBus."
+                                                    "Properties",
+                                                    "Set");
+                                        m.append(interfaceName,
+                                                 transaction->propertyName);
+                                        int r = sd_bus_message_open_container(
+                                            m.get(), SD_BUS_TYPE_VARIANT,
+                                            argType);
+                                        if (r < 0)
+                                        {
+                                            transaction->setErrorStatus(
+                                                "Unexpected Error");
+                                            return;
+                                        }
+                                        r = convertJsonToDbus(
+                                            m.get(), argType,
+                                            transaction->propertyValue);
+                                        if (r < 0)
+                                        {
+                                            if (r == -ERANGE)
+                                            {
+                                                transaction->setErrorStatus(
+                                                    "Provided property value "
+                                                    "is out of range for the "
+                                                    "property type");
+                                            }
+                                            else
+                                            {
+                                                transaction->setErrorStatus(
+                                                    "Invalid arg type");
+                                            }
+                                            return;
+                                        }
+                                        r = sd_bus_message_close_container(
+                                            m.get());
+                                        if (r < 0)
+                                        {
+                                            transaction->setErrorStatus(
+                                                "Unexpected Error");
+                                            return;
+                                        }
+                                        crow::connections::systemBus
+                                            ->async_send(
+                                                m,
+                                                [transaction](
+                                                    const boost::system::
+                                                        error_code& ec,
+                                                    sdbusplus::message_t& m2) {
+                                                    BMCWEB_LOG_DEBUG("sent");
+                                                    if (ec)
+                                                    {
+                                                        const sd_bus_error* e =
+                                                            m2.get_error();
+                                                        setErrorResponse(
+                                                            transaction
+                                                                ->asyncResp
+                                                                ->res,
+                                                            boost::beast::http::
+                                                                status::
+                                                                    forbidden,
+                                                            (e) != nullptr
+                                                                ? e->name
+                                                                : ec.category()
+                                                                      .name(),
+                                                            (e) != nullptr
+                                                                ? e->message
+                                                                : ec.message());
+                                                    }
+                                                    else
+                                                    {
+                                                        transaction->asyncResp
+                                                            ->res.jsonValue
+                                                                ["status"] =
+                                                            "ok";
+                                                        transaction->asyncResp
+                                                            ->res.jsonValue
+                                                                ["message"] =
+                                                            "200 OK";
+                                                        transaction->asyncResp
+                                                            ->res
+                                                            .jsonValue["data"] =
+                                                            nullptr;
+                                                    }
+                                                });
+                                    }
+                                }
+                                propNode =
+                                    propNode->NextSiblingElement("property");
+                            }
+                            ifaceNode =
+                                ifaceNode->NextSiblingElement("interface");
+                        }
+                    },
+                    connectionName, transaction->objectPath,
+                    "org.freedesktop.DBus.Introspectable", "Introspect");
+            }
+        });
+}
+
+inline void handleDBusUrl(const crow::Request& req,
+                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                          std::string& objectPath)
+{
+    // If accessing a single attribute, fill in and update objectPath,
+    // otherwise leave destProperty blank
+    std::string destProperty;
+    const char* attrSeperator = "/attr/";
+    size_t attrPosition = objectPath.find(attrSeperator);
+    if (attrPosition != std::string::npos)
+    {
+        destProperty = objectPath.substr(attrPosition + strlen(attrSeperator),
+                                         objectPath.length());
+        objectPath.resize(attrPosition);
+    }
+
+    if (req.method() == boost::beast::http::verb::post)
+    {
+        constexpr const char* actionSeperator = "/action/";
+        size_t actionPosition = objectPath.find(actionSeperator);
+        if (actionPosition != std::string::npos)
+        {
+            std::string postProperty =
+                objectPath.substr((actionPosition + strlen(actionSeperator)),
+                                  objectPath.length());
+            objectPath.resize(actionPosition);
+            handleAction(req, asyncResp, objectPath, postProperty);
+            return;
+        }
+    }
+    else if (req.method() == boost::beast::http::verb::get)
+    {
+        if (objectPath.ends_with("/enumerate"))
+        {
+            objectPath.erase(objectPath.end() - sizeof("enumerate"),
+                             objectPath.end());
+            handleEnumerate(asyncResp, objectPath);
+        }
+        else if (objectPath.ends_with("/list"))
+        {
+            objectPath.erase(objectPath.end() - sizeof("list"),
+                             objectPath.end());
+            handleList(asyncResp, objectPath);
+        }
+        else
+        {
+            // Trim any trailing "/" at the end
+            if (objectPath.ends_with("/"))
+            {
+                objectPath.pop_back();
+                handleList(asyncResp, objectPath, 1);
+            }
+            else
+            {
+                handleGet(asyncResp, objectPath, destProperty);
+            }
+        }
+        return;
+    }
+    else if (req.method() == boost::beast::http::verb::put)
+    {
+        handlePut(req, asyncResp, objectPath, destProperty);
+        return;
+    }
+    else if (req.method() == boost::beast::http::verb::delete_)
+    {
+        handleDelete(asyncResp, objectPath);
+        return;
+    }
+
+    setErrorResponse(asyncResp->res,
+                     boost::beast::http::status::method_not_allowed,
+                     methodNotAllowedDesc, methodNotAllowedMsg);
+}
+
+inline void handleBusSystemPost(
+    const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& processName, const std::string& requestedPath)
+{
+    std::vector<std::string> strs;
+
+    bmcweb::split(strs, requestedPath, '/');
+    std::string objectPath;
+    std::string interfaceName;
+    std::string methodName;
+    auto it = strs.begin();
+    if (it == strs.end())
+    {
+        objectPath = "/";
+    }
+    while (it != strs.end())
+    {
+        // Check if segment contains ".".  If it does, it must be an
+        // interface
+        if (it->find(".") != std::string::npos)
+        {
+            break;
+            // This check is necessary as the trailing slash gets
+            // parsed as part of our <path> specifier above, which
+            // causes the normal trailing backslash redirector to
+            // fail.
+        }
+        if (!it->empty())
+        {
+            objectPath += "/" + *it;
+        }
+        it++;
+    }
+    if (it != strs.end())
+    {
+        interfaceName = *it;
+        it++;
+
+        // after interface, we might have a method name
+        if (it != strs.end())
+        {
+            methodName = *it;
+            it++;
+        }
+    }
+    if (it != strs.end())
+    {
+        // if there is more levels past the method name, something
+        // went wrong, return not found
+        asyncResp->res.result(boost::beast::http::status::not_found);
+        return;
+    }
+    if (interfaceName.empty())
+    {
+        dbus::utility::async_method_call(
+            [asyncResp, processName,
+             objectPath](const boost::system::error_code& ec,
+                         const std::string& introspectXml) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR(
+                        "Introspect call failed with error: {} on process: {} path: {}",
+                        ec.message(), processName, objectPath);
+                    return;
+                }
+                tinyxml2::XMLDocument doc;
+
+                doc.Parse(introspectXml.c_str());
+                tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
+                if (pRoot == nullptr)
+                {
+                    BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
+                                     processName, objectPath);
+                    asyncResp->res.jsonValue["status"] = "XML parse error";
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+
+                BMCWEB_LOG_DEBUG("{}", introspectXml);
+                asyncResp->res.jsonValue["status"] = "ok";
+                asyncResp->res.jsonValue["bus_name"] = processName;
+                asyncResp->res.jsonValue["object_path"] = objectPath;
+
+                nlohmann::json& interfacesArray =
+                    asyncResp->res.jsonValue["interfaces"];
+                interfacesArray = nlohmann::json::array();
+                tinyxml2::XMLElement* interface =
+                    pRoot->FirstChildElement("interface");
+
+                while (interface != nullptr)
+                {
+                    const char* ifaceName = interface->Attribute("name");
+                    if (ifaceName != nullptr)
+                    {
+                        nlohmann::json::object_t interfaceObj;
+                        interfaceObj["name"] = ifaceName;
+                        interfacesArray.emplace_back(std::move(interfaceObj));
+                    }
+
+                    interface = interface->NextSiblingElement("interface");
+                }
+            },
+            processName, objectPath, "org.freedesktop.DBus.Introspectable",
+            "Introspect");
+    }
+    else if (methodName.empty())
+    {
+        dbus::utility::async_method_call(
+            [asyncResp, processName, objectPath,
+             interfaceName](const boost::system::error_code& ec,
+                            const std::string& introspectXml) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR(
+                        "Introspect call failed with error: {} on process: {} path: {}",
+                        ec.message(), processName, objectPath);
+                    return;
+                }
+                tinyxml2::XMLDocument doc;
+
+                doc.Parse(introspectXml.data(), introspectXml.size());
+                tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
+                if (pRoot == nullptr)
+                {
+                    BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
+                                     processName, objectPath);
+                    asyncResp->res.result(
+                        boost::beast::http::status::internal_server_error);
+                    return;
+                }
+
+                asyncResp->res.jsonValue["status"] = "ok";
+                asyncResp->res.jsonValue["bus_name"] = processName;
+                asyncResp->res.jsonValue["interface"] = interfaceName;
+                asyncResp->res.jsonValue["object_path"] = objectPath;
+
+                nlohmann::json& methodsArray =
+                    asyncResp->res.jsonValue["methods"];
+                methodsArray = nlohmann::json::array();
+
+                nlohmann::json& signalsArray =
+                    asyncResp->res.jsonValue["signals"];
+                signalsArray = nlohmann::json::array();
+
+                nlohmann::json& propertiesObj =
+                    asyncResp->res.jsonValue["properties"];
+                propertiesObj = nlohmann::json::object();
+
+                // if we know we're the only call, build the
+                // json directly
+                tinyxml2::XMLElement* interface =
+                    pRoot->FirstChildElement("interface");
+                while (interface != nullptr)
+                {
+                    const char* ifaceName = interface->Attribute("name");
+
+                    if (ifaceName != nullptr && ifaceName == interfaceName)
+                    {
+                        break;
+                    }
+
+                    interface = interface->NextSiblingElement("interface");
+                }
+                if (interface == nullptr)
+                {
+                    // if we got to the end of the list and
+                    // never found a match, throw 404
+                    asyncResp->res.result(
+                        boost::beast::http::status::not_found);
+                    return;
+                }
+
+                tinyxml2::XMLElement* methods =
+                    interface->FirstChildElement("method");
+                while (methods != nullptr)
+                {
+                    nlohmann::json argsArray = nlohmann::json::array();
+                    tinyxml2::XMLElement* arg =
+                        methods->FirstChildElement("arg");
+                    while (arg != nullptr)
+                    {
+                        nlohmann::json thisArg;
+                        for (const char* fieldName : std::array<const char*, 3>{
+                                 "name", "direction", "type"})
+                        {
+                            const char* fieldValue = arg->Attribute(fieldName);
+                            if (fieldValue != nullptr)
+                            {
+                                thisArg[fieldName] = fieldValue;
+                            }
+                        }
+                        argsArray.emplace_back(std::move(thisArg));
+                        arg = arg->NextSiblingElement("arg");
+                    }
+
+                    const char* name = methods->Attribute("name");
+                    if (name != nullptr)
+                    {
+                        std::string uri;
+                        uri.reserve(14 + processName.size() +
+                                    objectPath.size() + interfaceName.size() +
+                                    strlen(name));
+                        uri += "/bus/system/";
+                        uri += processName;
+                        uri += objectPath;
+                        uri += "/";
+                        uri += interfaceName;
+                        uri += "/";
+                        uri += name;
+
+                        nlohmann::json::object_t object;
+                        object["name"] = name;
+                        object["uri"] = std::move(uri);
+                        object["args"] = argsArray;
+
+                        methodsArray.emplace_back(std::move(object));
+                    }
+                    methods = methods->NextSiblingElement("method");
+                }
+                tinyxml2::XMLElement* signals =
+                    interface->FirstChildElement("signal");
+                while (signals != nullptr)
+                {
+                    nlohmann::json argsArray = nlohmann::json::array();
+
+                    tinyxml2::XMLElement* arg =
+                        signals->FirstChildElement("arg");
+                    while (arg != nullptr)
+                    {
+                        const char* name = arg->Attribute("name");
+                        const char* type = arg->Attribute("type");
+                        if (name != nullptr && type != nullptr)
+                        {
+                            nlohmann::json::object_t params;
+                            params["name"] = name;
+                            params["type"] = type;
+                            argsArray.push_back(std::move(params));
+                        }
+                        arg = arg->NextSiblingElement("arg");
+                    }
+                    const char* name = signals->Attribute("name");
+                    if (name != nullptr)
+                    {
+                        nlohmann::json::object_t object;
+                        object["name"] = name;
+                        object["args"] = argsArray;
+                        signalsArray.emplace_back(std::move(object));
+                    }
+
+                    signals = signals->NextSiblingElement("signal");
+                }
+
+                tinyxml2::XMLElement* property =
+                    interface->FirstChildElement("property");
+                while (property != nullptr)
+                {
+                    const char* name = property->Attribute("name");
+                    const char* type = property->Attribute("type");
+                    if (type != nullptr && name != nullptr)
+                    {
+                        sdbusplus::message_t m =
+                            crow::connections::systemBus->new_method_call(
+                                processName.c_str(), objectPath.c_str(),
+                                "org.freedesktop."
+                                "DBus."
+                                "Properties",
+                                "Get");
+                        m.append(interfaceName, name);
+                        nlohmann::json& propertyItem = propertiesObj[name];
+                        crow::connections::systemBus->async_send(
+                            m, [&propertyItem,
+                                asyncResp](const boost::system::error_code& ec2,
+                                           sdbusplus::message_t& msg) {
+                                if (ec2)
+                                {
+                                    return;
+                                }
+
+                                int r =
+                                    convertDBusToJSON("v", msg, propertyItem);
+                                if (r < 0)
+                                {
+                                    BMCWEB_LOG_ERROR(
+                                        "Couldn't convert vector to json");
+                                }
+                            });
+                    }
+                    property = property->NextSiblingElement("property");
+                }
+            },
+            processName, objectPath, "org.freedesktop.DBus.Introspectable",
+            "Introspect");
+    }
+    else
+    {
+        if (req.method() != boost::beast::http::verb::post)
+        {
+            asyncResp->res.result(boost::beast::http::status::not_found);
+            return;
+        }
+
+        nlohmann::json requestDbusData;
+        JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
+        if (ret == JsonParseResult::BadContentType)
+        {
+            setErrorResponse(asyncResp->res,
+                             boost::beast::http::status::unsupported_media_type,
+                             invalidContentType, unsupportedMediaMsg);
+            return;
+        }
+        if (ret != JsonParseResult::Success)
+        {
+            setErrorResponse(asyncResp->res,
+                             boost::beast::http::status::bad_request,
+                             noJsonDesc, badReqMsg);
+            return;
+        }
+
+        if (!requestDbusData.is_array())
+        {
+            asyncResp->res.result(boost::beast::http::status::bad_request);
+            return;
+        }
+        auto transaction = std::make_shared<InProgressActionData>(asyncResp);
+
+        transaction->path = objectPath;
+        transaction->methodName = methodName;
+        transaction->arguments = std::move(requestDbusData);
+
+        findActionOnInterface(transaction, processName);
+    }
+}
+
+inline void requestRoutes(App& app)
+{
+    BMCWEB_ROUTE(app, "/bus/")
+        .privileges({{"Login"}})
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+                nlohmann::json::array_t buses;
+                nlohmann::json& bus = buses.emplace_back();
+                bus["name"] = "system";
+                asyncResp->res.jsonValue["busses"] = std::move(buses);
+                asyncResp->res.jsonValue["status"] = "ok";
+            });
+
+    BMCWEB_ROUTE(app, "/bus/system/")
+        .privileges({{"Login"}})
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+                auto myCallback = [asyncResp](
+                                      const boost::system::error_code& ec,
+                                      std::vector<std::string>& names) {
+                    if (ec)
+                    {
+                        BMCWEB_LOG_ERROR("Dbus call failed with code {}", ec);
+                        asyncResp->res.result(
+                            boost::beast::http::status::internal_server_error);
+                    }
+                    else
+                    {
+                        std::ranges::sort(names);
+                        asyncResp->res.jsonValue["status"] = "ok";
+                        auto& objectsSub = asyncResp->res.jsonValue["objects"];
+                        for (const auto& name : names)
+                        {
+                            nlohmann::json::object_t object;
+                            object["name"] = name;
+                            objectsSub.emplace_back(std::move(object));
+                        }
+                    }
+                };
+                dbus::utility::async_method_call(
+                    std::move(myCallback), "org.freedesktop.DBus", "/",
+                    "org.freedesktop.DBus", "ListNames");
+            });
+
+    BMCWEB_ROUTE(app, "/list/")
+        .privileges({{"Login"}})
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+                handleList(asyncResp, "/");
+            });
+
+    BMCWEB_ROUTE(app, "/xyz/<path>")
+        .privileges({{"Login"}})
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request& req,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& path) {
+                std::string objectPath = "/xyz/" + path;
+                handleDBusUrl(req, asyncResp, objectPath);
+            });
+
+    BMCWEB_ROUTE(app, "/xyz/<path>")
+        .privileges({{"ConfigureComponents", "ConfigureManager"}})
+        .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
+                 boost::beast::http::verb::delete_)(
+            [](const crow::Request& req,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& path) {
+                std::string objectPath = "/xyz/" + path;
+                handleDBusUrl(req, asyncResp, objectPath);
+            });
+
+    BMCWEB_ROUTE(app, "/org/<path>")
+        .privileges({{"Login"}})
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request& req,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& path) {
+                std::string objectPath = "/org/" + path;
+                handleDBusUrl(req, asyncResp, objectPath);
+            });
+
+    BMCWEB_ROUTE(app, "/org/<path>")
+        .privileges({{"ConfigureComponents", "ConfigureManager"}})
+        .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
+                 boost::beast::http::verb::delete_)(
+            [](const crow::Request& req,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& path) {
+                std::string objectPath = "/org/" + path;
+                handleDBusUrl(req, asyncResp, objectPath);
+            });
+
+    BMCWEB_ROUTE(app, "/download/dump/<str>/")
+        .privileges({{"ConfigureManager"}})
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& dumpId) {
+                if (!validateFilename(dumpId))
+                {
+                    asyncResp->res.result(
+                        boost::beast::http::status::bad_request);
+                    return;
+                }
+                std::filesystem::path loc(
+                    "/var/lib/phosphor-debug-collector/dumps");
+
+                loc /= dumpId;
+
+                if (!std::filesystem::exists(loc) ||
+                    !std::filesystem::is_directory(loc))
+                {
+                    BMCWEB_LOG_ERROR("{}Not found", loc.string());
+                    asyncResp->res.result(
+                        boost::beast::http::status::not_found);
+                    return;
+                }
+                std::filesystem::directory_iterator files(loc);
+
+                for (const auto& file : files)
+                {
+                    if (asyncResp->res.openFile(file) !=
+                        crow::OpenCode::Success)
+                    {
+                        continue;
+                    }
+
+                    asyncResp->res.addHeader(
+                        boost::beast::http::field::content_type,
+                        "application/octet-stream");
+
+                    // Assuming only one dump file will be present in the dump
+                    // id directory
+                    std::string dumpFileName = file.path().filename().string();
+
+                    // Filename should be in alphanumeric, dot and underscore
+                    // Its based on phosphor-debug-collector application
+                    // dumpfile format
+                    static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
+                    if (!std::regex_match(dumpFileName, dumpFileRegex))
+                    {
+                        BMCWEB_LOG_ERROR("Invalid dump filename {}",
+                                         dumpFileName);
+                        asyncResp->res.result(
+                            boost::beast::http::status::not_found);
+                        return;
+                    }
+                    std::string contentDispositionParam =
+                        "attachment; filename=\"" + dumpFileName + "\"";
+
+                    asyncResp->res.addHeader(
+                        boost::beast::http::field::content_disposition,
+                        contentDispositionParam);
+
+                    return;
+                }
+                asyncResp->res.result(boost::beast::http::status::not_found);
+                return;
+            });
+
+    BMCWEB_ROUTE(app, "/bus/system/<str>/")
+        .privileges({{"Login"}})
+
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& connection) {
+                introspectObjects(connection, "/", asyncResp);
+            });
+
+    BMCWEB_ROUTE(app, "/bus/system/<str>/<path>")
+        .privileges({{"ConfigureComponents", "ConfigureManager"}})
+        .methods(boost::beast::http::verb::get,
+                 boost::beast::http::verb::post)(handleBusSystemPost);
+}
+} // namespace openbmc_mapper
+} // namespace crow
diff --git a/features/openbmc_rest/openbmc_dbus_rest_test.cpp b/features/openbmc_rest/openbmc_dbus_rest_test.cpp
new file mode 100644
index 0000000..1bc320e
--- /dev/null
+++ b/features/openbmc_rest/openbmc_dbus_rest_test.cpp
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#include "openbmc_dbus_rest.hpp"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace crow::openbmc_mapper
+{
+namespace
+{
+
+using ::testing::ElementsAre;
+// Also see redfish-core/ut/configfile_test.cpp
+TEST(OpenbmcDbusRestTest, ValidFilenameGood)
+{
+    EXPECT_TRUE(validateFilename("GoodConfigFile"));
+    EXPECT_TRUE(validateFilename("_Underlines_"));
+    EXPECT_TRUE(validateFilename("8675309"));
+    EXPECT_TRUE(validateFilename("-Dashes-"));
+    EXPECT_TRUE(validateFilename("With Spaces"));
+    EXPECT_TRUE(validateFilename("One.Dot"));
+    EXPECT_TRUE(validateFilename("trailingdot."));
+    EXPECT_TRUE(validateFilename("-_ o _-"));
+    EXPECT_TRUE(validateFilename(" "));
+    EXPECT_TRUE(validateFilename(" ."));
+}
+
+// There is no length test yet because validateFilename() does not care yet
+TEST(OpenbmcDbusRestTest, ValidFilenameBad)
+{
+    EXPECT_FALSE(validateFilename(""));
+    EXPECT_FALSE(validateFilename("Bad@file"));
+    EXPECT_FALSE(validateFilename("/../../../../../etc/badpath"));
+    EXPECT_FALSE(validateFilename("/../../etc/badpath"));
+    EXPECT_FALSE(validateFilename("/mydir/configFile"));
+    EXPECT_FALSE(validateFilename("/"));
+    EXPECT_FALSE(validateFilename(".leadingdot"));
+    EXPECT_FALSE(validateFilename("Two..Dots"));
+    EXPECT_FALSE(validateFilename("../../../../../../etc/shadow"));
+    EXPECT_FALSE(validateFilename("."));
+}
+
+TEST(OpenBmcDbusTest, TestArgSplit)
+{
+    // test the basic types
+    EXPECT_THAT(dbusArgSplit("x"), ElementsAre("x"));
+    EXPECT_THAT(dbusArgSplit("y"), ElementsAre("y"));
+    EXPECT_THAT(dbusArgSplit("b"), ElementsAre("b"));
+    EXPECT_THAT(dbusArgSplit("n"), ElementsAre("n"));
+    EXPECT_THAT(dbusArgSplit("q"), ElementsAre("q"));
+    EXPECT_THAT(dbusArgSplit("i"), ElementsAre("i"));
+    EXPECT_THAT(dbusArgSplit("u"), ElementsAre("u"));
+    EXPECT_THAT(dbusArgSplit("x"), ElementsAre("x"));
+    EXPECT_THAT(dbusArgSplit("t"), ElementsAre("t"));
+    EXPECT_THAT(dbusArgSplit("d"), ElementsAre("d"));
+    EXPECT_THAT(dbusArgSplit("h"), ElementsAre("h"));
+    // test arrays
+    EXPECT_THAT(dbusArgSplit("ai"), ElementsAre("ai"));
+    EXPECT_THAT(dbusArgSplit("ax"), ElementsAre("ax"));
+    // test tuples
+    EXPECT_THAT(dbusArgSplit("(sss)"), ElementsAre("(sss)"));
+    EXPECT_THAT(dbusArgSplit("(sss)b"), ElementsAre("(sss)", "b"));
+    EXPECT_THAT(dbusArgSplit("b(sss)"), ElementsAre("b", "(sss)"));
+
+    // Test nested types
+    EXPECT_THAT(dbusArgSplit("a{si}b"), ElementsAre("a{si}", "b"));
+    EXPECT_THAT(dbusArgSplit("a(sss)b"), ElementsAre("a(sss)", "b"));
+    EXPECT_THAT(dbusArgSplit("aa{si}b"), ElementsAre("aa{si}", "b"));
+    EXPECT_THAT(dbusArgSplit("i{si}b"), ElementsAre("i", "{si}", "b"));
+}
+} // namespace
+} // namespace crow::openbmc_mapper