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