xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision cb103130)
1 #pragma once
2 #include <crow/app.h>
3 #include <crow/websocket.h>
4 #include <sys/socket.h>
5 
6 #include <boost/container/flat_map.hpp>
7 #include <webserver_common.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& conn) :
20         conn(conn), hostSocket(conn.get_io_context()), doingWrite(false)
21     {
22         boost::asio::ip::tcp::endpoint endpoint(
23             boost::asio::ip::make_address("::1"), 5900);
24         hostSocket.async_connect(
25             endpoint, [this, &conn](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                         conn.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 << ", Commiting " << 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
94                                  << ", Sending payload size " << 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(
119             inputBuffer.data(), [this](const boost::system::error_code& ec,
120                                        std::size_t bytesWritten) {
121                 BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote "
122                                  << bytesWritten << "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<1024U * 50U> outputBuffer;
149     boost::beast::flat_static_buffer<1024U> inputBuffer;
150     bool doingWrite;
151 };
152 
153 static boost::container::flat_map<crow::websocket::Connection*,
154                                   std::unique_ptr<KvmSession>>
155     sessions;
156 
157 inline void requestRoutes(CrowApp& app)
158 {
159     sessions.reserve(maxSessions);
160 
161     BMCWEB_ROUTE(app, "/kvm/0")
162         .websocket()
163         .onopen([](crow::websocket::Connection& conn) {
164             BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
165 
166             if (sessions.size() == maxSessions)
167             {
168                 conn.close("Max sessions are already connected");
169                 return;
170             }
171 
172             sessions[&conn] = std::make_unique<KvmSession>(conn);
173         })
174         .onclose([](crow::websocket::Connection& conn,
175                     const std::string& reason) { sessions.erase(&conn); })
176         .onmessage([](crow::websocket::Connection& conn,
177                       const std::string& data, bool is_binary) {
178             if (sessions[&conn])
179             {
180                 sessions[&conn]->onMessage(data);
181             }
182         });
183 }
184 
185 } // namespace obmc_kvm
186 } // namespace crow
187