140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
32daf6725SEd Tanous #pragma once
43ccb3adbSEd Tanous #include "app.hpp"
5d7857201SEd Tanous #include "dbus_singleton.hpp"
6d7857201SEd Tanous #include "dbus_utility.hpp"
7*d98a2f93SEd Tanous #include "io_context_singleton.hpp"
8d7857201SEd Tanous #include "logging.hpp"
9faf100f9SEd Tanous #include "websocket.hpp"
103ccb3adbSEd Tanous
11d7857201SEd Tanous #include <sys/types.h>
12d7857201SEd Tanous #include <unistd.h>
132daf6725SEd Tanous
14d7857201SEd Tanous #include <boost/asio/buffer.hpp>
15d7857201SEd Tanous #include <boost/asio/error.hpp>
16d7857201SEd Tanous #include <boost/asio/io_context.hpp>
17d4d77e39SEd Tanous #include <boost/asio/local/stream_protocol.hpp>
18d7857201SEd Tanous #include <boost/beast/core/error.hpp>
19f948d810SNinad Palsule #include <boost/container/flat_map.hpp>
208b901d78SGunnar Mills #include <boost/system/error_code.hpp>
21d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp>
228b901d78SGunnar Mills
238b901d78SGunnar Mills #include <array>
24d7857201SEd Tanous #include <cstddef>
25d7857201SEd Tanous #include <functional>
268b901d78SGunnar Mills #include <memory>
278b901d78SGunnar Mills #include <string>
288b901d78SGunnar Mills #include <string_view>
29d7857201SEd Tanous #include <utility>
30d7857201SEd Tanous #include <vector>
312daf6725SEd Tanous
322daf6725SEd Tanous namespace crow
332daf6725SEd Tanous {
342daf6725SEd Tanous namespace obmc_console
352daf6725SEd Tanous {
362daf6725SEd Tanous
37f948d810SNinad Palsule // Update this value each time we add new console route.
38f948d810SNinad Palsule static constexpr const uint maxSessions = 32;
392daf6725SEd Tanous
40f948d810SNinad Palsule class ConsoleHandler : public std::enable_shared_from_this<ConsoleHandler>
41f948d810SNinad Palsule {
42f948d810SNinad Palsule public:
ConsoleHandler(boost::asio::io_context & ioc,crow::websocket::Connection & connIn)43f948d810SNinad Palsule ConsoleHandler(boost::asio::io_context& ioc,
44f948d810SNinad Palsule crow::websocket::Connection& connIn) :
45bd79bce8SPatrick Williams hostSocket(ioc), conn(connIn)
46f948d810SNinad Palsule {}
472daf6725SEd Tanous
48f948d810SNinad Palsule ~ConsoleHandler() = default;
492daf6725SEd Tanous
50f948d810SNinad Palsule ConsoleHandler(const ConsoleHandler&) = delete;
51f948d810SNinad Palsule ConsoleHandler(ConsoleHandler&&) = delete;
52f948d810SNinad Palsule ConsoleHandler& operator=(const ConsoleHandler&) = delete;
53f948d810SNinad Palsule ConsoleHandler& operator=(ConsoleHandler&&) = delete;
542daf6725SEd Tanous
doWrite()55f948d810SNinad Palsule void doWrite()
562daf6725SEd Tanous {
572daf6725SEd Tanous if (doingWrite)
582daf6725SEd Tanous {
5962598e31SEd Tanous BMCWEB_LOG_DEBUG("Already writing. Bailing out");
602daf6725SEd Tanous return;
612daf6725SEd Tanous }
622daf6725SEd Tanous
632daf6725SEd Tanous if (inputBuffer.empty())
642daf6725SEd Tanous {
6562598e31SEd Tanous BMCWEB_LOG_DEBUG("Outbuffer empty. Bailing out");
662daf6725SEd Tanous return;
672daf6725SEd Tanous }
682daf6725SEd Tanous
69f948d810SNinad Palsule doingWrite = true;
70f948d810SNinad Palsule hostSocket.async_write_some(
71f948d810SNinad Palsule boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
72f948d810SNinad Palsule [weak(weak_from_this())](const boost::beast::error_code& ec,
73f948d810SNinad Palsule std::size_t bytesWritten) {
74f948d810SNinad Palsule std::shared_ptr<ConsoleHandler> self = weak.lock();
75f948d810SNinad Palsule if (self == nullptr)
765238bd32SAppaRao Puli {
775238bd32SAppaRao Puli return;
785238bd32SAppaRao Puli }
795238bd32SAppaRao Puli
80f948d810SNinad Palsule self->doingWrite = false;
81f948d810SNinad Palsule self->inputBuffer.erase(0, bytesWritten);
822daf6725SEd Tanous
832daf6725SEd Tanous if (ec == boost::asio::error::eof)
842daf6725SEd Tanous {
85f948d810SNinad Palsule self->conn.close("Error in reading to host port");
862daf6725SEd Tanous return;
872daf6725SEd Tanous }
882daf6725SEd Tanous if (ec)
892daf6725SEd Tanous {
90bd79bce8SPatrick Williams BMCWEB_LOG_ERROR("Error in host serial write {}",
91bd79bce8SPatrick Williams ec.message());
922daf6725SEd Tanous return;
932daf6725SEd Tanous }
94f948d810SNinad Palsule self->doWrite();
952daf6725SEd Tanous });
962daf6725SEd Tanous }
972daf6725SEd Tanous
afterSendEx(const std::weak_ptr<ConsoleHandler> & weak)98099793f4SEd Tanous static void afterSendEx(const std::weak_ptr<ConsoleHandler>& weak)
99099793f4SEd Tanous {
100099793f4SEd Tanous std::shared_ptr<ConsoleHandler> self = weak.lock();
101099793f4SEd Tanous if (self == nullptr)
102099793f4SEd Tanous {
103099793f4SEd Tanous return;
104099793f4SEd Tanous }
105099793f4SEd Tanous self->doRead();
106099793f4SEd Tanous }
107099793f4SEd Tanous
doRead()108f948d810SNinad Palsule void doRead()
1092daf6725SEd Tanous {
11062598e31SEd Tanous BMCWEB_LOG_DEBUG("Reading from socket");
111f948d810SNinad Palsule hostSocket.async_read_some(
112099793f4SEd Tanous boost::asio::buffer(outputBuffer),
113f948d810SNinad Palsule [this, weakSelf(weak_from_this())](
114f948d810SNinad Palsule const boost::system::error_code& ec, std::size_t bytesRead) {
11562598e31SEd Tanous BMCWEB_LOG_DEBUG("read done. Read {} bytes", bytesRead);
116f948d810SNinad Palsule std::shared_ptr<ConsoleHandler> self = weakSelf.lock();
117f948d810SNinad Palsule if (self == nullptr)
118f948d810SNinad Palsule {
119f948d810SNinad Palsule return;
120f948d810SNinad Palsule }
1212daf6725SEd Tanous if (ec)
1222daf6725SEd Tanous {
12362598e31SEd Tanous BMCWEB_LOG_ERROR("Couldn't read from host serial port: {}",
12462598e31SEd Tanous ec.message());
125f948d810SNinad Palsule conn.close("Error connecting to host port");
1262daf6725SEd Tanous return;
1272daf6725SEd Tanous }
128099793f4SEd Tanous std::string_view payload(outputBuffer.data(), bytesRead);
129bd79bce8SPatrick Williams self->conn.sendEx(
130bd79bce8SPatrick Williams crow::websocket::MessageType::Binary, payload,
131099793f4SEd Tanous std::bind_front(afterSendEx, weak_from_this()));
1322daf6725SEd Tanous });
1332daf6725SEd Tanous }
1342daf6725SEd Tanous
connect(int fd)135f948d810SNinad Palsule bool connect(int fd)
1362daf6725SEd Tanous {
137f948d810SNinad Palsule boost::system::error_code ec;
138f948d810SNinad Palsule boost::asio::local::stream_protocol proto;
139a8d4b8e6SNinad Palsule
140f948d810SNinad Palsule hostSocket.assign(proto, fd, ec);
141f948d810SNinad Palsule
142f948d810SNinad Palsule if (ec)
143a8d4b8e6SNinad Palsule {
14462598e31SEd Tanous BMCWEB_LOG_ERROR(
14562598e31SEd Tanous "Failed to assign the DBUS socket Socket assign error: {}",
14662598e31SEd Tanous ec.message());
147f948d810SNinad Palsule return false;
148a8d4b8e6SNinad Palsule }
149a8d4b8e6SNinad Palsule
150f948d810SNinad Palsule conn.resumeRead();
151f948d810SNinad Palsule doWrite();
152f948d810SNinad Palsule doRead();
153f948d810SNinad Palsule return true;
154a8d4b8e6SNinad Palsule }
155f948d810SNinad Palsule
156f948d810SNinad Palsule boost::asio::local::stream_protocol::socket hostSocket;
157f948d810SNinad Palsule
158099793f4SEd Tanous std::array<char, 4096> outputBuffer{};
159f948d810SNinad Palsule
160f948d810SNinad Palsule std::string inputBuffer;
161f948d810SNinad Palsule bool doingWrite = false;
162f948d810SNinad Palsule crow::websocket::Connection& conn;
163f948d810SNinad Palsule };
164f948d810SNinad Palsule
165f948d810SNinad Palsule using ObmcConsoleMap = boost::container::flat_map<
166f948d810SNinad Palsule crow::websocket::Connection*, std::shared_ptr<ConsoleHandler>, std::less<>,
167f948d810SNinad Palsule std::vector<std::pair<crow::websocket::Connection*,
168f948d810SNinad Palsule std::shared_ptr<ConsoleHandler>>>>;
169f948d810SNinad Palsule
getConsoleHandlerMap()170f948d810SNinad Palsule inline ObmcConsoleMap& getConsoleHandlerMap()
171f948d810SNinad Palsule {
172f948d810SNinad Palsule static ObmcConsoleMap map;
173f948d810SNinad Palsule return map;
174f948d810SNinad Palsule }
175f948d810SNinad Palsule
176f948d810SNinad Palsule // Remove connection from the connection map and if connection map is empty
177f948d810SNinad Palsule // then remove the handler from handlers map.
onClose(crow::websocket::Connection & conn,const std::string & err)178f948d810SNinad Palsule inline void onClose(crow::websocket::Connection& conn, const std::string& err)
179f948d810SNinad Palsule {
18062598e31SEd Tanous BMCWEB_LOG_INFO("Closing websocket. Reason: {}", err);
181f948d810SNinad Palsule
182f948d810SNinad Palsule auto iter = getConsoleHandlerMap().find(&conn);
183f948d810SNinad Palsule if (iter == getConsoleHandlerMap().end())
184f948d810SNinad Palsule {
18562598e31SEd Tanous BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
186f948d810SNinad Palsule return;
187f948d810SNinad Palsule }
18862598e31SEd Tanous BMCWEB_LOG_DEBUG("Remove connection {} from obmc console", logPtr(&conn));
189f948d810SNinad Palsule
190f948d810SNinad Palsule // Removed last connection so remove the path
191f948d810SNinad Palsule getConsoleHandlerMap().erase(iter);
192a8d4b8e6SNinad Palsule }
193a8d4b8e6SNinad Palsule
connectConsoleSocket(crow::websocket::Connection & conn,const boost::system::error_code & ec,const sdbusplus::message::unix_fd & unixfd)194a8d4b8e6SNinad Palsule inline void connectConsoleSocket(crow::websocket::Connection& conn,
195a8d4b8e6SNinad Palsule const boost::system::error_code& ec,
196a8d4b8e6SNinad Palsule const sdbusplus::message::unix_fd& unixfd)
197a8d4b8e6SNinad Palsule {
1982daf6725SEd Tanous if (ec)
1992daf6725SEd Tanous {
20062598e31SEd Tanous BMCWEB_LOG_ERROR(
20162598e31SEd Tanous "Failed to call console Connect() method DBUS error: {}",
20262598e31SEd Tanous ec.message());
203f948d810SNinad Palsule conn.close("Failed to connect");
2042daf6725SEd Tanous return;
2052daf6725SEd Tanous }
2062daf6725SEd Tanous
207f948d810SNinad Palsule // Look up the handler
208f948d810SNinad Palsule auto iter = getConsoleHandlerMap().find(&conn);
209f948d810SNinad Palsule if (iter == getConsoleHandlerMap().end())
210a8d4b8e6SNinad Palsule {
21162598e31SEd Tanous BMCWEB_LOG_ERROR("Connection was already closed");
212a8d4b8e6SNinad Palsule return;
213a8d4b8e6SNinad Palsule }
214a8d4b8e6SNinad Palsule
215f948d810SNinad Palsule int fd = dup(unixfd);
216a8d4b8e6SNinad Palsule if (fd == -1)
217a8d4b8e6SNinad Palsule {
2187da633f0SEd Tanous BMCWEB_LOG_ERROR("Failed to dup the DBUS unixfd error");
219f948d810SNinad Palsule conn.close("Internal error");
220a8d4b8e6SNinad Palsule return;
221a8d4b8e6SNinad Palsule }
222a8d4b8e6SNinad Palsule
22362598e31SEd Tanous BMCWEB_LOG_DEBUG("Console duped FD: {}", fd);
224a8d4b8e6SNinad Palsule
225f948d810SNinad Palsule if (!iter->second->connect(fd))
226a8d4b8e6SNinad Palsule {
227a8d4b8e6SNinad Palsule close(fd);
228f948d810SNinad Palsule conn.close("Internal Error");
229a8d4b8e6SNinad Palsule }
230a8d4b8e6SNinad Palsule }
231a8d4b8e6SNinad Palsule
processConsoleObject(crow::websocket::Connection & conn,const std::string & consoleObjPath,const boost::system::error_code & ec,const::dbus::utility::MapperGetObject & objInfo)232bd79bce8SPatrick Williams inline void processConsoleObject(
233bd79bce8SPatrick Williams crow::websocket::Connection& conn, const std::string& consoleObjPath,
234052bcbf4SNinad Palsule const boost::system::error_code& ec,
235052bcbf4SNinad Palsule const ::dbus::utility::MapperGetObject& objInfo)
236052bcbf4SNinad Palsule {
237052bcbf4SNinad Palsule // Look up the handler
238052bcbf4SNinad Palsule auto iter = getConsoleHandlerMap().find(&conn);
239052bcbf4SNinad Palsule if (iter == getConsoleHandlerMap().end())
240052bcbf4SNinad Palsule {
24162598e31SEd Tanous BMCWEB_LOG_ERROR("Connection was already closed");
242052bcbf4SNinad Palsule return;
243052bcbf4SNinad Palsule }
244052bcbf4SNinad Palsule
245052bcbf4SNinad Palsule if (ec)
246052bcbf4SNinad Palsule {
24762598e31SEd Tanous BMCWEB_LOG_WARNING("getDbusObject() for consoles failed. DBUS error:{}",
24862598e31SEd Tanous ec.message());
249052bcbf4SNinad Palsule conn.close("getDbusObject() for consoles failed.");
250052bcbf4SNinad Palsule return;
251052bcbf4SNinad Palsule }
252052bcbf4SNinad Palsule
253052bcbf4SNinad Palsule const auto valueIface = objInfo.begin();
254052bcbf4SNinad Palsule if (valueIface == objInfo.end())
255052bcbf4SNinad Palsule {
25662598e31SEd Tanous BMCWEB_LOG_WARNING("getDbusObject() returned unexpected size: {}",
25762598e31SEd Tanous objInfo.size());
258052bcbf4SNinad Palsule conn.close("getDbusObject() returned unexpected size");
259052bcbf4SNinad Palsule return;
260052bcbf4SNinad Palsule }
261052bcbf4SNinad Palsule
262052bcbf4SNinad Palsule const std::string& consoleService = valueIface->first;
26362598e31SEd Tanous BMCWEB_LOG_DEBUG("Looking up unixFD for Service {} Path {}", consoleService,
26462598e31SEd Tanous consoleObjPath);
265052bcbf4SNinad Palsule // Call Connect() method to get the unix FD
266052bcbf4SNinad Palsule crow::connections::systemBus->async_method_call(
267052bcbf4SNinad Palsule [&conn](const boost::system::error_code& ec1,
268052bcbf4SNinad Palsule const sdbusplus::message::unix_fd& unixfd) {
269052bcbf4SNinad Palsule connectConsoleSocket(conn, ec1, unixfd);
270052bcbf4SNinad Palsule },
271052bcbf4SNinad Palsule consoleService, consoleObjPath, "xyz.openbmc_project.Console.Access",
272052bcbf4SNinad Palsule "Connect");
273052bcbf4SNinad Palsule }
274052bcbf4SNinad Palsule
275a8d4b8e6SNinad Palsule // Query consoles from DBUS and find the matching to the
276a8d4b8e6SNinad Palsule // rules string.
onOpen(crow::websocket::Connection & conn)277a8d4b8e6SNinad Palsule inline void onOpen(crow::websocket::Connection& conn)
278a8d4b8e6SNinad Palsule {
279052bcbf4SNinad Palsule std::string consoleLeaf;
280052bcbf4SNinad Palsule
28162598e31SEd Tanous BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
282a8d4b8e6SNinad Palsule
283f948d810SNinad Palsule if (getConsoleHandlerMap().size() >= maxSessions)
284a8d4b8e6SNinad Palsule {
285f948d810SNinad Palsule conn.close("Max sessions are already connected");
286f948d810SNinad Palsule return;
287a8d4b8e6SNinad Palsule }
288a8d4b8e6SNinad Palsule
289f948d810SNinad Palsule std::shared_ptr<ConsoleHandler> handler =
290*d98a2f93SEd Tanous std::make_shared<ConsoleHandler>(getIoContext(), conn);
291f948d810SNinad Palsule getConsoleHandlerMap().emplace(&conn, handler);
292f948d810SNinad Palsule
293f948d810SNinad Palsule conn.deferRead();
294f948d810SNinad Palsule
295052bcbf4SNinad Palsule // Keep old path for backward compatibility
296052bcbf4SNinad Palsule if (conn.url().path() == "/console0")
297052bcbf4SNinad Palsule {
298052bcbf4SNinad Palsule consoleLeaf = "default";
299052bcbf4SNinad Palsule }
300052bcbf4SNinad Palsule else
301052bcbf4SNinad Palsule {
302052bcbf4SNinad Palsule // Get the console id from console router path and prepare the console
303052bcbf4SNinad Palsule // object path and console service.
304052bcbf4SNinad Palsule consoleLeaf = conn.url().segments().back();
305052bcbf4SNinad Palsule }
306052bcbf4SNinad Palsule std::string consolePath =
307052bcbf4SNinad Palsule sdbusplus::message::object_path("/xyz/openbmc_project/console") /
308052bcbf4SNinad Palsule consoleLeaf;
309a8d4b8e6SNinad Palsule
31062598e31SEd Tanous BMCWEB_LOG_DEBUG("Console Object path = {} Request target = {}",
31162598e31SEd Tanous consolePath, conn.url().path());
312a8d4b8e6SNinad Palsule
313052bcbf4SNinad Palsule // mapper call lambda
314052bcbf4SNinad Palsule constexpr std::array<std::string_view, 1> interfaces = {
315052bcbf4SNinad Palsule "xyz.openbmc_project.Console.Access"};
316052bcbf4SNinad Palsule
317052bcbf4SNinad Palsule dbus::utility::getDbusObject(
318052bcbf4SNinad Palsule consolePath, interfaces,
319052bcbf4SNinad Palsule [&conn, consolePath](const boost::system::error_code& ec,
320052bcbf4SNinad Palsule const ::dbus::utility::MapperGetObject& objInfo) {
321052bcbf4SNinad Palsule processConsoleObject(conn, consolePath, ec, objInfo);
322052bcbf4SNinad Palsule });
323a8d4b8e6SNinad Palsule }
3242daf6725SEd Tanous
onMessage(crow::websocket::Connection & conn,const std::string & data,bool)325f948d810SNinad Palsule inline void onMessage(crow::websocket::Connection& conn,
326f948d810SNinad Palsule const std::string& data, bool /*isBinary*/)
327f948d810SNinad Palsule {
328f948d810SNinad Palsule auto handler = getConsoleHandlerMap().find(&conn);
329f948d810SNinad Palsule if (handler == getConsoleHandlerMap().end())
330f948d810SNinad Palsule {
33162598e31SEd Tanous BMCWEB_LOG_CRITICAL("Unable to find connection {}", logPtr(&conn));
332f948d810SNinad Palsule return;
333f948d810SNinad Palsule }
334f948d810SNinad Palsule handler->second->inputBuffer += data;
335f948d810SNinad Palsule handler->second->doWrite();
336f948d810SNinad Palsule }
337f948d810SNinad Palsule
requestRoutes(App & app)33823a21a1cSEd Tanous inline void requestRoutes(App& app)
3392daf6725SEd Tanous {
3402daf6725SEd Tanous BMCWEB_ROUTE(app, "/console0")
3413e72c202SNinad Palsule .privileges({{"OpenBMCHostConsole"}})
3422daf6725SEd Tanous .websocket()
343a8d4b8e6SNinad Palsule .onopen(onOpen)
344f948d810SNinad Palsule .onclose(onClose)
345f948d810SNinad Palsule .onmessage(onMessage);
346052bcbf4SNinad Palsule
347052bcbf4SNinad Palsule BMCWEB_ROUTE(app, "/console/<str>")
348052bcbf4SNinad Palsule .privileges({{"OpenBMCHostConsole"}})
349052bcbf4SNinad Palsule .websocket()
350052bcbf4SNinad Palsule .onopen(onOpen)
351052bcbf4SNinad Palsule .onclose(onClose)
352052bcbf4SNinad Palsule .onmessage(onMessage);
3532daf6725SEd Tanous }
3542daf6725SEd Tanous } // namespace obmc_console
3552daf6725SEd Tanous } // namespace crow
356