xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision 81d523a7)
1 #pragma once
2 #include <sys/socket.h>
3 
4 #include <app.hpp>
5 #include <async_resp.hpp>
6 #include <boost/container/flat_map.hpp>
7 #include <websocket.hpp>
8 
9 namespace crow
10 {
11 namespace obmc_kvm
12 {
13 
14 static constexpr const uint maxSessions = 4;
15 
16 class KvmSession
17 {
18   public:
19     explicit KvmSession(crow::websocket::Connection& connIn) :
20         conn(connIn), hostSocket(conn.getIoContext())
21     {
22         boost::asio::ip::tcp::endpoint endpoint(
23             boost::asio::ip::make_address("127.0.0.1"), 5900);
24         hostSocket.async_connect(
25             endpoint, [this, &connIn](const boost::system::error_code& ec) {
26                 if (ec)
27                 {
28                     BMCWEB_LOG_ERROR
29                         << "conn:" << &conn
30                         << ", Couldn't connect to KVM socket port: " << ec;
31                     if (ec != boost::asio::error::operation_aborted)
32                     {
33                         connIn.close("Error in connecting to KVM port");
34                     }
35                     return;
36                 }
37 
38                 doRead();
39             });
40     }
41 
42     void onMessage(const std::string& data)
43     {
44         if (data.length() > inputBuffer.capacity())
45         {
46             BMCWEB_LOG_ERROR << "conn:" << &conn
47                              << ", Buffer overrun when writing "
48                              << data.length() << " bytes";
49             conn.close("Buffer overrun");
50             return;
51         }
52 
53         BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Read " << data.size()
54                          << " bytes from websocket";
55         boost::asio::buffer_copy(inputBuffer.prepare(data.size()),
56                                  boost::asio::buffer(data));
57         BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Committing " << data.size()
58                          << " bytes from websocket";
59         inputBuffer.commit(data.size());
60 
61         BMCWEB_LOG_DEBUG << "conn:" << &conn << ", inputbuffer size "
62                          << inputBuffer.size();
63         doWrite();
64     }
65 
66   protected:
67     void doRead()
68     {
69         std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
70         BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Reading " << bytes
71                          << " from kvm socket";
72         hostSocket.async_read_some(
73             outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
74             [this](const boost::system::error_code& ec, std::size_t bytesRead) {
75             BMCWEB_LOG_DEBUG << "conn:" << &conn << ", read done.  Read "
76                              << bytesRead << " bytes";
77             if (ec)
78             {
79                 BMCWEB_LOG_ERROR
80                     << "conn:" << &conn
81                     << ", Couldn't read from KVM socket port: " << ec;
82                 if (ec != boost::asio::error::operation_aborted)
83                 {
84                     conn.close("Error in connecting to KVM port");
85                 }
86                 return;
87             }
88 
89             outputBuffer.commit(bytesRead);
90             std::string_view payload(
91                 static_cast<const char*>(outputBuffer.data().data()),
92                 bytesRead);
93             BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Sending payload size "
94                              << payload.size();
95             conn.sendBinary(payload);
96             outputBuffer.consume(bytesRead);
97 
98             doRead();
99             });
100     }
101 
102     void doWrite()
103     {
104         if (doingWrite)
105         {
106             BMCWEB_LOG_DEBUG << "conn:" << &conn
107                              << ", Already writing.  Bailing out";
108             return;
109         }
110         if (inputBuffer.size() == 0)
111         {
112             BMCWEB_LOG_DEBUG << "conn:" << &conn
113                              << ", inputBuffer empty.  Bailing out";
114             return;
115         }
116 
117         doingWrite = true;
118         hostSocket.async_write_some(inputBuffer.data(),
119                                     [this](const boost::system::error_code& ec,
120                                            std::size_t bytesWritten) {
121             BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote " << bytesWritten
122                              << "bytes";
123             doingWrite = false;
124             inputBuffer.consume(bytesWritten);
125 
126             if (ec == boost::asio::error::eof)
127             {
128                 conn.close("KVM socket port closed");
129                 return;
130             }
131             if (ec)
132             {
133                 BMCWEB_LOG_ERROR << "conn:" << &conn
134                                  << ", Error in KVM socket write " << ec;
135                 if (ec != boost::asio::error::operation_aborted)
136                 {
137                     conn.close("Error in reading to host port");
138                 }
139                 return;
140             }
141 
142             doWrite();
143         });
144     }
145 
146     crow::websocket::Connection& conn;
147     boost::asio::ip::tcp::socket hostSocket;
148     boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer;
149     boost::beast::flat_static_buffer<1024UL> inputBuffer;
150     bool doingWrite{false};
151 };
152 
153 static boost::container::flat_map<crow::websocket::Connection*,
154                                   std::unique_ptr<KvmSession>>
155     sessions;
156 
157 inline void requestRoutes(App& app)
158 {
159     sessions.reserve(maxSessions);
160 
161     BMCWEB_ROUTE(app, "/kvm/0")
162         .privileges({{"ConfigureComponents", "ConfigureManager"}})
163         .websocket()
164         .onopen([](crow::websocket::Connection& conn) {
165             BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
166 
167             if (sessions.size() == maxSessions)
168             {
169                 conn.close("Max sessions are already connected");
170                 return;
171             }
172 
173             sessions[&conn] = std::make_unique<KvmSession>(conn);
174         })
175         .onclose([](crow::websocket::Connection& conn, const std::string&) {
176             sessions.erase(&conn);
177         })
178         .onmessage([](crow::websocket::Connection& conn,
179                       const std::string& data, bool) {
180             if (sessions[&conn])
181             {
182                 sessions[&conn]->onMessage(data);
183             }
184         });
185 }
186 
187 } // namespace obmc_kvm
188 } // namespace crow
189