Add support for multiple consoles

This drop adds support for multiple consoles. The following changes are
made to achieve this.
- Kept the "/console0" route for backward compatibility
- Added a new route "/console/<str>" to support multiple consoles. All
  new consoles must use this route string.

Testing:
- Make sure that old console path /console0 is working.
 [INFO "http_connection.hpp":209] Request:  0x1bc2e60 HTTP/1.1
     GET /console0 ::ffff:x.x.xx.xxx
 [DEBUG "routing.hpp":1240] Matched rule (upgrade) '/console0' 1 / 2
 [DEBUG "obmc_console.hpp":212] Connection 0x1bdb67c opened
 [DEBUG "obmc_console.hpp":241] Console Object path =
    /xyz/openbmc_project/console/default service =
    xyz.openbmc_project.Console.default Request target = /console0
 [DEBUG "obmc_console.hpp":198] Console web socket path: /console0
    Console unix FD: 12 duped FD: 13
 [DEBUG "obmc_console.hpp":82] Reading from socket
 [DEBUG "obmc_console.hpp":162] Remove connection 0x1bdb67c from
    obmc console

- Make sure that new path for default console working
 [INFO "http_connection.hpp":209] Request:  0x1bd76a8 HTTP/1.1
    GET /console/default ::ffff:x.x.xx.xxx
 [DEBUG "routing.hpp":1240] Matched rule (upgrade) '/console/<str>'
     1 / 2
 [DEBUG "obmc_console.hpp":212] Connection 0x1baf82c opened
 [DEBUG "obmc_console.hpp":241] Console Object path =
    /xyz/openbmc_project/console/default service =
    xyz.openbmc_project.Console.default Request
    target = /console/default
 [DEBUG "obmc_console.hpp":198] Console web socket path:
    /console/default Console unix FD: 12 duped FD: 13
 [DEBUG "obmc_console.hpp":82] Reading from socket
 [INFO "obmc_console.hpp":154] Closing websocket. Reason:
 [DEBUG "obmc_console.hpp":162] Remove connection 0x1baf82c from
    obmc console

- Make sure that path for hypervisor console is working.
 [INFO "http_connection.hpp":209] Request:  0x1bc2e60 HTTP/1.1
    GET /console/hypervisor ::ffff:x.x.xx.xxx
 [DEBUG "routing.hpp":1240] Matched rule (upgrade) '/console/<str>'
     1 / 2
 [DEBUG "obmc_console.hpp":212] Connection 0x1bc5234 opened
 [DEBUG "obmc_console.hpp":241] Console Object path =
    /xyz/openbmc_project/console/hypervisor service =
    xyz.openbmc_project.Console.hypervisor Request
    target = /console/hypervisor
 [DEBUG "obmc_console.hpp":198] Console web socket path:
    /console/hypervisor Console unix FD: 12 duped FD: 13
 [DEBUG "obmc_console.hpp":82] Reading from socket
 [INFO "obmc_console.hpp":154] Closing websocket. Reason:
 [DEBUG "obmc_console.hpp":162] Remove connection 0x1bc5234 from
    obmc console

- Make sure that bad console path is failing properly due to DBUS error.
 [INFO "http_connection.hpp":209] Request:  0x1bd76a8 HTTP/1.1
    GET /console/badconsoleid ::ffff:x.x.xx.xxx
 [DEBUG "routing.hpp":1240] Matched rule (upgrade) '/console/<str>'
     1 / 2
 [DEBUG "obmc_console.hpp":212] Connection 0x1bdb67c opened
 [DEBUG "obmc_console.hpp":241] Console Object path =
    /xyz/openbmc_project/console/badconsoleid service =
    xyz.openbmc_project.Console.badconsoleid Request
    target = /console/badconsoleid
 [ERROR "obmc_console.hpp":174] Failed to call console Connect()
    method DBUS error: No route to host

Change-Id: I9b617bc51e3ddc605dd7f4d213c805d05d2cfead
Signed-off-by: Ninad Palsule <ninad@linux.ibm.com>
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/http/routing.hpp b/http/routing.hpp
index ac1c310..f3bcfbb 100644
--- a/http/routing.hpp
+++ b/http/routing.hpp
@@ -305,7 +305,7 @@
             crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>
             myConnection = std::make_shared<
                 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>(
-                req, std::move(adaptor), openHandler, messageHandler,
+                req, req.url(), std::move(adaptor), openHandler, messageHandler,
                 messageExHandler, closeHandler, errorHandler);
         myConnection->start();
     }
@@ -320,7 +320,7 @@
             boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>
             myConnection = std::make_shared<crow::websocket::ConnectionImpl<
                 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>(
-                req, std::move(adaptor), openHandler, messageHandler,
+                req, req.url(), std::move(adaptor), openHandler, messageHandler,
                 messageExHandler, closeHandler, errorHandler);
         myConnection->start();
     }
diff --git a/http/websocket.hpp b/http/websocket.hpp
index e61f58b..0faa8c6 100644
--- a/http/websocket.hpp
+++ b/http/websocket.hpp
@@ -45,7 +45,7 @@
     virtual void resumeRead() = 0;
     virtual boost::asio::io_context& getIoContext() = 0;
     virtual ~Connection() = default;
-
+    virtual boost::urls::url_view url() = 0;
     boost::beast::http::request<boost::beast::http::string_body> req;
 };
 
@@ -54,8 +54,8 @@
 {
   public:
     ConnectionImpl(
-        const crow::Request& reqIn, Adaptor adaptorIn,
-        std::function<void(Connection&)> openHandlerIn,
+        const crow::Request& reqIn, boost::urls::url_view urlViewIn,
+        Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
         std::function<void(Connection&, const std::string&, bool)>
             messageHandlerIn,
         std::function<void(crow::websocket::Connection&, std::string_view,
@@ -65,7 +65,7 @@
         std::function<void(Connection&, const std::string&)> closeHandlerIn,
         std::function<void(Connection&)> errorHandlerIn) :
         Connection(reqIn),
-        ws(std::move(adaptorIn)), inBuffer(inString, 131088),
+        uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
         openHandler(std::move(openHandlerIn)),
         messageHandler(std::move(messageHandlerIn)),
         messageExHandler(std::move(messageExHandlerIn)),
@@ -215,6 +215,11 @@
             });
     }
 
+    boost::urls::url_view url() override
+    {
+        return uri;
+    }
+
     void acceptDone()
     {
         BMCWEB_LOG_DEBUG << "Websocket accepted connection";
@@ -338,6 +343,8 @@
         doRead();
     }
 
+    boost::urls::url uri;
+
     boost::beast::websocket::stream<Adaptor, false> ws;
 
     bool readingDefered = false;
diff --git a/include/obmc_console.hpp b/include/obmc_console.hpp
index 7eaa153..f9b978d 100644
--- a/include/obmc_console.hpp
+++ b/include/obmc_console.hpp
@@ -185,8 +185,7 @@
     auto iter = getConsoleHandlerMap().find(&conn);
     if (iter == getConsoleHandlerMap().end())
     {
-        BMCWEB_LOG_ERROR << "Failed to find the handler";
-        conn.close("Internal error");
+        BMCWEB_LOG_ERROR << "Connection was already closed";
         return;
     }
 
@@ -199,8 +198,7 @@
         return;
     }
 
-    BMCWEB_LOG_DEBUG << "Console web socket path: " << conn.req.target()
-                     << " Console unix FD: " << unixfd << " duped FD: " << fd;
+    BMCWEB_LOG_DEBUG << "Console unix FD: " << unixfd << " duped FD: " << fd;
 
     if (!iter->second->connect(fd))
     {
@@ -209,10 +207,56 @@
     }
 }
 
+inline void
+    processConsoleObject(crow::websocket::Connection& conn,
+                         const std::string& consoleObjPath,
+                         const boost::system::error_code& ec,
+                         const ::dbus::utility::MapperGetObject& objInfo)
+{
+    // Look up the handler
+    auto iter = getConsoleHandlerMap().find(&conn);
+    if (iter == getConsoleHandlerMap().end())
+    {
+        BMCWEB_LOG_ERROR << "Connection was already closed";
+        return;
+    }
+
+    if (ec)
+    {
+        BMCWEB_LOG_WARNING << "getDbusObject() for consoles failed. DBUS error:"
+                           << ec.message();
+        conn.close("getDbusObject() for consoles failed.");
+        return;
+    }
+
+    const auto valueIface = objInfo.begin();
+    if (valueIface == objInfo.end())
+    {
+        BMCWEB_LOG_WARNING << "getDbusObject() returned unexpected size: "
+                           << objInfo.size();
+        conn.close("getDbusObject() returned unexpected size");
+        return;
+    }
+
+    const std::string& consoleService = valueIface->first;
+    BMCWEB_LOG_DEBUG << "Looking up unixFD for Service " << consoleService
+                     << " Path " << consoleObjPath;
+    // Call Connect() method to get the unix FD
+    crow::connections::systemBus->async_method_call(
+        [&conn](const boost::system::error_code& ec1,
+                const sdbusplus::message::unix_fd& unixfd) {
+        connectConsoleSocket(conn, ec1, unixfd);
+        },
+        consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
+        "Connect");
+}
+
 // Query consoles from DBUS and find the matching to the
 // rules string.
 inline void onOpen(crow::websocket::Connection& conn)
 {
+    std::string consoleLeaf;
+
     BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
 
     if (getConsoleHandlerMap().size() >= maxSessions)
@@ -227,23 +271,34 @@
 
     conn.deferRead();
 
-    // The console id 'default' is used for the console0
-    // We need to change it when we provide full multi-console support.
-    const std::string consolePath = "/xyz/openbmc_project/console/default";
-    const std::string consoleService = "xyz.openbmc_project.Console.default";
+    // Keep old path for backward compatibility
+    if (conn.url().path() == "/console0")
+    {
+        consoleLeaf = "default";
+    }
+    else
+    {
+        // Get the console id from console router path and prepare the console
+        // object path and console service.
+        consoleLeaf = conn.url().segments().back();
+    }
+    std::string consolePath =
+        sdbusplus::message::object_path("/xyz/openbmc_project/console") /
+        consoleLeaf;
 
     BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath
-                     << " service = " << consoleService
-                     << " Request target = " << conn.req.target();
+                     << " Request target = " << conn.url().path();
 
-    // Call Connect() method to get the unix FD
-    crow::connections::systemBus->async_method_call(
-        [&conn](const boost::system::error_code& ec,
-                const sdbusplus::message::unix_fd& unixfd) {
-        connectConsoleSocket(conn, ec, unixfd);
-        },
-        consoleService, consolePath, "xyz.openbmc_project.Console.Access",
-        "Connect");
+    // mapper call lambda
+    constexpr std::array<std::string_view, 1> interfaces = {
+        "xyz.openbmc_project.Console.Access"};
+
+    dbus::utility::getDbusObject(
+        consolePath, interfaces,
+        [&conn, consolePath](const boost::system::error_code& ec,
+                             const ::dbus::utility::MapperGetObject& objInfo) {
+        processConsoleObject(conn, consolePath, ec, objInfo);
+        });
 }
 
 inline void onMessage(crow::websocket::Connection& conn,
@@ -267,6 +322,13 @@
         .onopen(onOpen)
         .onclose(onClose)
         .onmessage(onMessage);
+
+    BMCWEB_ROUTE(app, "/console/<str>")
+        .privileges({{"OpenBMCHostConsole"}})
+        .websocket()
+        .onopen(onOpen)
+        .onclose(onClose)
+        .onmessage(onMessage);
 }
 } // namespace obmc_console
 } // namespace crow