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