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/asio/local/stream_protocol.hpp> 9 #include <boost/container/flat_set.hpp> 10 11 namespace crow 12 { 13 namespace obmc_console 14 { 15 16 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 17 static std::unique_ptr<boost::asio::local::stream_protocol::socket> hostSocket; 18 19 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 20 static std::array<char, 4096> outputBuffer; 21 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 22 static std::string inputBuffer; 23 24 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 25 static boost::container::flat_set<crow::websocket::Connection*> sessions; 26 27 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 28 static bool doingWrite = false; 29 30 inline void doWrite() 31 { 32 if (doingWrite) 33 { 34 BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; 35 return; 36 } 37 38 if (inputBuffer.empty()) 39 { 40 BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out"; 41 return; 42 } 43 44 if (!hostSocket) 45 { 46 BMCWEB_LOG_ERROR << "doWrite(): Socket closed."; 47 return; 48 } 49 50 doingWrite = true; 51 hostSocket->async_write_some( 52 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), 53 [](const boost::beast::error_code& ec, std::size_t bytesWritten) { 54 doingWrite = false; 55 inputBuffer.erase(0, bytesWritten); 56 57 if (ec == boost::asio::error::eof) 58 { 59 for (crow::websocket::Connection* session : sessions) 60 { 61 session->close("Error in reading to host port"); 62 } 63 return; 64 } 65 if (ec) 66 { 67 BMCWEB_LOG_ERROR << "Error in host serial write " << ec.message(); 68 return; 69 } 70 doWrite(); 71 }); 72 } 73 74 inline void doRead() 75 { 76 if (!hostSocket) 77 { 78 BMCWEB_LOG_ERROR << "doRead(): Socket closed."; 79 return; 80 } 81 82 BMCWEB_LOG_DEBUG << "Reading from socket"; 83 hostSocket->async_read_some( 84 boost::asio::buffer(outputBuffer.data(), outputBuffer.size()), 85 [](const boost::system::error_code& ec, std::size_t bytesRead) { 86 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes"; 87 if (ec) 88 { 89 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: " 90 << ec.message(); 91 for (crow::websocket::Connection* session : sessions) 92 { 93 session->close("Error in connecting to host port"); 94 } 95 return; 96 } 97 std::string_view payload(outputBuffer.data(), bytesRead); 98 for (crow::websocket::Connection* session : sessions) 99 { 100 session->sendBinary(payload); 101 } 102 doRead(); 103 }); 104 } 105 106 // If connection is active then remove it from the connection map 107 inline bool removeConnection(crow::websocket::Connection& conn) 108 { 109 bool ret = false; 110 111 if (sessions.erase(&conn) != 0U) 112 { 113 ret = true; 114 } 115 116 if (sessions.empty()) 117 { 118 hostSocket = nullptr; 119 inputBuffer.clear(); 120 inputBuffer.shrink_to_fit(); 121 } 122 return ret; 123 } 124 125 inline void connectConsoleSocket(crow::websocket::Connection& conn, 126 const boost::system::error_code& ec, 127 const sdbusplus::message::unix_fd& unixfd) 128 { 129 int fd = -1; 130 131 if (ec) 132 { 133 BMCWEB_LOG_ERROR << "Failed to call console Connect() method" 134 << " DBUS error: " << ec.message(); 135 if (removeConnection(conn)) 136 { 137 conn.close("Failed to call console Connect() method"); 138 } 139 return; 140 } 141 142 // Make sure that connection is still open. 143 if (!sessions.contains(&conn)) 144 { 145 return; 146 } 147 148 fd = dup(unixfd); 149 if (fd == -1) 150 { 151 BMCWEB_LOG_ERROR << "Failed to dup the DBUS unixfd" 152 << " error: " << strerror(errno); 153 if (removeConnection(conn)) 154 { 155 conn.close("Failed to dup the DBUS unixfd"); 156 } 157 return; 158 } 159 160 BMCWEB_LOG_DEBUG << "Console web socket path: " << conn.req.target() 161 << " Console unix FD: " << unixfd << " duped FD: " << fd; 162 163 if (hostSocket == nullptr) 164 { 165 boost::system::error_code ec1; 166 boost::asio::local::stream_protocol proto; 167 hostSocket = 168 std::make_unique<boost::asio::local::stream_protocol::socket>( 169 conn.getIoContext()); 170 171 hostSocket->assign(proto, fd, ec1); 172 173 if (ec1) 174 { 175 close(fd); 176 BMCWEB_LOG_ERROR << "Failed to assign the DBUS socket" 177 << " Socket assign error: " << ec1.message(); 178 if (removeConnection(conn)) 179 { 180 conn.close("Failed to assign the DBUS socket"); 181 } 182 } 183 else 184 { 185 conn.resumeRead(); 186 doWrite(); 187 doRead(); 188 } 189 } 190 else 191 { 192 BMCWEB_LOG_DEBUG << "Socket already exist so close the new fd: " << fd; 193 close(fd); 194 } 195 } 196 197 // Query consoles from DBUS and find the matching to the 198 // rules string. 199 inline void onOpen(crow::websocket::Connection& conn) 200 { 201 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; 202 203 // Save the connection in the map 204 sessions.insert(&conn); 205 206 // We need to wait for dbus and the websockets to hook up before data is 207 // sent/received. Tell the core to hold off messages until the sockets are 208 // up 209 if (hostSocket == nullptr) 210 { 211 conn.deferRead(); 212 } 213 214 // The console id 'default' is used for the console0 215 // We need to change it when we provide full multi-console support. 216 const std::string consolePath = "/xyz/openbmc_project/console/default"; 217 const std::string consoleService = "xyz.openbmc_project.Console.default"; 218 219 BMCWEB_LOG_DEBUG << "Console Object path = " << consolePath 220 << " service = " << consoleService 221 << " Request target = " << conn.req.target(); 222 223 // Call Connect() method to get the unix FD 224 crow::connections::systemBus->async_method_call( 225 [&conn](const boost::system::error_code& ec, 226 const sdbusplus::message::unix_fd& unixfd) { 227 connectConsoleSocket(conn, ec, unixfd); 228 }, 229 consoleService, consolePath, "xyz.openbmc_project.Console.Access", 230 "Connect"); 231 } 232 233 inline void requestRoutes(App& app) 234 { 235 BMCWEB_ROUTE(app, "/console0") 236 .privileges({{"OpenBMCHostConsole"}}) 237 .websocket() 238 .onopen(onOpen) 239 .onclose([](crow::websocket::Connection& conn, 240 [[maybe_unused]] const std::string& reason) { 241 BMCWEB_LOG_INFO << "Closing websocket. Reason: " << reason; 242 243 removeConnection(conn); 244 }) 245 .onmessage([]([[maybe_unused]] crow::websocket::Connection& conn, 246 const std::string& data, [[maybe_unused]] bool isBinary) { 247 inputBuffer += data; 248 doWrite(); 249 }); 250 } 251 } // namespace obmc_console 252 } // namespace crow 253