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