// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * Copyright (C) 2015, 2017, 2019, 2021-2024 Kernkonzept GmbH.
 * Author(s): Alexander Warg <alexander.warg@kernkonzept.com>
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include "ipc_epiface"

namespace L4 {

/**
 * \defgroup client_server_ipc The L4Re IPC Framework
 *
 * The mechanisms for IPC communication between L4Re applications.
 */

/**
 * \ingroup client_server_ipc
 *
 * Server-Side framework for implementing object-oriented servers.´
 * \defgroup cxx_ipc_server Server-Side IPC framework
 */
/**
 * \ingroup cxx_ipc_server
 *
 * Helper classes for L4::Server instantiation.
 */
namespace Ipc_svr {

/**
 * \ingroup cxx_ipc_server
 *
 * Reply mode for server loop.
 *
 * The reply mode specifies if the server loop shall do a compound reply
 * and wait operation (#Reply_compound), which is the most performant
 * method.  Note, setup_wait() is called before the reply.  The other
 * way is to call reply and wait separately and call setup_wait in between.
 *
 * The actual mode is determined by the return value of the before_reply()
 * hook in the LOOP_HOOKS of L4::Server.
 */
enum Reply_mode
{
  Reply_compound, ///< Server shall use a compound reply and wait (fast).
  Reply_separate  ///< Server shall call reply and wait separately.
};

/**
 * \ingroup cxx_ipc_server
 *
 * Mix in for LOOP_HOOKS to ignore IPC errors.
 */
struct Ignore_errors
{ static void error(l4_msgtag_t, l4_utcb_t *) {} };

/**
 * \ingroup cxx_ipc_server
 *
 * Mix in for LOOP_HOOKS to use a 0 send and a infinite receive timeout.
 */
struct Default_timeout
{ static l4_timeout_t timeout() { return L4_IPC_SEND_TIMEOUT_0; } };

/**
 * \ingroup cxx_ipc_server
 *
 * Mix in for LOOP_HOOKS to always use compound reply and wait.
 */
struct Compound_reply
{
  static Reply_mode before_reply(l4_msgtag_t, l4_utcb_t *)
  { return Reply_compound; }
};

/**
 * \ingroup cxx_ipc_server
 *
 * Mix in for LOOP_HOOKS for setup_wait no op.
 */
struct Default_setup_wait
{ static void setup_wait(l4_utcb_t *, Reply_mode) {} };

/**
 * Direct dispatch helper, for forwarding dispatch calls to
 * a registry \a R.
 * \tparam R  Data type of the registry that is used for dispatching to
 *            different server objects, usually based on the protected IPC
 *            label.
 */
template< typename R >
struct Direct_dispatch
{
  /// stores a reference to the registry object
  R &r;

  /// Make a direct dispatcher
  Direct_dispatch(R &r) : r(r) {}

  /// call operator forwarding to r.dispatch()
  l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb)
  { return r.dispatch(tag, obj, utcb); }
};

/**
 * Direct dispatch helper, for forwarding dispatch calls via a pointer to
 * a registry \a R.
 * \tparam R  Data type of the registry that is used for dispatching to
 *            different server objects, usually based on the protected IPC
 *            label.
 */
template< typename R >
struct Direct_dispatch<R*>
{
  /// stores a pointer to the registry object
  R *r;

  /// Make a direct dispatcher
  Direct_dispatch(R *r) : r(r) {}

  /// call operator forwarding to r->dispatch()
  l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb)
  { return r->dispatch(tag, obj, utcb); }
};

#ifdef __EXCEPTIONS
/**
 * Dispatch helper wrapping try {} catch {} around the dispatch call.
 * \tparam R    Data type of the registry used for dispatching to objects.
 * \tparam Exc  Data type of the exceptions that shall be caught.
 *              This data type must provide a member err_no() that returns
 *              the negative integer (int) error code for the exception.
 *
 * This dispatcher wraps Direct_dispatch<R> with a try-catch (Exc).
 */
template< typename R, typename Exc> // = L4::Runtime_error>
struct Exc_dispatch : private Direct_dispatch<R>
{
  /// Make an exception handling dispatcher
  Exc_dispatch(R r) : Direct_dispatch<R>(r) {}

  /**
   * Dispatch the call via Direct_dispatch<R>() and handle exceptions.
   */
  l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb)
  {
    try
      {
        return Direct_dispatch<R>::operator () (tag, obj, utcb);
      }
    catch (Exc &e)
      {
        return l4_msgtag(e.err_no(), 0, 0, 0);
      }
    catch (int err)
      {
        return l4_msgtag(err, 0, 0, 0);
      }
    catch (long err)
      {
        return l4_msgtag(err, 0, 0, 0);
      }
  }
};

/**
 * Dispatch helper that, in addition to what Exc_dispatch does, prints exception
 * messages.
 * \tparam R        Data type of the registry used for dispatching to objects.
 * \tparam Exc      Data type of the exceptions that shall be caught.
 *                  This data type must provide a member err_no() that returns
 *                  the negative integer (int) error code for the exception.
 *                  In addition, methods str() and extra_str() are required
 *                  that return a c-string describing the error.
 * \tparam Printer  A type that provides a printf() member that is used (with
 *                  the usual format string syntax) to print error messages.
 *
 * This dispatcher wraps Direct_dispatch<R> with a try-catch (Exc).
 */
template< typename R, typename Exc, typename Printer >
struct Dbg_dispatch : private Direct_dispatch<R>
{
  /// Make an exception handling dispatcher
  Dbg_dispatch(R r, Printer p) : Direct_dispatch<R>(r), _printer(p) {}

  /**
   * Dispatch the call via Direct_dispatch<R>(), handle exceptions, and print
   * the exception message.
   */
  l4_msgtag_t operator () (l4_msgtag_t tag, l4_umword_t obj, l4_utcb_t *utcb)
  {
    try
      {
        return Direct_dispatch<R>::operator () (tag, obj, utcb);
      }
    catch (Exc &e)
      {
        _printer.printf("Error in handling IPC: %s: %s\n", e.str(),
                        e.extra_str());
        return l4_msgtag(e.err_no(), 0, 0, 0);
      }
    catch (int err)
      {
        _printer.printf("Error in handling IPC: %d (%s)\n", err,
                        l4sys_errtostr(err));
        return l4_msgtag(err, 0, 0, 0);
      }
    catch (long err)
      {
        _printer.printf("Error in handling IPC: %ld (%s)\n", err,
                        l4sys_errtostr(err));
        return l4_msgtag(err, 0, 0, 0);
      }
  }

  Printer _printer;
};
#endif

/**
 * \ingroup cxx_ipc_server
 *
 * Empty implementation of Server_iface.
 *
 * This implementation of Server_iface provides no buffer or timeout management
 * at all it just returns errors for all calls that express other than empty
 * demands.  However, this may be useful for very simple servers that serve
 * simple server objects only.
 */
class Br_manager_no_buffers : public Server_iface
{
public:
  /**
   * \copydoc  Server_iface::alloc_buffer_demand()
   * \return success (0) if demand is empty, -L4_ENOMEM else.
   */
  int alloc_buffer_demand(Demand const &demand) override
  {
    if (!demand.no_demand())
      return -L4_ENOMEM;
    return L4_EOK;
  }

  /// Returns L4::Cap<void>::Invalid, we have no buffer management
  L4::Cap<void> get_rcv_cap(int) const override
  { return L4::Cap<void>::Invalid; }

  /// Returns -L4_ENOMEM, we have no buffer management
  int realloc_rcv_cap(int) override
  { return -L4_ENOMEM; }

  /// Returns -L4_ENOSYS, we have no timeout queue
  int add_timeout(Timeout *, l4_kernel_clock_t) override
  { return -L4_ENOSYS; }

  /// Returns -L4_ENOSYS, we have no timeout queue
  int remove_timeout(Timeout *) override
  { return -L4_ENOSYS; }

protected:
  /// Returns 1 as first free buffer
  unsigned first_free_br() const
  { return 1; }

  /// Setup wait function for the server loop (Server<>).
  void setup_wait(l4_utcb_t *utcb, L4::Ipc_svr::Reply_mode)
  {
    l4_buf_regs_t *br = l4_utcb_br_u(utcb);
    br->bdr = 0;
    br->br[0] = 0;
  }
};

/**
 * \ingroup cxx_ipc_server
 *
 * Default LOOP_HOOKS.
 *
 * Combination of Ignore_errors, Default_timeout, Compound_reply,
 * and Br_manager_no_buffers.
 */
struct Default_loop_hooks :
  public Ignore_errors, public Default_timeout, public Compound_reply,
  Br_manager_no_buffers
{};

}

/**
 * \ingroup cxx_ipc_server
 *
 * Basic server loop for handling client requests.
 * \param LOOP_HOOKS the server inherits from LOOP_HOOKS and calls the
 *        hooks defined in LOOP_HOOKS in the server loop.
 *        See Ipc_svr::Default_loop_hooks, Ipc_svr::Ignore_errors,
 *        Ipc_svr::Default_timeout, Ipc_svr::Compound_reply, and
 *        Ipc_svr::Br_manager_no_buffers.
 *
 * This is basically a simple server loop that uses a single message buffer
 * for receiving requests and sending replies. The dispatcher determines
 * how incoming messages are handled.
 */
template< typename LOOP_HOOKS = Ipc_svr::Default_loop_hooks >
class Server :
  public LOOP_HOOKS
{
public:
  /**
   * Initializes the server loop.
   *
   * Note that this variant is deprecated. The UTCB can be specified with
   * the server loop function (l4_utcb() is the default). (2021-10)
   */
  /* Internal note: After all users have been converted, remove this
   * constructor. Also remove the constructor below then. */
  explicit Server(l4_utcb_t *)
    L4_DEPRECATED("Do not specify the UTCB with the constructor. "
	          "Supply it on the loop function if needed.")
  {}

  /**
   * Initializes the server loop.
   */
  /* Internal note: Remove this constructor when the above deprecated
   * constructor with the UTCB pointer is also removed. */
  Server() {}

  /**
   * The server loop.
   *
   * This function usually never returns, it waits for
   * incoming messages calls the dispatcher, sends a reply and waits again.
   */
  template< typename DISPATCH >
  inline L4_NORETURN void internal_loop(DISPATCH dispatch, l4_utcb_t *);

  /**
   * Server loop without exception handling.
   */
  template< typename R >
  inline L4_NORETURN void loop_noexc(R r, l4_utcb_t *u = l4_utcb())
  { internal_loop(Ipc_svr::Direct_dispatch<R>(r), u); }

#ifdef __EXCEPTIONS
  /**
   * Server loop with internal exception handling.
   *
   * This server loop translates L4::Runtime_error exceptions
   * into negative error return codes sent to the caller.
   */
  template< typename EXC, typename R >
  inline L4_NORETURN void loop(R r, l4_utcb_t *u = l4_utcb())
  {
    internal_loop(Ipc_svr::Exc_dispatch<R, EXC>(r), u);
    // function will never return
  }

  /**
   * Server loop with internal exception handling including message printing.
   *
   * Exceptions are translated into error codes, just like in loop(). In
   * addition, error messages are printed using the Printer argument.
   */
  template< typename EXC, typename R, typename Printer >
  inline L4_NORETURN void loop_dbg(R r, Printer p, l4_utcb_t *u = l4_utcb())
  {
    internal_loop(Ipc_svr::Dbg_dispatch<R, EXC, Printer>(r, p), u);
    // function will never return
  }
#endif
protected:
  /// Internal implementation for reply and wait
  inline l4_msgtag_t reply_n_wait(l4_msgtag_t reply, l4_umword_t *p, l4_utcb_t *);
};

template< typename L >
inline l4_msgtag_t
Server<L>::reply_n_wait(l4_msgtag_t reply, l4_umword_t *p, l4_utcb_t *utcb)
{
  if (reply.label() != -L4_ENOREPLY)
    {
      Ipc_svr::Reply_mode m = this->before_reply(reply, utcb);
      if (m == Ipc_svr::Reply_compound)
        {
          this->setup_wait(utcb, m);
          return l4_ipc_reply_and_wait(utcb, reply, p, this->timeout());
        }
      else
        {
          l4_msgtag_t res = l4_ipc_send(L4_INVALID_CAP | L4_SYSF_REPLY, utcb, reply, this->timeout());
          if (res.has_error())
            return res;
        }
    }
  this->setup_wait(utcb, Ipc_svr::Reply_separate);
  return l4_ipc_wait(utcb, p, this->timeout());
}

template< typename L >
template< typename DISPATCH >
inline L4_NORETURN void
Server<L>::internal_loop(DISPATCH dispatch, l4_utcb_t *utcb)
{
  l4_msgtag_t res;
  l4_umword_t p;
  l4_msgtag_t r = l4_msgtag(-L4_ENOREPLY, 0, 0, 0);

  while (true)
    {
      res = reply_n_wait(r, &p, utcb);
      if (res.has_error())
        {
          this->error(res, utcb);
          r = l4_msgtag(-L4_ENOREPLY, 0, 0, 0);
          continue;
        }

      r = dispatch(res, p, utcb);
    }
}

} // namespace L4
