#pragma once #include "app.hpp" #include "async_resp.hpp" #include "websocket.hpp" #include #include namespace crow { namespace obmc_kvm { static constexpr const uint maxSessions = 4; class KvmSession : public std::enable_shared_from_this { public: explicit KvmSession(crow::websocket::Connection& connIn) : conn(connIn), hostSocket(conn.getIoContext()) { boost::asio::ip::tcp::endpoint endpoint( boost::asio::ip::make_address("127.0.0.1"), 5900); hostSocket.async_connect( endpoint, [this, &connIn](const boost::system::error_code& ec) { if (ec) { BMCWEB_LOG_ERROR( "conn:{}, Couldn't connect to KVM socket port: {}", logPtr(&conn), ec); if (ec != boost::asio::error::operation_aborted) { connIn.close("Error in connecting to KVM port"); } return; } doRead(); }); } void onMessage(const std::string& data) { if (data.length() > inputBuffer.capacity()) { BMCWEB_LOG_ERROR("conn:{}, Buffer overrun when writing {} bytes", logPtr(&conn), data.length()); conn.close("Buffer overrun"); return; } BMCWEB_LOG_DEBUG("conn:{}, Read {} bytes from websocket", logPtr(&conn), data.size()); size_t copied = boost::asio::buffer_copy( inputBuffer.prepare(data.size()), boost::asio::buffer(data)); BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket", logPtr(&conn), copied); inputBuffer.commit(copied); BMCWEB_LOG_DEBUG("conn:{}, inputbuffer size {}", logPtr(&conn), inputBuffer.size()); doWrite(); } protected: void doRead() { std::size_t bytes = outputBuffer.capacity() - outputBuffer.size(); BMCWEB_LOG_DEBUG("conn:{}, Reading {} from kvm socket", logPtr(&conn), bytes); hostSocket.async_read_some( outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), [this, weak(weak_from_this())](const boost::system::error_code& ec, std::size_t bytesRead) { auto self = weak.lock(); if (self == nullptr) { return; } BMCWEB_LOG_DEBUG("conn:{}, read done. Read {} bytes", logPtr(&conn), bytesRead); if (ec) { BMCWEB_LOG_ERROR( "conn:{}, Couldn't read from KVM socket port: {}", logPtr(&conn), ec); if (ec != boost::asio::error::operation_aborted) { conn.close("Error in connecting to KVM port"); } return; } outputBuffer.commit(bytesRead); std::string_view payload( static_cast(outputBuffer.data().data()), bytesRead); BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}", logPtr(&conn), payload.size()); conn.sendBinary(payload); outputBuffer.consume(bytesRead); doRead(); }); } void doWrite() { if (doingWrite) { BMCWEB_LOG_DEBUG("conn:{}, Already writing. Bailing out", logPtr(&conn)); return; } if (inputBuffer.size() == 0) { BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty. Bailing out", logPtr(&conn)); return; } doingWrite = true; hostSocket.async_write_some( inputBuffer.data(), [this, weak(weak_from_this())](const boost::system::error_code& ec, std::size_t bytesWritten) { auto self = weak.lock(); if (self == nullptr) { return; } BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn), bytesWritten); doingWrite = false; inputBuffer.consume(bytesWritten); if (ec == boost::asio::error::eof) { conn.close("KVM socket port closed"); return; } if (ec) { BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}", logPtr(&conn), ec); if (ec != boost::asio::error::operation_aborted) { conn.close("Error in reading to host port"); } return; } doWrite(); }); } crow::websocket::Connection& conn; boost::asio::ip::tcp::socket hostSocket; boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer; boost::beast::flat_static_buffer<1024UL> inputBuffer; bool doingWrite{false}; }; using SessionMap = boost::container::flat_map>; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static SessionMap sessions; inline void requestRoutes(App& app) { sessions.reserve(maxSessions); BMCWEB_ROUTE(app, "/kvm/0") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .websocket() .onopen([](crow::websocket::Connection& conn) { BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); if (sessions.size() == maxSessions) { conn.close("Max sessions are already connected"); return; } sessions[&conn] = std::make_shared(conn); }) .onclose([](crow::websocket::Connection& conn, const std::string&) { sessions.erase(&conn); }) .onmessage([](crow::websocket::Connection& conn, const std::string& data, bool) { if (sessions[&conn]) { sessions[&conn]->onMessage(data); } }); } } // namespace obmc_kvm } // namespace crow