xref: /openbmc/bmcweb/include/obmc_console.hpp (revision d8ef9915)
1 #pragma once
2 #include <sys/socket.h>
3 
4 #include <app.hpp>
5 #include <async_resp.hpp>
6 #include <boost/asio/local/stream_protocol.hpp>
7 #include <boost/container/flat_map.hpp>
8 #include <boost/container/flat_set.hpp>
9 #include <websocket.hpp>
10 
11 namespace crow
12 {
13 namespace obmc_console
14 {
15 
16 static std::unique_ptr<boost::asio::local::stream_protocol::socket> hostSocket;
17 
18 static std::array<char, 4096> outputBuffer;
19 static std::string inputBuffer;
20 
21 static boost::container::flat_set<crow::websocket::Connection*> sessions;
22 
23 static bool doingWrite = false;
24 
25 inline void doWrite()
26 {
27     if (doingWrite)
28     {
29         BMCWEB_LOG_DEBUG << "Already writing.  Bailing out";
30         return;
31     }
32 
33     if (inputBuffer.empty())
34     {
35         BMCWEB_LOG_DEBUG << "Outbuffer empty.  Bailing out";
36         return;
37     }
38 
39     if (!hostSocket)
40     {
41         BMCWEB_LOG_ERROR << "doWrite(): Socket closed.";
42         return;
43     }
44 
45     doingWrite = true;
46     hostSocket->async_write_some(
47         boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
48         [](boost::beast::error_code ec, std::size_t bytesWritten) {
49             doingWrite = false;
50             inputBuffer.erase(0, bytesWritten);
51 
52             if (ec == boost::asio::error::eof)
53             {
54                 for (crow::websocket::Connection* session : sessions)
55                 {
56                     session->close("Error in reading to host port");
57                 }
58                 return;
59             }
60             if (ec)
61             {
62                 BMCWEB_LOG_ERROR << "Error in host serial write " << ec;
63                 return;
64             }
65             doWrite();
66         });
67 }
68 
69 inline void doRead()
70 {
71     if (!hostSocket)
72     {
73         BMCWEB_LOG_ERROR << "doRead(): Socket closed.";
74         return;
75     }
76 
77     BMCWEB_LOG_DEBUG << "Reading from socket";
78     hostSocket->async_read_some(
79         boost::asio::buffer(outputBuffer.data(), outputBuffer.size()),
80         [](const boost::system::error_code& ec, std::size_t bytesRead) {
81             BMCWEB_LOG_DEBUG << "read done.  Read " << bytesRead << " bytes";
82             if (ec)
83             {
84                 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: "
85                                  << ec;
86                 for (crow::websocket::Connection* session : sessions)
87                 {
88                     session->close("Error in connecting to host port");
89                 }
90                 return;
91             }
92             std::string_view payload(outputBuffer.data(), bytesRead);
93             for (crow::websocket::Connection* session : sessions)
94             {
95                 session->sendBinary(payload);
96             }
97             doRead();
98         });
99 }
100 
101 inline void connectHandler(const boost::system::error_code& ec)
102 {
103     if (ec)
104     {
105         BMCWEB_LOG_ERROR << "Couldn't connect to host serial port: " << ec;
106         for (crow::websocket::Connection* session : sessions)
107         {
108             session->close("Error in connecting to host port");
109         }
110         return;
111     }
112 
113     doWrite();
114     doRead();
115 }
116 
117 inline void requestRoutes(App& app)
118 {
119     BMCWEB_ROUTE(app, "/console0")
120         .privileges({"ConfigureComponents", "ConfigureManager"})
121         .websocket()
122         .onopen([](crow::websocket::Connection& conn,
123                    const std::shared_ptr<bmcweb::AsyncResp>&) {
124             BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
125 
126             sessions.insert(&conn);
127             if (hostSocket == nullptr)
128             {
129                 const std::string consoleName("\0obmc-console", 13);
130                 boost::asio::local::stream_protocol::endpoint ep(consoleName);
131 
132                 hostSocket = std::make_unique<
133                     boost::asio::local::stream_protocol::socket>(
134                     conn.getIoContext());
135                 hostSocket->async_connect(ep, connectHandler);
136             }
137         })
138         .onclose([](crow::websocket::Connection& conn,
139                     [[maybe_unused]] const std::string& reason) {
140             BMCWEB_LOG_INFO << "Closing websocket. Reason: " << reason;
141 
142             sessions.erase(&conn);
143             if (sessions.empty())
144             {
145                 hostSocket = nullptr;
146                 inputBuffer.clear();
147                 inputBuffer.shrink_to_fit();
148             }
149         })
150         .onmessage([]([[maybe_unused]] crow::websocket::Connection& conn,
151                       const std::string& data, [[maybe_unused]] bool isBinary) {
152             inputBuffer += data;
153             doWrite();
154         });
155 }
156 } // namespace obmc_console
157 } // namespace crow
158