Move getPortInfo to Redfish Utility

Plan to use getPortInfo() to get the SSH SerialConsole in the
ComputerSystem.
This commit moves the getPortInfo functionality into the redfish
utility.

Tested: manually tested on Witherspoon system, there is no change in
output. Run Redfish validator, no error found.

Before:

"HTTPS": {
  "Certificates": {
    "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/
Certificates"
  },
  "Port": 443,
  "ProtocolEnabled": true
},
"IPMI": {
  "Port": 623,
  "ProtocolEnabled": true
},
"SSH": {
  "Port": 22,
  "ProtocolEnabled": true
}

After:

"HTTPS": {
  "Certificates": {
    "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/
Certificates"
  },
  "Port": 443,
  "ProtocolEnabled": true
},
"IPMI": {
  "Port": 623,
  "ProtocolEnabled": true
},
"SSH": {
  "Port": 22,
  "ProtocolEnabled": true
}

Change-Id: I126827fbbecec59adcf630b88e31bc5ff8151588
Signed-off-by: Abhishek Patel <Abhishek.Patel@ibm.com>
diff --git a/redfish-core/lib/network_protocol.hpp b/redfish-core/lib/network_protocol.hpp
index 6c28b9e..0fd6eba 100644
--- a/redfish-core/lib/network_protocol.hpp
+++ b/redfish-core/lib/network_protocol.hpp
@@ -17,6 +17,7 @@
 
 #include "error_messages.hpp"
 #include "openbmc_dbus_rest.hpp"
+#include "redfish_util.hpp"
 
 #include <app.hpp>
 #include <registries/privilege_registry.hpp>
@@ -30,35 +31,7 @@
 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
 std::string getHostName();
 
-enum NetworkProtocolUnitStructFields
-{
-    NET_PROTO_UNIT_NAME,
-    NET_PROTO_UNIT_DESC,
-    NET_PROTO_UNIT_LOAD_STATE,
-    NET_PROTO_UNIT_ACTIVE_STATE,
-    NET_PROTO_UNIT_SUB_STATE,
-    NET_PROTO_UNIT_DEVICE,
-    NET_PROTO_UNIT_OBJ_PATH,
-    NET_PROTO_UNIT_ALWAYS_0,
-    NET_PROTO_UNIT_ALWAYS_EMPTY,
-    NET_PROTO_UNIT_ALWAYS_ROOT_PATH
-};
-
-enum NetworkProtocolListenResponseElements
-{
-    NET_PROTO_LISTEN_TYPE,
-    NET_PROTO_LISTEN_STREAM
-};
-
-/**
- * @brief D-Bus Unit structure returned in array from ListUnits Method
- */
-using UnitStruct =
-    std::tuple<std::string, std::string, std::string, std::string, std::string,
-               std::string, sdbusplus::message::object_path, uint32_t,
-               std::string, sdbusplus::message::object_path>;
-
-const static std::array<std::pair<const char*, const char*>, 3> protocolToDBus{
+const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{
     {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}};
 
 inline void
@@ -191,114 +164,48 @@
     Privileges effectiveUserPrivileges =
         redfish::getUserPrivileges(req.userRole);
 
-    crow::connections::systemBus->async_method_call(
-        [asyncResp,
-         &effectiveUserPrivileges](const boost::system::error_code e,
-                                   const std::vector<UnitStruct>& r) {
-            if (e)
-            {
-                asyncResp->res.jsonValue = nlohmann::json::object();
-                messages::internalError(asyncResp->res);
-                return;
-            }
-            // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
-            // something only ConfigureManager can access then only display when
-            // the user has permissions ConfigureManager
-            if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
-                                                 effectiveUserPrivileges))
-            {
-                asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
-                    {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
-                                  "HTTPS/Certificates"}};
-            }
-            for (auto& unit : r)
-            {
-                /* Only traverse through <xyz>.socket units */
-                const std::string& unitName =
-                    std::get<NET_PROTO_UNIT_NAME>(unit);
-                if (!boost::ends_with(unitName, ".socket"))
+    // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
+    // something only ConfigureManager can access then only display when
+    // the user has permissions ConfigureManager
+    if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
+                                         effectiveUserPrivileges))
+    {
+        asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
+            {"@odata.id",
+             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}};
+    }
+
+    for (const auto& protocol : protocolToDBus)
+    {
+        const std::string& protocolName = protocol.first;
+        const std::string& serviceName = protocol.second;
+        getPortStatusAndPath(
+            serviceName,
+            [asyncResp, protocolName](const boost::system::error_code ec,
+                                      const std::string& socketPath,
+                                      bool isProtocolEnabled) {
+                if (ec)
                 {
-                    continue;
+                    messages::internalError(asyncResp->res);
+                    return;
                 }
-
-                for (auto& kv : protocolToDBus)
-                {
-                    // We are interested in services, which starts with
-                    // mapped service name
-                    if (!boost::starts_with(unitName, kv.second))
-                    {
-                        continue;
-                    }
-                    const char* rfServiceKey = kv.first;
-                    const std::string& socketPath =
-                        std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
-                    const std::string& unitState =
-                        std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
-
-                    asyncResp->res.jsonValue[rfServiceKey]["ProtocolEnabled"] =
-                        (unitState == "running") || (unitState == "listening");
-
-                    crow::connections::systemBus->async_method_call(
-                        [asyncResp, rfServiceKey{std::string(rfServiceKey)}](
-                            const boost::system::error_code ec,
-                            const std::variant<std::vector<
-                                std::tuple<std::string, std::string>>>& resp) {
-                            if (ec)
-                            {
-                                messages::internalError(asyncResp->res);
-                                return;
-                            }
-                            const std::vector<
-                                std::tuple<std::string, std::string>>*
-                                responsePtr = std::get_if<std::vector<
-                                    std::tuple<std::string, std::string>>>(
-                                    &resp);
-                            if (responsePtr == nullptr ||
-                                responsePtr->size() < 1)
-                            {
-                                return;
-                            }
-
-                            const std::string& listenStream =
-                                std::get<NET_PROTO_LISTEN_STREAM>(
-                                    (*responsePtr)[0]);
-                            std::size_t lastColonPos = listenStream.rfind(':');
-                            if (lastColonPos == std::string::npos)
-                            {
-                                // Not a port
-                                return;
-                            }
-                            std::string portStr =
-                                listenStream.substr(lastColonPos + 1);
-                            if (portStr.empty())
-                            {
-                                return;
-                            }
-                            char* endPtr = nullptr;
-                            errno = 0;
-                            // Use strtol instead of stroi to avoid
-                            // exceptions
-                            long port =
-                                std::strtol(portStr.c_str(), &endPtr, 10);
-                            if ((errno == 0) && (*endPtr == '\0'))
-                            {
-                                asyncResp->res.jsonValue[rfServiceKey]["Port"] =
-                                    port;
-                            }
+                asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
+                    isProtocolEnabled;
+                getPortNumber(
+                    socketPath,
+                    [asyncResp, protocolName](
+                        const boost::system::error_code ec, int portNumber) {
+                        if (ec)
+                        {
+                            messages::internalError(asyncResp->res);
                             return;
-                        },
-                        "org.freedesktop.systemd1", socketPath,
-                        "org.freedesktop.DBus.Properties", "Get",
-                        "org.freedesktop.systemd1.Socket", "Listen");
-
-                    // We found service, break the inner loop.
-                    break;
-                }
-            }
-        },
-        "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
-        "org.freedesktop.systemd1.Manager", "ListUnits");
-}
+                        }
+                        asyncResp->res.jsonValue[protocolName]["Port"] =
+                            portNumber;
+                    });
+            });
+    }
+} // namespace redfish
 
 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH
 inline void
diff --git a/redfish-core/lib/redfish_util.hpp b/redfish-core/lib/redfish_util.hpp
index 5494a23..60626a5 100644
--- a/redfish-core/lib/redfish_util.hpp
+++ b/redfish-core/lib/redfish_util.hpp
@@ -19,6 +19,34 @@
 namespace redfish
 {
 
+enum NetworkProtocolUnitStructFields
+{
+    NET_PROTO_UNIT_NAME,
+    NET_PROTO_UNIT_DESC,
+    NET_PROTO_UNIT_LOAD_STATE,
+    NET_PROTO_UNIT_ACTIVE_STATE,
+    NET_PROTO_UNIT_SUB_STATE,
+    NET_PROTO_UNIT_DEVICE,
+    NET_PROTO_UNIT_OBJ_PATH,
+    NET_PROTO_UNIT_ALWAYS_0,
+    NET_PROTO_UNIT_ALWAYS_EMPTY,
+    NET_PROTO_UNIT_ALWAYS_ROOT_PATH
+};
+
+enum NetworkProtocolListenResponseElements
+{
+    NET_PROTO_LISTEN_TYPE,
+    NET_PROTO_LISTEN_STREAM
+};
+
+/**
+ * @brief D-Bus Unit structure returned in array from ListUnits Method
+ */
+using UnitStruct =
+    std::tuple<std::string, std::string, std::string, std::string, std::string,
+               std::string, sdbusplus::message::object_path, uint32_t,
+               std::string, sdbusplus::message::object_path>;
+
 template <typename CallbackFunc>
 void getMainChassisId(std::shared_ptr<bmcweb::AsyncResp> asyncResp,
                       CallbackFunc&& callback)
@@ -59,5 +87,141 @@
             "xyz.openbmc_project.Inventory.Item.Board",
             "xyz.openbmc_project.Inventory.Item.Chassis"});
 }
+
+template <typename CallbackFunc>
+void getPortStatusAndPath(const std::string& serviceName,
+                          CallbackFunc&& callback)
+{
+    crow::connections::systemBus->async_method_call(
+        [serviceName,
+         callback{std::move(callback)}](const boost::system::error_code ec,
+                                        const std::vector<UnitStruct>& r) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR << ec;
+                // return error code
+                callback(ec, "", false);
+                return;
+            }
+
+            for (const UnitStruct& unit : r)
+            {
+                // Only traverse through <xyz>.socket units
+                const std::string& unitName =
+                    std::get<NET_PROTO_UNIT_NAME>(unit);
+
+                // find "." into unitsName
+                size_t lastCharPos = unitName.rfind('.');
+                if (lastCharPos == std::string::npos)
+                {
+                    continue;
+                }
+
+                // is unitsName end with ".socket"
+                std::string unitNameEnd = unitName.substr(lastCharPos);
+                if (unitNameEnd.compare(".socket") != 0)
+                {
+                    continue;
+                }
+
+                // find "@" into unitsName
+                if (size_t atCharPos = unitName.rfind('@');
+                    atCharPos != std::string::npos)
+                {
+                    lastCharPos = atCharPos;
+                }
+
+                // unitsName without "@eth(x).socket", only <xyz>
+                // unitsName without ".socket", only <xyz>
+                std::string unitNameStr = unitName.substr(0, lastCharPos);
+
+                // We are interested in services, which starts with
+                // mapped service name
+                if (unitNameStr != serviceName)
+                {
+                    continue;
+                }
+
+                const std::string& socketPath =
+                    std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
+                const std::string& unitState =
+                    std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
+
+                bool isProtocolEnabled =
+                    ((unitState == "running") || (unitState == "listening"));
+                // We found service, return from inner loop.
+                callback(ec, socketPath, isProtocolEnabled);
+                return;
+            }
+
+            //  no service foudn, throw error
+            boost::system::error_code ec1 =
+                boost::system::errc::make_error_code(
+                    boost::system::errc::no_such_process);
+            // return error code
+            callback(ec1, "", false);
+            BMCWEB_LOG_ERROR << ec1;
+        },
+        "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+        "org.freedesktop.systemd1.Manager", "ListUnits");
+}
+
+template <typename CallbackFunc>
+void getPortNumber(const std::string& socketPath, CallbackFunc&& callback)
+{
+    crow::connections::systemBus->async_method_call(
+        [callback{std::move(callback)}](
+            const boost::system::error_code ec,
+            const std::variant<
+                std::vector<std::tuple<std::string, std::string>>>& resp) {
+            if (ec)
+            {
+                BMCWEB_LOG_ERROR << ec;
+                callback(ec, 0);
+                return;
+            }
+            const std::vector<
+                std::tuple<std::string, std::string>>* responsePtr =
+                std::get_if<std::vector<std::tuple<std::string, std::string>>>(
+                    &resp);
+            if (responsePtr == nullptr || responsePtr->size() < 1)
+            {
+                // Network Protocol Listen Response Elements is empty
+                boost::system::error_code ec1 =
+                    boost::system::errc::make_error_code(
+                        boost::system::errc::bad_message);
+                // return error code
+                callback(ec1, 0);
+                BMCWEB_LOG_ERROR << ec1;
+                return;
+            }
+            const std::string& listenStream =
+                std::get<NET_PROTO_LISTEN_STREAM>((*responsePtr)[0]);
+            const char* pa = &listenStream[listenStream.rfind(':') + 1];
+            int port{0};
+            if (auto [p, ec2] = std::from_chars(pa, nullptr, port);
+                ec2 != std::errc())
+            {
+                // there is only two possibility invalid_argument and
+                // result_out_of_range
+                boost::system::error_code ec3 =
+                    boost::system::errc::make_error_code(
+                        boost::system::errc::invalid_argument);
+                if (ec2 == std::errc::result_out_of_range)
+                {
+                    ec3 = boost::system::errc::make_error_code(
+                        boost::system::errc::result_out_of_range);
+                }
+                // return error code
+                callback(ec3, 0);
+                BMCWEB_LOG_ERROR << ec3;
+            }
+            callback(ec, port);
+        },
+        "org.freedesktop.systemd1", socketPath,
+        "org.freedesktop.DBus.Properties", "Get",
+        "org.freedesktop.systemd1.Socket", "Listen");
+}
+
 } // namespace redfish
 #endif