#pragma once #include #include #include #include #include #include namespace crow { namespace obmc_kvm { static constexpr const uint maxSessions = 4; class KvmSession { public: explicit KvmSession(crow::websocket::Connection& conn) : conn(conn), hostSocket(conn.get_io_context()), doingWrite(false) { boost::asio::ip::tcp::endpoint endpoint( boost::asio::ip::make_address("127.0.0.1"), 5900); hostSocket.async_connect( endpoint, [this, &conn](const boost::system::error_code& ec) { if (ec) { BMCWEB_LOG_ERROR << "conn:" << &conn << ", Couldn't connect to KVM socket port: " << ec; if (ec != boost::asio::error::operation_aborted) { conn.close("Error in connecting to KVM port"); } return; } doRead(); }); } void onMessage(const std::string& data) { if (data.length() > inputBuffer.capacity()) { BMCWEB_LOG_ERROR << "conn:" << &conn << ", Buffer overrun when writing " << data.length() << " bytes"; conn.close("Buffer overrun"); return; } BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Read " << data.size() << " bytes from websocket"; boost::asio::buffer_copy(inputBuffer.prepare(data.size()), boost::asio::buffer(data)); BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Commiting " << data.size() << " bytes from websocket"; inputBuffer.commit(data.size()); BMCWEB_LOG_DEBUG << "conn:" << &conn << ", inputbuffer size " << inputBuffer.size(); doWrite(); } protected: void doRead() { std::size_t bytes = outputBuffer.capacity() - outputBuffer.size(); BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Reading " << bytes << " from kvm socket"; hostSocket.async_read_some( outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), [this](const boost::system::error_code& ec, std::size_t bytesRead) { BMCWEB_LOG_DEBUG << "conn:" << &conn << ", read done. Read " << bytesRead << " bytes"; if (ec) { BMCWEB_LOG_ERROR << "conn:" << &conn << ", Couldn't read from KVM socket port: " << 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:" << &conn << ", Sending payload size " << payload.size(); conn.sendBinary(payload); outputBuffer.consume(bytesRead); doRead(); }); } void doWrite() { if (doingWrite) { BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Already writing. Bailing out"; return; } if (inputBuffer.size() == 0) { BMCWEB_LOG_DEBUG << "conn:" << &conn << ", inputBuffer empty. Bailing out"; return; } doingWrite = true; hostSocket.async_write_some( inputBuffer.data(), [this](const boost::system::error_code& ec, std::size_t bytesWritten) { BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote " << bytesWritten << "bytes"; doingWrite = false; inputBuffer.consume(bytesWritten); if (ec == boost::asio::error::eof) { conn.close("KVM socket port closed"); return; } if (ec) { BMCWEB_LOG_ERROR << "conn:" << &conn << ", Error in KVM socket write " << 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<1024U * 50U> outputBuffer; boost::beast::flat_static_buffer<1024U> inputBuffer; bool doingWrite; }; static boost::container::flat_map> sessions; inline void requestRoutes(CrowApp& app) { sessions.reserve(maxSessions); BMCWEB_ROUTE(app, "/kvm/0") .requires({"ConfigureComponents", "ConfigureManager"}) .websocket() .onopen([](crow::websocket::Connection& conn, std::shared_ptr asyncResp) { BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; if (sessions.size() == maxSessions) { conn.close("Max sessions are already connected"); return; } sessions[&conn] = std::make_unique(conn); }) .onclose([](crow::websocket::Connection& conn, const std::string& reason) { sessions.erase(&conn); }) .onmessage([](crow::websocket::Connection& conn, const std::string& data, bool is_binary) { if (sessions[&conn]) { sessions[&conn]->onMessage(data); } }); } } // namespace obmc_kvm } // namespace crow