Retrieve port info for systems service

In systems.hpp for Serialconsole, SSH service-enabled and its Port
number info is hardcoded. Same for IPMI service-enabled info is also
hardcoded.

Implementation:

 SSH:
  check for obmc-console-ssh@2200.service service is enable or not, and
  yes then, on which port number. Retrieve service-related information
  and pass that into DMTF for "/redfish/v1/Systems/system/".

 IPMI:-
  check for phosphor-ipmi-net@eth0.socket service is enable or not, and
  pass that info DMTF for "/redfish/v1/Systems/system/".

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

Before:

"SerialConsole": {
  "IPMI": {
    "ServiceEnabled": true
  },
  "MaxConcurrentSessions": 15,
  "SSH": {
    "HotKeySequenceDisplay": "Press ~. to exit console",
    "Port": 2200,
    "ServiceEnabled": true
  }
}

After:

 Note: SSH Info retrieve via Dbus ListUnit API

"SerialConsole": {
  "IPMI": {
    "ServiceEnabled": true
  },
  "MaxConcurrentSessions": 15,
  "SSH": {
    "HotKeySequenceDisplay": "Press ~. to exit console",
    "Port": 2200,
    "ServiceEnabled": true
  }
}

Signed-off-by: Abhishek Patel <Abhishek.Patel@ibm.com>
Change-Id: I70009ee785aab3ca4a61fe0d96fbc5b340831647
diff --git a/redfish-core/lib/network_protocol.hpp b/redfish-core/lib/network_protocol.hpp
index 8e2fc4b..00b7bfc 100644
--- a/redfish-core/lib/network_protocol.hpp
+++ b/redfish-core/lib/network_protocol.hpp
@@ -39,13 +39,16 @@
 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
 std::string getHostName();
 
-static constexpr const char* sshServiceName = "dropbear";
-static constexpr const char* httpsServiceName = "bmcweb";
-static constexpr const char* ipmiServiceName = "phosphor-ipmi-net";
-static constexpr std::array<std::pair<const char*, const char*>, 3>
-    protocolToService = {{{"SSH", sshServiceName},
-                          {"HTTPS", httpsServiceName},
-                          {"IPMI", ipmiServiceName}}};
+static constexpr std::string_view sshServiceName = "dropbear";
+static constexpr std::string_view httpsServiceName = "bmcweb";
+static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net";
+
+// Mapping from Redfish NetworkProtocol key name to backend service that hosts
+// that protocol.
+static constexpr std::array<std::pair<std::string_view, std::string_view>, 3>
+    networkProtocolToDbus = {{{"SSH", sshServiceName},
+                              {"HTTPS", httpsServiceName},
+                              {"IPMI", ipmiServiceName}}};
 
 inline void extractNTPServersAndDomainNamesData(
     const dbus::utility::ManagedObjectType& dbusData,
@@ -113,6 +116,38 @@
         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
 }
 
+inline void afterNetworkPortRequest(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const boost::system::error_code& ec,
+    const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
+{
+    if (ec)
+    {
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    for (const auto& data : socketData)
+    {
+        const std::string& socketPath = get<0>(data);
+        const std::string& protocolName = get<1>(data);
+        bool isProtocolEnabled = get<2>(data);
+
+        asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
+            isProtocolEnabled;
+        asyncResp->res.jsonValue[protocolName]["Port"] = nullptr;
+        getPortNumber(socketPath, [asyncResp, protocolName](
+                                      const boost::system::error_code& ec2,
+                                      int portNumber) {
+            if (ec2)
+            {
+                messages::internalError(asyncResp->res);
+                return;
+            }
+            asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
+        });
+    }
+}
+
 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                            const crow::Request& req)
 {
@@ -134,7 +169,7 @@
     // but from security perspective it is not recommended to use.
     // Hence using protocolEnabled as false to make it OCP and security-wise
     // compliant
-    asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
+    asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
 
     std::string hostName = getHostName();
@@ -179,43 +214,8 @@
             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
     }
 
-    for (const auto& protocol : protocolToService)
-    {
-        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 the service is not installed, that is not an error
-            if (ec == boost::system::errc::no_such_process)
-            {
-                asyncResp->res.jsonValue[protocolName]["Port"] =
-                    nlohmann::detail::value_t::null;
-                asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
-                    false;
-                return;
-            }
-            if (ec)
-            {
-                messages::internalError(asyncResp->res);
-                return;
-            }
-            asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
-                isProtocolEnabled;
-            getPortNumber(socketPath, [asyncResp, protocolName](
-                                          const boost::system::error_code& ec2,
-                                          int portNumber) {
-                if (ec2)
-                {
-                    messages::internalError(asyncResp->res);
-                    return;
-                }
-                asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
-            });
-            });
-    }
+    getPortStatusAndPath(std::span(networkProtocolToDbus),
+                         std::bind_front(afterNetworkPortRequest, asyncResp));
 } // namespace redfish
 
 inline void handleNTPProtocolEnabled(
@@ -457,7 +457,7 @@
         });
 }
 
-inline std::string encodeServiceObjectPath(const std::string& serviceName)
+inline std::string encodeServiceObjectPath(std::string_view serviceName)
 {
     sdbusplus::message::object_path objPath(
         "/xyz/openbmc_project/control/service");
diff --git a/redfish-core/lib/redfish_util.hpp b/redfish-core/lib/redfish_util.hpp
index 9d358c0..a742ca6 100644
--- a/redfish-core/lib/redfish_util.hpp
+++ b/redfish-core/lib/redfish_util.hpp
@@ -97,21 +97,25 @@
 }
 
 template <typename CallbackFunc>
-void getPortStatusAndPath(const std::string& serviceName,
-                          CallbackFunc&& callback)
+void getPortStatusAndPath(
+    std::span<const std::pair<std::string_view, std::string_view>>
+        protocolToDBus,
+    CallbackFunc&& callback)
 {
     crow::connections::systemBus->async_method_call(
-        [serviceName, callback{std::forward<CallbackFunc>(callback)}](
+        [protocolToDBus, callback{std::forward<CallbackFunc>(callback)}](
             const boost::system::error_code& ec,
             const std::vector<UnitStruct>& r) {
+        std::vector<std::tuple<std::string, std::string, bool>> socketData;
         if (ec)
         {
             BMCWEB_LOG_ERROR << ec;
             // return error code
-            callback(ec, "", false);
+            callback(ec, socketData);
             return;
         }
 
+        // save all service output into vector
         for (const UnitStruct& unit : r)
         {
             // Only traverse through <xyz>.socket units
@@ -142,31 +146,31 @@
             // 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)
+            for (const auto& kv : protocolToDBus)
             {
-                continue;
+                // We are interested in services, which starts with
+                // mapped service name
+                if (unitNameStr != kv.second)
+                {
+                    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"));
+
+                socketData.emplace_back(socketPath, std::string(kv.first),
+                                        isProtocolEnabled);
+                // We found service, return from inner loop.
+                break;
             }
-
-            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;
+        callback(ec, socketData);
         },
         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
         "org.freedesktop.systemd1.Manager", "ListUnits");
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 59abd4f..6174610 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -41,6 +41,10 @@
 namespace redfish
 {
 
+const static std::array<std::pair<std::string_view, std::string_view>, 2>
+    protocolToDBusForSystems{
+        {{"SSH", "obmc-console-ssh"}, {"IPMI", "phosphor-ipmi-net"}}};
+
 /**
  * @brief Updates the Functional State of DIMMs
  *
@@ -2879,6 +2883,42 @@
         "</redfish/v1/JsonSchemas/ComputerSystem/ComputerSystem.json>; rel=describedby");
 }
 
+inline void afterPortRequest(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const boost::system::error_code& ec,
+    const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
+{
+    if (ec)
+    {
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    for (const auto& data : socketData)
+    {
+        const std::string& socketPath = get<0>(data);
+        const std::string& protocolName = get<1>(data);
+        bool isProtocolEnabled = get<2>(data);
+        nlohmann::json& dataJson = asyncResp->res.jsonValue["SerialConsole"];
+        dataJson[protocolName]["ServiceEnabled"] = isProtocolEnabled;
+        // need to retrieve port number for
+        // obmc-console-ssh service
+        if (protocolName == "SSH")
+        {
+            getPortNumber(socketPath, [asyncResp, protocolName](
+                                          const boost::system::error_code ec1,
+                                          int portNumber) {
+                if (ec1)
+                {
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                nlohmann::json& dataJson1 =
+                    asyncResp->res.jsonValue["SerialConsole"];
+                dataJson1[protocolName]["Port"] = portNumber;
+            });
+        }
+    }
+}
 /**
  * Systems derived class for delivering Computer Systems Schema.
  */
@@ -2965,6 +3005,8 @@
         asyncResp->res
             .jsonValue["SerialConsole"]["SSH"]["HotKeySequenceDisplay"] =
             "Press ~. to exit console";
+        getPortStatusAndPath(std::span{protocolToDBusForSystems},
+                             std::bind_front(afterPortRequest, asyncResp));
 
 #ifdef BMCWEB_ENABLE_KVM
         // Fill in GraphicalConsole info