blob: a15004ea656c39e1af7491f518455c3e2783320a [file] [log] [blame]
#pragma once
#include <crow/app.h>
#include <crow/websocket.h>
#include <sys/socket.h>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <webserver_common.hpp>
namespace crow
{
namespace obmc_console
{
static std::unique_ptr<boost::asio::local::stream_protocol::socket> host_socket;
static std::array<char, 4096> outputBuffer;
static std::string inputBuffer;
static boost::container::flat_set<crow::websocket::Connection*> 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<sockaddr_un*>(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