1 #pragma once 2 #include <sys/socket.h> 3 4 #include <app.hpp> 5 #include <async_resp.hpp> 6 #include <boost/asio/local/stream_protocol.hpp> 7 #include <boost/container/flat_map.hpp> 8 #include <boost/container/flat_set.hpp> 9 #include <websocket.hpp> 10 11 namespace crow 12 { 13 namespace obmc_console 14 { 15 16 static std::unique_ptr<boost::asio::local::stream_protocol::socket> hostSocket; 17 18 static std::array<char, 4096> outputBuffer; 19 static std::string inputBuffer; 20 21 static boost::container::flat_set<crow::websocket::Connection*> sessions; 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 33 if (inputBuffer.empty()) 34 { 35 BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out"; 36 return; 37 } 38 39 if (!hostSocket) 40 { 41 BMCWEB_LOG_ERROR << "doWrite(): Socket closed."; 42 return; 43 } 44 45 doingWrite = true; 46 hostSocket->async_write_some( 47 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), 48 [](boost::beast::error_code ec, std::size_t bytesWritten) { 49 doingWrite = false; 50 inputBuffer.erase(0, bytesWritten); 51 52 if (ec == boost::asio::error::eof) 53 { 54 for (crow::websocket::Connection* session : sessions) 55 { 56 session->close("Error in reading to host port"); 57 } 58 return; 59 } 60 if (ec) 61 { 62 BMCWEB_LOG_ERROR << "Error in host serial write " << ec; 63 return; 64 } 65 doWrite(); 66 }); 67 } 68 69 inline void doRead() 70 { 71 if (!hostSocket) 72 { 73 BMCWEB_LOG_ERROR << "doRead(): Socket closed."; 74 return; 75 } 76 77 BMCWEB_LOG_DEBUG << "Reading from socket"; 78 hostSocket->async_read_some( 79 boost::asio::buffer(outputBuffer.data(), outputBuffer.size()), 80 [](const boost::system::error_code& ec, std::size_t bytesRead) { 81 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes"; 82 if (ec) 83 { 84 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: " 85 << ec; 86 for (crow::websocket::Connection* session : sessions) 87 { 88 session->close("Error in connecting to host port"); 89 } 90 return; 91 } 92 std::string_view payload(outputBuffer.data(), bytesRead); 93 for (crow::websocket::Connection* session : sessions) 94 { 95 session->sendBinary(payload); 96 } 97 doRead(); 98 }); 99 } 100 101 inline void connectHandler(const boost::system::error_code& ec) 102 { 103 if (ec) 104 { 105 BMCWEB_LOG_ERROR << "Couldn't connect to host serial port: " << ec; 106 for (crow::websocket::Connection* session : sessions) 107 { 108 session->close("Error in connecting to host port"); 109 } 110 return; 111 } 112 113 doWrite(); 114 doRead(); 115 } 116 117 inline void requestRoutes(App& app) 118 { 119 BMCWEB_ROUTE(app, "/console0") 120 .privileges({"ConfigureComponents", "ConfigureManager"}) 121 .websocket() 122 .onopen([](crow::websocket::Connection& conn, 123 const std::shared_ptr<bmcweb::AsyncResp>&) { 124 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 125 126 sessions.insert(&conn); 127 if (hostSocket == nullptr) 128 { 129 const std::string consoleName("\0obmc-console", 13); 130 boost::asio::local::stream_protocol::endpoint ep(consoleName); 131 132 hostSocket = std::make_unique< 133 boost::asio::local::stream_protocol::socket>( 134 conn.getIoContext()); 135 hostSocket->async_connect(ep, connectHandler); 136 } 137 }) 138 .onclose([](crow::websocket::Connection& conn, 139 [[maybe_unused]] const std::string& reason) { 140 BMCWEB_LOG_INFO << "Closing websocket. Reason: " << reason; 141 142 sessions.erase(&conn); 143 if (sessions.empty()) 144 { 145 hostSocket = nullptr; 146 inputBuffer.clear(); 147 inputBuffer.shrink_to_fit(); 148 } 149 }) 150 .onmessage([]([[maybe_unused]] crow::websocket::Connection& conn, 151 const std::string& data, [[maybe_unused]] bool isBinary) { 152 inputBuffer += data; 153 doWrite(); 154 }); 155 } 156 } // namespace obmc_console 157 } // namespace crow 158