Implement Cable schema

This commit implements Cable and Cable collection schema
on bmcweb.

Testing:
Validator:
@odata.id       /redfish/v1/Cables      odata   Exists  PASS
@odata.type     #CableCollection.CableCollection        odata   Exists
PASS
Members@odata.count     2       odata   Exists  PASS
Members Array (size: 2) links: Cable    Yes     ...
Members[0]      Link: /redfish/v1/Cables/dp0_cable0     link: Cable
Yes     PASS
Members[1]      Link: /redfish/v1/Cables/dp0_cable1     link: Cable
Yes     PASS
Description     Collection of Cable Entries     none    Yes     PASS
Name    Cable Collection        none    Yes     PASS
Oem     -       Resource.Oem    No      Optional

Property Name   Value   Type    Exists  Result
@odata.id       /redfish/v1/Cables/dp0_cable0   odata   Exists  PASS
@odata.type     #Cable.v1_0_0.Cable     odata   Exists  PASS
CableType               string  Yes     PASS
LengthMeters    -       number  No      Optional
Id      dp0_cable0      none    Yes     PASS
Name    Cable           none    Yes     PASS

Property Name   Value   Type    Exists  Result
@odata.id       /redfish/v1/Cables/dp0_cable1   odata   Exists  PASS
@odata.type     #Cable.v1_0_0.Cable     odata   Exists  PASS
CableType               string  Yes     PASS
LengthMeters    -       number  No      Optional
Id      dp0_cable1      none    Yes     PASS
Name    Cable           none    Yes     PASS

Note: Removed some of the fields that are optional to reduce commit msg

Tesing with Curl commands:
$ curl -k -X GET https://{$bmc}/redfish/v1/Cables
{
  "@odata.id": "/redfish/v1/Cables",
  "@odata.type": "#CableCollection.CableCollection",
  "Description": "Collection of Cable Entries",
  "Members": [
    {
      "@odata.id": "/redfish/v1/Cables/dp0_cable0"
    },
    {
      "@odata.id": "/redfish/v1/Cables/dp0_cable1"
    }
  ],
  "Members@odata.count": 2,
  "Name": "Cable Collection"
}

$ curl -k -X GET https://{$bmc}/redfish/v1/Cables/dp0_cable0
{
  "@odata.id": "/redfish/v1/Cables/dp0_cable0",
  "@odata.type": "#Cable.v1_0_0.Cable",
  "CableType": "",
  "Id": "dp0_cable0",
  "Name": "Cable"
}

$ curl -k -X GET https://{$bmc}/redfish/v1/Cables/dp0_cable1
{
  "@odata.id": "/redfish/v1/Cables/dp0_cable1",
  "@odata.type": "#Cable.v1_0_0.Cable",
  "CableType": "",
  "Id": "dp0_cable1",
  "Name": "Cable"
}

Set Length property to 1.5 meters using busctl, and check the properties
busctl set-property xyz.openbmc_project.Inventory.Manager \
 /xyz/openbmc_project/inventory/cables/dp0_cable0 \
 xyz.openbmc_project.Inventory.Item.Cable Length d 1.5

$ curl -k -X GET https://{$bmc}/redfish/v1/Cables/dp0_cable0
{
  "@odata.id": "/redfish/v1/Cables/dp0_cable0",
  "@odata.type": "#Cable.v1_0_0.Cable",
  "CableType": "",
  "Id": "dp0_cable0",
  "LengthMeters": 1.5,
  "Name": "Cable"
}

Signed-off-by: Shantappa Teekappanavar <sbteeks@yahoo.com>
Change-Id: I832ff1c1053f4d8100d04a42cc8046a61e8c1613
diff --git a/redfish-core/lib/cable.hpp b/redfish-core/lib/cable.hpp
new file mode 100644
index 0000000..05da5f6
--- /dev/null
+++ b/redfish-core/lib/cable.hpp
@@ -0,0 +1,184 @@
+#pragma once
+#include <boost/container/flat_map.hpp>
+#include <utils/json_utils.hpp>
+
+namespace redfish
+{
+/**
+ * @brief Fill cable specific properties.
+ * @param[in,out]   resp        HTTP response.
+ * @param[in]       ec          Error code corresponding to Async method call.
+ * @param[in]       properties  List of Cable Properties key/value pairs.
+ */
+inline void
+    fillCableProperties(crow::Response& resp,
+                        const boost::system::error_code ec,
+                        const dbus::utility::DBusPropertiesMap& properties)
+{
+    if (ec)
+    {
+        BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+        messages::internalError(resp);
+        return;
+    }
+
+    for (const auto& [propKey, propVariant] : properties)
+    {
+        if (propKey == "CableTypeDescription")
+        {
+            const std::string* cableTypeDescription =
+                std::get_if<std::string>(&propVariant);
+            if (cableTypeDescription == nullptr)
+            {
+                messages::internalError(resp);
+                return;
+            }
+            resp.jsonValue["CableType"] = *cableTypeDescription;
+        }
+        else if (propKey == "Length")
+        {
+            const double* cableLength = std::get_if<double>(&propVariant);
+            if (cableLength == nullptr)
+            {
+                messages::internalError(resp);
+                return;
+            }
+
+            if (!std::isfinite(*cableLength))
+            {
+                if (std::isnan(*cableLength))
+                {
+                    continue;
+                }
+                messages::internalError(resp);
+                return;
+            }
+
+            resp.jsonValue["LengthMeters"] = *cableLength;
+        }
+    }
+}
+
+/**
+ * @brief Api to get Cable properties.
+ * @param[in,out]   asyncResp       Async HTTP response.
+ * @param[in]       cableObjectPath Object path of the Cable.
+ * @param[in]       serviceMap      A map to hold Service and corresponding
+ * interface list for the given cable id.
+ */
+inline void
+    getCableProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                       const std::string& cableObjectPath,
+                       const dbus::utility::MapperServiceMap& serviceMap)
+{
+    BMCWEB_LOG_DEBUG << "Get Properties for cable " << cableObjectPath;
+
+    for (const auto& [service, interfaces] : serviceMap)
+    {
+        for (const auto& interface : interfaces)
+        {
+            if (interface != "xyz.openbmc_project.Inventory.Item.Cable")
+            {
+                continue;
+            }
+
+            crow::connections::systemBus->async_method_call(
+                [asyncResp](
+                    const boost::system::error_code ec,
+                    const dbus::utility::DBusPropertiesMap& properties) {
+                    fillCableProperties(asyncResp->res, ec, properties);
+                },
+                service, cableObjectPath, "org.freedesktop.DBus.Properties",
+                "GetAll", interface);
+        }
+    }
+}
+
+/**
+ * The Cable schema
+ */
+inline void requestRoutesCable(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Cables/<str>/")
+        .privileges(redfish::privileges::getCable)
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+               const std::string& cableId) {
+                BMCWEB_LOG_DEBUG << "Cable Id: " << cableId;
+                auto respHandler =
+                    [asyncResp,
+                     cableId](const boost::system::error_code ec,
+                              const dbus::utility::MapperGetSubTreeResponse&
+                                  subtree) {
+                        if (ec.value() == EBADR)
+                        {
+                            messages::resourceNotFound(
+                                asyncResp->res, "#Cable.v1_0_0.Cable", cableId);
+                            return;
+                        }
+
+                        if (ec)
+                        {
+                            BMCWEB_LOG_ERROR << "DBUS response error " << ec;
+                            messages::internalError(asyncResp->res);
+                            return;
+                        }
+
+                        for (const auto& [objectPath, serviceMap] : subtree)
+                        {
+                            sdbusplus::message::object_path path(objectPath);
+                            if (path.filename() != cableId)
+                            {
+                                continue;
+                            }
+
+                            asyncResp->res.jsonValue["@odata.type"] =
+                                "#Cable.v1_0_0.Cable";
+                            asyncResp->res.jsonValue["@odata.id"] =
+                                "/redfish/v1/Cables/" + cableId;
+                            asyncResp->res.jsonValue["Id"] = cableId;
+                            asyncResp->res.jsonValue["Name"] = "Cable";
+
+                            getCableProperties(asyncResp, objectPath,
+                                               serviceMap);
+                            return;
+                        }
+                        messages::resourceNotFound(asyncResp->res, "Cable",
+                                                   cableId);
+                    };
+
+                crow::connections::systemBus->async_method_call(
+                    respHandler, "xyz.openbmc_project.ObjectMapper",
+                    "/xyz/openbmc_project/object_mapper",
+                    "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+                    "/xyz/openbmc_project/inventory", 0,
+                    std::array<const char*, 1>{
+                        "xyz.openbmc_project.Inventory.Item.Cable"});
+            });
+}
+
+/**
+ * Collection of Cable resource instances
+ */
+inline void requestRoutesCableCollection(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Cables/")
+        .privileges(redfish::privileges::getCableCollection)
+        .methods(boost::beast::http::verb::get)(
+            [](const crow::Request&,
+               const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+                asyncResp->res.jsonValue["@odata.type"] =
+                    "#CableCollection.CableCollection";
+                asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Cables";
+                asyncResp->res.jsonValue["Name"] = "Cable Collection";
+                asyncResp->res.jsonValue["Description"] =
+                    "Collection of Cable Entries";
+
+                collection_util::getCollectionMembers(
+                    asyncResp, "/redfish/v1/Cables",
+                    {"xyz.openbmc_project.Inventory.Item.Cable"});
+            });
+}
+
+} // namespace redfish
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index 63cc210..62be63f 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -29,7 +29,8 @@
 {
 
     std::string uuid = persistent_data::getConfig().systemUuid;
-    asyncResp->res.jsonValue["@odata.type"] = "#ServiceRoot.v1_5_0.ServiceRoot";
+    asyncResp->res.jsonValue["@odata.type"] =
+        "#ServiceRoot.v1_11_0.ServiceRoot";
     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1";
     asyncResp->res.jsonValue["Id"] = "RootService";
     asyncResp->res.jsonValue["Name"] = "Root Service";
@@ -62,6 +63,7 @@
         {"@odata.id", "/redfish/v1/EventService"}};
     asyncResp->res.jsonValue["TelemetryService"] = {
         {"@odata.id", "/redfish/v1/TelemetryService"}};
+    asyncResp->res.jsonValue["Cables"] = {{"@odata.id", "/redfish/v1/Cables"}};
 }
 
 inline void requestRoutesServiceRoot(App& app)