1 #pragma once 2 #include <crow/app.h> 3 #include <crow/websocket.h> 4 #include <sys/socket.h> 5 6 #include <boost/container/flat_map.hpp> 7 #include <boost/container/flat_set.hpp> 8 #include <webserver_common.hpp> 9 10 namespace crow 11 { 12 namespace obmc_kvm 13 { 14 15 static std::unique_ptr<boost::asio::ip::tcp::socket> hostSocket; 16 17 // TODO(ed) validate that these buffer sizes are sane 18 static boost::beast::flat_static_buffer<1024U * 50U> outputBuffer; 19 static boost::beast::flat_static_buffer<1024U> inputBuffer; 20 21 static crow::websocket::Connection* session = nullptr; 22 23 static bool doingWrite = false; 24 25 inline void doWrite() 26 { 27 if (doingWrite) 28 { 29 BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; 30 return; 31 } 32 if (inputBuffer.size() == 0) 33 { 34 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out"; 35 return; 36 } 37 38 doingWrite = true; 39 hostSocket->async_write_some( 40 inputBuffer.data(), 41 [](boost::beast::error_code ec, std::size_t bytes_written) { 42 BMCWEB_LOG_DEBUG << "Wrote " << bytes_written << "bytes"; 43 doingWrite = false; 44 inputBuffer.consume(bytes_written); 45 46 if (session == nullptr) 47 { 48 return; 49 } 50 if (ec == boost::asio::error::eof) 51 { 52 session->close("KVM socket port closed"); 53 return; 54 } 55 if (ec) 56 { 57 session->close("Error in reading to host port"); 58 BMCWEB_LOG_ERROR << "Error in KVM socket write " << ec; 59 return; 60 } 61 doWrite(); 62 }); 63 } 64 65 inline void doRead(); 66 67 inline void readDone(const boost::system::error_code& ec, std::size_t bytesRead) 68 { 69 outputBuffer.commit(bytesRead); 70 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes"; 71 if (ec) 72 { 73 BMCWEB_LOG_ERROR << "Couldn't read from KVM socket port: " << ec; 74 if (session != nullptr) 75 { 76 session->close("Error in connecting to KVM port"); 77 } 78 return; 79 } 80 if (session == nullptr) 81 { 82 return; 83 } 84 85 boost::beast::string_view payload( 86 static_cast<const char*>(outputBuffer.data().data()), bytesRead); 87 BMCWEB_LOG_DEBUG << "Sending payload size " << payload.size(); 88 session->sendBinary(payload); 89 outputBuffer.consume(bytesRead); 90 91 doRead(); 92 } 93 94 inline void doRead() 95 { 96 std::size_t bytes = outputBuffer.capacity() - outputBuffer.size(); 97 BMCWEB_LOG_DEBUG << "Reading " << bytes << " from kvm socket"; 98 hostSocket->async_read_some( 99 outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), 100 readDone); 101 } 102 103 inline void connectHandler(const boost::system::error_code& ec) 104 { 105 if (ec) 106 { 107 BMCWEB_LOG_ERROR << "Couldn't connect to KVM socket port: " << ec; 108 if (session != nullptr) 109 { 110 session->close("Error in connecting to KVM port"); 111 } 112 return; 113 } 114 115 doWrite(); 116 doRead(); 117 } 118 119 inline void requestRoutes(CrowApp& app) 120 { 121 BMCWEB_ROUTE(app, "/kvm/0") 122 .websocket() 123 .onopen([](crow::websocket::Connection& conn) { 124 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 125 126 if (session != nullptr) 127 { 128 conn.close("User already connected"); 129 return; 130 } 131 132 session = &conn; 133 if (hostSocket == nullptr) 134 { 135 boost::asio::ip::tcp::endpoint endpoint( 136 boost::asio::ip::address::from_string("127.0.0.1"), 5900); 137 138 hostSocket = std::make_unique<boost::asio::ip::tcp::socket>( 139 conn.get_io_context()); 140 hostSocket->async_connect(endpoint, connectHandler); 141 } 142 }) 143 .onclose( 144 [](crow::websocket::Connection& conn, const std::string& reason) { 145 session = nullptr; 146 hostSocket = nullptr; 147 inputBuffer.reset(); 148 outputBuffer.reset(); 149 }) 150 .onmessage([](crow::websocket::Connection& conn, 151 const std::string& data, bool is_binary) { 152 if (data.length() > inputBuffer.capacity()) 153 { 154 BMCWEB_LOG_ERROR << "Buffer overrun when writing " 155 << data.length() << " bytes"; 156 conn.close("Buffer overrun"); 157 return; 158 } 159 160 BMCWEB_LOG_DEBUG << "Read " << data.size() 161 << " bytes from websocket"; 162 boost::asio::buffer_copy(inputBuffer.prepare(data.size()), 163 boost::asio::buffer(data)); 164 BMCWEB_LOG_DEBUG << "commiting " << data.size() 165 << " bytes from websocket"; 166 inputBuffer.commit(data.size()); 167 168 BMCWEB_LOG_DEBUG << "inputbuffer size " << inputBuffer.size(); 169 doWrite(); 170 }); 171 } 172 } // namespace obmc_kvm 173 } // namespace crow 174