/*
 * tube-dbus.c - Source for GabbleTubeDBus
 * Copyright (C) 2007-2008 Collabora Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "tube-dbus.h"

#include <string.h>
#include <errno.h>

#include <glib/gstdio.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <wocky/wocky.h>
#include <telepathy-glib/telepathy-glib.h>
#include <telepathy-glib/telepathy-glib-dbus.h>

#include "extensions/extensions.h"

#define DEBUG_FLAG GABBLE_DEBUG_TUBES

#include "bytestream-factory.h"
#include "bytestream-ibb.h"
#include "bytestream-iface.h"
#include "connection.h"
#include "debug.h"
#include "disco.h"
#include "gabble-signals-marshal.h"
#include "muc-tube-dbus.h"
#include "namespaces.h"
#include "presence-cache.h"
#include "tube-iface.h"
#include "util.h"

/* When we receive D-Bus messages to be delivered to the application and the
 * application is not yet connected to the D-Bus tube, theses D-Bus messages
 * are queued and delivered when the application connects to the D-Bus tube.
 *
 * If the application never connects, there is a risk that the contact sends
 * too many messages and eat all the memory. To avoid this, there is an
 * arbitrary limit on the queue size set to 4MB. */
#define MAX_QUEUE_SIZE (4096*1024)

static void tube_iface_init (gpointer g_iface, gpointer iface_data);
static void dbustube_iface_init (gpointer g_iface, gpointer iface_data);

G_DEFINE_TYPE_WITH_CODE (GabbleTubeDBus, gabble_tube_dbus,
    TP_TYPE_BASE_CHANNEL,
    G_IMPLEMENT_INTERFACE (GABBLE_TYPE_TUBE_IFACE, tube_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_DBUS_TUBE,
      dbustube_iface_init);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_TUBE,
      NULL);
    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
      tp_external_group_mixin_iface_init);
);

static const gchar * const gabble_tube_dbus_channel_allowed_properties[] = {
    TP_IFACE_CHANNEL ".TargetHandle",
    TP_IFACE_CHANNEL ".TargetID",
    TP_IFACE_CHANNEL_TYPE_DBUS_TUBE ".ServiceName",
    NULL
};

/* signals */
enum
{
  OPENED,
  CLOSED,
  OFFERED,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = {0};

/* properties */
enum
{
  PROP_SELF_HANDLE = 1,
  PROP_ID,
  PROP_BYTESTREAM,
  PROP_STREAM_ID,
  PROP_TYPE,
  PROP_SERVICE,
  PROP_PARAMETERS,
  PROP_STATE,
  PROP_DBUS_ADDRESS,
  PROP_DBUS_NAME,
  PROP_DBUS_NAMES,
  PROP_MUC,
  PROP_SUPPORTED_ACCESS_CONTROLS,
  LAST_PROPERTY
};

struct _GabbleTubeDBusPrivate
{
  TpHandle self_handle;
  guint64 id;
  GabbleBytestreamIface *bytestream;
  gchar *stream_id;
  gchar *service;
  GHashTable *parameters;
  GabbleMucChannel *muc;
  TpSocketAccessControl access_control;
  /* GArray of guint */
  GArray *supported_access_controls;

  /* For outgoing tubes, TRUE if the offer has been sent over the network. For
   * incoming tubes, always TRUE.
   */
  gboolean offered;

  /* our unique D-Bus name on the virtual tube bus (NULL for 1-1 D-Bus tubes)*/
  gchar *dbus_local_name;
  /* the address that we are listening for D-Bus connections on */
  gchar *dbus_srv_addr;
  /* the path of the UNIX socket used by the D-Bus server */
  gchar *socket_path;
  /* the server that's listening on dbus_srv_addr */
  DBusServer *dbus_srv;
  /* the connection to dbus_srv from a local client, or NULL */
  DBusConnection *dbus_conn;
  /* the queue of D-Bus messages to be delivered to a local client when it
   * will connect */
  GSList *dbus_msg_queue;
  /* current size of the queue in bytes. The maximum is MAX_QUEUE_SIZE */
  unsigned long dbus_msg_queue_size;
  /* mapping of contact handle -> D-Bus name (empty for 1-1 D-Bus tubes) */
  GHashTable *dbus_names;
  /* mapping of D-Bus name -> contact handle */
  GHashTable *dbus_name_to_handle;

  /* Message reassembly buffer (CONTACT tubes only) */
  GString *reassembly_buffer;
  /* Number of bytes that will be in the next message, 0 if unknown */
  guint32 reassembly_bytes_needed;

  gboolean dispose_has_run;
};

#define GABBLE_TUBE_DBUS_GET_PRIVATE(obj) ((obj)->priv)

static GPtrArray *
gabble_tube_dbus_get_interfaces (TpBaseChannel *base)
{
  GPtrArray *interfaces;

  interfaces = TP_BASE_CHANNEL_CLASS (
      gabble_tube_dbus_parent_class)->get_interfaces (base);

  g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_TUBE);

  return interfaces;
}

static void data_received_cb (GabbleBytestreamIface *stream, TpHandle sender,
    GString *data, gpointer user_data);

/*
 * Characters used are permissible both in filenames and in D-Bus names. (See
 * D-Bus specification for restrictions.)
 */
static void
generate_ascii_string (guint len,
                       gchar *buf)
{
  const gchar *chars =
    "0123456789"
    "abcdefghijklmnopqrstuvwxyz"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "_-";
  guint i;

  for (i = 0; i < len; i++)
    buf[i] = chars[g_random_int_range (0, 64)];
}

static DBusHandlerResult
filter_cb (DBusConnection *conn,
           DBusMessage *msg,
           void *user_data)
{
  GabbleTubeDBus *tube = GABBLE_TUBE_DBUS (user_data);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (tube);
  gchar *marshalled = NULL;
  gint len;

  if (dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_SIGNAL &&
      !tp_strdiff (dbus_message_get_interface (msg),
        "org.freedesktop.DBus.Local") &&
      !tp_strdiff (dbus_message_get_member (msg), "Disconnected"))
    {
      /* connection was disconnected */
      DEBUG ("connection was disconnected");
      dbus_connection_close (priv->dbus_conn);
      tp_clear_pointer (&priv->dbus_conn, dbus_connection_unref);
      goto out;
    }

  if (priv->dbus_local_name != NULL)
    {
      if (!dbus_message_set_sender (msg, priv->dbus_local_name))
        DEBUG ("dbus_message_set_sender failed");
    }

  if (!dbus_message_marshal (msg, &marshalled, &len))
    goto out;

  if (GABBLE_IS_BYTESTREAM_MUC (priv->bytestream))
    {
      /* This bytestream support direct send */
      const gchar *dest;

      dest = dbus_message_get_destination (msg);

      if (dest != NULL)
        {
          TpHandle handle;

          handle = GPOINTER_TO_UINT (g_hash_table_lookup (
                priv->dbus_name_to_handle, dest));

          if (handle == 0)
            {
              DEBUG ("Unknown D-Bus name: %s", dest);
              goto out;
            }

          gabble_bytestream_muc_send_to (
              GABBLE_BYTESTREAM_MUC (priv->bytestream), handle, len,
              marshalled);

          goto out;
        }
    }

  gabble_bytestream_iface_send (priv->bytestream, len, marshalled);

out:
  if (marshalled != NULL)
    g_free (marshalled);

  return DBUS_HANDLER_RESULT_HANDLED;
}

static dbus_bool_t
allow_all_connections (DBusConnection *conn,
                       unsigned long uid,
                       void *data)
{
  return TRUE;
}

static void
new_connection_cb (DBusServer *server,
                   DBusConnection *conn,
                   void *data)
{
  GabbleTubeDBus *tube = GABBLE_TUBE_DBUS (data);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (tube);
  guint32 serial;
  GSList *i;

  if (priv->dbus_conn != NULL)
    /* we already have a connection; drop this new one */
    /* return without reffing conn means it will be dropped */
    return;

  DEBUG ("got connection");

  dbus_connection_ref (conn);
  dbus_connection_setup_with_g_main (conn, NULL);
  dbus_connection_add_filter (conn, filter_cb, tube, NULL);
  priv->dbus_conn = conn;

  if (priv->access_control == TP_SOCKET_ACCESS_CONTROL_LOCALHOST)
    {
      /* By default libdbus use Credentials access control. If user wants
       * to use the Localhost access control, we need to bypass this check. */
      dbus_connection_set_unix_user_function (conn, allow_all_connections,
          NULL, NULL);
    }

  /* We may have received messages to deliver before the local connection is
   * established. Theses messages are kept in the dbus_msg_queue list and are
   * delivered as soon as we get the connection. */
  DEBUG ("%u messages in the queue (%lu bytes)",
         g_slist_length (priv->dbus_msg_queue), priv->dbus_msg_queue_size);
  priv->dbus_msg_queue = g_slist_reverse (priv->dbus_msg_queue);
  for (i = priv->dbus_msg_queue; i != NULL; i = g_slist_delete_link (i, i))
    {
      DBusMessage *msg = i->data;
      DEBUG ("delivering queued message from '%s' to '%s' on the "
             "new connection",
             dbus_message_get_sender (msg),
             dbus_message_get_destination (msg));
      dbus_connection_send (priv->dbus_conn, msg, &serial);
      dbus_message_unref (msg);
    }
  priv->dbus_msg_queue = NULL;
  priv->dbus_msg_queue_size = 0;
}

static void
gabble_tube_dbus_close (TpBaseChannel *base)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (base);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);

  /* Take a ref to ourselves as when we emit tube-closed
   * GabbleTubesChannel will drop our last ref but we still need to
   * declare ourselves as destroyed. This is rubbish, but will
   * disappear when we finally remove the Tubes channel type.. */
  g_object_ref (base);

  if (priv->bytestream != NULL)
    gabble_bytestream_iface_close (priv->bytestream, NULL);
  else
    g_signal_emit (G_OBJECT (self), signals[CLOSED],0);

  tp_base_channel_destroyed (base);

  g_object_unref (base);
}

/* There is two step to enable receiving a D-Bus connection from the local
 * application:
 * - listen on the socket
 * - add the socket in the mainloop
 *
 * We need to know the socket path to return from the AcceptDBusTube D-Bus
 * call but the socket in the mainloop must be added only when we are ready
 * to receive connections, that is when the bytestream is fully open with the
 * remote contact.
 *
 * See also Bug 13891:
 * https://bugs.freedesktop.org/show_bug.cgi?id=13891
 * */
static gboolean
create_dbus_server (GabbleTubeDBus *self,
                    GError **err)
{
#define SERVER_LISTEN_MAX_TRIES 5
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  guint i;

  if (priv->dbus_srv != NULL)
    return TRUE;

  for (i = 0; i < SERVER_LISTEN_MAX_TRIES; i++)
    {
      gchar suffix[8];
      DBusError error;

      g_free (priv->dbus_srv_addr);
      g_free (priv->socket_path);

      generate_ascii_string (8, suffix);
      priv->socket_path = g_strdup_printf ("%s/dbus-gabble-%.8s",
          g_get_tmp_dir (), suffix);
      priv->dbus_srv_addr = g_strdup_printf ("unix:path=%s",
          priv->socket_path);

      dbus_error_init (&error);
      priv->dbus_srv = dbus_server_listen (priv->dbus_srv_addr, &error);

      if (priv->dbus_srv != NULL)
        break;

      DEBUG ("dbus_server_listen failed (try %u): %s: %s", i, error.name,
          error.message);
      dbus_error_free (&error);
    }

  if (priv->dbus_srv == NULL)
    {
      DEBUG ("all attempts failed. Close the tube");

      g_free (priv->dbus_srv_addr);
      priv->dbus_srv_addr = NULL;

      g_free (priv->socket_path);
      priv->socket_path = NULL;

      g_set_error (err, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
          "Can't create D-Bus server");
      return FALSE;
    }

  DEBUG ("listening on %s", priv->dbus_srv_addr);

  dbus_server_set_new_connection_function (priv->dbus_srv, new_connection_cb,
      self, NULL);

  return TRUE;
}

static void
tube_dbus_open (GabbleTubeDBus *self)
{
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  TpBaseChannel *base = TP_BASE_CHANNEL (self);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);

  g_signal_connect (priv->bytestream, "data-received",
      G_CALLBACK (data_received_cb), self);

  /* TODO: we should remove this call once muc D-Bus tube new API are
   * implemented  as the server should already exist. */
  if (!create_dbus_server (self, NULL))
    tp_base_channel_close (TP_BASE_CHANNEL (self));

  if (priv->dbus_srv != NULL)
    {
      dbus_server_setup_with_g_main (priv->dbus_srv, NULL);
    }

  if (cls->target_handle_type == TP_HANDLE_TYPE_ROOM)
    {
      /* add yourself in dbus names */
      gabble_tube_dbus_add_name (self, priv->self_handle,
          priv->dbus_local_name);

      gabble_muc_channel_send_presence (priv->muc);
    }
}

static void
gabble_tube_dbus_init (GabbleTubeDBus *self)
{
  GabbleTubeDBusPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
      GABBLE_TYPE_TUBE_DBUS, GabbleTubeDBusPrivate);

  self->priv = priv;
}

static TpTubeChannelState
get_tube_state (GabbleTubeDBus *self)
{
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  GabbleBytestreamState bytestream_state;

  if (!priv->offered)
    return TP_TUBE_CHANNEL_STATE_NOT_OFFERED;

  if (priv->bytestream == NULL)
    /* bytestream not yet created as we're waiting for the SI reply */
    return TP_TUBE_CHANNEL_STATE_REMOTE_PENDING;

  g_object_get (priv->bytestream, "state", &bytestream_state, NULL);

  switch (bytestream_state)
    {
      case GABBLE_BYTESTREAM_STATE_OPEN:
        return TP_TUBE_CHANNEL_STATE_OPEN;
        break;
      case GABBLE_BYTESTREAM_STATE_LOCAL_PENDING:
      case GABBLE_BYTESTREAM_STATE_ACCEPTED:
        return TP_TUBE_CHANNEL_STATE_LOCAL_PENDING;
        break;
      case GABBLE_BYTESTREAM_STATE_INITIATING:
        return TP_TUBE_CHANNEL_STATE_REMOTE_PENDING;
        break;
      default:
        g_return_val_if_reached (0);
    }
}

static void
bytestream_state_changed_cb (GabbleBytestreamIface *bytestream,
                             GabbleBytestreamState state,
                             gpointer user_data)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (user_data);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  TpBaseChannel *base = TP_BASE_CHANNEL (self);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);

  if (state == GABBLE_BYTESTREAM_STATE_CLOSED)
    {
      tp_clear_object (&priv->bytestream);
      g_signal_emit (G_OBJECT (self), signals[CLOSED], 0);

      if (cls->target_handle_type == TP_HANDLE_TYPE_ROOM)
        gabble_muc_channel_send_presence (priv->muc);
    }
  else if (state == GABBLE_BYTESTREAM_STATE_OPEN)
    {
      tube_dbus_open (self);

      tp_svc_channel_interface_tube_emit_tube_channel_state_changed (self,
          TP_TUBE_CHANNEL_STATE_OPEN);

      g_signal_emit (G_OBJECT (self), signals[OPENED], 0);
    }
}

static void
gabble_tube_dbus_dispose (GObject *object)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (object);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);

  DEBUG ("called");

  if (priv->dispose_has_run)
    return;

  priv->dispose_has_run = TRUE;

  if (priv->bytestream != NULL)
    gabble_bytestream_iface_close (priv->bytestream, NULL);

  if (priv->dbus_conn != NULL)
    dbus_connection_close (priv->dbus_conn);

  tp_clear_pointer (&priv->dbus_conn, dbus_connection_unref);

  if (priv->dbus_srv != NULL)
    dbus_server_disconnect (priv->dbus_srv);

  tp_clear_pointer (&priv->dbus_srv, dbus_server_unref);

  if (priv->socket_path != NULL)
    {
      if (g_unlink (priv->socket_path) != 0)
        {
          DEBUG ("unlink of %s failed: %s", priv->socket_path,
              g_strerror (errno));
        }
    }

  if (priv->dbus_msg_queue != NULL)
    {
      GSList *i;
      for (i = priv->dbus_msg_queue; i != NULL; i = g_slist_delete_link (i, i))
        {
          DBusMessage *msg = i->data;
          dbus_message_unref (msg);
        }
      priv->dbus_msg_queue = NULL;
      priv->dbus_msg_queue_size = 0;
    }

  tp_clear_pointer (&priv->dbus_srv_addr, g_free);
  tp_clear_pointer (&priv->socket_path, g_free);
  tp_clear_pointer (&priv->dbus_local_name, g_free);
  tp_clear_pointer (&priv->dbus_names, g_hash_table_unref);
  tp_clear_pointer (&priv->dbus_name_to_handle, g_hash_table_unref);

  if (priv->reassembly_buffer)
    g_string_free (priv->reassembly_buffer, TRUE);

  if (G_OBJECT_CLASS (gabble_tube_dbus_parent_class)->dispose)
    G_OBJECT_CLASS (gabble_tube_dbus_parent_class)->dispose (object);
}

static void
gabble_tube_dbus_finalize (GObject *object)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (object);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);

  g_free (priv->stream_id);
  g_free (priv->service);
  g_hash_table_unref (priv->parameters);
  g_array_unref (priv->supported_access_controls);

  if (priv->muc != NULL)
    tp_external_group_mixin_finalize (object);

  G_OBJECT_CLASS (gabble_tube_dbus_parent_class)->finalize (object);
}

static void
gabble_tube_dbus_get_property (GObject *object,
                               guint property_id,
                               GValue *value,
                               GParamSpec *pspec)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (object);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);

  switch (property_id)
    {
      case PROP_SELF_HANDLE:
        g_value_set_uint (value, priv->self_handle);
        break;
      case PROP_ID:
        g_value_set_uint64 (value, priv->id);
        break;
      case PROP_BYTESTREAM:
        g_value_set_object (value, priv->bytestream);
        break;
      case PROP_STREAM_ID:
        g_value_set_string (value, priv->stream_id);
        break;
      case PROP_TYPE:
        g_value_set_uint (value, TP_TUBE_TYPE_DBUS);
        break;
      case PROP_SERVICE:
        g_value_set_string (value, priv->service);
        break;
      case PROP_PARAMETERS:
        g_value_set_boxed (value, priv->parameters);
        break;
      case PROP_STATE:
        g_value_set_uint (value, get_tube_state (self));
        break;
      case PROP_DBUS_ADDRESS:
        g_value_set_string (value, priv->dbus_srv_addr);
        break;
      case PROP_DBUS_NAME:
        g_value_set_string (value, priv->dbus_local_name);
        break;
      case PROP_DBUS_NAMES:
        g_value_set_boxed (value, priv->dbus_names);
        break;
      case PROP_MUC:
        g_value_set_object (value, priv->muc);
        break;
      case PROP_SUPPORTED_ACCESS_CONTROLS:
        g_value_set_boxed (value, priv->supported_access_controls);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
gabble_tube_dbus_set_property (GObject *object,
                               guint property_id,
                               const GValue *value,
                               GParamSpec *pspec)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (object);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);

  switch (property_id)
    {
      case PROP_SELF_HANDLE:
        priv->self_handle = g_value_get_uint (value);
        break;
      case PROP_ID:
        priv->id = g_value_get_uint64 (value);
        break;
      case PROP_BYTESTREAM:
        if (priv->bytestream == NULL)
          {
            GabbleBytestreamState state;

            priv->bytestream = g_value_get_object (value);
            g_object_ref (priv->bytestream);

            g_object_get (priv->bytestream, "state", &state, NULL);
            if (state == GABBLE_BYTESTREAM_STATE_OPEN)
              {
                tube_dbus_open (self);
              }

            g_signal_connect (priv->bytestream, "state-changed",
                G_CALLBACK (bytestream_state_changed_cb), self);
          }
        break;
      case PROP_STREAM_ID:
        g_free (priv->stream_id);
        priv->stream_id = g_value_dup_string (value);
        break;
      case PROP_SERVICE:
        g_free (priv->service);
        priv->service = g_value_dup_string (value);
        break;
      case PROP_PARAMETERS:
        if (priv->parameters != NULL)
          g_hash_table_unref (priv->parameters);
        priv->parameters = g_value_dup_boxed (value);
        break;
      case PROP_MUC:
        priv->muc = g_value_get_object (value);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
gabble_tube_dbus_constructed (GObject *obj)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (obj);
  GabbleTubeDBusPrivate *priv = self->priv;
  TpBaseChannel *base = TP_BASE_CHANNEL (obj);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);
  TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
  GabbleConnection *conn = GABBLE_CONNECTION (base_conn);
  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
      base_conn, TP_HANDLE_TYPE_CONTACT);
  guint access_control;

  void (*chain_up) (GObject *) =
    ((GObjectClass *) gabble_tube_dbus_parent_class)->constructed;

  if (chain_up != NULL)
    chain_up (obj);

  priv->dbus_names = g_hash_table_new_full (g_direct_hash, g_direct_equal,
      NULL, g_free);

  g_assert (priv->self_handle != 0);
  if (cls->target_handle_type == TP_HANDLE_TYPE_ROOM)
    {
      /* We have to create a pseudo-IBB bytestream that will be
       * used by this MUC tube to communicate.
       */
      GabbleBytestreamMuc *bytestream;
      gchar *nick;

      g_assert (priv->stream_id != NULL);

      priv->dbus_name_to_handle = g_hash_table_new_full (g_str_hash,
         g_str_equal, NULL, NULL);

      g_assert (wocky_decode_jid (
          tp_handle_inspect (contact_repo, priv->self_handle),
          NULL, NULL, &nick));
      g_assert (nick != NULL);

      priv->dbus_local_name = _gabble_generate_dbus_unique_name (nick);

      DEBUG ("local name: %s", priv->dbus_local_name);

      bytestream = gabble_bytestream_factory_create_muc (
          conn->bytestream_factory,
          tp_base_channel_get_target_handle (base),
          priv->stream_id,
          GABBLE_BYTESTREAM_STATE_LOCAL_PENDING);

      g_object_set (self, "bytestream", bytestream, NULL);

      g_free (nick);

      g_assert (priv->muc != NULL);
      tp_external_group_mixin_init (obj, (GObject *) priv->muc);
    }
  else
    {
      /* The D-Bus names mapping is used in muc tubes only */
      priv->dbus_local_name = NULL;
      priv->dbus_name_to_handle = NULL;

      /* For contact (IBB) tubes we need to be able to reassemble messages. */
      priv->reassembly_buffer = g_string_new ("");
      priv->reassembly_bytes_needed = 0;

      g_assert (priv->muc == NULL);

      if (tp_base_channel_is_requested (base))
        {
          /* We created this outgoing 1-1 D-Bus tube and so will need SOCKS5
           * proxies when we'll offer it. */
          gabble_bytestream_factory_query_socks5_proxies (
              conn->bytestream_factory);
        }
    }

  /* Tube needs to be offered if we initiated AND requested it. Being
   * the initiator is not enough as we could re-join a muc containing and old
   * tube we created when we were in this room some time ago. In that case, we
   * have to accept it if we want to re-join the tube. */
  if (tp_base_channel_get_initiator (base) == priv->self_handle &&
      tp_base_channel_is_requested (base))
    {
      priv->offered = FALSE;
    }
  else
    {
      /* Incoming tubes have already been offered, as it were. */
      priv->offered = TRUE;
    }

  /* default access control is Credentials as that's the one used by the old
   * tube API */
  priv->access_control = TP_SOCKET_ACCESS_CONTROL_CREDENTIALS;

  /* Set SupportedAccessesControl */
  priv->supported_access_controls = g_array_sized_new (FALSE, FALSE,
      sizeof (guint), 1);
  access_control = TP_SOCKET_ACCESS_CONTROL_CREDENTIALS;
  g_array_append_val (priv->supported_access_controls, access_control);
  access_control = TP_SOCKET_ACCESS_CONTROL_LOCALHOST;
  g_array_append_val (priv->supported_access_controls, access_control);
}

static void
gabble_tube_dbus_fill_immutable_properties (TpBaseChannel *chan,
    GHashTable *properties)
{
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_CLASS (
      gabble_tube_dbus_parent_class);

  cls->fill_immutable_properties (chan, properties);

  tp_dbus_properties_mixin_fill_properties_hash (
      G_OBJECT (chan), properties,
      TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, "ServiceName",
      TP_IFACE_CHANNEL_TYPE_DBUS_TUBE, "SupportedAccessControls",
      NULL);

  if (!tp_base_channel_is_requested (chan))
    {
      tp_dbus_properties_mixin_fill_properties_hash (
          G_OBJECT (chan), properties,
          TP_IFACE_CHANNEL_INTERFACE_TUBE, "Parameters",
          NULL);
    }
}

static gchar *
gabble_tube_dbus_get_object_path_suffix (TpBaseChannel *base)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (base);

  return g_strdup_printf ("DBusTubeChannel/%u/%" G_GUINT64_FORMAT,
      tp_base_channel_get_target_handle (base),
      self->priv->id);
}

static void
gabble_tube_dbus_class_init (GabbleTubeDBusClass *gabble_tube_dbus_class)
{
  static TpDBusPropertiesMixinPropImpl dbus_tube_props[] = {
      { "ServiceName", "service", NULL },
      { "DBusNames", "dbus-names", NULL },
      { "SupportedAccessControls", "supported-access-controls", NULL },
      { NULL }
  };
  static TpDBusPropertiesMixinPropImpl tube_iface_props[] = {
      { "Parameters", "parameters", NULL },
      { "State", "state", NULL },
      { NULL }
  };
  static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
      { TP_IFACE_CHANNEL_TYPE_DBUS_TUBE,
        tp_dbus_properties_mixin_getter_gobject_properties,
        NULL,
        dbus_tube_props,
      },
      { TP_IFACE_CHANNEL_INTERFACE_TUBE,
        tp_dbus_properties_mixin_getter_gobject_properties,
        NULL,
        tube_iface_props,
      },
      { NULL }
  };
  GObjectClass *object_class = G_OBJECT_CLASS (gabble_tube_dbus_class);
  TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (gabble_tube_dbus_class);
  GParamSpec *param_spec;

  object_class->get_property = gabble_tube_dbus_get_property;
  object_class->set_property = gabble_tube_dbus_set_property;
  object_class->constructed = gabble_tube_dbus_constructed;
  object_class->dispose = gabble_tube_dbus_dispose;
  object_class->finalize = gabble_tube_dbus_finalize;

  base_class->channel_type = TP_IFACE_CHANNEL_TYPE_DBUS_TUBE;
  base_class->get_interfaces = gabble_tube_dbus_get_interfaces;
  base_class->target_handle_type = TP_HANDLE_TYPE_CONTACT;
  base_class->close = gabble_tube_dbus_close;
  base_class->fill_immutable_properties =
    gabble_tube_dbus_fill_immutable_properties;
  base_class->get_object_path_suffix =
    gabble_tube_dbus_get_object_path_suffix;

  g_type_class_add_private (gabble_tube_dbus_class,
      sizeof (GabbleTubeDBusPrivate));

  g_object_class_override_property (object_class, PROP_SELF_HANDLE,
    "self-handle");
  g_object_class_override_property (object_class, PROP_ID,
    "id");
  g_object_class_override_property (object_class, PROP_TYPE,
    "type");
  g_object_class_override_property (object_class, PROP_SERVICE,
    "service");
  g_object_class_override_property (object_class, PROP_PARAMETERS,
    "parameters");
  g_object_class_override_property (object_class, PROP_STATE,
    "state");

  param_spec = g_param_spec_object (
      "bytestream",
      "Object implementing the GabbleBytestreamIface interface",
      "Bytestream object used for streaming data for this"
      "tube object.",
      G_TYPE_OBJECT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_BYTESTREAM,
      param_spec);

  param_spec = g_param_spec_string (
      "stream-id",
      "stream id",
      "The identifier of this tube's bytestream",
      "",
      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_STREAM_ID,
      param_spec);

  param_spec = g_param_spec_string (
      "dbus-address",
      "D-Bus address",
      "The D-Bus address on which this tube will listen for connections",
      "",
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_DBUS_ADDRESS,
      param_spec);

  param_spec = g_param_spec_string (
      "dbus-name",
      "D-Bus name",
      "The local D-Bus name on the virtual bus (used for muc tubes only).",
      "",
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_DBUS_NAME, param_spec);

  param_spec = g_param_spec_boxed (
      "dbus-names",
      "D-Bus names",
      "Mapping of contact handles to D-Bus names (used for muc tubes only).",
      TP_HASH_TYPE_DBUS_TUBE_PARTICIPANTS,
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class, PROP_DBUS_NAMES, param_spec);

  param_spec = g_param_spec_object ("muc", "GabbleMucChannel object",
      "Gabble text MUC channel corresponding to this Tube channel object, "
      "if the handle type is ROOM.",
      GABBLE_TYPE_MUC_CHANNEL,
      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_property (object_class, PROP_MUC, param_spec);

  param_spec = g_param_spec_boxed (
      "supported-access-controls",
      "Supported access-controls",
      "GArray containing supported access controls.",
      DBUS_TYPE_G_UINT_ARRAY,
      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (object_class,
      PROP_SUPPORTED_ACCESS_CONTROLS, param_spec);

  signals[OPENED] =
    g_signal_new ("tube-opened",
                  G_OBJECT_CLASS_TYPE (gabble_tube_dbus_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  signals[CLOSED] =
    g_signal_new ("tube-closed",
                  G_OBJECT_CLASS_TYPE (gabble_tube_dbus_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  signals[OFFERED] =
    g_signal_new ("tube-offered",
                  G_OBJECT_CLASS_TYPE (gabble_tube_dbus_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0,
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  gabble_tube_dbus_class->dbus_props_class.interfaces = prop_interfaces;
  tp_dbus_properties_mixin_class_init (object_class,
      G_STRUCT_OFFSET (GabbleTubeDBusClass, dbus_props_class));

  tp_external_group_mixin_init_dbus_properties (object_class);
}

static void
bytestream_negotiate_cb (GabbleBytestreamIface *bytestream,
                         WockyStanza *msg,
                         GObject *object,
                         gpointer user_data)
{
  GabbleTubeIface *tube = user_data;

  if (bytestream == NULL)
    {
      /* Tube was declined by remote user. Close it */
      gabble_tube_iface_close (tube, TRUE);
      return;
    }

  /* Tube was accepted by remote user */

  g_object_set (tube,
      "bytestream", bytestream,
      NULL);

  gabble_tube_iface_accept (tube, NULL);
}

gboolean
gabble_tube_dbus_offer (GabbleTubeDBus *tube,
                        GError **error)
{
  GabbleTubeDBusPrivate *priv = tube->priv;
  TpBaseChannel *base = TP_BASE_CHANNEL (tube);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);
  TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
  GabbleConnection *conn = GABBLE_CONNECTION (base_conn);

  if (priv->offered)
    {
      g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
          "Tube has already been offered");
      return FALSE;
    }

  if (cls->target_handle_type == TP_HANDLE_TYPE_CONTACT)
    {
      TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
          base_conn, TP_HANDLE_TYPE_CONTACT);
      const gchar *jid, *resource;
      gchar *full_jid;
      GabblePresence *presence;
      WockyNode *tube_node, *si_node;
      WockyStanza *msg;

      jid = tp_handle_inspect (contact_repo,
          tp_base_channel_get_target_handle (base));
      presence = gabble_presence_cache_get (conn->presence_cache,
          tp_base_channel_get_target_handle (base));

      if (presence == NULL)
        {
          DEBUG ("can't find contact %s's presence", jid);
          g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
              "can't find contact %s's presence", jid);
          return FALSE;
        }

      resource = gabble_presence_pick_resource_by_caps (presence, 0,
          gabble_capability_set_predicate_has, NS_TUBES);

      if (resource == NULL)
        {
          DEBUG ("contact %s doesn't have tubes capabilities", jid);
          g_set_error (error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
              "contact %s doesn't have tubes capabilities", jid);
          return FALSE;
        }

      full_jid = g_strdup_printf ("%s/%s", jid, resource);
      msg = gabble_bytestream_factory_make_stream_init_iq (full_jid,
          priv->stream_id, NS_TUBES);
      si_node = wocky_node_get_child_ns (
          wocky_stanza_get_top_node (msg), "si", NS_SI);
      g_assert (si_node != NULL);

      tube_node = wocky_node_add_child_ns (si_node, "tube", NS_TUBES);
      gabble_tube_iface_publish_in_node (GABBLE_TUBE_IFACE (tube),
          base_conn, tube_node);

      tube->priv->offered = TRUE;
      gabble_bytestream_factory_negotiate_stream (
          conn->bytestream_factory, msg, priv->stream_id,
          bytestream_negotiate_cb, tube, G_OBJECT (tube));

      /* We don't create the bytestream of private D-Bus tube yet.
       * It will be when we'll receive the answer of the SI request */
      g_object_unref (msg);
      g_free (full_jid);

      tp_svc_channel_interface_tube_emit_tube_channel_state_changed (tube,
          TP_TUBE_CHANNEL_STATE_REMOTE_PENDING);
    }
  else
    {
      tube->priv->offered = TRUE;
      g_object_set (priv->bytestream,
          "state", GABBLE_BYTESTREAM_STATE_OPEN,
          NULL);

      gabble_muc_channel_send_presence (priv->muc);
    }

  if (!create_dbus_server (tube, error))
    return FALSE;

  g_signal_emit (G_OBJECT (tube), signals[OFFERED], 0);

  return TRUE;
}

static void
message_received (GabbleTubeDBus *tube,
                  TpHandle sender,
                  const char *data,
                  size_t len)
{
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (tube);
  TpBaseChannel *base = TP_BASE_CHANNEL (tube);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);
  DBusMessage *msg;
  DBusError error = {0,};
  const gchar *sender_name;
  const gchar *destination;
  guint32 serial;

  msg = dbus_message_demarshal (data, len, &error);

  if (msg == NULL)
    {
      /* message was corrupted */
      DEBUG ("received corrupted message from %d: %s: %s", sender,
          error.name, error.message);
      dbus_error_free (&error);
      return;
    }

  if (cls->target_handle_type == TP_HANDLE_TYPE_ROOM)
    {
      destination = dbus_message_get_destination (msg);
      /* If destination is NULL this msg is broadcasted (signals) so we don't
       * have to check it */
      if (destination != NULL && tp_strdiff (priv->dbus_local_name,
            destination))
        {
          /* This message is not intended for this participant.
           * Discard it. */
          DEBUG ("message not intended for this participant (destination = "
              "%s)", destination);
          goto unref;
        }

      sender_name = g_hash_table_lookup (priv->dbus_names,
          GUINT_TO_POINTER (sender));

      if (tp_strdiff (sender_name, dbus_message_get_sender (msg)))
        {
          DEBUG ("invalid sender %s (expected %s for sender handle %d)",
                 dbus_message_get_sender (msg), sender_name, sender);
          goto unref;
        }
    }

  if (!priv->dbus_conn)
    {
      DEBUG ("no D-Bus connection: queuing the message");

      /* If the application never connects to the private dbus connection, we
       * don't want to eat all the memory. Only queue MAX_QUEUE_SIZE bytes. If
       * there are more messages, drop them. */
      if (priv->dbus_msg_queue_size + len > MAX_QUEUE_SIZE)
        {
          DEBUG ("D-Bus message queue size limit reached (%u bytes). "
                 "Ignore this message.",
                 MAX_QUEUE_SIZE);
          goto unref;
        }

      priv->dbus_msg_queue = g_slist_prepend (priv->dbus_msg_queue, msg);
      priv->dbus_msg_queue_size += len;

      /* returns without unref the message */
      return;
    }

  DEBUG ("delivering message from '%s' to '%s'",
         dbus_message_get_sender (msg),
         dbus_message_get_destination (msg));

  /* XXX: what do do if this returns FALSE? */
  dbus_connection_send (priv->dbus_conn, msg, &serial);

unref:
  dbus_message_unref (msg);
}

static guint32
collect_le32 (char *str)
{
  unsigned char *bytes = (unsigned char *) str;

  return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}

static guint32
collect_be32 (char *str)
{
  unsigned char *bytes = (unsigned char *) str;

  return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 24) | bytes[3];
}

static void
data_received_cb (GabbleBytestreamIface *stream,
                  TpHandle sender,
                  GString *data,
                  gpointer user_data)
{
  GabbleTubeDBus *tube = GABBLE_TUBE_DBUS (user_data);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (tube);
  TpBaseChannel *base = TP_BASE_CHANNEL (tube);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);

  if (cls->target_handle_type == TP_HANDLE_TYPE_CONTACT)
    {
      GString *buf = priv->reassembly_buffer;

      g_assert (buf != NULL);

      g_string_append_len (buf, data->str, data->len);
      DEBUG ("Received %" G_GSIZE_FORMAT " bytes, so we now have %"
          G_GSIZE_FORMAT " bytes in reassembly buffer", data->len, buf->len);

      /* Each D-Bus message has a 16-byte fixed header, in which
       *
       * * byte 0 is 'l' (ell) or 'B' for endianness
       * * bytes 4-7 are body length "n" in bytes in that endianness
       * * bytes 12-15 are length "m" of param array in bytes in that
       *   endianness
       *
       * followed by m + n + ((8 - (m % 8)) % 8) bytes of other content.
       */

      while (buf->len >= 16)
        {
          guint32 body_length, params_length, m;

          /* see if we have a whole message and have already calculated
           * how many bytes it needs */

          if (priv->reassembly_bytes_needed != 0)
            {
              if (buf->len >= priv->reassembly_bytes_needed)
                {
                  DEBUG ("Received complete D-Bus message of size %"
                      G_GINT32_FORMAT, priv->reassembly_bytes_needed);
                  message_received (tube, sender, buf->str,
                      priv->reassembly_bytes_needed);
                  g_string_erase (buf, 0, priv->reassembly_bytes_needed);
                  priv->reassembly_bytes_needed = 0;
                }
              else
                {
                  /* we'll have to wait for more data */
                  break;
                }
            }

          if (buf->len < 16)
            break;

          /* work out how big the next message is going to be */

          if (buf->str[0] == DBUS_BIG_ENDIAN)
            {
              body_length = collect_be32 (buf->str + 4);
              m = collect_be32 (buf->str + 12);
            }
          else if (buf->str[0] == DBUS_LITTLE_ENDIAN)
            {
              body_length = collect_le32 (buf->str + 4);
              m = collect_le32 (buf->str + 12);
            }
          else
            {
              DEBUG ("D-Bus message has unknown endianness byte 0x%x, "
                  "closing tube", (unsigned int) buf->str[0]);
              gabble_tube_iface_close ((GabbleTubeIface *) tube, TRUE);
              return;
            }

          /* pad to 8-byte boundary */
          params_length = m + ((8 - (m % 8)) % 8);
          g_assert (params_length % 8 == 0);
          g_assert (params_length >= m);
          g_assert (params_length < m + 8);

          priv->reassembly_bytes_needed = params_length + body_length + 16;

          /* n.b.: this looks as if it could be simplified to just the third
           * test, but that would be wrong if the addition had overflowed, so
           * don't do that. The first and second tests are sufficient to
           * ensure no overflow on 32-bit platforms */
          if (body_length > DBUS_MAXIMUM_MESSAGE_LENGTH ||
              params_length > DBUS_MAXIMUM_ARRAY_LENGTH ||
              priv->reassembly_bytes_needed > DBUS_MAXIMUM_MESSAGE_LENGTH)
            {
              DEBUG ("D-Bus message is too large to be valid, closing tube");
              gabble_tube_iface_close ((GabbleTubeIface *) tube, TRUE);
              return;
            }

          g_assert (priv->reassembly_bytes_needed != 0);
          DEBUG ("We need %" G_GINT32_FORMAT " bytes for the next full "
              "message", priv->reassembly_bytes_needed);
        }
    }
  else
    {
      /* MUC bytestreams are message-boundary preserving, which is necessary,
       * because we can't assume we started at the beginning */
      g_assert (GABBLE_IS_BYTESTREAM_MUC (priv->bytestream));
      message_received (tube, sender, data->str, data->len);
    }
}

GabbleTubeDBus *
gabble_tube_dbus_new (GabbleConnection *conn,
                      TpHandle handle,
                      TpHandleType handle_type,
                      TpHandle self_handle,
                      TpHandle initiator,
                      const gchar *service,
                      GHashTable *parameters,
                      const gchar *stream_id,
                      guint64 id,
                      GabbleBytestreamIface *bytestream,
                      GabbleMucChannel *muc,
                      gboolean requested)
{
  GabbleTubeDBus *tube;
  GType gtype = GABBLE_TYPE_TUBE_DBUS;

  if (handle_type == TP_HANDLE_TYPE_ROOM)
    gtype = GABBLE_TYPE_MUC_TUBE_DBUS;

  tube = g_object_new (gtype,
      "connection", conn,
      "handle", handle,
      "self-handle", self_handle,
      "initiator-handle", initiator,
      "service", service,
      "parameters", parameters,
      "stream-id", stream_id,
      "id", id,
      "muc", muc,
      "requested", requested,
      NULL);

  if (bytestream != NULL)
    g_object_set (tube, "bytestream", bytestream, NULL);

  return tube;
}

static void
augment_si_accept_iq (WockyNode *si,
                      gpointer user_data)
{
  wocky_node_add_child_ns (si, "tube", NS_TUBES);
}

/*
 * gabble_tube_dbus_accept
 *
 * Implements gabble_tube_iface_accept on GabbleTubeIface
 */
static gboolean
gabble_tube_dbus_accept (GabbleTubeIface *tube,
                         GError **error)
{
  GabbleTubeDBus *self = GABBLE_TUBE_DBUS (tube);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  TpBaseChannel *base = TP_BASE_CHANNEL (tube);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);
  GabbleBytestreamState state;

  g_assert (priv->bytestream != NULL);

  g_object_get (priv->bytestream,
      "state", &state,
      NULL);

  if (state != GABBLE_BYTESTREAM_STATE_LOCAL_PENDING)
    return TRUE;

  if (cls->target_handle_type == TP_HANDLE_TYPE_CONTACT)
    {
      /* Bytestream was created using a SI request so
       * we have to accept it */

      DEBUG ("accept the SI request");

      gabble_bytestream_iface_accept (priv->bytestream, augment_si_accept_iq,
          self);
    }
  else
    {
      /* No SI so the bytestream is open */
      DEBUG ("no SI, bytestream open");
      g_object_set (priv->bytestream,
          "state", GABBLE_BYTESTREAM_STATE_OPEN,
          NULL);
    }

  if (!create_dbus_server (self, error))
    return FALSE;

  return TRUE;
}

/*
 * gabble_tube_iface_dbus_close
 *
 * Implements gabble_tube_iface_close on GabbleTubeIface
 */
static void
gabble_tube_iface_dbus_close (GabbleTubeIface *tube,
    gboolean closed_remotely)
{
  tp_base_channel_close (TP_BASE_CHANNEL (tube));
}

/**
 * gabble_tube_dbus_add_bytestream
 *
 * Implements gabble_tube_iface_add_bytestream on GabbleTubeIface
 */
static void
gabble_tube_dbus_add_bytestream (GabbleTubeIface *tube,
                                 GabbleBytestreamIface *bytestream)
{
  /* FIXME: should we support this, if we don't have a bytestream yet? */
  DEBUG ("D-Bus doesn't support extra bytestream");
  gabble_bytestream_iface_close (bytestream, NULL);
}

gboolean
gabble_tube_dbus_add_name (GabbleTubeDBus *self,
                           TpHandle handle,
                           const gchar *name)
{
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  TpBaseChannel *base = TP_BASE_CHANNEL (self);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);
  TpBaseConnection *base_conn = tp_base_channel_get_connection (base);
  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
      base_conn, TP_HANDLE_TYPE_CONTACT);
  gchar *name_copy;
  GHashTable *added;
  GArray *removed;

  g_assert (cls->target_handle_type == TP_HANDLE_TYPE_ROOM);
  g_assert (g_hash_table_size (priv->dbus_names) ==
      g_hash_table_size (priv->dbus_name_to_handle));

  if (g_hash_table_lookup (priv->dbus_names, GUINT_TO_POINTER (handle))
      != NULL)
    {
      DEBUG ("contact %d has already announced his D-Bus name", handle);
      return FALSE;
    }

  if (g_hash_table_lookup (priv->dbus_name_to_handle, name) != NULL)
    {
      DEBUG ("D-Bus name %s already used", name);
      return FALSE;
    }

  if (g_str_has_prefix (name, ":2."))
    {
      gchar *nick, *supposed_name;
      const gchar *jid;

      jid = tp_handle_inspect (contact_repo, handle);
      g_assert (wocky_decode_jid (jid, NULL, NULL, &nick));
      supposed_name = _gabble_generate_dbus_unique_name (nick);
      g_free (nick);

      if (tp_strdiff (name, supposed_name))
        {
          DEBUG ("contact %s announces %s as D-Bus name but it should be %s",
              jid, name, supposed_name);
          g_free (supposed_name);
          return FALSE;
        }
      g_free (supposed_name);
    }

  name_copy = g_strdup (name);
  g_hash_table_insert (priv->dbus_names, GUINT_TO_POINTER (handle),
      name_copy);

  g_hash_table_insert (priv->dbus_name_to_handle, name_copy,
      GUINT_TO_POINTER (handle));

  /* Fire DBusNamesChanged (new API) */
  added = g_hash_table_new (g_direct_hash, g_direct_equal);
  removed = g_array_new (FALSE, FALSE, sizeof (TpHandle));

  g_hash_table_insert (added, GUINT_TO_POINTER (handle), (gchar *) name);

  tp_svc_channel_type_dbus_tube_emit_dbus_names_changed (self, added,
      removed);

  g_hash_table_unref (added);
  g_array_unref (removed);

  return TRUE;
}

gboolean
gabble_tube_dbus_remove_name (GabbleTubeDBus *self,
                              TpHandle handle)
{
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  TpBaseChannel *base = TP_BASE_CHANNEL (self);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (base);
  const gchar *name;
  GHashTable *added;
  GArray *removed;

  g_assert (cls->target_handle_type == TP_HANDLE_TYPE_ROOM);

  name = g_hash_table_lookup (priv->dbus_names, GUINT_TO_POINTER (handle));
  if (name == NULL)
    return FALSE;

  g_hash_table_remove (priv->dbus_name_to_handle, name);
  g_hash_table_remove (priv->dbus_names, GUINT_TO_POINTER (handle));

  g_assert (g_hash_table_size (priv->dbus_names) ==
      g_hash_table_size (priv->dbus_name_to_handle));

  /* Fire DBusNamesChanged (new API) */
  added = g_hash_table_new (g_direct_hash, g_direct_equal);
  removed = g_array_new (FALSE, FALSE, sizeof (TpHandle));

  g_array_append_val (removed, handle);

  tp_svc_channel_type_dbus_tube_emit_dbus_names_changed (self, added,
      removed);

  g_hash_table_unref (added);
  g_array_unref (removed);
  return TRUE;
}

gboolean
gabble_tube_dbus_handle_in_names (GabbleTubeDBus *self,
                                  TpHandle handle)
{
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (self);
  TpBaseChannelClass *cls = TP_BASE_CHANNEL_GET_CLASS (self);

  g_assert (cls->target_handle_type == TP_HANDLE_TYPE_ROOM);

  return (g_hash_table_lookup (priv->dbus_names, GUINT_TO_POINTER (handle))
      != NULL);
}

gchar *
_gabble_generate_dbus_unique_name (const gchar *nick)
{
  gchar *encoded, *result;
  size_t len;
  guint i;

  len = strlen (nick);

  if (len <= 186)
    {
      encoded = g_base64_encode ((const guchar *) nick, len);
    }
  else
    {
      guchar sha1[20];
      GString *tmp;

      sha1_bin (nick, len, sha1);
      tmp = g_string_sized_new (169 + 20);

      g_string_append_len (tmp, nick, 169);
      g_string_append_len (tmp, (const gchar *) sha1, 20);

      encoded = g_base64_encode ((const guchar *) tmp->str, tmp->len);

      g_string_free (tmp, TRUE);
    }

  for (i = 0; encoded[i] != '\0'; i++)
    {
      switch (encoded[i])
        {
          case '+':
            encoded[i] = '_';
            break;
          case '/':
            encoded[i] = '-';
            break;
          case '=':
            encoded[i] = 'A';
            break;
        }
    }

  result = g_strdup_printf (":2.%s", encoded);

  g_free (encoded);
  return result;
}

const gchar * const *
gabble_tube_dbus_channel_get_allowed_properties (void)
{
  return gabble_tube_dbus_channel_allowed_properties;
}

static gboolean
gabble_tube_dbus_check_access_control (GabbleTubeDBus *self,
    guint access_control,
    GError **error)
{
  switch (access_control)
    {
      case TP_SOCKET_ACCESS_CONTROL_CREDENTIALS:
      case TP_SOCKET_ACCESS_CONTROL_LOCALHOST:
        break;

      default:
        g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
            "%u socket access control is not supported", access_control);
        return FALSE;
    }

  return TRUE;
}

/**
 * gabble_tube_dbus_offer_async
 *
 * Implemnets D-Bus method Offer on interface
 * org.freedesktop.Telepathy.Channel.Type.DBusTube
 */
static void
gabble_tube_dbus_offer_async (TpSvcChannelTypeDBusTube *self,
    GHashTable *parameters,
    guint access_control,
    DBusGMethodInvocation *context)
{
  GabbleTubeDBus *tube = GABBLE_TUBE_DBUS (self);
  GabbleTubeDBusPrivate *priv = GABBLE_TUBE_DBUS_GET_PRIVATE (tube);
  GError *error = NULL;

  if (!gabble_tube_dbus_check_access_control (tube, access_control, &error))
    {
      dbus_g_method_return_error (context, error);
      g_error_free (error);
      return;
    }

  priv->access_control = access_control;

  g_object_set (self, "parameters", parameters, NULL);

  if (gabble_tube_dbus_offer (tube, &error))
    {
      tp_svc_channel_type_dbus_tube_return_from_offer (context,
          tube->priv->dbus_srv_addr);
    }
  else
    {
      g_assert (error != NULL);
      dbus_g_method_return_error (context, error);
      g_error_free (error);
    }
}

/**
 * gabble_tube_dbus_accept_async
 *
 * Implements D-Bus method Accept on interface
 * org.freedesktop.Telepathy.Channel.Type.DBusTube
 */
static void
gabble_tube_dbus_accept_async (TpSvcChannelTypeDBusTube *self,
    guint access_control,
    DBusGMethodInvocation *context)
{
  GabbleTubeDBus *tube = GABBLE_TUBE_DBUS (self);
  GError *error = NULL;

  if (!gabble_tube_dbus_check_access_control (tube, access_control, &error))
    {
      dbus_g_method_return_error (context, error);
      g_error_free (error);
      return;
    }

  if (gabble_tube_dbus_accept (GABBLE_TUBE_IFACE (tube), &error))
    {
      tp_svc_channel_type_dbus_tube_return_from_accept (context,
          tube->priv->dbus_srv_addr);
      ;
    }
  else
    {
      g_assert (error != NULL);
      dbus_g_method_return_error (context, error);
      g_error_free (error);
    }
}

static void
tube_iface_init (gpointer g_iface,
                 gpointer iface_data)
{
  GabbleTubeIfaceClass *klass = (GabbleTubeIfaceClass *) g_iface;

  klass->accept = gabble_tube_dbus_accept;
  klass->close = gabble_tube_iface_dbus_close;
  klass->add_bytestream = gabble_tube_dbus_add_bytestream;
}

static void
dbustube_iface_init (gpointer g_iface,
                     gpointer iface_data)
{
  TpSvcChannelTypeDBusTubeClass *klass =
      (TpSvcChannelTypeDBusTubeClass *) g_iface;

#define IMPLEMENT(x, suffix) tp_svc_channel_type_dbus_tube_implement_##x (\
    klass, gabble_tube_dbus_##x##suffix)
  IMPLEMENT(offer,_async);
  IMPLEMENT(accept,_async);
#undef IMPLEMENT
}
