| #ifndef THIRD_PARTY_GBMCWEB_INCLUDE_KVM_WEBSOCKET_H_ | |
| #define THIRD_PARTY_GBMCWEB_INCLUDE_KVM_WEBSOCKET_H_ | |
| #include <sys/socket.h> | |
| #include <cstddef> | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include "boost/container/flat_map.hpp" // NOLINT | |
| #include "app.hpp" | |
| #include "logging.hpp" | |
| #include "websocket.hpp" | |
| namespace crow { | |
| namespace obmc_kvm { | |
| static constexpr const uint maxSessions = 4; | |
| class KvmSession { | |
| 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:" << &conn | |
| << ", Couldn't connect to KVM socket port: " << 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:" << &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 << ", Committing " << 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<const char*>(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<1024UL * 50UL> outputBuffer; | |
| boost::beast::flat_static_buffer<1024UL> inputBuffer; | |
| bool doingWrite{false}; | |
| }; | |
| using SessionMap = boost::container::flat_map<crow::websocket::Connection*, | |
| std::unique_ptr<KvmSession>>; | |
| // 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 " << &conn << " opened"; | |
| if (sessions.size() == maxSessions) { | |
| conn.close("Max sessions are already connected"); | |
| return; | |
| } | |
| sessions[&conn] = std::make_unique<KvmSession>(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 | |
| #endif // THIRD_PARTY_GBMCWEB_INCLUDE_KVM_WEBSOCKET_H_ |