<commit_before>// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
#include "beast-sound-engine.hh"

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;

#include <limits.h>
#include <stdlib.h>

#undef B0 // undo pollution from termios.h

static Bse::ServerH bse_server;
static Jsonipc::IpcDispatcher *dispatcher = NULL;
static bool verbose = false, verbose_binary = false;
static GPollFD embedding_pollfd = { -1, 0, 0 };
static std::string authenticated_subprotocol = "";

// Configure websocket server
struct CustomServerConfig : public websocketpp::config::asio {
  static const size_t connection_read_buffer_size = 16384;
};
using ServerEndpoint = websocketpp::server<CustomServerConfig>;
static ServerEndpoint websocket_server;

static std::string
handle_jsonipc (const std::string &message, const websocketpp::connection_hdl &hdl)
{
  ptrdiff_t conid = 0;
  if (verbose)
    {
      conid = ptrdiff_t (websocket_server.get_con_from_hdl (hdl).get());
      Bse::printerr ("%p: REQUEST: %s\n", conid, message);
    }
  const std::string reply = dispatcher->dispatch_message (message);
  if (verbose)
    {
      const bool iserror = bool (Bse::Re::search (R"(^\{("id":[0-9]+,)?"error":)", reply));
      if (iserror)
        {
          using namespace Bse::AnsiColors;
          auto R1 = color (BOLD) + color (FG_RED), R0 = color (FG_DEFAULT) + color (BOLD_OFF);
          Bse::printerr ("%p: %sREPLY:%s   %s\n", conid, R1, R0, reply);
        }
      else
        Bse::printerr ("%p: REPLY:   %s\n", conid, reply);
    }
  return reply;
}

static std::string
user_agent_nick (const std::string &useragent)
{
  using namespace Bse;
  std::string nick;
  if      (Re::search (R"(\bFirefox/)", useragent))
    nick += "Firefox";
  else if (Re::search (R"(\bElectron/)", useragent))
    nick += "Electron";
  else if (Re::search (R"(\bChrome/)", useragent))
    nick += "Chrome";
  else if (Re::search (R"(\bSafari/)", useragent))
    nick += "Safari";
  else
    nick += "Unknown";
  return nick;
}

static bool
ws_validate_connection (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  // using subprotocol as auth string
  const std::vector<std::string> &subprotocols = con->get_requested_subprotocols();
  if (subprotocols.size() == 0 && authenticated_subprotocol.empty())
    return true;
  if (subprotocols.size() == 1)
    {
      if (subprotocols[0] == authenticated_subprotocol)
        {
          con->select_subprotocol (subprotocols[0]);
          return true;
        }
    }
  return false;
}

static std::vector<ServerEndpoint::connection_ptr> ws_opened_connections;

static void
ws_open (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  ws_opened_connections.push_back (con);
  // https://github.com/zaphoyd/websocketpp/issues/694#issuecomment-454623641
  const auto &socket = con->get_raw_socket();
  const auto &address = socket.remote_endpoint().address();
  const int rport = socket.remote_endpoint().port();
  const websocketpp::http::parser::request &rq = con->get_request();
  const websocketpp::http::parser::header_list &headermap = rq.get_headers();
  std::string useragent;
  for (auto it : headermap) // request headers
    if (it.first == "User-Agent")
      useragent = it.second;
  std::string nick = user_agent_nick (useragent);
  if (!nick.empty())
    nick = "(" + nick + ")";
  using namespace Bse::AnsiColors;
  auto B1 = color (BOLD);
  auto B0 = color (BOLD_OFF);
  Bse::printout ("%p: %sACCEPT:%s %s:%d/ %s\n", ptrdiff_t (con.get()), B1, B0, address.to_string().c_str(), rport, nick);
  // Bse::printout ("User-Agent: %s\n", useragent);
}

static void
ws_close (websocketpp::connection_hdl hdl)
{
  websocketpp::lib::error_code ec;
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl, ec);
  if (ec)
    {
      Bse::printerr ("%s: failed to access connection: %s", __func__, ec.message());
      return;
    }
  using namespace Bse::AnsiColors;
  const auto B1 = color (BOLD);
  const auto B0 = color (BOLD_OFF);
  const ptrdiff_t conid = ptrdiff_t (con.get());
  auto it = std::find (ws_opened_connections.begin(), ws_opened_connections.end(), con);
  Bse::printout ("%p: %sCLOSED%s%s\n", ptrdiff_t (con.get()), B1, B0,
                 it != ws_opened_connections.end() ? "" : " (unknown)");
  if (it != ws_opened_connections.end())
    ws_opened_connections.erase (it);
}

static websocketpp::connection_hdl *bse_current_websocket_hdl = NULL;

static void
ws_message (websocketpp::connection_hdl hdl, server::message_ptr msg)
{
  const std::string &message = msg->get_payload();
  // send message to BSE thread and block until its been handled
  Aida::ScopedSemaphore sem;
  auto handle_wsmsg = [&message, &hdl, &sem] () {
    bse_current_websocket_hdl = &hdl;
    std::string reply = handle_jsonipc (message, hdl);
    bse_current_websocket_hdl = NULL;
    if (!reply.empty())
      websocket_server.send (hdl, reply, websocketpp::frame::opcode::text);
    sem.post();
  };
  bse_server.__iface_ptr__()->__execution_context_mt__().enqueue_mt (handle_wsmsg);
  sem.wait();
}

struct IpcHandlerImpl : Bse::IpcHandler {
  virtual
  ~IpcHandlerImpl()
  {}
  virtual ptrdiff_t
  current_connection_id () override
  {
    if (bse_current_websocket_hdl)
      {<commit_msg>BSE: beast-sound-engine.cc: keep Jsonipc::InstanceMap per websocket connection<commit_after>// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
#include "beast-sound-engine.hh"

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;

#include <limits.h>
#include <stdlib.h>

#undef B0 // undo pollution from termios.h

static Bse::ServerH bse_server;
static Jsonipc::IpcDispatcher *dispatcher = NULL;
static bool verbose = false, verbose_binary = false;
static GPollFD embedding_pollfd = { -1, 0, 0 };
static std::string authenticated_subprotocol = "";

// Configure websocket server
struct CustomServerConfig : public websocketpp::config::asio {
  static const size_t connection_read_buffer_size = 16384;
};
using ServerEndpoint = websocketpp::server<CustomServerConfig>;
static ServerEndpoint websocket_server;

/// Check if two connection_hdl pointers are the same
static inline bool
websocketpp_connection_hdl_equals (const websocketpp::connection_hdl &a, const websocketpp::connection_hdl &b)
{
  // the connection_hdl is implemented as a weak_ptr, which provide no operator==().
  // but a and b can be ordered via owner_before, by means of comparing the offsets of the
  // weak_ptr control blocks. which means get_con_from_hdl() would yield the same connection_ptr.
  return !a.owner_before (b) && !b.owner_before (a);
}

// Provide websocket connection dependent InstanceMap
static std::vector<std::pair<websocketpp::connection_hdl, Jsonipc::InstanceMap*>> ws_instance_maps;

static std::string
handle_jsonipc (const std::string &message, const websocketpp::connection_hdl &hdl)
{
  ptrdiff_t conid = 0;
  if (verbose)
    {
      conid = ptrdiff_t (websocket_server.get_con_from_hdl (hdl).get());
      Bse::printerr ("%p: REQUEST: %s\n", conid, message);
    }
  Jsonipc::InstanceMap *imap = nullptr;
  for (const auto &pair : ws_instance_maps)
    if (websocketpp_connection_hdl_equals (pair.first, hdl))
      {
        imap = pair.second;
        break;
      }
  if (!imap)
    {
      imap = new Jsonipc::InstanceMap();
      ws_instance_maps.push_back (std::make_pair (hdl, imap));
    }
  Jsonipc::Scope message_scope (*imap);
  const std::string reply = dispatcher->dispatch_message (message);
  if (verbose)
    {
      const bool iserror = bool (Bse::Re::search (R"(^\{("id":[0-9]+,)?"error":)", reply));
      if (iserror)
        {
          using namespace Bse::AnsiColors;
          auto R1 = color (BOLD) + color (FG_RED), R0 = color (FG_DEFAULT) + color (BOLD_OFF);
          Bse::printerr ("%p: %sREPLY:%s   %s\n", conid, R1, R0, reply);
        }
      else
        Bse::printerr ("%p: REPLY:   %s\n", conid, reply);
    }
  return reply;
}

static std::string
user_agent_nick (const std::string &useragent)
{
  using namespace Bse;
  std::string nick;
  if      (Re::search (R"(\bFirefox/)", useragent))
    nick += "Firefox";
  else if (Re::search (R"(\bElectron/)", useragent))
    nick += "Electron";
  else if (Re::search (R"(\bChrome/)", useragent))
    nick += "Chrome";
  else if (Re::search (R"(\bSafari/)", useragent))
    nick += "Safari";
  else
    nick += "Unknown";
  return nick;
}

static bool
ws_validate_connection (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  // using subprotocol as auth string
  const std::vector<std::string> &subprotocols = con->get_requested_subprotocols();
  if (subprotocols.size() == 0 && authenticated_subprotocol.empty())
    return true;
  if (subprotocols.size() == 1)
    {
      if (subprotocols[0] == authenticated_subprotocol)
        {
          con->select_subprotocol (subprotocols[0]);
          return true;
        }
    }
  return false;
}

static std::vector<ServerEndpoint::connection_ptr> ws_opened_connections;

static void
ws_open (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  ws_opened_connections.push_back (con);
  // https://github.com/zaphoyd/websocketpp/issues/694#issuecomment-454623641
  const auto &socket = con->get_raw_socket();
  const auto &address = socket.remote_endpoint().address();
  const int rport = socket.remote_endpoint().port();
  const websocketpp::http::parser::request &rq = con->get_request();
  const websocketpp::http::parser::header_list &headermap = rq.get_headers();
  std::string useragent;
  for (auto it : headermap) // request headers
    if (it.first == "User-Agent")
      useragent = it.second;
  std::string nick = user_agent_nick (useragent);
  if (!nick.empty())
    nick = "(" + nick + ")";
  using namespace Bse::AnsiColors;
  auto B1 = color (BOLD);
  auto B0 = color (BOLD_OFF);
  Bse::printout ("%p: %sACCEPT:%s %s:%d/ %s\n", ptrdiff_t (con.get()), B1, B0, address.to_string().c_str(), rport, nick);
  // Bse::printout ("User-Agent: %s\n", useragent);
}

static void
ws_close (websocketpp::connection_hdl hdl)
{
  websocketpp::lib::error_code ec;
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl, ec);
  if (ec)
    {
      Bse::printerr ("%s: failed to access connection: %s", __func__, ec.message());
      return;
    }
  using namespace Bse::AnsiColors;
  const auto B1 = color (BOLD);
  const auto B0 = color (BOLD_OFF);
  const ptrdiff_t conid = ptrdiff_t (con.get());
  auto it = std::find (ws_opened_connections.begin(), ws_opened_connections.end(), con);
  Bse::printout ("%p: %sCLOSED%s%s\n", ptrdiff_t (con.get()), B1, B0,
                 it != ws_opened_connections.end() ? "" : " (unknown)");
  if (it != ws_opened_connections.end())
    ws_opened_connections.erase (it);
  for (auto it = ws_instance_maps.begin(); it != ws_instance_maps.end(); ++it)
    if (websocketpp_connection_hdl_equals (it->first, hdl))
      {
        Jsonipc::InstanceMap *imap = it->second;
        ws_instance_maps.erase (it);
        delete imap;
        break;
      }
}

static websocketpp::connection_hdl *bse_current_websocket_hdl = NULL;

static void
ws_message (websocketpp::connection_hdl hdl, server::message_ptr msg)
{
  const std::string &message = msg->get_payload();
  // send message to BSE thread and block until its been handled
  Aida::ScopedSemaphore sem;
  auto handle_wsmsg = [&message, &hdl, &sem] () {
    bse_current_websocket_hdl = &hdl;
    std::string reply = handle_jsonipc (message, hdl);
    bse_current_websocket_hdl = NULL;
    if (!reply.empty())
      websocket_server.send (hdl, reply, websocketpp::frame::opcode::text);
    sem.post();
  };
  bse_server.__iface_ptr__()->__execution_context_mt__().enqueue_mt (handle_wsmsg);
  sem.wait();
}

/// Provide an IPC handler implementation that marshals and sends binary data onto the wire.
struct IpcHandlerImpl : Bse::IpcHandler {
  virtual
  ~IpcHandlerImpl()
  {}
  virtual ptrdiff_t
  current_connection_id () override
  {
    if (bse_current_websocket_hdl)
      {
{'fraction_non_alphanumeric': 0.14167536744036624, 'fraction_numerical': 0.007067705405188338, 'mean_word_length': 4.190496040016674, 'pattern_counts': {'":': 4, '<': 21, '<?xml version=': 0, '>': 37, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5597485', 'n_tokens_mistral': 4432, 'n_tokens_neox': 4292, 'n_words': 991}