// vi:set ft=cpp: -*- Mode: C++ -*-
/**
 * \file
 * \brief Base exceptions
 * \ingroup l4cxx_exceptions
 */
/*
 * (c) 2008-2009 Adam Lackorzynski <adam@os.inf.tu-dresden.de>,
 *               Alexander Warg <warg@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include <l4/cxx/l4types.h>
#include <l4/cxx/basic_ostream>
#include <l4/sys/err.h>
#include <l4/sys/capability>


/**
 * \defgroup l4cxx_exceptions C++ Exceptions
 * \ingroup api_l4re
 */
/**@{*/

#ifndef L4_CXX_NO_EXCEPTION_BACKTRACE
# define L4_CXX_EXCEPTION_BACKTRACE 20  ///< Number of instruction pointers in backtrace
#endif

#if defined(L4_CXX_EXCEPTION_BACKTRACE)
#include <l4/util/backtrace.h>
#endif

/**@}*/
namespace L4
{
  /**
   * \addtogroup l4cxx_exceptions
   */
  /**@{*/
  /**
   * \brief Back-trace support for exceptions.
   * \headerfile l4/cxx/exceptions
   *
   * This class holds an array of at most #L4_CXX_EXCEPTION_BACKTRACE
   * instruction pointers containing the call trace at the instant when an
   * exception was thrown.
   */
  class Exception_tracer
  {
#if defined(L4_CXX_EXCEPTION_BACKTRACE)
  private:
    void *_pc_array[L4_CXX_EXCEPTION_BACKTRACE];
    int   _frame_cnt;

  protected:
    /**
     * \brief Create a back trace.
     */
#if defined(__PIC__)
    Exception_tracer() noexcept : _frame_cnt(0) {}
#else
    Exception_tracer() noexcept
    : _frame_cnt(l4util_backtrace(_pc_array, L4_CXX_EXCEPTION_BACKTRACE)) {}
#endif

  public:
    /**
     * \brief Get the array containing the call trace.
     */
    void const *const *pc_array() const noexcept { return _pc_array; }
    /**
     * \brief Get the number of entries that are valid in the call trace.
     */
    int frame_count() const noexcept { return _frame_cnt; }
#else
  protected:
    /**
     * \brief Create a back trace.
     */
    Exception_tracer() noexcept {}

  public:
    /**
     * \brief Get the array containing the call trace.
     */
    void const *const *pc_array() const noexcept { return 0; }
    /**
     * \brief Get the number of entries that are valid in the call trace.
     */
    int frame_count() const noexcept { return 0; }
#endif
  };

  /**
   * \brief Base class for all exceptions, thrown by the L4Re framework.
   * \headerfile l4/cxx/exceptions
   *
   * This is the abstract base of all exceptions thrown within the
   * L4Re framework. It is basically also a good idea to use it as base of
   * all user defined exceptions.
   */
  class Base_exception : public Exception_tracer
  {
  protected:
    /// Create a base exception.
    Base_exception() noexcept {}

  public:
    /**
     * Return a human readable string for the exception.
     */
    virtual char const *str() const noexcept = 0;

    /// Destruction
    virtual ~Base_exception() noexcept {}
  };

  /**
   * \brief Exception for an abstract runtime error.
   * \headerfile l4/cxx/exceptions
   *
   * This is the base class for a set of exceptions that cover all errors
   * that have a C error value (see #l4_error_code_t).
   */
  class Runtime_error : public Base_exception
  {
  private:
    long _errno;
    char _extra[80];

  public:
    /**
     * Create a new Runtime_error.
     *
     * \param err_no  Error value for this runtime error.
     * \param extra   Description of what happened when the error occurred.
     */
    explicit Runtime_error(long err_no, char const *extra = 0) noexcept
      : _errno(err_no)
    {
      if (!extra)
        _extra[0] = 0;
      else
        {
          unsigned i = 0;
          for (; i < sizeof(_extra) && extra[i]; ++i)
            _extra[i] = extra[i];
          _extra[i < sizeof(_extra) ? i : sizeof(_extra) - 1] = 0;
        }
    }
    char const *str() const noexcept override
    { return l4sys_errtostr(_errno); }

    /**
     * Get the description text for this runtime error.
     *
     * \return  Pointer to the description string.
     */
    char const *extra_str() const { return _extra; }
    ~Runtime_error() noexcept {}

    /**
     * Get the error value for this runtime error.
     *
     * \return  Error value.
     */
    long err_no() const noexcept { return _errno; }
  };

  /**
   * \brief Exception signalling insufficient memory.
   * \headerfile l4/cxx/exceptions
   */
  class Out_of_memory : public Runtime_error
  {
  public:
    /// Create an out-of-memory exception.
    explicit Out_of_memory(char const *extra = "") noexcept
    : Runtime_error(-L4_ENOMEM, extra) {}
    /// Destruction
    ~Out_of_memory() noexcept {}
  };


  /**
   * \brief Exception for duplicate element insertions.
   * \headerfile l4/cxx/exceptions
   */
  class Element_already_exists : public Runtime_error
  {
  public:
    explicit Element_already_exists(char const *e = "") noexcept
    : Runtime_error(-L4_EEXIST, e) {}
    ~Element_already_exists() noexcept {}
  };

  /**
   * \brief Exception for an unknown condition.
   * \headerfile l4/cxx/exceptions
   *
   * This error is usually used when a server returns an unknown return state
   * to the client, this may indicate incompatible messages used by the client
   * and the server.
   */
  class Unknown_error : public Base_exception
  {
  public:
    Unknown_error() noexcept {}
    char const *str() const noexcept override { return "unknown error"; }
    ~Unknown_error() noexcept {}
  };

  /**
   * \brief Exception for a failed lookup (element not found).
   * \headerfile l4/cxx/exceptions
   */
  class Element_not_found : public Runtime_error
  {
  public:
    explicit Element_not_found(char const *e = "") noexcept
    : Runtime_error(-L4_ENOENT, e) {}
  };

  /**
   * \brief Indicates that an invalid object was invoked.
   * \headerfile l4/cxx/exceptions
   *
   * An Object is invalid if it has L4_INVALID_ID as server L4 UID,
   * or if the server does not know the object ID.
   */
  class Invalid_capability : public Base_exception
  {
  private:
    Cap<void> const _o;

  public:
    /**
     * \brief Create an Invalid_object exception for the Object o.
     * \param o The object that caused the server side error.
     */
    explicit Invalid_capability(Cap<void> const &o) noexcept : _o(o) {}
    template< typename T>
    explicit Invalid_capability(Cap<T> const &o) noexcept : _o(o.cap()) {}
    char const *str() const noexcept override { return "invalid object"; }

    /**
     * \brief Get the object that caused the error.
     * \return The object that caused the error on invocation.
     */
    Cap<void> const &cap() const noexcept { return _o; }
    ~Invalid_capability() noexcept {}
  };

  /**
   * \brief Error conditions during IPC.
   * \headerfile l4/cxx/exceptions
   *
   * This exception encapsulates all IPC error conditions of L4 IPC.
   */
  class Com_error : public Runtime_error
  {
  public:
    /**
     * \brief Create a Com_error for the given L4 IPC error code.
     * \param err The L4 IPC error code (l4_ipc... return value).
     */
    explicit Com_error(long err) noexcept : Runtime_error(err) {}

    ~Com_error() noexcept {}
  };

  /**
   * \brief Access out of bounds.
   */
  class Bounds_error : public Runtime_error
  {
  public:
    explicit Bounds_error(char const *e = "") noexcept
    : Runtime_error(-L4_ERANGE, e) {}
    ~Bounds_error() noexcept {}
  };
  /**@}*/
};

inline
L4::BasicOStream &
operator << (L4::BasicOStream &o, L4::Base_exception const &e)
{
  o << "Exception: " << e.str() << ".\n";
  if (e.frame_count())
    {
      o << "Backtrace:\n";
      for (int i = 0; i < e.frame_count(); ++i)
        o << "  " << L4::n_hex(l4_addr_t(e.pc_array()[i])) << '\n';
    }

  return o;
}

inline
L4::BasicOStream &
operator << (L4::BasicOStream &o, L4::Runtime_error const &e)
{
  o << "Exception: " << e.str();
  if (e.extra_str() && e.extra_str()[0])
    o << ": " << e.extra_str();
  o << ".\n";
  if (e.frame_count())
    {
      o << "Backtrace:\n";
      for (int i = 0; i < e.frame_count(); ++i)
        o << "  " << L4::n_hex(l4_addr_t(e.pc_array()[i])) << '\n';
    }

  return o;
}
