// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2014 Alexander Warg <alexander.warg@kernkonzept.com>
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include "types"
#include <l4/sys/utcb.h>
#include <l4/sys/err.h>

namespace L4 {

/// IPC related functionality
namespace Ipc {

/// IPC Message related functionality
namespace Msg {

/**
 * Pad bytes to the given alignment \a align (in bytes)
 * \param bytes  The input value in bytes
 * \param align  The alignment value in bytes
 * \return the result after padding \a bytes to \a align.
 */
constexpr unsigned long align_to(unsigned long bytes, unsigned long align) noexcept
{ return (bytes + align - 1) & ~(align - 1); }

/**
 * Pad \a bytes to the alignment of the type \a T.
 * \tparam T      The data type used for the alignment
 * \param  bytes  The value to add the padding to
 * \return \a bytes padded to achieve the alignment of \a T.
 */
template<typename T>
constexpr unsigned long align_to(unsigned long bytes) noexcept
{ return align_to(bytes, __alignof(T)); }

/**
 * Check if there is enough space for T from offset to limit.
 * \tparam T       The data type that shall be fitted at \a offset
 * \param  offset  The current offset in bytes (must already be padded
 *                 if desired).
 * \param  limit   The limit in bytes that must not be exceeded after adding
 *                 the size of \a T.
 * \return true if the limit will not be exceeded, false else.
 */
template<typename T>
constexpr bool check_size(unsigned offset, unsigned limit) noexcept
{
  return offset + sizeof(T) <= limit;
}

/**
 * Check if there is enough space for an array of T from offset to limit.
 * \tparam T       The data type that shall be fitted at \a offset
 * \tparam CTYPE   Type of the \a cnt parameter
 * \param  offset  The current offset in bytes (must already be padded
 *                 if desired).
 * \param  limit   The limit in bytes that must not be exceeded after adding
 *                 \a cnt times the size of \a T.
 * \param  cnt     The number of elements of type \a T that shall be put
 *                 at \a offset.
 * \return true if the limit will not be exceeded, false else.
 */
template<typename T, typename CTYPE>
inline bool check_size(unsigned offset, unsigned limit, CTYPE cnt) noexcept
{
  if (L4_UNLIKELY(sizeof(CTYPE) <= sizeof(unsigned) &&
                  ~0U / sizeof(T) <= static_cast<unsigned>(cnt)))
    return false;

  if (L4_UNLIKELY(sizeof(CTYPE) > sizeof(unsigned) &&
                  static_cast<CTYPE>(~0U / sizeof(T)) <= cnt))
    return false;

  return sizeof(T) * cnt <= limit - offset;
}


enum
{
  /// number of bytes for one message word
  Word_bytes = sizeof(l4_umword_t),
  /// number of message words for one message item
  Item_words = 2,
  /// number of bytes for one message item
  Item_bytes = Word_bytes * Item_words,
  /// number of message words available in the UTCB
  Mr_words   = L4_UTCB_GENERIC_DATA_SIZE,
  /// number of bytes available in the UTCB message registers
  Mr_bytes   = Word_bytes * Mr_words,
  /// number of bytes available in the UTCB buffer registers
  Br_bytes   = Word_bytes * L4_UTCB_GENERIC_BUFFERS_SIZE,
};


/**
 * Add some data to a message at offs.
 * \tparam T      The type of the data to add
 * \param  msg    pointer to the start of the message
 * \param  offs   The current offset within the message, this shall be padded to
 *                the alignment of \a T if \a v is added.
 * \param  limit  The size limit in bytes that offset must not exceed.
 * \param  v      The value to add to the message
 * \return The new offset when successful, a negative value if the given
 *         limit will be exceeded.
 */
template<typename T>
inline int msg_add(char *msg, unsigned offs, unsigned limit, T v) noexcept
{
  offs = align_to<T>(offs);
  if (L4_UNLIKELY(!check_size<T>(offs, limit)))
    return -L4_EMSGTOOLONG;
  *reinterpret_cast<L4::Types::Remove_const_t<T> *>(msg + offs) = v;
  return offs + sizeof(T);
}

/**
 * Get some data from a message at offs.
 * \tparam T      The type of the data to read
 * \param  msg    Pointer to the start of the message
 * \param  offs   The current offset within the message, this shall be padded to
 *                the alignment of \a T if a \a v can be read.
 * \param  limit  The size limit in bytes that offset must not exceed.
 * \param  v      A reference to receive the value from the message
 * \return The new offset when successful, a negative value if the given
 *         limit will be exceeded.
 */
template<typename T>
inline int msg_get(char *msg, unsigned offs, unsigned limit, T &v) noexcept
{
  offs = align_to<T>(offs);
  if (L4_UNLIKELY(!check_size<T>(offs, limit)))
    return -L4_EMSGTOOSHORT;
  v = *reinterpret_cast<T *>(msg + offs);
  return offs + sizeof(T);
}

/// Phase during IPC argument mashalling
enum class Phase
{
  Data,     /// First phase: copy bytes to/from MRs
  Items,    /// Second phase: place/read items into/from MRs
  Buffers,  /// Third phase: setup buffer registers (client side only)
};

// implementation details
namespace Detail {

// Helper struct to get the plain type of the IPC argument. The various
// specializations remove the pointer or reference.
template<typename T> struct _Plain
{
  using type = T;
  static T &deref(T &v) noexcept { return v; }
  static T const &deref(T const &v) noexcept { return v; }
};

template<typename T> struct _Plain<T *>
{
  using type = T;
  static T &deref(T *v) noexcept { return *v; }
};

template<typename T> struct _Plain<T &>
{
  using type = T;
  static T &deref(T &v) noexcept { return v; }

};

template<typename T> struct _Plain<T const &>
{
  using type = T;
  static T const &deref(T const &v) noexcept { return v; }
};

template<typename T> struct _Plain<T const *>
{
  using type = T;
  static T const &deref(T const *v) noexcept { return *v; }
};

}

/**
 * Empty client side marshalling code.
 *
 * Used as basis for concrete marshalling code.
 */
template<typename T> struct Clnt_noops
{
  /**
   * Write IPC data of `arg` into UTCB.
   *
   * Called only for input arguments.
   *
   * \param msg     Pointer to first usable UTCB MR
   * \param offset  Byte offset into UTCB MRs
   * \param limit   Maximum number of bytes available in `msg` array
   * \param arg     The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int to_msg_data([[maybe_unused]] char *msg, unsigned offset,
                                   [[maybe_unused]] unsigned limit,
                                   [[maybe_unused]] T const &arg) noexcept
  { return offset; }

  /**
   * Write IPC send items of `arg` into UTCB.
   *
   * Called only for input arguments.
   *
   * \param msg     Pointer to first usable UTCB MR
   * \param offset  Byte offset into UTCB MRs
   * \param limit   Maximum number of bytes available in `msg` array
   * \param arg     The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int to_msg_items([[maybe_unused]] char *msg, unsigned offset,
                                    [[maybe_unused]] unsigned limit,
                                    [[maybe_unused]] T const &arg) noexcept
  { return offset; }

  /**
   * Setup receive buffers for object capabilities in UTCB.
   *
   * Called only for output arguments.
   *
   * \param brs     Pointer to first usable UTCB BR
   * \param offset  Byte offset into UTCB BRs
   * \param limit   Maximum number of bytes available in `brs` array
   * \param arg     The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `brs` for next IPC argument
   */
  static constexpr int to_msg_buffers([[maybe_unused]] char *brs,
                                      unsigned offset,
                                      [[maybe_unused]] unsigned limit,
                                      [[maybe_unused]] T const &arg) noexcept
  { return offset; }

  /**
   * Read IPC data for `arg` from UTCB.
   *
   * Called only for output arguments.
   *
   * \param      msg    Pointer to first filled UTCB MR
   * \param      offset Byte offset into UTCB MRs
   * \param      limit  Number of bytes available in `msg` array
   * \param[out] arg    The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int from_msg_data([[maybe_unused]] char *msg,
                                     unsigned offset,
                                     [[maybe_unused]] unsigned limit,
                                     [[maybe_unused]] T &arg) noexcept
  { return offset; }

  /**
   * Read IPC return items for `arg` from UTCB.
   *
   * Called only for output arguments.
   *
   * \param      msg    Pointer to first filled UTCB MR
   * \param      offset Byte offset into UTCB MRs
   * \param      limit  Number of bytes available in `msg` array
   * \param[out] arg    The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int from_msg_items([[maybe_unused]] char *msg,
                                      unsigned offset,
                                      [[maybe_unused]] unsigned limit,
                                      [[maybe_unused]] T &arg) noexcept
  { return offset; }
};

/**
 * Empty server side marshalling code.
 *
 * Used as basis for concrete marshalling code.
 */
template<typename T> struct Svr_noops
{
  /**
   * Write IPC data of `arg` into UTCB.
   *
   * Called only for output arguments.
   *
   * \param msg     Pointer to first usable UTCB MR
   * \param offset  Byte offset into UTCB MRs
   * \param limit   Maximum number of bytes available in `msg` array
   * \param arg     The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int from_svr_data([[maybe_unused]] char *msg,
                                     unsigned offset,
                                     [[maybe_unused]] unsigned limit,
                                     [[maybe_unused]] T const &arg) noexcept
  { return offset; }

  /**
   * Write IPC send items of `arg` into UTCB.
   *
   * Called only for output arguments.
   *
   * \param msg     Pointer to first usable UTCB MR
   * \param offset  Byte offset into UTCB MRs
   * \param limit   Maximum number of bytes available in `msg` array
   * \param arg     The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int from_svr_items([[maybe_unused]] char *msg,
                                      unsigned offset,
                                      [[maybe_unused]] unsigned limit,
                                      [[maybe_unused]] T const &arg) noexcept
  { return offset; }

  /**
   * Reserve IPC data bytes in UTCB.
   *
   * Called only for output arguments. The argument type may remember the
   * `msg` pointer if the data is put directly into the UTCB.
   *
   * \param msg     Pointer to first usable UTCB MR
   * \param offset  Byte offset into UTCB MRs
   * \param limit   Maximum number of bytes available in `msg` array
   * \param arg     The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int reserve_data([[maybe_unused]] char *msg,
                                    unsigned offset,
                                    [[maybe_unused]] unsigned limit,
                                    [[maybe_unused]] T &arg) noexcept
  { return offset; }

  /**
   * Read IPC data for `arg` from UTCB.
   *
   * Called only for input arguments.
   *
   * \param      msg    Pointer to first filled UTCB MR
   * \param      offset Byte offset into UTCB MRs
   * \param      limit  Number of bytes available in `msg` array
   * \param[out] arg    The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int to_svr_data([[maybe_unused]] char *msg,
                                   unsigned offset,
                                   [[maybe_unused]] unsigned limit,
                                   [[maybe_unused]] T &arg) noexcept
  { return offset; }

  /**
   * Read IPC return items for `arg` from UTCB.
   *
   * Called only for input arguments.
   *
   * \param      msg    Pointer to first filled UTCB MR
   * \param      offset Byte offset into UTCB MRs
   * \param      limit  Number of bytes available in `msg` array
   * \param[out] arg    The IPC argument
   *
   * \retval  <0  Error code
   * \retval  >=0 Offset into `msg` for next IPC argument
   */
  static constexpr int to_svr_items([[maybe_unused]] char *msg,
                                    unsigned offset,
                                    [[maybe_unused]] unsigned limit,
                                    [[maybe_unused]] T &arg) noexcept
  { return offset; }
};

/**
 * Defines client-side handling of 'MTYPE' as RPC argument.
 *
 * This provides the default implementation for trivially copyable data types.
 * Other types override the marshalling operations by partially specializing
 * Clnt_val_ops.
 *
 * The template is always instantiated with the
 * `Detail::_Plain<typename Elem<ARG>::arg_type>::type` of the IPC method
 * signature.
 *
 * \tparam MTYPE  Elem<T>::arg_type (where T is the type used in the RPC
 *                definition)
 */
template<typename MTYPE>
struct Clnt_val_ops : Clnt_noops<MTYPE>
{
  static_assert(L4::Types::Is_trivially_copyable_v<MTYPE>,
                "Only trivially copyable types can be used as IPC arguments");

  /// Copy a T into the message (arg must be either MTYPE or MTYPE const &)
  static int to_msg_data(char *msg, unsigned offset, unsigned limit,
                         MTYPE const &arg) noexcept
  { return msg_add<MTYPE>(msg, offset, limit, arg); }

  /// Copy data from the message to a T client reference (arg must be MTYPE &)
  static int from_msg_data(char *msg, unsigned offset, unsigned limit,
                           MTYPE &arg) noexcept
  { return msg_get<MTYPE>(msg, offset, limit, arg); }
};

/**
 * Defines server-side handling of 'MTYPE' as RPC argument.
 *
 * This provides the default implementation for trivially copyable data types.
 * Other types override the marshalling operations by partially specializing
 * Svr_val_ops.
 *
 * The template is always instantiated with the `Elem<ARG>::svr_type` of the
 * IPC method signature.
 *
 * \tparam MTYPE  Elem<T>::arg_type (where T is the type used in the RPC
 *                definition)
 */
template<typename MTYPE>
struct Svr_val_ops : Svr_noops<MTYPE>
{
  static_assert(L4::Types::Is_trivially_copyable_v<MTYPE>,
                "Only trivially copyable types can be used as IPC arguments");

  /// Copy data from the message to a T server reference (arg must be MTYPE &)
  static int to_svr_data(char *msg, unsigned offset, unsigned limit,
                         MTYPE &arg) noexcept
  { return msg_get<MTYPE>(msg, offset, limit, arg); }

  /// Prepare a T output operand (arg must be MTYPE &)
  static int reserve_data(char *, unsigned offs, unsigned limit,
                          MTYPE &) noexcept
  {
    offs = align_to<MTYPE>(offs);
    if (L4_UNLIKELY(!check_size<MTYPE>(offs, limit)))
      return -L4_EMSGTOOLONG;
    return offs + sizeof(MTYPE);
  }

  /// Copy a T into the message (arg must be either MTYPE or MTYPE const &)
  static int from_svr_data(char *msg, unsigned offset, unsigned limit,
                           MTYPE const &arg) noexcept
  { return msg_add<MTYPE>(msg, offset, limit, arg); }
};

/**
 * Helper struct to describe various aspects of an IPC argument.
 *
 * There are standard specializations to handle input and output arguments of
 * trivial data types. More complex types need to add further specializations
 * as needed.
 */
template<typename T> struct Elem
{
  /// The type used in client argument list
  using arg_type = T;
  /// The data type used to store the T on the server-side
  using svr_type = T;
  /// The argument type for the server-side function
  using svr_arg_type = T; // might be const & (depending on the size)
  /// Is it an optional argument?
  static constexpr bool Is_optional = false;
  /// Is the argument transferred from client to server?
  static constexpr bool Is_input = true;
  /// Is the argument transferred from server to client?
  static constexpr bool Is_output = false;
};

template<typename T> struct Elem<T &>
{
  using arg_type = T &;
  using svr_type = T;
  ///< pass a reference to the server-function too
  using svr_arg_type = T &;
  static constexpr bool Is_optional = false;
  static constexpr bool Is_input = false;
  static constexpr bool Is_output = true;
};

template<typename T> struct Elem<T const &>
{
  using arg_type = T const &;
  using svr_type = T;
  // as the RPC uses a const reference we use it here too,
  // we could also use pass by value depending on the size
  using svr_arg_type = T const &;
  static constexpr bool Is_optional = false;
  static constexpr bool Is_input = true;
  static constexpr bool Is_output = false;
};

template<typename T> struct Elem<T *> : Elem<T &>
{
  using arg_type = T *;
};

template<typename T> struct Elem<T const *> : Elem<T const &>
{
  using arg_type = T const *;
};

/// Type trait defining a valid RPC parameter type.
template<typename T> struct Is_valid_rpc_type : L4::Types::True {};

template<typename T>
struct _Elem : Elem<T>
{
  static_assert(Is_valid_rpc_type<T>::value,
                "L4::Ipc::Msg::_Elem<T>: type T is not a valid RPC parameter type.");
};

namespace Detail {

template<typename T>
struct _Clnt_val_ops :
  Clnt_val_ops<typename Detail::_Plain<T>::type> {};

template<typename T,
         typename ELEM = _Elem<T>,
         typename CLNT_OPS = _Clnt_val_ops<typename ELEM::arg_type>
         >
struct _Clnt_xmit : CLNT_OPS {};

template<typename T,
         typename ELEM = _Elem<T>,
         typename SVR_OPS = Svr_val_ops<typename ELEM::svr_type>
         >
struct _Svr_xmit : SVR_OPS {};

} //namespace Detail

// Specialize to alias a type X to T in client call signature.
// Needs to be combined with a matching Elem< X > : Elem< T > specialization.
// Or alternatively X needs to inherit from T.
template<typename T> struct Clnt_xmit : Detail::_Clnt_xmit<T> {};
// Specialize to alias a type X to T in server op_... signature.
// Needs to be combined with a matching Elem< X > : Elem< T > specialization.
// Or alternatively X needs to inherit from T.
template<typename T> struct Svr_xmit : Detail::_Svr_xmit<T> {};

}}} // namespace Msg, Ipc, L4


