xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision a778c026)
1 #pragma once
2 #include <app.h>
3 #include <sys/socket.h>
4 #include <websocket.h>
5 
6 #include <async_resp.hpp>
7 #include <boost/container/flat_map.hpp>
8 #include <webserver_common.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& conn) :
21         conn(conn), hostSocket(conn.get_io_context()), doingWrite(false)
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, &conn](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                         conn.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 << ", Commiting " << 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
95                                  << ", Sending payload size " << 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(
120             inputBuffer.data(), [this](const boost::system::error_code& ec,
121                                        std::size_t bytesWritten) {
122                 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote "
123                                  << bytesWritten << "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<1024U * 50U> outputBuffer;
150     boost::beast::flat_static_buffer<1024U> inputBuffer;
151     bool doingWrite;
152 };
153 
154 static boost::container::flat_map<crow::websocket::Connection*,
155                                   std::unique_ptr<KvmSession>>
156     sessions;
157 
158 inline void requestRoutes(CrowApp& app)
159 {
160     sessions.reserve(maxSessions);
161 
162     BMCWEB_ROUTE(app, "/kvm/0")
163         .requires({"ConfigureComponents", "ConfigureManager"})
164         .websocket()
165         .onopen([](crow::websocket::Connection& conn,
166                    std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
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,
178                     const std::string& reason) { sessions.erase(&conn); })
179         .onmessage([](crow::websocket::Connection& conn,
180                       const std::string& data, bool is_binary) {
181             if (sessions[&conn])
182             {
183                 sessions[&conn]->onMessage(data);
184             }
185         });
186 }
187 
188 } // namespace obmc_kvm
189 } // namespace crow
190