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