| #ifndef THIRD_PARTY_GBMCWEB_INCLUDE_OBMC_CONSOLE_H_ | |
| #define THIRD_PARTY_GBMCWEB_INCLUDE_OBMC_CONSOLE_H_ | |
| #include <sys/socket.h> | |
| #include <array> | |
| #include <cstddef> | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include "boost/asio/local/stream_protocol.hpp" // NOLINT | |
| #include "boost/container/flat_set.hpp" // NOLINT | |
| #include "app.hpp" | |
| #include "logging.hpp" | |
| #include "websocket.hpp" | |
| namespace crow { | |
| namespace obmc_console { | |
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | |
| static std::unique_ptr<boost::asio::local::stream_protocol::socket> hostSocket; | |
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | |
| static std::array<char, 4096> outputBuffer; | |
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | |
| static std::string inputBuffer; // NOLINT | |
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | |
| static boost::container::flat_set<crow::websocket::Connection*> sessions; | |
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | |
| static bool doingWrite = false; | |
| inline void doWrite() { | |
| if (doingWrite) { | |
| BMCWEB_LOG_DEBUG << "Already writing. Bailing out"; | |
| return; | |
| } | |
| if (inputBuffer.empty()) { | |
| BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out"; | |
| return; | |
| } | |
| if (!hostSocket) { | |
| BMCWEB_LOG_ERROR << "doWrite(): Socket closed."; | |
| return; | |
| } | |
| doingWrite = true; | |
| hostSocket->async_write_some( | |
| boost::asio::buffer(inputBuffer.data(), inputBuffer.size()), | |
| [](boost::beast::error_code ec, std::size_t bytesWritten) { | |
| doingWrite = false; | |
| inputBuffer.erase(0, bytesWritten); | |
| if (ec == boost::asio::error::eof) { | |
| for (crow::websocket::Connection* session : sessions) { | |
| session->close("Error in reading to host port"); | |
| } | |
| return; | |
| } | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Error in host serial write " << ec; | |
| return; | |
| } | |
| doWrite(); | |
| }); | |
| } | |
| inline void doRead() { | |
| if (!hostSocket) { | |
| BMCWEB_LOG_ERROR << "doRead(): Socket closed."; | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Reading from socket"; | |
| hostSocket->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 (crow::websocket::Connection* session : sessions) { | |
| session->close("Error in connecting to host port"); | |
| } | |
| return; | |
| } | |
| std::string_view payload(outputBuffer.data(), bytesRead); | |
| for (crow::websocket::Connection* session : sessions) { | |
| session->sendBinary(payload); | |
| } | |
| doRead(); | |
| }); | |
| } | |
| inline void connectHandler(const boost::system::error_code& ec) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Couldn't connect to host serial port: " << ec; | |
| for (crow::websocket::Connection* session : sessions) { | |
| session->close("Error in connecting to host port"); | |
| } | |
| return; | |
| } | |
| doWrite(); | |
| doRead(); | |
| } | |
| inline void requestRoutes(App& app) { | |
| BMCWEB_ROUTE(app, "/console0") | |
| .privileges({{"ConfigureComponents", "ConfigureManager"}}) | |
| .websocket() | |
| .onopen([](crow::websocket::Connection& conn) { | |
| BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened"; | |
| sessions.insert(&conn); | |
| if (hostSocket == nullptr) { | |
| const std::string consoleName("\0obmc-console", 13); | |
| boost::asio::local::stream_protocol::endpoint ep(consoleName); | |
| hostSocket = | |
| std::make_unique<boost::asio::local::stream_protocol::socket>( | |
| conn.getIoContext()); | |
| hostSocket->async_connect(ep, connectHandler); | |
| } | |
| }) | |
| .onclose([](crow::websocket::Connection& conn, | |
| [[maybe_unused]] const std::string& reason) { | |
| BMCWEB_LOG_INFO << "Closing websocket. Reason: " << reason; | |
| sessions.erase(&conn); | |
| if (sessions.empty()) { | |
| hostSocket = nullptr; | |
| inputBuffer.clear(); | |
| inputBuffer.shrink_to_fit(); | |
| } | |
| }) | |
| .onmessage([]([[maybe_unused]] crow::websocket::Connection& conn, | |
| const std::string& data, [[maybe_unused]] bool isBinary) { | |
| inputBuffer += data; | |
| doWrite(); | |
| }); | |
| } | |
| } // namespace obmc_console | |
| } // namespace crow | |
| #endif // THIRD_PARTY_GBMCWEB_INCLUDE_OBMC_CONSOLE_H_ |