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 inline void WriteDone(const boost::system::error_code& ec, 28 std::size_t bytesWritten) 29 { 30 BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes"; 31 doingWrite = false; 32 inputBuffer.consume(bytesWritten); 33 34 if (session == nullptr) 35 { 36 return; 37 } 38 if (ec == boost::asio::error::eof) 39 { 40 session->close("KVM socket port closed"); 41 return; 42 } 43 if (ec) 44 { 45 session->close("Error in reading to host port"); 46 BMCWEB_LOG_ERROR << "Error in KVM socket write " << ec; 47 return; 48 } 49 50 doWrite(); 51 } 52 53 inline void doWrite() 54 { 55 if (doingWrite) 56 { 57 BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; 58 return; 59 } 60 if (inputBuffer.size() == 0) 61 { 62 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out"; 63 return; 64 } 65 66 doingWrite = true; 67 hostSocket->async_write_some(inputBuffer.data(), WriteDone); 68 } 69 70 inline void doRead(); 71 72 inline void readDone(const boost::system::error_code& ec, std::size_t bytesRead) 73 { 74 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes"; 75 if (ec) 76 { 77 BMCWEB_LOG_ERROR << "Couldn't read from KVM socket port: " << ec; 78 if (session != nullptr) 79 { 80 session->close("Error in connecting to KVM port"); 81 } 82 return; 83 } 84 if (session == nullptr) 85 { 86 return; 87 } 88 89 outputBuffer.commit(bytesRead); 90 boost::beast::string_view payload( 91 static_cast<const char*>(outputBuffer.data().data()), bytesRead); 92 BMCWEB_LOG_DEBUG << "Sending payload size " << payload.size(); 93 session->sendBinary(payload); 94 outputBuffer.consume(bytesRead); 95 96 doRead(); 97 } 98 99 inline void doRead() 100 { 101 std::size_t bytes = outputBuffer.capacity() - outputBuffer.size(); 102 BMCWEB_LOG_DEBUG << "Reading " << bytes << " from kvm socket"; 103 hostSocket->async_read_some( 104 outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()), 105 readDone); 106 } 107 108 inline void connectHandler(const boost::system::error_code& ec) 109 { 110 if (ec) 111 { 112 BMCWEB_LOG_ERROR << "Couldn't connect to KVM socket port: " << ec; 113 if (session != nullptr) 114 { 115 session->close("Error in connecting to KVM port"); 116 } 117 return; 118 } 119 120 doRead(); 121 } 122 123 inline void requestRoutes(CrowApp& app) 124 { 125 BMCWEB_ROUTE(app, "/kvm/0") 126 .websocket() 127 .onopen([](crow::websocket::Connection& conn) { 128 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 129 130 if (session != nullptr) 131 { 132 conn.close("User already connected"); 133 return; 134 } 135 136 session = &conn; 137 if (hostSocket == nullptr) 138 { 139 boost::asio::ip::tcp::endpoint endpoint( 140 boost::asio::ip::make_address("127.0.0.1"), 5900); 141 142 hostSocket = std::make_unique<boost::asio::ip::tcp::socket>( 143 conn.get_io_context()); 144 hostSocket->async_connect(endpoint, connectHandler); 145 } 146 }) 147 .onclose( 148 [](crow::websocket::Connection& conn, const std::string& reason) { 149 session = nullptr; 150 hostSocket = nullptr; 151 #if BOOST_VERSION >= 107000 152 inputBuffer.clear(); 153 outputBuffer.clear(); 154 #else 155 inputBuffer.reset(); 156 outputBuffer.reset(); 157 #endif 158 }) 159 .onmessage([](crow::websocket::Connection& conn, 160 const std::string& data, bool is_binary) { 161 if (data.length() > inputBuffer.capacity()) 162 { 163 BMCWEB_LOG_ERROR << "Buffer overrun when writing " 164 << data.length() << " bytes"; 165 conn.close("Buffer overrun"); 166 return; 167 } 168 169 BMCWEB_LOG_DEBUG << "Read " << data.size() 170 << " bytes from websocket"; 171 boost::asio::buffer_copy(inputBuffer.prepare(data.size()), 172 boost::asio::buffer(data)); 173 BMCWEB_LOG_DEBUG << "commiting " << data.size() 174 << " bytes from websocket"; 175 inputBuffer.commit(data.size()); 176 177 BMCWEB_LOG_DEBUG << "inputbuffer size " << inputBuffer.size(); 178 doWrite(); 179 }); 180 } 181 } // namespace obmc_kvm 182 } // namespace crow 183