blob: df13a674612b24a03812ba64b0579a3773eb573b [file] [log] [blame]
#pragma once
#include "app.hpp"
#include "async_resp.hpp"
#include "websocket.hpp"
#include <sys/socket.h>
#include <boost/container/flat_map.hpp>
namespace crow
{
namespace obmc_kvm
{
static constexpr const uint maxSessions = 4;
class KvmSession : public std::enable_shared_from_this<KvmSession>
{
public:
explicit KvmSession(crow::websocket::Connection& connIn) :
conn(connIn), hostSocket(conn.getIoContext())
{
boost::asio::ip::tcp::endpoint endpoint(
boost::asio::ip::make_address("127.0.0.1"), 5900);
hostSocket.async_connect(
endpoint, [this, &connIn](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR(
"conn:{}, Couldn't connect to KVM socket port: {}",
logPtr(&conn), ec);
if (ec != boost::asio::error::operation_aborted)
{
connIn.close("Error in connecting to KVM port");
}
return;
}
doRead();
});
}
void onMessage(const std::string& data)
{
if (data.length() > inputBuffer.capacity())
{
BMCWEB_LOG_ERROR("conn:{}, Buffer overrun when writing {} bytes",
logPtr(&conn), data.length());
conn.close("Buffer overrun");
return;
}
BMCWEB_LOG_DEBUG("conn:{}, Read {} bytes from websocket", logPtr(&conn),
data.size());
size_t copied = boost::asio::buffer_copy(
inputBuffer.prepare(data.size()), boost::asio::buffer(data));
BMCWEB_LOG_DEBUG("conn:{}, Committing {} bytes from websocket",
logPtr(&conn), copied);
inputBuffer.commit(copied);
BMCWEB_LOG_DEBUG("conn:{}, inputbuffer size {}", logPtr(&conn),
inputBuffer.size());
doWrite();
}
protected:
void doRead()
{
std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
BMCWEB_LOG_DEBUG("conn:{}, Reading {} from kvm socket", logPtr(&conn),
bytes);
hostSocket.async_read_some(
outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
[this, weak(weak_from_this())](const boost::system::error_code& ec,
std::size_t bytesRead) {
auto self = weak.lock();
if (self == nullptr)
{
return;
}
BMCWEB_LOG_DEBUG("conn:{}, read done. Read {} bytes",
logPtr(&conn), bytesRead);
if (ec)
{
BMCWEB_LOG_ERROR(
"conn:{}, Couldn't read from KVM socket port: {}",
logPtr(&conn), ec);
if (ec != boost::asio::error::operation_aborted)
{
conn.close("Error in connecting to KVM port");
}
return;
}
outputBuffer.commit(bytesRead);
std::string_view payload(
static_cast<const char*>(outputBuffer.data().data()),
bytesRead);
BMCWEB_LOG_DEBUG("conn:{}, Sending payload size {}", logPtr(&conn),
payload.size());
conn.sendBinary(payload);
outputBuffer.consume(bytesRead);
doRead();
});
}
void doWrite()
{
if (doingWrite)
{
BMCWEB_LOG_DEBUG("conn:{}, Already writing. Bailing out",
logPtr(&conn));
return;
}
if (inputBuffer.size() == 0)
{
BMCWEB_LOG_DEBUG("conn:{}, inputBuffer empty. Bailing out",
logPtr(&conn));
return;
}
doingWrite = true;
hostSocket.async_write_some(
inputBuffer.data(),
[this, weak(weak_from_this())](const boost::system::error_code& ec,
std::size_t bytesWritten) {
auto self = weak.lock();
if (self == nullptr)
{
return;
}
BMCWEB_LOG_DEBUG("conn:{}, Wrote {}bytes", logPtr(&conn),
bytesWritten);
doingWrite = false;
inputBuffer.consume(bytesWritten);
if (ec == boost::asio::error::eof)
{
conn.close("KVM socket port closed");
return;
}
if (ec)
{
BMCWEB_LOG_ERROR("conn:{}, Error in KVM socket write {}",
logPtr(&conn), ec);
if (ec != boost::asio::error::operation_aborted)
{
conn.close("Error in reading to host port");
}
return;
}
doWrite();
});
}
crow::websocket::Connection& conn;
boost::asio::ip::tcp::socket hostSocket;
boost::beast::flat_static_buffer<1024UL * 50UL> outputBuffer;
boost::beast::flat_static_buffer<1024UL> inputBuffer;
bool doingWrite{false};
};
using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
std::shared_ptr<KvmSession>>;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static SessionMap sessions;
inline void requestRoutes(App& app)
{
sessions.reserve(maxSessions);
BMCWEB_ROUTE(app, "/kvm/0")
.privileges({{"ConfigureComponents", "ConfigureManager"}})
.websocket()
.onopen([](crow::websocket::Connection& conn) {
BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
if (sessions.size() == maxSessions)
{
conn.close("Max sessions are already connected");
return;
}
sessions[&conn] = std::make_shared<KvmSession>(conn);
})
.onclose([](crow::websocket::Connection& conn, const std::string&) {
sessions.erase(&conn);
})
.onmessage([](crow::websocket::Connection& conn,
const std::string& data, bool) {
if (sessions[&conn])
{
sessions[&conn]->onMessage(data);
}
});
}
} // namespace obmc_kvm
} // namespace crow