140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
33eb2f35fSEd Tanous #pragma once
43ccb3adbSEd Tanous #include "app.hpp"
5*d98a2f93SEd Tanous #include "io_context_singleton.hpp"
6d7857201SEd Tanous #include "logging.hpp"
7faf100f9SEd Tanous #include "websocket.hpp"
83ccb3adbSEd Tanous
9d7857201SEd Tanous #include <sys/types.h>
103eb2f35fSEd Tanous
11d7857201SEd Tanous #include <boost/asio/buffer.hpp>
12d7857201SEd Tanous #include <boost/asio/error.hpp>
13d7857201SEd Tanous #include <boost/asio/ip/address.hpp>
14d7857201SEd Tanous #include <boost/asio/ip/tcp.hpp>
15d7857201SEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
163eb2f35fSEd Tanous #include <boost/container/flat_map.hpp>
173eb2f35fSEd Tanous
18d7857201SEd Tanous #include <cstddef>
19d7857201SEd Tanous #include <memory>
20d7857201SEd Tanous #include <string>
21d7857201SEd Tanous
223eb2f35fSEd Tanous namespace crow
233eb2f35fSEd Tanous {
243eb2f35fSEd Tanous namespace obmc_kvm
253eb2f35fSEd Tanous {
263eb2f35fSEd Tanous
27a133b291SJae Hyun Yoo static constexpr const uint maxSessions = 4;
283eb2f35fSEd Tanous
298e73b906SXinnan Xie class KvmSession : public std::enable_shared_from_this<KvmSession>
30c68604bdSJae Hyun Yoo {
31a133b291SJae Hyun Yoo public:
KvmSession(crow::websocket::Connection & connIn)3223a21a1cSEd Tanous explicit KvmSession(crow::websocket::Connection& connIn) :
33*d98a2f93SEd Tanous conn(connIn), hostSocket(getIoContext())
343eb2f35fSEd Tanous {
353eb2f35fSEd Tanous boost::asio::ip::tcp::endpoint endpoint(
36b8c7eb23SJohnathan Mantey boost::asio::ip::make_address("127.0.0.1"), 5900);
37a133b291SJae Hyun Yoo hostSocket.async_connect(
3823a21a1cSEd Tanous endpoint, [this, &connIn](const boost::system::error_code& ec) {
39a133b291SJae Hyun Yoo if (ec)
40a133b291SJae Hyun Yoo {
4162598e31SEd Tanous BMCWEB_LOG_ERROR(
4262598e31SEd Tanous "conn:{}, Couldn't connect to KVM socket port: {}",
4362598e31SEd Tanous logPtr(&conn), ec);
44a133b291SJae Hyun Yoo if (ec != boost::asio::error::operation_aborted)
45a133b291SJae Hyun Yoo {
4623a21a1cSEd Tanous connIn.close("Error in connecting to KVM port");
473eb2f35fSEd Tanous }
48a133b291SJae Hyun Yoo return;
49a133b291SJae Hyun Yoo }
50a133b291SJae Hyun Yoo
51a133b291SJae Hyun Yoo doRead();
52a133b291SJae Hyun Yoo });
53a133b291SJae Hyun Yoo }
54a133b291SJae Hyun Yoo
onMessage(const std::string & data)55a133b291SJae Hyun Yoo void onMessage(const std::string& data)
56a133b291SJae Hyun Yoo {
573eb2f35fSEd Tanous if (data.length() > inputBuffer.capacity())
583eb2f35fSEd Tanous {
5962598e31SEd Tanous BMCWEB_LOG_ERROR("conn:{}, Buffer overrun when writing {} bytes",
6062598e31SEd Tanous logPtr(&conn), data.length());
613eb2f35fSEd Tanous conn.close("Buffer overrun");
623eb2f35fSEd Tanous return;
633eb2f35fSEd Tanous }
643eb2f35fSEd Tanous
6562598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, Read {} bytes from websocket", logPtr(&conn),
6662598e31SEd Tanous data.size());
6744106f34SEd Tanous size_t copied = boost::asio::buffer_copy(
6844106f34SEd Tanous inputBuffer.prepare(data.size()), boost::asio::buffer(data));
6962598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket",
7044106f34SEd Tanous logPtr(&conn), copied);
7144106f34SEd Tanous inputBuffer.commit(copied);
723eb2f35fSEd Tanous
7362598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, inputbuffer size {}", logPtr(&conn),
7462598e31SEd Tanous inputBuffer.size());
75a133b291SJae Hyun Yoo doWrite();
76a133b291SJae Hyun Yoo }
77a133b291SJae Hyun Yoo
78a133b291SJae Hyun Yoo protected:
doRead()79a133b291SJae Hyun Yoo void doRead()
80a133b291SJae Hyun Yoo {
81a133b291SJae Hyun Yoo std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
8262598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, Reading {} from kvm socket", logPtr(&conn),
8362598e31SEd Tanous bytes);
84a133b291SJae Hyun Yoo hostSocket.async_read_some(
85a133b291SJae Hyun Yoo outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
868e73b906SXinnan Xie [this, weak(weak_from_this())](const boost::system::error_code& ec,
878e73b906SXinnan Xie std::size_t bytesRead) {
888e73b906SXinnan Xie auto self = weak.lock();
898e73b906SXinnan Xie if (self == nullptr)
908e73b906SXinnan Xie {
918e73b906SXinnan Xie return;
928e73b906SXinnan Xie }
9362598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, read done. Read {} bytes",
9462598e31SEd Tanous logPtr(&conn), bytesRead);
95a133b291SJae Hyun Yoo if (ec)
96a133b291SJae Hyun Yoo {
9762598e31SEd Tanous BMCWEB_LOG_ERROR(
9862598e31SEd Tanous "conn:{}, Couldn't read from KVM socket port: {}",
9962598e31SEd Tanous logPtr(&conn), ec);
100a133b291SJae Hyun Yoo if (ec != boost::asio::error::operation_aborted)
101a133b291SJae Hyun Yoo {
102a133b291SJae Hyun Yoo conn.close("Error in connecting to KVM port");
103a133b291SJae Hyun Yoo }
104a133b291SJae Hyun Yoo return;
105a133b291SJae Hyun Yoo }
106a133b291SJae Hyun Yoo
107a133b291SJae Hyun Yoo outputBuffer.commit(bytesRead);
108a133b291SJae Hyun Yoo std::string_view payload(
109a133b291SJae Hyun Yoo static_cast<const char*>(outputBuffer.data().data()),
110a133b291SJae Hyun Yoo bytesRead);
111bd79bce8SPatrick Williams BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}",
112bd79bce8SPatrick Williams logPtr(&conn), payload.size());
113a133b291SJae Hyun Yoo conn.sendBinary(payload);
114a133b291SJae Hyun Yoo outputBuffer.consume(bytesRead);
115a133b291SJae Hyun Yoo
116a133b291SJae Hyun Yoo doRead();
117a133b291SJae Hyun Yoo });
118a133b291SJae Hyun Yoo }
119a133b291SJae Hyun Yoo
doWrite()120a133b291SJae Hyun Yoo void doWrite()
121a133b291SJae Hyun Yoo {
122a133b291SJae Hyun Yoo if (doingWrite)
123a133b291SJae Hyun Yoo {
12462598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, Already writing. Bailing out",
12562598e31SEd Tanous logPtr(&conn));
126a133b291SJae Hyun Yoo return;
127a133b291SJae Hyun Yoo }
128a133b291SJae Hyun Yoo if (inputBuffer.size() == 0)
129a133b291SJae Hyun Yoo {
13062598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty. Bailing out",
13162598e31SEd Tanous logPtr(&conn));
132a133b291SJae Hyun Yoo return;
133a133b291SJae Hyun Yoo }
134a133b291SJae Hyun Yoo
135a133b291SJae Hyun Yoo doingWrite = true;
1368e73b906SXinnan Xie hostSocket.async_write_some(
1378e73b906SXinnan Xie inputBuffer.data(),
1388e73b906SXinnan Xie [this, weak(weak_from_this())](const boost::system::error_code& ec,
139a133b291SJae Hyun Yoo std::size_t bytesWritten) {
1408e73b906SXinnan Xie auto self = weak.lock();
1418e73b906SXinnan Xie if (self == nullptr)
1428e73b906SXinnan Xie {
1438e73b906SXinnan Xie return;
1448e73b906SXinnan Xie }
14562598e31SEd Tanous BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn),
14662598e31SEd Tanous bytesWritten);
147a133b291SJae Hyun Yoo doingWrite = false;
148a133b291SJae Hyun Yoo inputBuffer.consume(bytesWritten);
149a133b291SJae Hyun Yoo
150a133b291SJae Hyun Yoo if (ec == boost::asio::error::eof)
151a133b291SJae Hyun Yoo {
152a133b291SJae Hyun Yoo conn.close("KVM socket port closed");
153a133b291SJae Hyun Yoo return;
154a133b291SJae Hyun Yoo }
155a133b291SJae Hyun Yoo if (ec)
156a133b291SJae Hyun Yoo {
15762598e31SEd Tanous BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}",
15862598e31SEd Tanous logPtr(&conn), ec);
159a133b291SJae Hyun Yoo if (ec != boost::asio::error::operation_aborted)
160a133b291SJae Hyun Yoo {
161a133b291SJae Hyun Yoo conn.close("Error in reading to host port");
162a133b291SJae Hyun Yoo }
163a133b291SJae Hyun Yoo return;
164a133b291SJae Hyun Yoo }
165a133b291SJae Hyun Yoo
1663eb2f35fSEd Tanous doWrite();
1673eb2f35fSEd Tanous });
1683eb2f35fSEd Tanous }
169a133b291SJae Hyun Yoo
170a133b291SJae Hyun Yoo crow::websocket::Connection& conn;
171a133b291SJae Hyun Yoo boost::asio::ip::tcp::socket hostSocket;
1726de264ccSEd Tanous boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer;
1736de264ccSEd Tanous boost::beast::flat_static_buffer<1024UL> inputBuffer;
174f5b191a6SEd Tanous bool doingWrite{false};
175a133b291SJae Hyun Yoo };
176a133b291SJae Hyun Yoo
177cf9e417dSEd Tanous using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
1788e73b906SXinnan Xie std::shared_ptr<KvmSession>>;
179cf9e417dSEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
180cf9e417dSEd Tanous static SessionMap sessions;
181a133b291SJae Hyun Yoo
requestRoutes(App & app)18252cc112dSEd Tanous inline void requestRoutes(App& app)
183a133b291SJae Hyun Yoo {
184a133b291SJae Hyun Yoo sessions.reserve(maxSessions);
185a133b291SJae Hyun Yoo
186a133b291SJae Hyun Yoo BMCWEB_ROUTE(app, "/kvm/0")
187432a890cSEd Tanous .privileges({{"ConfigureComponents", "ConfigureManager"}})
188a133b291SJae Hyun Yoo .websocket()
1897772638eSzhanghch05 .onopen([](crow::websocket::Connection& conn) {
19062598e31SEd Tanous BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
191a133b291SJae Hyun Yoo
192a133b291SJae Hyun Yoo if (sessions.size() == maxSessions)
193a133b291SJae Hyun Yoo {
194a133b291SJae Hyun Yoo conn.close("Max sessions are already connected");
195a133b291SJae Hyun Yoo return;
196a133b291SJae Hyun Yoo }
197a133b291SJae Hyun Yoo
1988e73b906SXinnan Xie sessions[&conn] = std::make_shared<KvmSession>(conn);
199a133b291SJae Hyun Yoo })
200cb13a392SEd Tanous .onclose([](crow::websocket::Connection& conn, const std::string&) {
201cb13a392SEd Tanous sessions.erase(&conn);
202cb13a392SEd Tanous })
203a133b291SJae Hyun Yoo .onmessage([](crow::websocket::Connection& conn,
204cb13a392SEd Tanous const std::string& data, bool) {
205a133b291SJae Hyun Yoo if (sessions[&conn])
206a133b291SJae Hyun Yoo {
207a133b291SJae Hyun Yoo sessions[&conn]->onMessage(data);
208a133b291SJae Hyun Yoo }
209a133b291SJae Hyun Yoo });
210a133b291SJae Hyun Yoo }
211a133b291SJae Hyun Yoo
2123eb2f35fSEd Tanous } // namespace obmc_kvm
2133eb2f35fSEd Tanous } // namespace crow
214