// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (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/avl_tree>
#include <l4/cxx/std_ops>
#include <l4/sys/cxx/ipc_epiface>
#include <l4/cxx/string>
#include <l4/re/util/debug>

#include <l4/sys/capability>
#include <l4/re/namespace>

#include <stddef.h>
#include <string.h>

namespace L4Re { namespace Util { namespace Names {

/**
 * \brief Name class.
 */
class Name : public cxx::String
{
public:

  Name(const char *name = "") : String(name, __builtin_strlen(name)) {}
  Name(const char *name, unsigned long len) : String(name, len) {}
  Name(cxx::String const &n) : String(n) {}
  char const *name() const { return start(); }
  bool operator < (Name const &r) const
  {
    unsigned long l = cxx::min(len(), r.len());
    int v = memcmp(start(), r.start(), l);
    return v < 0 || (v == 0 && len() < r.len());
  }
};


/**
 * \internal
 */
class Obj
{
protected:
  unsigned _f;
  union
  {
    l4_cap_idx_t _cap;
    L4::Epiface *_obj;
  };


public:
  enum Flags
  {
    F_rw        = L4Re::Namespace::Rw,
    F_strong    = L4Re::Namespace::Strong,

    F_trusted   = L4Re::Namespace::Trusted,

    F_rights_mask = F_rw | F_strong | F_trusted,

    F_cap        = 0x100,
    F_local      = 0x200,
    F_replacable = 0x400,
    F_base_mask  = 0xf00,
  };


  unsigned flags() const { return _f; }
  void restrict_flags(unsigned max_rights)
  { _f &= (~F_rights_mask | (max_rights & F_rights_mask)); }

  bool is_rw() const { return (_f & F_rw) == F_rw; }
  bool is_strong() const { return _f & F_strong; }

  bool is_valid() const { return _f & F_cap; }
  bool is_complete() const { return is_valid(); }
  bool is_local() const { return _f & F_local; }
  bool is_replacable() const { return _f & F_replacable; }
  bool is_trusted() const { return _f & F_trusted; }

  L4::Epiface *obj() const { if (is_local()) return _obj; return 0; }
  L4::Cap<void> cap() const
  {
    if (!is_local())
      return L4::Cap<void>(_cap);
    if (!_obj)
      return L4::Cap<void>::Invalid;
    return _obj->obj_cap();
  }


  void set(Obj const &o, unsigned flags)
  {
    *this = o;
    restrict_flags(flags);
  }

  explicit Obj(unsigned flags = 0)
  : _f(flags), _cap(L4_INVALID_CAP)
  {}

  Obj(unsigned f, L4::Cap<void> const &cap)
  : _f((f & ~F_base_mask) | F_cap), _cap(cap.cap())
  {}

  Obj(unsigned f, L4::Epiface *o)
  : _f((f & ~F_base_mask) | F_cap | F_local), _obj(o)
  {}

  void reset(unsigned flags)
  {
    _f = (_f & F_replacable) | (flags & ~(F_cap | F_local));
    _cap = L4_INVALID_CAP;
  }


};


/**
 * \internal
 */
class Entry : public cxx::Avl_tree_node
{
private:
  friend class Name_space;
  Name _n;
  Obj  _o;

  bool _dynamic;

public:
  Entry(Name const &n, Obj const &o, bool dynamic = false)
  : _n(n), _o(o), _dynamic(dynamic) {}

  Name const &name() const { return _n; }
  Obj const *obj() const { return &_o; }
  Obj *obj() { return &_o; }
  void obj(Obj const &o) { _o = o; }

  bool is_placeholder() const
  { return !obj()->is_complete(); }

  bool is_dynamic() const { return _dynamic; }

  void set(Obj const &o)
  {
    obj()->set(o, obj()->flags());
  }

private:
  void * operator new (size_t s);
  void operator delete(void *b);

};

struct Names_get_key
{
  typedef Name Key_type;
  static Key_type const &key_of(Entry const *e)
  { return e->name(); }
};


/**
 * Abstract server-side implementation of the L4::Namespace interface.
 *
 * \note The derived class is resposible for resource management through the
 *       provided interfaces. This includes freeing all resources on
 *       destruction!
 */
class Name_space
{
  friend class Entry;

private:
  typedef cxx::Avl_tree<Entry, Names_get_key> Tree;
  Tree _tree;

protected:
  L4Re::Util::Dbg const &_dbg;
  L4Re::Util::Err const &_err;

public:

  typedef Tree::Const_iterator Const_iterator;

  Const_iterator begin() const { return _tree.begin(); }
  Const_iterator end() const { return _tree.end(); }

  Name_space(L4Re::Util::Dbg const &dbg, L4Re::Util::Err const &err)
  : _dbg(dbg), _err(err)
  {}

  /**
   * The destructor of the derived class is responsible for freeing resources.
   */
  virtual ~Name_space() {}

  Entry *find(Name const &name) const  { return _tree.find_node(name); }
  Entry *remove(Name const &name) { return _tree.remove(name); }
  Entry *find_iter(Name const &pname) const
  {
    Name name = pname;
    _dbg.printf("resolve '%.*s': ", name.len(), name.start());
    Name_space const *ns = this;
    while (ns)
      {
        cxx::String::Index sep = name.find("/");
        cxx::String part;
        if (!name.eof(sep))
          part = name.head(sep);
        else
          part = name;

        _dbg.cprintf(" '%.*s'", part.len(), part.start());
        Entry *o = ns->find(Name(part.start(), part.len()));

        if (!o)
          {
            _dbg.cprintf(": resolution failed: '%.*s' remaining\n",
                        name.len(), name.start());
            return 0;
          }

        auto const *obj = o->obj()->obj();
        ns = dynamic_cast<Name_space const *>(obj);
        if (ns)
          {
            if (!name.eof(sep))
              {
                name = name.substr(sep + 1);
                continue;
              }
          }

        _dbg.cprintf(": found object: %p (%s)\n",
                     obj, obj ? typeid(*obj).name() : "");

        return o;
      }

    return 0;
  }

  bool insert(Entry *e)  { return _tree.insert(e).second; }

  void dump(bool rec = false, int indent = 0) const;

protected:
  // server support --------------------------------------------
  /**
   * Allocate a new entry for the given name.
   *
   * \param n     Name of the entry, must be copied.
   * \param flags Entry flags, see Obj::Flags.
   *
   * \return A pointer to the newly allocated entry or NULL on error.
   *
   * This method is called when a new entry was received. It must
   * allocate memory, copy `n` out of the receive buffer and wrap
   * everything in an Entry.
   */
  virtual Entry *alloc_dynamic_entry(Name const &n, unsigned flags) = 0;

  /**
   * Free an entry previously allocated with alloc_dynamic_entry().
   *
   * \param e  Entry to free.
   */
  virtual void free_dynamic_entry(Entry *e) = 0;

  /**
   * Return a pointer to the epiface assigned to a given label.
   *
   * \param[in]  data     Label or in the local case the capability slot of
   *                      the receiving capability.
   * \param[in]  is_local If true, a local capability slot was supplied, if
   *                      false the label of a locally bound IPC gate.
   * \param[out] lo       Pointer to epiface responsible for the capability.
   *
   * \return #L4_EOK if a valid interface could be found or an error message
   *         otherwise.
   *
   * This method is called when a new entry is registered and some
   * local ID was received for the capability. In this case, the
   * generic implementation needs to get the epiface in order to get
   * the capability.
   *
   * The callee must make sure that the epiface remains valid until
   * free_epiface is called. In particular, the capability slot must
   * not be reallocated as long as the namespace server holds a
   * reference to the epiface.
   */
  virtual int get_epiface(l4_umword_t data, bool is_local, L4::Epiface **lo) = 0;

  /**
   * Return the receive capability for permanent use.
   *
   * \param[out] cap Capability slot with the received capability. Must
   *                 be permanently available until free_capability() is called.
   *
   * This method is called when a new entry is registered together
   * with a capability mapping. It must decide whether and where to
   * store the capability and return the final capability slot.
   * Typical implementations return the capability slot in the receive
   * window and allocate a new receive window.
   */
  virtual int copy_receive_cap(L4::Cap<void> *cap) = 0;

  /**
   * Free a capability previously acquired with copy_receive_cap().
   *
   * \param[in] cap Capability to free.
   *
   * Counterpart of #copy_receive_cap. Free the capability slot when
   * the entry is deleted or changed.
   */
  virtual void free_capability(L4::Cap<void> cap) = 0;

  /**
   * Free epiface previously acquired with get_epiface().
   *
   * \param[in] epiface Epiface to free.
   *
   * Called when an entry that points to an epiface is deleted
   * allowing implementations that hold resources to free them.
   */
  virtual void free_epiface(L4::Epiface *epiface) = 0;

  int insert_entry(Name const &name, unsigned flags, Entry **e)
  {
    Entry *n = find(name);
    if (n && n->obj()->is_valid())
      {
        if (!(flags & L4Re::Namespace::Overwrite)
            && n->obj()->cap().validate(L4_BASE_TASK_CAP).label() > 0)
          return -L4_EEXIST;

        if (n->obj()->is_local())
          free_epiface(n->obj()->obj());
        else
          free_capability(n->obj()->cap());

        if (n->is_dynamic())
          {
            remove(n->name());
            free_dynamic_entry(n);
            n = 0;
          }
        else
          {
            if (!n->obj()->is_replacable())
              return -L4_EEXIST;
            n->obj()->reset(Obj::F_rw);
          }
      }

    flags &= L4Re::Namespace::Cap_flags;
    if (!n)
      {
        if (!(n = alloc_dynamic_entry(name, flags)))
          return -L4_ENOMEM;
        else
          {
            if (!insert(n))
              {
                free_dynamic_entry(n);
                return -L4_EEXIST;
              }
          }
      }

    *e = n;
    return 0;
  }

public:
  // server interface ------------------------------------------
  int op_query(L4Re::Namespace::Rights,
               L4::Ipc::Array_in_buf<char, unsigned long> const &name,
               L4::Ipc::Snd_fpage &snd_cap, L4::Ipc::Opt<L4::Opcode> &dummy,
               L4::Ipc::Opt<L4::Ipc::Array_ref<char, unsigned long> > &out_name)
  {
#if 1
    _dbg.printf("query: [%ld] '%.*s'\n", name.length,
                static_cast<int>(name.length), name.data);
#endif

    char const *sep
      = static_cast<char const*>(memchr(name.data, '/', name.length));
    unsigned long part;
    if (sep)
      part = sep - name.data;
    else
      part = name.length;

    Entry *n = find(Name(name.data, part));
    if (!n)
      return -L4_ENOENT;
    else if (!n->obj()->is_valid())
      return -L4_EAGAIN;
    else
      {
        if (n->obj()->cap().validate(L4_BASE_TASK_CAP).label() <= 0)
          {
            if (n->obj()->is_local())
              free_epiface(n->obj()->obj());
            else
              free_capability(n->obj()->cap());

            if (n->is_dynamic())
              {
                remove(n->name());
                free_dynamic_entry(n);
              }
            return -L4_ENOENT;
          }

        // make picky clients happy
        dummy.set_valid();
        // prevent warning about writing uninitialized data in IPC framework
        dummy = 0;

        l4_umword_t result = 0;

        out_name.set_valid();
        if (part < name.length)
          {
            result |= L4Re::Namespace::Partly_resolved;
            memcpy(out_name->data, name.data + part + 1, name.length - part - 1);
            out_name->length = name.length - part - 1;
          }
        else
          out_name->length = 0;

        unsigned flags = L4_CAP_FPAGE_R;
        if (n->obj()->is_rw())     flags |= L4_CAP_FPAGE_W;
        if (n->obj()->is_strong()) flags |= L4_CAP_FPAGE_S;

        snd_cap = L4::Ipc::Snd_fpage(n->obj()->cap(), flags);
        _dbg.printf(" result = %lx flgs=%x strg=%d\n",
                    result, flags, static_cast<int>(n->obj()->is_strong()));
        return result;
      }
  }

  int op_register_obj(L4Re::Namespace::Rights, unsigned flags,
                      L4::Ipc::Array_in_buf<char, unsigned long> const &name,
                      L4::Ipc::Snd_fpage &cap)
  {
    if (name.length == 0 || memchr(name.data, '/', name.length))
        return -L4_EINVAL;

    L4::Cap<void> reg_cap(L4_INVALID_CAP);
    L4::Epiface *src_o = 0;

    // Did we receive something we have handed out ourselves? If yes,
    // register the object under the given name but do not allocate
    // anything more.
    if (cap.id_received() || cap.local_id_received())
      {
        if (int ret = get_epiface(cap.data(), cap.local_id_received(), &src_o))
          return ret;

        // Make sure rights are restricted to the mapped rights.
        flags &= (cap.data() & 0x3UL) | ~0x3UL;
      }
    else if (cap.cap_received())
      {
        if (int ret = copy_receive_cap(&reg_cap))
          return ret;
      }
    else if (!cap.is_valid())
      {
        reg_cap = L4::Cap<void>::Invalid;
      }
    else
      return -L4_EINVAL;

    // got a valid entry to register
    _dbg.printf("register: '%.*s' flags=%x\n", static_cast<int>(name.length),
                name.data, flags);

    Name _name(name.data, name.length);

    Entry *n;
    if (int r = insert_entry(_name, flags, &n))
      {
        if (cap.cap_received())
          free_capability(reg_cap);
        if (src_o)
          free_epiface(src_o);

        return r;
      }

    if (src_o)
      n->set(Names::Obj(flags & L4Re::Namespace::Cap_flags, src_o));
    else if (reg_cap.is_valid())
      n->set(Names::Obj(flags & L4Re::Namespace::Cap_flags, reg_cap));

    return 0;
  }

  int op_unlink(L4Re::Namespace::Rights,
                L4::Ipc::Array_in_buf<char, unsigned long> const &name)

  {
#if 1
    _dbg.printf("unlink: [%ld] '%.*s'\n", name.length,
                static_cast<int>(name.length), name.data);
#endif

    char const *sep
      = static_cast<char const*>(memchr(name.data, '/', name.length));
    unsigned long part;
    if (sep)
      part = sep - name.data;
    else
      part = name.length;

    Entry *n = find(Name(name.data, part));
    if (!n || !n->obj()->is_valid())
      return -L4_ENOENT;

    if (n->obj()->is_local())
      free_epiface(n->obj()->obj());
    else
      free_capability(n->obj()->cap());

    if (n->is_dynamic())
      {
        remove(n->name());
        free_dynamic_entry(n);
      }
    else
      return -L4_EACCESS;

    return 0;
  }
};

}}}

