blob: 7e0ab85cbc135f3e2b7ec354214263bd24a0f90f [file] [log] [blame]
#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_