#pragma once #include #include #include #include #include #include namespace crow { namespace obmc_console { static std::unique_ptr host_socket; static std::array outputBuffer; static std::string inputBuffer; static boost::container::flat_set sessions; static bool doingWrite = false; void doWrite() { if (doingWrite) { BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; return; } if (inputBuffer.empty()) { BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out"; return; } doingWrite = true; host_socket->async_write_some( boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), [](boost::beast::error_code ec, std::size_t bytes_written) { doingWrite = false; inputBuffer.erase(0, bytes_written); if (ec == boost::asio::error::eof) { for (auto session : sessions) { session->close("Error in reading to host port"); } return; } if (ec) { BMCWEB_LOG_ERROR << "Error in host serial write " << ec; return; } doWrite(); }); } void doRead() { BMCWEB_LOG_DEBUG << "Reading from socket"; host_socket->async_read_some( boost::asio::buffer(outputBuffer.data(), outputBuffer.size()), [](const boost::system::error_code& ec, std::size_t bytesRead) { BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes"; if (ec) { BMCWEB_LOG_ERROR << "Couldn't read from host serial port: " << ec; for (auto session : sessions) { session->close("Error in connecting to host port"); } return; } boost::beast::string_view payload(outputBuffer.data(), bytesRead); for (auto session : sessions) { session->sendText(payload); } doRead(); }); } void connectHandler(const boost::system::error_code& ec) { if (ec) { BMCWEB_LOG_ERROR << "Couldn't connect to host serial port: " << ec; for (auto session : sessions) { session->close("Error in connecting to host port"); } return; } doWrite(); doRead(); } void requestRoutes(CrowApp& app) { BMCWEB_ROUTE(app, "/console0") .websocket() .onopen([](crow::websocket::Connection& conn) { BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; sessions.insert(&conn); if (host_socket == nullptr) { const std::string consoleName("\0obmc-console", 13); boost::asio::local::stream_protocol::endpoint ep(consoleName); // This is a hack. For whatever reason boost local endpoint has // a check to see if a string is null terminated, and if it is, // it drops the path character count by 1. For abstract // sockets, we need the count to be the full sizeof(s->sun_path) // (ie 108), even though our path _looks_ like it's null // terminated. This is likely a bug in asio that needs to be // submitted Todo(ed). so the cheat here is to break the // abstraction for a minute, write a 1 to the last byte, this // causes the check at the end of resize here: // https://www.boost.org/doc/libs/1_68_0/boost/asio/local/detail/impl/endpoint.ipp // to not decrement us unesssesarily. struct sockaddr_un* s = reinterpret_cast(ep.data()); s->sun_path[sizeof(s->sun_path) - 1] = 1; ep.resize(sizeof(sockaddr_un)); s->sun_path[sizeof(s->sun_path) - 1] = 0; host_socket = std::make_unique< boost::asio::local::stream_protocol::socket>( conn.getIoService()); host_socket->async_connect(ep, connectHandler); } }) .onclose( [](crow::websocket::Connection& conn, const std::string& reason) { sessions.erase(&conn); if (sessions.empty()) { host_socket = nullptr; inputBuffer.clear(); inputBuffer.shrink_to_fit(); } }) .onmessage([](crow::websocket::Connection& conn, const std::string& data, bool is_binary) { inputBuffer += data; doWrite(); }); } } // namespace obmc_console } // namespace crow