text: #pragma once
  #include <sys/socket.h>
  
  #include <app.hpp>
  #include <async_resp.hpp>
  #include <boost/container/flat_map.hpp>
  #include <websocket.hpp>
  
  namespace crow
  {
  namespace obmc_kvm
  {
  
  static constexpr const uint maxSessions = 4;
  
  class 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:" << &conn
                          << ", Couldn't connect to KVM socket port: " << 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:" << &conn
                               << ", Buffer overrun when writing "
                               << data.length() << " bytes";
              conn.close("Buffer overrun");
              return;
          }
  
          BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Read " << data.size()
                           << " bytes from websocket";
          boost::asio::buffer_copy(inputBuffer.prepare(data.size()),
                                   boost::asio::buffer(data));
          BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Committing " << data.size()
                           << " bytes from websocket";
          inputBuffer.commit(data.size());
  
          BMCWEB_LOG_DEBUG << "conn:" << &conn << ", inputbuffer size "
                           << inputBuffer.size();
          doWrite();
      }
  
    protected:
      void doRead()
      {
          std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
          BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Reading " << bytes
                           << " from kvm socket";
          hostSocket.async_read_some(
              outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
              [this](const boost::system::error_code& ec, std::size_t bytesRead) {
                  BMCWEB_LOG_DEBUG << "conn:" << &conn << ", read done.  Read "
                                   << bytesRead << " bytes";
                  if (ec)
                  {
                      BMCWEB_LOG_ERROR
                          << "conn:" << &conn
                          << ", Couldn't read from KVM socket port: " << 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:" << &conn
                                   << ", Sending payload size " << payload.size();
                  conn.sendBinary(payload);
                  outputBuffer.consume(bytesRead);
  
                  doRead();
              });
      }
  
      void doWrite()
      {
          if (doingWrite)
          {
              BMCWEB_LOG_DEBUG << "conn:" << &conn
                               << ", Already writing.  Bailing out";
              return;
          }
          if (inputBuffer.size() == 0)
          {
              BMCWEB_LOG_DEBUG << "conn:" << &conn
                               << ", inputBuffer empty.  Bailing out";
              return;
          }
  
          doingWrite = true;
          hostSocket.async_write_some(
              inputBuffer.data(), [this](const boost::system::error_code& ec,
                                         std::size_t bytesWritten) {
                  BMCWEB_LOG_DEBUG << "conn:" << &conn << ", Wrote "
                                   << bytesWritten << "bytes";
                  doingWrite = false;
                  inputBuffer.consume(bytesWritten);
  
                  if (ec == boost::asio::error::eof)
                  {
                      conn.close("KVM socket port closed");
                      return;
                  }
                  if (ec)
                  {
                      BMCWEB_LOG_ERROR << "conn:" << &conn
                                       << ", Error in KVM socket write " << 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};
  };
  
  static boost::container::flat_map<crow::websocket::Connection*,
                                    std::unique_ptr<KvmSession>>
      sessions;
  
  inline void requestRoutes(App& app)
  {
      sessions.reserve(maxSessions);
  
      BMCWEB_ROUTE(app, "/kvm/0")
          .privileges({{"ConfigureComponents", "ConfigureManager"}})
          .websocket()
          .onopen([](crow::websocket::Connection& conn,
                     const std::shared_ptr<bmcweb::AsyncResp>&) {
              BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
  
              if (sessions.size() == maxSessions)
              {
                  conn.close("Max sessions are already connected");
                  return;
              }
  
              sessions[&conn] = std::make_unique<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
  