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 18 { 19 public: 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 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 boost::asio::buffer_copy(inputBuffer.prepare(data.size()), 56 boost::asio::buffer(data)); 57 BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket", 58 logPtr(&conn), data.size()); 59 inputBuffer.commit(data.size()); 60 61 BMCWEB_LOG_DEBUG("conn:{}, inputbuffer size {}", logPtr(&conn), 62 inputBuffer.size()); 63 doWrite(); 64 } 65 66 protected: 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](const boost::system::error_code& ec, std::size_t bytesRead) { 75 BMCWEB_LOG_DEBUG("conn:{}, read done. Read {} bytes", 76 logPtr(&conn), bytesRead); 77 if (ec) 78 { 79 BMCWEB_LOG_ERROR( 80 "conn:{}, Couldn't read from KVM socket port: {}", 81 logPtr(&conn), ec); 82 if (ec != boost::asio::error::operation_aborted) 83 { 84 conn.close("Error in connecting to KVM port"); 85 } 86 return; 87 } 88 89 outputBuffer.commit(bytesRead); 90 std::string_view payload( 91 static_cast<const char*>(outputBuffer.data().data()), 92 bytesRead); 93 BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}", logPtr(&conn), 94 payload.size()); 95 conn.sendBinary(payload); 96 outputBuffer.consume(bytesRead); 97 98 doRead(); 99 }); 100 } 101 102 void doWrite() 103 { 104 if (doingWrite) 105 { 106 BMCWEB_LOG_DEBUG("conn:{}, Already writing. Bailing out", 107 logPtr(&conn)); 108 return; 109 } 110 if (inputBuffer.size() == 0) 111 { 112 BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty. Bailing out", 113 logPtr(&conn)); 114 return; 115 } 116 117 doingWrite = true; 118 hostSocket.async_write_some(inputBuffer.data(), 119 [this](const boost::system::error_code& ec, 120 std::size_t bytesWritten) { 121 BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn), 122 bytesWritten); 123 doingWrite = false; 124 inputBuffer.consume(bytesWritten); 125 126 if (ec == boost::asio::error::eof) 127 { 128 conn.close("KVM socket port closed"); 129 return; 130 } 131 if (ec) 132 { 133 BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}", 134 logPtr(&conn), ec); 135 if (ec != boost::asio::error::operation_aborted) 136 { 137 conn.close("Error in reading to host port"); 138 } 139 return; 140 } 141 142 doWrite(); 143 }); 144 } 145 146 crow::websocket::Connection& conn; 147 boost::asio::ip::tcp::socket hostSocket; 148 boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer; 149 boost::beast::flat_static_buffer<1024UL> inputBuffer; 150 bool doingWrite{false}; 151 }; 152 153 using SessionMap = boost::container::flat_map<crow::websocket::Connection*, 154 std::unique_ptr<KvmSession>>; 155 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 156 static SessionMap sessions; 157 158 inline void requestRoutes(App& app) 159 { 160 sessions.reserve(maxSessions); 161 162 BMCWEB_ROUTE(app, "/kvm/0") 163 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 164 .websocket() 165 .onopen([](crow::websocket::Connection& conn) { 166 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn)); 167 168 if (sessions.size() == maxSessions) 169 { 170 conn.close("Max sessions are already connected"); 171 return; 172 } 173 174 sessions[&conn] = std::make_unique<KvmSession>(conn); 175 }) 176 .onclose([](crow::websocket::Connection& conn, const std::string&) { 177 sessions.erase(&conn); 178 }) 179 .onmessage([](crow::websocket::Connection& conn, 180 const std::string& data, bool) { 181 if (sessions[&conn]) 182 { 183 sessions[&conn]->onMessage(data); 184 } 185 }); 186 } 187 188 } // namespace obmc_kvm 189 } // namespace crow 190