Rearrange features
The backends are different things compared to generic code. Today,
these are all included in the /include folder, but it's not very clear
what options control which backends, or how things map together. This
also means that we can't separate ownership between the various
companies.
This commit is a proposal to try to create a features folder,
separated by the code for the various backends, to make interacting
with this easier. It takes the form
features/<option name>/files.hpp
features/<option name>/files_test.hpp
Note, redfish-core was already at top level, and contains lots of code,
so to prevent lots of conflicts, it's simply symlinked into that folder
to make clear that it is a backend, but not to move the implementation
and cause code conflicts.
Tested: Unit tests pass. Code compiles.
Change-Id: Idcc80ffcfd99c876734ee41d53f894ca5583fed5
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/features/kvm/kvm_websocket.hpp b/features/kvm/kvm_websocket.hpp
new file mode 100644
index 0000000..adf4c5e
--- /dev/null
+++ b/features/kvm/kvm_websocket.hpp
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: Copyright OpenBMC Authors
+#pragma once
+#include "app.hpp"
+#include "io_context_singleton.hpp"
+#include "logging.hpp"
+#include "websocket.hpp"
+
+#include <sys/types.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/ip/address.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/beast/core/flat_static_buffer.hpp>
+#include <boost/container/flat_map.hpp>
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+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(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
diff --git a/features/kvm/meson.build b/features/kvm/meson.build
new file mode 100644
index 0000000..ab60765
--- /dev/null
+++ b/features/kvm/meson.build
@@ -0,0 +1 @@
+incdir += include_directories('.')