[Telepathy-commits] [telepathy-gabble/master] Continuing work on new jingle engine.

Senko Rasic senko at phyrexia.lan
Tue Dec 2 04:33:54 PST 2008


---
 src/Makefile.am                            |   10 +-
 src/connection.c                           |    7 +
 src/connection.h                           |    4 +
 src/jingle-content.c                       |  297 +++++++++++++------
 src/jingle-content.h                       |   14 +
 src/jingle-factory.c                       |   84 ++++--
 src/jingle-factory.h                       |   16 +-
 src/jingle-media-rtp.c                     |  160 ++++-------
 src/jingle-media-rtp.h                     |    6 +-
 src/jingle-session.c                       |  290 ++++++++++++++-----
 src/jingle-session.h                       |   13 +
 src/jingle-transport-google.c              |  117 ++++----
 src/jingle-transport-iface.c               |   37 ++-
 src/jingle-transport-iface.h               |   12 +-
 src/media-channel.c                        |  343 ++++++++++++++++++++--
 src/media-factory.c                        |  440 ++--------------------------
 src/media-stream.c                         |   66 ++++-
 src/namespaces.h                           |    7 +
 src/types.h                                |    8 +
 tests/twisted/jingle/jingletest.py         |    3 +-
 tests/twisted/jingle/test-incoming-call.py |   29 +-
 21 files changed, 1086 insertions(+), 877 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index da0b4bd..eca6b00 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -108,7 +108,15 @@ libgabble_convenience_la_our_sources = \
     util.h \
     util.c \
     vcard-manager.h \
-    vcard-manager.c
+    vcard-manager.c \
+    jingle-content.c \
+    jingle-description-iface.c \
+    jingle-factory.c \
+    jingle-media-rtp.c \
+    jingle-session.c \
+    jingle-transport-google.c \
+    jingle-transport-iface.c
+
 
 # we don't want to subject third-party source to whitespace removal -
 # it's more useful to keep it a verbatim copy
diff --git a/src/connection.c b/src/connection.c
index 01c9a8b..28bd1c3 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -69,6 +69,7 @@
 #include "private-tubes-factory.h"
 #include "util.h"
 #include "vcard-manager.h"
+#include "jingle-factory.h"
 
 static guint disco_reply_timeout = 5000;
 
@@ -220,6 +221,9 @@ _gabble_connection_create_channel_factories (TpBaseConnection *conn)
   self->private_tubes_factory = gabble_private_tubes_factory_new (self);
   g_ptr_array_add (self->channel_managers, self->private_tubes_factory);
 
+  self->jingle_factory = g_object_new (GABBLE_TYPE_JINGLE_FACTORY,
+    "connection", self, NULL);
+
   g_ptr_array_add (self->channel_managers,
       g_object_new (GABBLE_TYPE_MEDIA_FACTORY,
         "connection", self,
@@ -735,6 +739,9 @@ gabble_connection_dispose (GObject *object)
   g_object_unref (self->vcard_manager);
   self->vcard_manager = NULL;
 
+  g_object_unref (self->jingle_factory);
+  self->jingle_factory = NULL;
+
   /* remove borrowed references before TpBaseConnection unrefs the channel
    * factories */
   self->roster = NULL;
diff --git a/src/connection.h b/src/connection.h
index c954fda..1bcc24a 100644
--- a/src/connection.h
+++ b/src/connection.h
@@ -32,6 +32,7 @@
 #include "types.h"
 #include "error.h"
 #include "muc-factory.h"
+#include "jingle-factory.h"
 
 G_BEGIN_DECLS
 
@@ -147,6 +148,9 @@ struct _GabbleConnection {
     /* outstanding avatar requests */
     GHashTable *avatar_requests;
 
+    /* jingle factory */
+    GabbleJingleFactory *jingle_factory;
+
     /* temporary, for requestotron support */
     GPtrArray *channel_factories;
     GPtrArray *channel_managers;
diff --git a/src/jingle-content.c b/src/jingle-content.c
index 8d6decd..8d23d70 100644
--- a/src/jingle-content.c
+++ b/src/jingle-content.c
@@ -36,22 +36,22 @@
 #include "jingle-session.h"
 #include "jingle-transport-iface.h"
 
-G_DEFINE_TYPE(GabbleJingleContent, gabble_jingle_content, G_TYPE_OBJECT);
-
 /* signal enum */
 enum
 {
+  READY,
   LAST_SIGNAL
 };
 
-// FIXME static guint signals[LAST_SIGNAL] = {0};
+static guint signals[LAST_SIGNAL] = {0};
 
 /* properties */
 enum
 {
   PROP_CONNECTION = 1,
-  PROP_FACTORY,
   PROP_SESSION,
+  PROP_CONTENT_NS,
+  PROP_TRANSPORT_NS,
   PROP_NAME,
   PROP_SENDERS,
   PROP_STATE,
@@ -61,18 +61,17 @@ enum
 typedef struct _GabbleJingleContentPrivate GabbleJingleContentPrivate;
 struct _GabbleJingleContentPrivate
 {
-  GabbleConnection *conn;
-  GabbleJingleFactory *factory;
-  GabbleJingleSession *session;
-
   gchar *name;
   gchar *creator;
   gboolean created_by_initiator;
   JingleContentState state;
   JingleContentSenders senders;
 
-  GabbleJingleDescriptionIface *description;
+  gchar *content_ns;
+  gchar *transport_ns;
+
   GabbleJingleTransportIface *transport;
+  gboolean has_local_codecs;
 
   gboolean dispose_has_run;
 };
@@ -89,6 +88,8 @@ static const gchar *content_senders_table[] = {
   NULL
 };
 
+G_DEFINE_TYPE(GabbleJingleContent, gabble_jingle_content, G_TYPE_OBJECT);
+
 static void
 gabble_jingle_content_init (GabbleJingleContent *obj)
 {
@@ -100,13 +101,16 @@ gabble_jingle_content_init (GabbleJingleContent *obj)
   priv->state = JINGLE_CONTENT_STATE_EMPTY;
   priv->created_by_initiator = TRUE;
   priv->dispose_has_run = FALSE;
+
+  obj->conn = NULL;
+  obj->session = NULL;
 }
 
 static void
 gabble_jingle_content_dispose (GObject *object)
 {
-  GabbleJingleContent *sess = GABBLE_JINGLE_CONTENT (object);
-  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (sess);
+  GabbleJingleContent *content = GABBLE_JINGLE_CONTENT (object);
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (content);
 
   if (priv->dispose_has_run)
     return;
@@ -114,20 +118,18 @@ gabble_jingle_content_dispose (GObject *object)
   DEBUG ("dispose called");
   priv->dispose_has_run = TRUE;
 
-  if (priv->description)
-      g_object_unref (priv->description);
-  priv->description = NULL;
-
-  if (priv->transport)
-      g_object_unref (priv->transport);
-  priv->transport = NULL;
-
   g_free (priv->name);
   priv->name = NULL;
 
   g_free (priv->creator);
   priv->creator = NULL;
 
+  g_free (priv->content_ns);
+  priv->content_ns = NULL;
+
+  g_free (priv->transport_ns);
+  priv->transport_ns = NULL;
+
   if (G_OBJECT_CLASS (gabble_jingle_content_parent_class)->dispose)
     G_OBJECT_CLASS (gabble_jingle_content_parent_class)->dispose (object);
 }
@@ -143,13 +145,10 @@ gabble_jingle_content_get_property (GObject *object,
 
   switch (property_id) {
     case PROP_CONNECTION:
-      g_value_set_object (value, priv->conn);
-      break;
-    case PROP_FACTORY:
-      g_value_set_object (value, priv->factory);
+      g_value_set_object (value, self->conn);
       break;
     case PROP_SESSION:
-      g_value_set_object (value, priv->session);
+      g_value_set_object (value, self->session);
       break;
     case PROP_NAME:
       g_value_set_string (value, priv->name);
@@ -177,13 +176,19 @@ gabble_jingle_content_set_property (GObject *object,
 
   switch (property_id) {
     case PROP_CONNECTION:
-      priv->conn = g_value_get_object (value);
-      break;
-    case PROP_FACTORY:
-      priv->factory = g_value_get_object (value);
+      self->conn = g_value_get_object (value);
+      DEBUG ("setting self->conn to %p", self->conn);
       break;
     case PROP_SESSION:
-      priv->factory = g_value_get_object (value);
+      self->session = g_value_get_object (value);
+      break;
+    case PROP_CONTENT_NS:
+      g_free (priv->content_ns);
+      priv->content_ns = g_value_dup_string (value);
+      break;
+    case PROP_TRANSPORT_NS:
+      g_free (priv->transport_ns);
+      priv->transport_ns = g_value_dup_string (value);
       break;
     case PROP_SENDERS:
       priv->senders = g_value_get_uint (value);
@@ -200,8 +205,8 @@ gabble_jingle_content_set_property (GObject *object,
 static void
 gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (cls);
   GParamSpec *param_spec;
+  GObjectClass *object_class = G_OBJECT_CLASS (cls);
 
   g_type_class_add_private (cls, sizeof (GabbleJingleContentPrivate));
 
@@ -220,16 +225,6 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
                                     G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
 
-  param_spec = g_param_spec_object ("factory", "GabbleJingleFactory object",
-                                    "Jingle factory object that has transport "
-                                    "and description namespace handlers.",
-                                    GABBLE_TYPE_JINGLE_FACTORY,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
   param_spec = g_param_spec_object ("session", "GabbleJingleSession object",
                                     "Jingle session object that owns this content.",
                                     GABBLE_TYPE_JINGLE_SESSION,
@@ -237,7 +232,7 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NICK |
                                     G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+  g_object_class_install_property (object_class, PROP_SESSION, param_spec);
 
   param_spec = g_param_spec_string ("name", "Content name",
                                     "A unique content name in the session.",
@@ -247,6 +242,25 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
                                     G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_NAME, param_spec);
 
+  param_spec = g_param_spec_string ("content-ns", "Content namespace",
+                                    "Namespace identifying the content type.",
+                                    NULL,
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_STATIC_NAME |
+                                    G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_CONTENT_NS, param_spec);
+
+  param_spec = g_param_spec_string ("transport-ns", "Transport namespace",
+                                    "Namespace identifying the transport type.",
+                                    NULL,
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_STATIC_NAME |
+                                    G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_TRANSPORT_NS, param_spec);
+
+
 
   param_spec = g_param_spec_uint ("senders", "Stream senders",
                                   "Valid senders for the stream.",
@@ -265,22 +279,40 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
   g_object_class_install_property (object_class, PROP_STATE, param_spec);
 
   /* signal definitions */
+
+  signals[READY] =
+    g_signal_new ("ready",
+                  G_OBJECT_CLASS_TYPE (cls),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
 }
 
 #define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
 #define SET_OUT_ORDER(txt) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER, txt)
 #define SET_CONFLICT(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT, txt)
 
+static void
+parse_description (GabbleJingleContent *c, LmMessageNode *desc_node,
+    GError **error)
+{
+  void (*virtual_method)(GabbleJingleContent *, LmMessageNode *,
+      GError **) = GABBLE_JINGLE_CONTENT_GET_CLASS (c)->parse_description;
+
+  g_assert (virtual_method != NULL);
+  virtual_method (c, desc_node, error);
+}
+
 void
 gabble_jingle_content_parse_add (GabbleJingleContent *c,
     LmMessageNode *content_node, gboolean google_mode, GError **error)
 {
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (c);
-  const gchar *name, *creator, *senders, *xmlns;
-  LmMessageNode *desc_node, *trans_node;
-  JingleDescriptionMaker dmaker;
-  GabbleJingleDescriptionIface *desc = NULL;
-  JingleTransportMaker tmaker;
+  const gchar *name, *creator, *senders;
+  LmMessageNode *trans_node, *desc_node;
+  GType transport_type = 0;
   GabbleJingleTransportIface *trans = NULL;
 
   desc_node = lm_message_node_get_child (content_node, "description");
@@ -289,80 +321,82 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
   name = lm_message_node_get_attribute (content_node, "name");
   senders = lm_message_node_get_attribute (content_node, "senders");
 
-  if (desc_node == NULL)
+  g_assert (priv->transport_ns == NULL);
+
+  if (google_mode)
     {
-      SET_BAD_REQ ("content description is missing");
-      return;
+      DEBUG ("content in google mode!");
+      if (creator == NULL)
+          creator = "initiator";
+
+      if (name == NULL)
+          name = "gtalk";
+
+      if (senders == NULL)
+          senders = "both";
+
+      if (trans_node == NULL)
+        {
+          /* gtalk lj0.3 assumes google-p2p transport */
+          g_object_set (c->session, "dialect", JINGLE_DIALECT_GTALK3, NULL);
+          transport_type = GPOINTER_TO_INT (
+              g_hash_table_lookup (c->conn->jingle_factory->transports, ""));
+        }
     }
 
-  xmlns = lm_message_node_get_attribute (desc_node, "xmlns");
-  dmaker = g_hash_table_lookup (priv->factory->descriptions, xmlns);
 
-  if (dmaker == NULL)
+  if ((trans_node == NULL) || (creator == NULL) || (name == NULL))
     {
-      SET_BAD_REQ ("unsupported content description");
+      SET_BAD_REQ ("missing required content attributes or elements");
       return;
     }
 
-  if (!google_mode)
+  /* if we didn't set it to google-p2p implicitly already, detect it */
+  if (transport_type == 0)
     {
-      if ((trans_node == NULL) || (creator == NULL) || (name == NULL))
+      const gchar *ns = lm_message_node_get_attribute (trans_node, "xmlns");
+      DEBUG ("ns is %s", ns);
+
+      transport_type = GPOINTER_TO_INT (
+          g_hash_table_lookup (c->conn->jingle_factory->transports, ns));
+
+      if (transport_type == 0)
         {
-          SET_BAD_REQ ("missing required content attributes or elements");
+          SET_BAD_REQ ("unsupported content transport");
           return;
         }
-    }
-  else
-    {
-      /* explicit is better than implicit */
-      if (creator == NULL)
-        creator = "initiator";
-
-      if (name == NULL)
-        name = "audio";
-    }
 
-  if (trans_node)
-    {
-      xmlns = lm_message_node_get_attribute (trans_node, "xmlns");
-      tmaker = g_hash_table_lookup (priv->factory->transports, NULL);
-    }
-  else
-    {
-      /* older gtalk assumes google-p2p */
-      g_object_set (priv->session, "dialect", JINGLE_DIALECT_GTALK3, NULL);
-      tmaker = g_hash_table_lookup (priv->factory->transports, NULL);
+      priv->transport_ns = g_strdup (ns);
     }
 
   priv->created_by_initiator = (!tp_strdiff (creator, "initiator"));
+  DEBUG ("senders == %s", senders);
   priv->senders = _string_to_enum (content_senders_table, senders);
   if (priv->senders == JINGLE_CONTENT_SENDERS_NONE)
     {
-      SET_BAD_REQ ("invalid content senders in stream");
+      SET_BAD_REQ ("invalid content senders");
       return;
     }
 
-  desc = dmaker (c);
-  trans = tmaker (c);
-
-  gabble_jingle_transport_iface_parse (trans, trans_node, error);
+  parse_description (c, desc_node, error);
   if (*error)
-    {
-      g_object_unref (desc);
-      g_object_unref (trans);
       return;
-    }
 
-  gabble_jingle_description_iface_parse (desc, desc_node, error);
+  DEBUG ("content creating new transport type %s", g_type_name (transport_type));
+
+  trans = g_object_new (transport_type,
+                       "content", c,
+                       "transport-ns", priv->transport_ns,
+                       NULL);
+
+  /* FIXME: I think candidates can't be specified in content addition/session
+   * init, so disabling this:
+  gabble_jingle_transport_iface_parse_candidates (trans, trans_node, error);
   if (*error)
     {
-      g_object_unref (desc);
       g_object_unref (trans);
       return;
-    }
-
-  g_assert (priv->description == NULL);
-  priv->description = desc;
+    } */
 
   g_assert (priv->transport == NULL);
   priv->transport = trans;
@@ -383,14 +417,18 @@ gabble_jingle_content_produce_node (GabbleJingleContent *c,
   LmMessageNode *parent, gboolean full)
 {
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (c);
-  LmMessageNode *content_node;
+  LmMessageNode *content_node, *trans_node;
   JingleDialect dialect;
+  void (*produce_desc)(GabbleJingleContent *, LmMessageNode *) =
+    GABBLE_JINGLE_CONTENT_GET_CLASS (c)->produce_description;
 
-  g_object_get (priv->session, "dialect", &dialect, NULL);
+  g_object_get (c->session, "dialect", &dialect, NULL);
 
   if ((dialect == JINGLE_DIALECT_GTALK3) ||
       (dialect == JINGLE_DIALECT_GTALK4))
     {
+      DEBUG ("content node setting to parent??");
+
       /* content-* isn't used in GTalk anyways, so we always have to include
        * the full content description */
       g_assert (full == TRUE);
@@ -399,18 +437,27 @@ gabble_jingle_content_produce_node (GabbleJingleContent *c,
     }
   else
     {
+      DEBUG ("creator: %s", priv->creator);
+      DEBUG ("name: %s", priv->name);
+      DEBUG ("senders: %s", _enum_to_string (content_senders_table, priv->senders));
+
       content_node = lm_message_node_add_child (parent, "content", NULL);
       lm_message_node_set_attributes (content_node,
           "creator", priv->creator,
           "name", priv->name,
-          "senders", _enum_to_string (content_senders_table, priv->senders));
+          "senders", _enum_to_string (content_senders_table, priv->senders),
+          NULL);
+      DEBUG ("created new content node %p", content_node);
     }
 
   if (!full)
     return;
 
-  gabble_jingle_description_iface_produce (priv->description, content_node);
-  gabble_jingle_transport_iface_produce (priv->transport, content_node);
+  produce_desc (c, content_node);
+
+  /* We can do it here, don't need to call into transport object for this */
+  trans_node = lm_message_node_add_child (content_node, "transport", NULL);
+  lm_message_node_set_attribute (trans_node, "xmlns", priv->transport_ns);
 }
 
 void
@@ -433,3 +480,61 @@ gabble_jingle_content_update_senders (GabbleJingleContent *c,
   g_object_notify ((GObject *) c, "senders");
 }
 
+void
+gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+
+  gabble_jingle_transport_iface_add_candidates (priv->transport, li);
+}
+
+gboolean
+gabble_jingle_content_is_ready (GabbleJingleContent *self)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+  JingleTransportState state;
+
+  if (!priv->has_local_codecs)
+      return FALSE;
+
+  g_object_get (priv->transport, "state", &state, NULL);
+
+  return (state == JINGLE_TRANSPORT_STATE_CONNECTED);
+}
+
+/* FIXME: if local codecs are set multiple times, this will be emitted
+ * multiple times - is that scenario possible at all? */
+static void
+maybe_emit_ready (GabbleJingleContent *self)
+{
+    if (!gabble_jingle_content_is_ready (self))
+        return;
+
+    g_signal_emit (self, signals[READY], 0);
+}
+
+void
+gabble_jingle_content_set_local_codecs (GabbleJingleContent *self)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+  priv->has_local_codecs = TRUE;
+
+  /* FIXME: actually this function should call appropriate subclass interface
+   * method to set/push local codecs */
+
+  maybe_emit_ready (self);
+}
+
+/* FIXME: ok, situation sucks because Content sets the transport state,
+ * but also catches the change notification. we ideally want user to
+ * be able to access transprot directly, not through content? */
+void
+gabble_jingle_content_set_transport_state (GabbleJingleContent *self,
+    JingleTransportState state)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+
+  g_object_set (priv->transport, "state", state, NULL);
+  maybe_emit_ready (self);
+}
+
diff --git a/src/jingle-content.h b/src/jingle-content.h
index adbdbad..8ec0d37 100644
--- a/src/jingle-content.h
+++ b/src/jingle-content.h
@@ -71,11 +71,19 @@ GType gabble_jingle_content_get_type (void);
 
 struct _GabbleJingleContentClass {
     GObjectClass parent_class;
+
+    void  (*parse_description) (GabbleJingleContent *, LmMessageNode *,
+        GError **);
+    void  (*produce_description) (GabbleJingleContent *, LmMessageNode *);
 };
 
 struct _GabbleJingleContent {
     GObject parent;
     gpointer priv;
+
+    GabbleConnection *conn;
+    GabbleJingleFactory *factory;
+    GabbleJingleSession *session;
 };
 
 void gabble_jingle_content_parse_add (GabbleJingleContent *c,
@@ -85,5 +93,11 @@ void gabble_jingle_content_update_senders (GabbleJingleContent *c,
 void gabble_jingle_content_produce_node (GabbleJingleContent *c,
   LmMessageNode *parent, gboolean full);
 
+void gabble_jingle_content_add_candidates (GabbleJingleContent *self, GList *li);
+gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self);
+void gabble_jingle_content_set_local_codecs (GabbleJingleContent *content);
+void gabble_jingle_content_set_transport_state (GabbleJingleContent *content,
+    JingleTransportState state);
+
 #endif /* __JINGLE_CONTENT_H__ */
 
diff --git a/src/jingle-factory.c b/src/jingle-factory.c
index eec1bee..68c19f8 100644
--- a/src/jingle-factory.c
+++ b/src/jingle-factory.c
@@ -35,7 +35,8 @@
 #include "namespaces.h"
 #include "jingle-session.h"
 
-#include <loudmouth/loudmouth.h>
+#include "jingle-media-rtp.h"
+#include "jingle-transport-google.h"
 
 
 G_DEFINE_TYPE(GabbleJingleFactory, gabble_jingle_factory, G_TYPE_OBJECT);
@@ -74,7 +75,10 @@ static LmHandlerResult
 jingle_cb (LmMessageHandler *handler, LmConnection *lmconn,
     LmMessage *message, gpointer user_data);
 static GabbleJingleSession *create_session (GabbleJingleFactory *fac,
-    const gchar *sid, TpHandle peer);
+    const gchar *sid, TpHandle peer, const gchar *peer_resource);
+
+static void session_terminated_cb (GabbleJingleSession *sess,
+    gboolean local_terminator, GabbleJingleFactory *fac);
 
 static void
 gabble_jingle_factory_init (GabbleJingleFactory *obj)
@@ -90,7 +94,7 @@ gabble_jingle_factory_init (GabbleJingleFactory *obj)
   obj->transports = g_hash_table_new_full (g_str_hash, g_str_equal,
       NULL, NULL);
 
-  obj->descriptions = g_hash_table_new_full (g_str_hash, g_str_equal,
+  obj->content_types = g_hash_table_new_full (g_str_hash, g_str_equal,
       NULL, NULL);
 
   priv->jingle_cb = NULL;
@@ -114,11 +118,11 @@ gabble_jingle_factory_dispose (GObject *object)
   g_hash_table_destroy (priv->sessions);
   priv->sessions = NULL;
 
-  g_hash_table_destroy (fac->descriptions);
-  fac->descriptions = NULL;
+  g_hash_table_destroy (fac->content_types);
+  fac->content_types = NULL;
 
   g_hash_table_destroy (fac->transports);
-  fac->descriptions = NULL;
+  fac->transports = NULL;
 
   lm_connection_unregister_message_handler (priv->conn->lmconn,
       priv->jingle_cb, LM_MESSAGE_TYPE_IQ);
@@ -184,6 +188,11 @@ gabble_jingle_factory_constructor (GType type,
   lm_connection_register_message_handler (priv->conn->lmconn,
       priv->jingle_cb, LM_MESSAGE_TYPE_IQ, LM_HANDLER_PRIORITY_FIRST);
 
+  // FIXME: better way to do it?
+
+  jingle_media_rtp_register (self);
+  jingle_transport_google_register (self);
+
   return obj;
 }
 
@@ -227,7 +236,6 @@ sid_in_use (GabbleJingleFactory *factory, const gchar *sid)
 static gchar *
 get_unique_sid (GabbleJingleFactory *factory)
 {
-  GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (factory);
   guint32 val;
   gchar *sid = NULL;
   gboolean unique = FALSE;
@@ -242,8 +250,6 @@ get_unique_sid (GabbleJingleFactory *factory)
       unique = !sid_in_use (factory, sid);
     }
 
-  g_hash_table_insert (priv->sessions, sid, NULL);
-
   return sid;
 }
 
@@ -255,15 +261,8 @@ register_session (GabbleJingleFactory *factory,
   GabbleJingleFactoryPrivate *priv = GABBLE_JINGLE_FACTORY_GET_PRIVATE (factory);
   gchar *sid_copy;
 
-  if (sid == NULL)
-    {
-      sid_copy = get_unique_sid (factory);
-    }
-  else
-    {
-      sid_copy = g_strdup (sid);
-    }
-
+  sid_copy = g_strdup (sid);
+  g_assert (g_hash_table_lookup (priv->sessions, sid_copy) == NULL);
   g_hash_table_insert (priv->sessions, sid_copy, sess);
 }
 
@@ -304,7 +303,7 @@ jingle_cb (LmMessageHandler *handler,
   if (sess == NULL)
     {
       new_session = TRUE;
-      sess = create_session (self, sid, 0);
+      sess = create_session (self, sid, 0, NULL);
     }
 
   /* now act on the message */
@@ -321,7 +320,8 @@ jingle_cb (LmMessageHandler *handler,
     }
 
   /* on parse error */
-  g_object_unref (sess);
+  if (new_session)
+      _jingle_factory_unregister_session (self, sid);
 
 REQUEST_ERROR:
   _gabble_connection_send_iq_error (
@@ -339,48 +339,72 @@ REQUEST_ERROR:
  */
 static GabbleJingleSession *
 create_session (GabbleJingleFactory *fac,
-    const gchar *sid, TpHandle peer)
+    const gchar *sid, TpHandle peer, const gchar *peer_resource)
 {
   GabbleJingleFactoryPrivate *priv =
       GABBLE_JINGLE_FACTORY_GET_PRIVATE (fac);
   GabbleJingleSession *sess;
-  gboolean local_initiator = TRUE;
+  gboolean local_initiator;
 
   if (sid != NULL)
     {
       g_assert (NULL == g_hash_table_lookup (priv->sessions, sid));
       local_initiator = FALSE;
     }
+  else
+    {
+      sid = get_unique_sid (fac);
+      local_initiator = TRUE;
+    }
 
   sess = g_object_new (GABBLE_TYPE_JINGLE_SESSION,
                        "session-id", sid,
                        "connection", priv->conn,
                        "local-initiator", local_initiator,
                        "peer", peer,
+                       "peer-resource", peer_resource,
                        NULL);
 
+  g_signal_connect (sess, "terminated",
+    (GCallback) session_terminated_cb, fac);
+
+  DEBUG ("new session %s @ %p created", sid, sess);
   register_session (fac, sid, sess);
   return sess;
 }
 
 GabbleJingleSession *
-gabble_jingle_factory_initiate_session (GabbleJingleFactory *fac,
-    TpHandle peer)
+gabble_jingle_factory_create_session (GabbleJingleFactory *fac,
+    TpHandle peer, const gchar *peer_resource)
 {
-  return create_session (fac, NULL, peer);
+  return create_session (fac, NULL, peer, peer_resource);
 }
 
 void
 gabble_jingle_factory_register_transport (GabbleJingleFactory *factory,
-    gchar *namespace, JingleTransportMaker maker)
+    gchar *namespace, GType transport_type)
 {
-  g_hash_table_insert (factory->transports, namespace, maker);
+  g_hash_table_insert (factory->transports, namespace,
+      GINT_TO_POINTER (transport_type));
 }
 
 void
-gabble_jingle_factory_register_description (GabbleJingleFactory *factory,
-    gchar *namespace, JingleDescriptionMaker maker)
+gabble_jingle_factory_register_content_type (GabbleJingleFactory *factory,
+    gchar *namespace, GType content_type)
+{
+  g_hash_table_insert (factory->content_types, namespace,
+      GINT_TO_POINTER (content_type));
+}
+
+static void
+session_terminated_cb (GabbleJingleSession *session,
+    gboolean local_terminator, GabbleJingleFactory *factory)
 {
-  g_hash_table_insert (factory->descriptions, namespace, maker);
+  const gchar *sid;
+  DEBUG ("removing terminated session");
+
+  g_object_get (session, "session-id", &sid, NULL);
+
+  _jingle_factory_unregister_session (factory, sid);
 }
 
diff --git a/src/jingle-factory.h b/src/jingle-factory.h
index 715e5b1..352bb08 100644
--- a/src/jingle-factory.h
+++ b/src/jingle-factory.h
@@ -115,27 +115,27 @@ struct _GabbleJingleFactoryClass {
     GObjectClass parent_class;
 };
 
-typedef GabbleJingleTransportIface * (*JingleTransportMaker) (GabbleJingleContent *c);
-typedef GabbleJingleDescriptionIface * (*JingleDescriptionMaker) (GabbleJingleContent *c);
+typedef GabbleJingleContent * (*JingleContentMaker) (GabbleJingleSession *);
+typedef GabbleJingleTransportIface * (*JingleTransportMaker) (GabbleJingleContent *);
 
 struct _GabbleJingleFactory {
     GObject parent;
 
-    GHashTable *descriptions;
+    GHashTable *content_types;
     GHashTable *transports;
 
     gpointer priv;
 };
 
+void gabble_jingle_factory_register_content_type (GabbleJingleFactory *factory,
+    gchar *namespace, GType content_type);
 void gabble_jingle_factory_register_transport (GabbleJingleFactory *factory,
-    gchar *namespace, JingleTransportMaker maker);
-void gabble_jingle_factory_register_description (GabbleJingleFactory *factory,
-    gchar *namespace, JingleDescriptionMaker maker);
+    gchar *namespace, GType transport_type);
 void _jingle_factory_unregister_session (GabbleJingleFactory *factory,
     const gchar *sid);
 
-GabbleJingleSession *gabble_jingle_factory_initiate_session (GabbleJingleFactory
-    *fac, TpHandle peer);
+GabbleJingleSession *gabble_jingle_factory_create_session (GabbleJingleFactory
+    *fac, TpHandle peer, const gchar *peer_resource);
 
 G_END_DECLS;
 
diff --git a/src/jingle-media-rtp.c b/src/jingle-media-rtp.c
index 88faf1d..24c2e79 100644
--- a/src/jingle-media-rtp.c
+++ b/src/jingle-media-rtp.c
@@ -18,6 +18,10 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+/* Media/RTP content type deals with audio/video content, ie. jingle calls. It
+ * supports standard Jingle drafts (v0.15, v0.26) and Google's jingle variants
+ * (libjingle 0.3/0.4). */
+
 #include "jingle-media-rtp.h"
 
 #include <stdio.h>
@@ -37,29 +41,21 @@
 #include "jingle-session.h"
 #include "jingle-content.h"
 
-static void
-description_iface_init (gpointer g_iface, gpointer iface_data);
-
-G_DEFINE_TYPE_WITH_CODE (GabbleJingleMediaRtp,
-    gabble_jingle_media_rtp, G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE (GABBLE_TYPE_JINGLE_DESCRIPTION_IFACE,
-        description_iface_init));
+G_DEFINE_TYPE (GabbleJingleMediaRtp,
+    gabble_jingle_media_rtp, GABBLE_TYPE_JINGLE_CONTENT);
 
 /* signal enum */
 enum
 {
-  NEW_CANDIDATE,
+  REMOTE_CODECS,
   LAST_SIGNAL
 };
 
-// static guint signals[LAST_SIGNAL] = {0};
+static guint signals[LAST_SIGNAL] = {0};
 
 /* properties */
 enum
 {
-  PROP_CONNECTION,
-  PROP_SESSION,
-  PROP_CONTENT,
   PROP_MEDIA_TYPE,
   LAST_PROPERTY
 };
@@ -84,10 +80,6 @@ typedef struct {
 typedef struct _GabbleJingleMediaRtpPrivate GabbleJingleMediaRtpPrivate;
 struct _GabbleJingleMediaRtpPrivate
 {
-  GabbleConnection *conn;
-  GabbleJingleSession *session;
-  GabbleJingleContent *content;
-
   GList *local_codecs;
   // GList *remote_codecs;
   JingleMediaType media_type;
@@ -104,7 +96,6 @@ gabble_jingle_media_rtp_init (GabbleJingleMediaRtp *obj)
      G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_JINGLE_MEDIA_RTP,
          GabbleJingleMediaRtpPrivate);
   obj->priv = priv;
-
   priv->dispose_has_run = FALSE;
 }
 
@@ -154,15 +145,6 @@ gabble_jingle_media_rtp_get_property (GObject *object,
   GabbleJingleMediaRtpPrivate *priv = GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (trans);
 
   switch (property_id) {
-    case PROP_CONNECTION:
-      g_value_set_object (value, priv->conn);
-      break;
-    case PROP_SESSION:
-      g_value_set_object (value, priv->session);
-      break;
-    case PROP_CONTENT:
-      g_value_set_object (value, priv->content);
-      break;
     case PROP_MEDIA_TYPE:
       g_value_set_uint (value, priv->media_type);
       break;
@@ -183,15 +165,6 @@ gabble_jingle_media_rtp_set_property (GObject *object,
       GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (trans);
 
   switch (property_id) {
-    case PROP_CONNECTION:
-      priv->conn = g_value_get_object (value);
-      break;
-    case PROP_SESSION:
-      priv->session = g_value_get_object (value);
-      break;
-    case PROP_CONTENT:
-      priv->content = g_value_get_object (value);
-      break;
     case PROP_MEDIA_TYPE:
       priv->media_type = g_value_get_uint (value);
       break;
@@ -202,10 +175,17 @@ gabble_jingle_media_rtp_set_property (GObject *object,
 }
 
 static void
+parse_description (GabbleJingleContent *content, LmMessageNode *desc_node,
+    GError **error);
+static void produce_description (GabbleJingleContent *obj,
+    LmMessageNode *content_node);
+
+static void
 gabble_jingle_media_rtp_class_init (GabbleJingleMediaRtpClass *cls)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (cls);
-  GParamSpec *param_spec;
+  GabbleJingleContentClass *content_class = GABBLE_JINGLE_CONTENT_CLASS (cls);
+  // GParamSpec *param_spec;
 
   g_type_class_add_private (cls, sizeof (GabbleJingleMediaRtpPrivate));
 
@@ -213,64 +193,15 @@ gabble_jingle_media_rtp_class_init (GabbleJingleMediaRtpClass *cls)
   object_class->set_property = gabble_jingle_media_rtp_set_property;
   object_class->dispose = gabble_jingle_media_rtp_dispose;
 
-  /* property definitions */
-  param_spec = g_param_spec_object ("connection", "GabbleConnection object",
-                                    "Gabble connection object used for exchanging "
-                                    "messages.",
-                                    GABBLE_TYPE_CONNECTION,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
-  param_spec = g_param_spec_object ("session", "GabbleJingleSession object",
-                                    "The session using this transport object.",
-                                    GABBLE_TYPE_JINGLE_SESSION,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_SESSION, param_spec);
-
-  param_spec = g_param_spec_object ("content", "GabbleJingleContent object",
-                                    "Jingle content object using this transport.",
-                                    GABBLE_TYPE_JINGLE_CONTENT,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
-
-  param_spec = g_param_spec_object ("content", "GabbleJingleContent object",
-                                    "Jingle content object using this transport.",
-                                    GABBLE_TYPE_JINGLE_CONTENT,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
+  content_class->parse_description = parse_description;
+  content_class->produce_description = produce_description;
 
   /* signal definitions */
-}
-
-static GabbleJingleDescriptionIface *
-new_description (GabbleJingleContent *content)
-{
-  GabbleJingleMediaRtp *self;
-  GabbleJingleSession *sess;
-  GabbleConnection *conn;
-
-  g_object_get (content, "connection", &conn,
-      "session", &sess, NULL);
 
-  self = g_object_new (GABBLE_TYPE_JINGLE_MEDIA_RTP,
-    "connection", conn,
-    "session", sess,
-    "content", content,
-    NULL);
-
-  return GABBLE_JINGLE_DESCRIPTION_IFACE (self);
+  signals[REMOTE_CODECS] = g_signal_new ("remote-codecs",
+        G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+        0, NULL, NULL, g_cclosure_marshal_VOID__POINTER,
+        G_TYPE_NONE, 1, G_TYPE_POINTER);
 }
 
 #define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
@@ -278,10 +209,10 @@ new_description (GabbleJingleContent *content)
 #define SET_CONFLICT(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT, txt)
 
 static void
-parse_description (GabbleJingleDescriptionIface *iface,
+parse_description (GabbleJingleContent *content,
     LmMessageNode *desc_node, GError **error)
 {
-  GabbleJingleMediaRtp *self = GABBLE_JINGLE_MEDIA_RTP (iface);
+  GabbleJingleMediaRtp *self = GABBLE_JINGLE_MEDIA_RTP (content);
   GabbleJingleMediaRtpPrivate *priv = GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (self);
   JingleMediaType mtype = JINGLE_MEDIA_TYPE_NONE;
   gboolean google_mode = FALSE;
@@ -380,6 +311,9 @@ parse_description (GabbleJingleDescriptionIface *iface,
       p->clockrate = clockrate;
       p->channels = channels;
 
+      DEBUG ("new remote codec: id = %u, name = %s, clockrate = %u, channels = %u",
+          p->id, p->name, p->clockrate, p->channels);
+
       codecs = g_list_append (codecs, p);
     }
 
@@ -400,17 +334,18 @@ parse_description (GabbleJingleDescriptionIface *iface,
 
   priv->media_type = mtype;
 
-  g_signal_emit_by_name (priv->content, "remote-codecs", codecs);
+  g_signal_emit (self, signals[REMOTE_CODECS], 0, codecs);
 
   /* append them to the known remote codecs */
   // priv->remote_codecs = g_list_concat (priv->remote_codecs, codecs);
 }
 
 static void
-produce_node (GabbleJingleDescriptionIface *obj, LmMessageNode *content_node)
+produce_description (GabbleJingleContent *obj, LmMessageNode *content_node)
 {
   GabbleJingleMediaRtp *desc =
     GABBLE_JINGLE_MEDIA_RTP (obj);
+  GabbleJingleSession *sess;
   GabbleJingleMediaRtpPrivate *priv =
     GABBLE_JINGLE_MEDIA_RTP_GET_PRIVATE (desc);
   LmMessageNode *desc_node;
@@ -418,7 +353,10 @@ produce_node (GabbleJingleDescriptionIface *obj, LmMessageNode *content_node)
   JingleDialect dialect;
   const gchar *xmlns = NULL;
 
-  g_object_get (priv->session, "dialect", &dialect, NULL);
+  g_object_get (obj, "session", &sess, NULL);
+  g_object_get (sess, "dialect", &dialect, NULL);
+
+  DEBUG ("using content node %p", content_node);
 
   desc_node = lm_message_node_add_child (content_node, "description", NULL);
 
@@ -489,32 +427,36 @@ produce_node (GabbleJingleDescriptionIface *obj, LmMessageNode *content_node)
     }
 }
 
+/*
 static void
-description_iface_init (gpointer g_iface, gpointer iface_data)
+content_iface_init (gpointer g_iface, gpointer iface_data)
 {
-  GabbleJingleDescriptionIfaceClass *klass = (GabbleJingleDescriptionIfaceClass *) g_iface;
+  GabbleJingleContentClass *klass = (GabbleJingleContentClass *) g_iface;
 
-  klass->parse = parse_description;
-  klass->produce = produce_node;
-  // klass->add_codecs = add_codecs;
+  klass->parse_description = parse_description;
+  klass->produce_description = produce_node;
 }
+*/
 
 void
 jingle_media_rtp_register (GabbleJingleFactory *factory)
 {
   /* Current (v0.25) Jingle draft URI */
-  gabble_jingle_factory_register_description (factory,
-      NS_JINGLE_RTP_TMP, new_description);
+  gabble_jingle_factory_register_content_type (factory,
+      NS_JINGLE_RTP_TMP, GABBLE_TYPE_JINGLE_MEDIA_RTP);
 
   /* Old Jingle audio/video namespaces */
-  gabble_jingle_factory_register_description (factory,
-      NS_JINGLE_DESCRIPTION_AUDIO, new_description);
+  gabble_jingle_factory_register_content_type (factory,
+      NS_JINGLE_DESCRIPTION_AUDIO,
+      GABBLE_TYPE_JINGLE_MEDIA_RTP);
 
-  gabble_jingle_factory_register_description (factory,
-      NS_JINGLE_DESCRIPTION_VIDEO, new_description);
+  gabble_jingle_factory_register_content_type (factory,
+      NS_JINGLE_DESCRIPTION_VIDEO,
+      GABBLE_TYPE_JINGLE_MEDIA_RTP);
 
   /* GTalk audio call namespace */
-  gabble_jingle_factory_register_description (factory,
-      NS_GOOGLE_SESSION_PHONE, new_description);
+  gabble_jingle_factory_register_content_type (factory,
+      NS_GOOGLE_SESSION_PHONE,
+      GABBLE_TYPE_JINGLE_MEDIA_RTP);
 }
 
diff --git a/src/jingle-media-rtp.h b/src/jingle-media-rtp.h
index 5f4f8aa..ec8d107 100644
--- a/src/jingle-media-rtp.h
+++ b/src/jingle-media-rtp.h
@@ -24,6 +24,8 @@
 #include <loudmouth/loudmouth.h>
 #include "types.h"
 
+#include "jingle-content.h"
+
 G_BEGIN_DECLS
 
 typedef struct _GabbleJingleMediaRtpClass GabbleJingleMediaRtpClass;
@@ -48,11 +50,11 @@ GType gabble_jingle_media_rtp_get_type (void);
                               GabbleJingleMediaRtpClass))
 
 struct _GabbleJingleMediaRtpClass {
-    GObjectClass parent_class;
+    GabbleJingleContentClass parent_class;
 };
 
 struct _GabbleJingleMediaRtp {
-    GObject parent;
+    GabbleJingleContent parent;
     gpointer priv;
 };
 
diff --git a/src/jingle-session.c b/src/jingle-session.c
index ddd4d79..92d8abe 100644
--- a/src/jingle-session.c
+++ b/src/jingle-session.c
@@ -1,21 +1,21 @@
-/*
- * gabble-jingle-session.c - Source for GabbleJingleSession
- * Copyright (C) 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
- */
+  /*
+   * gabble-jingle-session.c - Source for GabbleJingleSession
+   * Copyright (C) 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 "jingle-session.h"
 
@@ -42,17 +42,17 @@ G_DEFINE_TYPE(GabbleJingleSession, gabble_jingle_session, G_TYPE_OBJECT);
 /* signal enum */
 enum
 {
-  FOO,
+  NEW_CONTENT,
+  TERMINATED,
   LAST_SIGNAL
 };
 
-// FIXME static guint signals[LAST_SIGNAL] = {0};
+static guint signals[LAST_SIGNAL] = {0};
 
 /* properties */
 enum
 {
   PROP_CONNECTION = 1,
-  PROP_FACTORY,
   PROP_SESSION_ID,
   PROP_PEER,
   PROP_PEER_RESOURCE,
@@ -66,9 +66,7 @@ typedef struct _GabbleJingleSessionPrivate GabbleJingleSessionPrivate;
 struct _GabbleJingleSessionPrivate
 {
   GabbleConnection *conn;
-  GabbleJingleFactory *factory;
 
-  TpHandle peer;
   gchar *peer_resource;
   gchar *peer_jid;
   gchar *initiator;
@@ -111,14 +109,14 @@ static JingleAction allowed_actions[6][8] = {
   /* JS_STATE_PENDING_CREATED */
   { JINGLE_ACTION_SESSION_INITIATE, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_INITIATE_SENT */
-  { JINGLE_ACTION_UNKNOWN },
+  { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_INITIATED */
   { JINGLE_ACTION_SESSION_ACCEPT, JINGLE_ACTION_SESSION_TERMINATE,
     JINGLE_ACTION_TRANSPORT_INFO,
     JINGLE_ACTION_CONTENT_MODIFY, JINGLE_ACTION_CONTENT_ACCEPT,
     JINGLE_ACTION_CONTENT_REMOVE, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_ACCEPT_SENT */
-  { JINGLE_ACTION_UNKNOWN },
+  { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_ACTIVE */
   { JINGLE_ACTION_CONTENT_MODIFY, JINGLE_ACTION_CONTENT_ADD,
     JINGLE_ACTION_CONTENT_REMOVE, JINGLE_ACTION_CONTENT_REPLACE,
@@ -157,15 +155,17 @@ gabble_jingle_session_dispose (GObject *object)
   DEBUG ("dispose called");
   priv->dispose_has_run = TRUE;
 
-  _jingle_factory_unregister_session (priv->factory, priv->sid);
+  // Note: dispose is called FROM unregister_session (because refcount
+  // decreases when hash table releses the session object)
+  // _jingle_factory_unregister_session (priv->conn->jingle_factory, priv->sid);
 
   g_hash_table_destroy (priv->contents);
   priv->contents = NULL;
 
-  if (priv->peer)
+  if (sess->peer)
     {
-      tp_handle_unref (contact_repo, priv->peer);
-      priv->peer = 0;
+      tp_handle_unref (contact_repo, sess->peer);
+      sess->peer = 0;
     }
 
   g_free (priv->sid);
@@ -180,6 +180,7 @@ gabble_jingle_session_dispose (GObject *object)
   g_free (priv->initiator);
   priv->initiator = NULL;
 
+  DEBUG ("got here in dispose");
   if (G_OBJECT_CLASS (gabble_jingle_session_parent_class)->dispose)
     G_OBJECT_CLASS (gabble_jingle_session_parent_class)->dispose (object);
 }
@@ -197,9 +198,6 @@ gabble_jingle_session_get_property (GObject *object,
     case PROP_CONNECTION:
       g_value_set_object (value, priv->conn);
       break;
-    case PROP_FACTORY:
-      g_value_set_object (value, priv->factory);
-      break;
     case PROP_SESSION_ID:
       g_value_set_string (value, priv->sid);
       break;
@@ -207,7 +205,7 @@ gabble_jingle_session_get_property (GObject *object,
       g_value_set_boolean (value, priv->local_initiator);
       break;
     case PROP_PEER:
-      g_value_set_uint (value, priv->peer);
+      g_value_set_uint (value, sess->peer);
       break;
     case PROP_PEER_RESOURCE:
       g_value_set_string (value, priv->peer_resource);
@@ -236,9 +234,12 @@ gabble_jingle_session_set_property (GObject *object,
   switch (property_id) {
     case PROP_CONNECTION:
       priv->conn = g_value_get_object (value);
+      g_debug ("i am %p, priv->conn == %p", sess, priv->conn);
+      g_assert (priv->conn != NULL);
       break;
-    case PROP_FACTORY:
-      priv->factory = g_value_get_object (value);
+    case PROP_SESSION_ID:
+      g_free (priv->sid);
+      priv->sid = g_value_dup_string (value);
       break;
     case PROP_LOCAL_INITIATOR:
       priv->local_initiator = g_value_get_boolean (value);
@@ -247,10 +248,15 @@ gabble_jingle_session_set_property (GObject *object,
       priv->dialect = g_value_get_uint (value);
       break;
     case PROP_PEER:
-      priv->peer = g_value_get_uint (value);
+      sess->peer = g_value_get_uint (value);
+      break;
+    case PROP_PEER_RESOURCE:
+      g_free (priv->peer_resource);
+      priv->peer_resource = g_value_dup_string (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      g_assert_not_reached ();
       break;
   }
 }
@@ -278,16 +284,6 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls)
                                     G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
 
-  param_spec = g_param_spec_object ("factory", "GabbleJingleFactory object",
-                                    "Jingle factory object that owns this "
-                                    "jingle session.",
-                                    GABBLE_TYPE_JINGLE_FACTORY,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
   param_spec = g_param_spec_string ("session-id", "Session ID",
                                     "A unique session identifier used "
                                     "throughout all communication.",
@@ -340,14 +336,19 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls)
 
   param_spec = g_param_spec_uint ("dialect", "Jingle dialect",
                                   "Jingle dialect used for this session.",
-                                  0, G_MAXUINT32, 0,
+                                  0, G_MAXUINT32, JINGLE_DIALECT_ERROR,
                                   G_PARAM_READWRITE |
                                   G_PARAM_STATIC_NAME |
                                   G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_PEER, param_spec);
+  g_object_class_install_property (object_class, PROP_DIALECT, param_spec);
 
 
   /* signal definitions */
+
+  signals[TERMINATED] = g_signal_new ("terminated",
+        G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST,
+        0, NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
+        G_TYPE_NONE, 1, G_TYPE_POINTER);
 }
 
 typedef void (*HandlerFunc)(GabbleJingleSession *sess,
@@ -434,6 +435,8 @@ action_is_allowed (JingleAction action, JingleState state)
   return FALSE;
 }
 
+static void set_state (GabbleJingleSession *sess, JingleState state);
+
 #define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
 #define SET_OUT_ORDER(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER, txt)
 #define SET_CONFLICT(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT, txt)
@@ -451,7 +454,7 @@ _foreach_content (GabbleJingleSession *sess, LmMessageNode *node,
        NULL != content_node;
        content_node = content_node->next)
     {
-      if (!tp_strdiff (content_node->name, "content"))
+      if (tp_strdiff (content_node->name, "content"))
         continue;
 
       name = lm_message_node_get_attribute (content_node, "name");
@@ -463,45 +466,69 @@ _foreach_content (GabbleJingleSession *sess, LmMessageNode *node,
     }
 }
 
+static void content_ready_cb (GabbleJingleContent *c, GabbleJingleSession *sess);
+
 static void
 _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
     LmMessageNode *content_node, GError **error)
 {
   GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   const gchar *name = lm_message_node_get_attribute (content_node, "name");
+  LmMessageNode *desc_node = lm_message_node_get_child (content_node, "description");
+  GType content_type = 0;
+  const gchar *content_ns = NULL;
+
+  if (desc_node)
+    {
+      content_ns = lm_message_node_get_attribute (desc_node, "xmlns");
+      content_type =
+          GPOINTER_TO_INT (g_hash_table_lookup (priv->conn->jingle_factory->content_types,
+          content_ns));
+    }
+
+  if (content_type == 0)
+    {
+      SET_BAD_REQ ("unsupported content type");
+      DEBUG ("unsupported content type with ns %s", content_ns);
+      return;
+    }
+
+  DEBUG ("remote end adds new content named '%s' of type %s", name, g_type_name (content_type));
 
   if (c != NULL)
     {
       JingleContentState state;
 
-      /* streams added by the session initiator may replace similarly-named
-       * streams which we are trying to add (but havn't had acknowledged) */
+      /* contents added by the session initiator may replace similarly-named
+       * contents which we are trying to add (but haven't had acknowledged) */
 
       g_object_get (c, "state", &state, NULL);
       if (state < JINGLE_CONTENT_STATE_ACKNOWLEDGED)
         {
           if (priv->local_initiator)
             {
-              SET_CONFLICT ("session initiator is creating a stream "
+              SET_CONFLICT ("session initiator is creating a content "
                   "named \"%s\" already", name);
               return;
             }
         }
       else
         {
-          SET_CONFLICT ("stream called \"%s\" already exists, rejecting", name);
+          SET_CONFLICT ("content called \"%s\" already exists, rejecting", name);
           return;
         }
-
     }
 
-
-  c = g_object_new (GABBLE_TYPE_JINGLE_CONTENT,
+  DEBUG ("session creating new content type, conn == %p, jf == %p", priv->conn, priv->conn->jingle_factory);
+  c = g_object_new (content_type,
                     "connection", priv->conn,
-                    "factory", priv->factory,
                     "session", sess,
+                    "content-ns", content_ns,
                     NULL);
 
+  g_signal_connect (c, "ready",
+      (GCallback) content_ready_cb, sess);
+
   gabble_jingle_content_parse_add (c, content_node, FALSE, error);
   if (*error)
     {
@@ -509,7 +536,7 @@ _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
       return;
     }
 
-  /* This will override existing stream if it exists. */
+  /* This will override existing content if it exists. */
   g_hash_table_replace (priv->contents, g_strdup (name), c);
 }
 
@@ -522,17 +549,17 @@ _each_content_remove (GabbleJingleSession *sess, GabbleJingleContent *c,
 
   if (c == NULL)
     {
-      SET_BAD_REQ ("stream called \"%s\" doesn't exist", name);
+      SET_BAD_REQ ("content called \"%s\" doesn't exist", name);
       return;
     }
 
   if (g_hash_table_size (priv->contents) == 1)
     {
-      SET_BAD_REQ ("unable to remove the last stream in a session");
+      SET_BAD_REQ ("unable to remove the last content in a session");
       return;
     }
 
-  /* This should have the effect of shutting the stream down.
+  /* This should have the effect of shutting the content down.
    * FIXME: do we need to have REMOVING state at all? */
   g_hash_table_remove (priv->contents, name);
 }
@@ -545,7 +572,7 @@ _each_content_modify (GabbleJingleSession *sess, GabbleJingleContent *c,
 
   if (c == NULL)
     {
-      SET_BAD_REQ ("stream called \"%s\" doesn't exist", name);
+      SET_BAD_REQ ("content called \"%s\" doesn't exist", name);
       return;
     }
 
@@ -591,6 +618,8 @@ on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node,
     {
       _foreach_content (sess, node, _each_content_add, error);
     }
+
+  set_state (sess, JS_STATE_PENDING_INITIATED);
 }
 
 static void
@@ -629,17 +658,25 @@ on_content_accept (GabbleJingleSession *sess, LmMessageNode *node,
   // _foreach_content (sess, node, _each_content_replace, error);
 }
 
+static void
+on_session_terminate (GabbleJingleSession *sess, LmMessageNode *node,
+    GError **error)
+{
+  DEBUG ("remote end terminates the session");
+  set_state (sess, JS_STATE_ENDED);
+  g_signal_emit (sess, signals[TERMINATED], 0, FALSE);
+}
 
 static HandlerFunc handlers[] = {
-  on_content_accept,
-  on_content_add,
-  on_content_modify,
-  on_content_remove,
-  on_content_replace,
-  NULL, /* jingle_on_session_accept */
+    on_content_accept,
+    on_content_add,
+    on_content_modify,
+    on_content_remove,
+    on_content_replace,
+    NULL, /* jingle_on_session_accept */
   NULL, /* jingle_on_session_info */
   on_session_initiate,
-  NULL, /* jingle_on_session_terminate */
+  on_session_terminate, /* jingle_on_session_terminate */
   NULL /* jingle_on_transport_info */
 };
 
@@ -657,6 +694,8 @@ jingle_state_machine_dance (GabbleJingleSession *sess, JingleAction action,
       return;
     }
 
+  g_assert (handlers[action] != NULL);
+
   handlers[action] (sess, node, error);
 }
 
@@ -777,9 +816,11 @@ gabble_jingle_session_parse (GabbleJingleSession *sess, LmMessage *message, GErr
   if (sess == NULL)
     return sid;
 
+  DEBUG("jingle action '%s' from '%s' in session '%s'", actxt, from, sid);
+
   priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   contact_repo = tp_base_connection_get_handles (
-      (TpBaseConnection *)priv->conn, TP_HANDLE_TYPE_CONTACT);
+      (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
 
   if (!action_is_allowed (action, priv->state))
     {
@@ -787,8 +828,8 @@ gabble_jingle_session_parse (GabbleJingleSession *sess, LmMessage *message, GErr
       return NULL;
     }
 
-  priv->peer = tp_handle_ensure (contact_repo, from, NULL, NULL);
-  if (priv->peer == 0)
+  sess->peer = tp_handle_ensure (contact_repo, from, NULL, NULL);
+  if (sess->peer == 0)
     {
       SET_BAD_REQ ("unable to get sender handle");
       return NULL;
@@ -858,6 +899,8 @@ gabble_jingle_session_new_message (GabbleJingleSession *sess,
         produce_action (action, priv->dialect),
       NULL);
 
+  *sess_node = session_node;
+
   return msg;
 }
 
@@ -867,8 +910,7 @@ _try_session_accept_check (gpointer key, gpointer data, gpointer user_data)
   GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
   gboolean *is_ready = (gboolean *) user_data;
 
-  if (!gabble_jingle_content_is_ready (c))
-    *is_ready = FALSE;
+  *is_ready = gabble_jingle_content_is_ready (c);
 }
 
 static void
@@ -887,6 +929,8 @@ try_session_accept (GabbleJingleSession *sess)
   LmMessage *msg;
   LmMessageNode *sess_node;
 
+  /* FIXME: should check if we're in the state where accepting is possible */
+
   gboolean content_ready = TRUE;
 
   if (!priv->locally_accepted)
@@ -900,9 +944,25 @@ try_session_accept (GabbleJingleSession *sess)
   msg = gabble_jingle_session_new_message (sess, JINGLE_ACTION_SESSION_ACCEPT,
       &sess_node);
 
-  g_hash_table_foreach (priv->contents, _try_session_accept_fill, &sess_node);
+  g_hash_table_foreach (priv->contents, _try_session_accept_fill, sess_node);
+
+  DEBUG ("i'm really trying to accept this session!");
+
+  _gabble_connection_send (priv->conn, msg, NULL);
+
+  lm_message_unref (msg);
+
+  set_state (sess, JS_STATE_ACTIVE);
+  /* FIXME: i'm certain i forgot something */
+}
+
+static void
+set_state (GabbleJingleSession *sess, JingleState state)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
 
-  /* FIXME: Actually send the message, change local session state, etc. */
+  priv->state = state;
+  g_object_notify (G_OBJECT (sess), "state");
 }
 
 void
@@ -918,7 +978,7 @@ gabble_jingle_session_accept (GabbleJingleSession *sess)
 void
 gabble_jingle_session_terminate (GabbleJingleSession *sess)
 {
-  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  // GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   LmMessage *msg;
   LmMessageNode *sess_node;
 
@@ -947,10 +1007,50 @@ gabble_jingle_session_remove_content (GabbleJingleSession *sess,
   gabble_jingle_content_produce_node (c, sess_node, FALSE);
   /* FIXME: actually send it, mark it as in removal, etc */
 
+  /* FIXME: emit a 'content-removed' signal */
   /* This should g_object_unref the content */
   g_hash_table_remove (priv->contents, content_name);
 }
 
+gboolean
+gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
+    const gchar *content_ns, const gchar *transport_ns)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  GabbleJingleContent *c;
+  GType content_type;
+
+  if (g_hash_table_lookup (priv->contents, name) != NULL)
+    {
+      return FALSE;
+    }
+
+  content_type =
+      GPOINTER_TO_INT (g_hash_table_lookup (priv->conn->jingle_factory->content_types,
+      content_ns));
+
+  g_assert (content_type != 0);
+
+  c = g_object_new (content_type,
+                    "connection", priv->conn,
+                    "session", sess,
+                    "content-ns", content_ns,
+                    "transport-ns", transport_ns,
+                    NULL);
+
+  g_hash_table_insert (priv->contents, g_strdup (name), c);
+
+  g_signal_emit (sess, signals[NEW_CONTENT], 0, c);
+
+  if (!priv->local_initiator || (priv->state > JS_STATE_PENDING_CREATED))
+    {
+      /* FIXME: we should transmit "content-add" action to the peer. or
+       * when it's ready? */
+    }
+
+  return TRUE;
+}
+
 void
 gabble_jingle_session_change_direction (GabbleJingleSession *sess,
     const gchar *content_name, JingleContentSenders senders)
@@ -974,3 +1074,39 @@ gabble_jingle_session_change_direction (GabbleJingleSession *sess,
   /* FIXME: send the message, mark the nodes as pending change, etc */
 }
 
+/* Note: if there are multiple content types, not guaranteed which one will
+ * be returned. Typically, the same GType will know how to handle related
+ * contents found in a session (e.g. media-rtp for audio/video), so that
+ * should not be a problem. Returns 0 if there are no contents yet. */
+GType
+gabble_jingle_session_get_content_type (GabbleJingleSession *sess)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  GabbleJingleContent *c;
+
+  GList *li = g_hash_table_get_values (priv->contents);
+
+  if (li == NULL)
+      return 0;
+
+  c = li->data;
+  g_list_free (li);
+
+  return G_OBJECT_TYPE (c);
+}
+
+/* FIXME: probably should make this into a property */
+GList *
+gabble_jingle_session_get_contents (GabbleJingleSession *sess)
+{
+  GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
+  return g_hash_table_get_values (priv->contents);
+}
+
+static void
+content_ready_cb (GabbleJingleContent *c, GabbleJingleSession *sess)
+{
+  try_session_accept (sess);
+}
+
+
diff --git a/src/jingle-session.h b/src/jingle-session.h
index 39e0813..6fcad38 100644
--- a/src/jingle-session.h
+++ b/src/jingle-session.h
@@ -55,6 +55,8 @@ struct _GabbleJingleSessionClass {
 struct _GabbleJingleSession {
     GObject parent;
     gpointer priv;
+
+    TpHandle peer;
 };
 
 const gchar *gabble_jingle_session_parse (GabbleJingleSession *sess,
@@ -67,6 +69,17 @@ gint _string_to_enum (const gchar **table, const gchar *val);
 
 void gabble_jingle_session_accept (GabbleJingleSession *sess);
 void gabble_jingle_session_terminate (GabbleJingleSession *sess);
+void gabble_jingle_session_remove_content (GabbleJingleSession *sess,
+    const gchar *content_name);
+void gabble_jingle_session_change_direction (GabbleJingleSession *sess,
+    const gchar *content_name, JingleContentSenders senders);
+
+gboolean
+gabble_jingle_session_add_content (GabbleJingleSession *sess, const gchar *name,
+    const gchar *content_ns, const gchar *transport_ns);
+
+GType gabble_jingle_session_get_content_type (GabbleJingleSession *);
+GList *gabble_jingle_session_get_contents (GabbleJingleSession *sess);
 
 #endif /* __JINGLE_SESSION_H__ */
 
diff --git a/src/jingle-transport-google.c b/src/jingle-transport-google.c
index f2bc290..202fb57 100644
--- a/src/jingle-transport-google.c
+++ b/src/jingle-transport-google.c
@@ -57,18 +57,18 @@ static guint signals[LAST_SIGNAL] = {0};
 /* properties */
 enum
 {
-  PROP_CONNECTION,
-  PROP_SESSION,
-  PROP_CONTENT,
+  PROP_CONTENT = 1,
+  PROP_TRANSPORT_NS,
+  PROP_STATE,
   LAST_PROPERTY
 };
 
 typedef struct _GabbleJingleTransportGooglePrivate GabbleJingleTransportGooglePrivate;
 struct _GabbleJingleTransportGooglePrivate
 {
-  GabbleConnection *conn;
-  GabbleJingleSession *session;
   GabbleJingleContent *content;
+  JingleTransportState state;
+  gchar *transport_ns;
 
   GList *local_candidates;
   // GList *remote_candidates;
@@ -127,6 +127,9 @@ gabble_jingle_transport_google_dispose (GObject *object)
   _free_candidates (priv->local_candidates);
   priv->local_candidates = NULL;
 
+  g_free (priv->transport_ns);
+  priv->transport_ns = NULL;
+
   if (G_OBJECT_CLASS (gabble_jingle_transport_google_parent_class)->dispose)
     G_OBJECT_CLASS (gabble_jingle_transport_google_parent_class)->dispose (object);
 }
@@ -141,15 +144,15 @@ gabble_jingle_transport_google_get_property (GObject *object,
   GabbleJingleTransportGooglePrivate *priv = GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (trans);
 
   switch (property_id) {
-    case PROP_CONNECTION:
-      g_value_set_object (value, priv->conn);
-      break;
-    case PROP_SESSION:
-      g_value_set_object (value, priv->session);
-      break;
     case PROP_CONTENT:
       g_value_set_object (value, priv->content);
       break;
+    case PROP_TRANSPORT_NS:
+      g_value_set_string (value, priv->transport_ns);
+      break;
+    case PROP_STATE:
+      g_value_set_uint (value, priv->state);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -167,15 +170,16 @@ gabble_jingle_transport_google_set_property (GObject *object,
       GABBLE_JINGLE_TRANSPORT_GOOGLE_GET_PRIVATE (trans);
 
   switch (property_id) {
-    case PROP_CONNECTION:
-      priv->conn = g_value_get_object (value);
-      break;
-    case PROP_SESSION:
-      priv->session = g_value_get_object (value);
-      break;
     case PROP_CONTENT:
       priv->content = g_value_get_object (value);
       break;
+    case PROP_TRANSPORT_NS:
+      g_free (priv->transport_ns);
+      priv->transport_ns = g_value_dup_string (value);
+      break;
+    case PROP_STATE:
+      priv->state = g_value_get_uint (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -195,33 +199,35 @@ gabble_jingle_transport_google_class_init (GabbleJingleTransportGoogleClass *cls
   object_class->dispose = gabble_jingle_transport_google_dispose;
 
   /* property definitions */
-  param_spec = g_param_spec_object ("connection", "GabbleConnection object",
-                                    "Gabble connection object used for exchanging "
-                                    "messages.",
-                                    GABBLE_TYPE_CONNECTION,
-                                    G_PARAM_CONSTRUCT_ONLY |
-                                    G_PARAM_READWRITE |
-                                    G_PARAM_STATIC_NICK |
-                                    G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
-  param_spec = g_param_spec_object ("session", "GabbleJingleSession object",
-                                    "The session using this transport object.",
-                                    GABBLE_TYPE_JINGLE_SESSION,
+  param_spec = g_param_spec_object ("content", "GabbleJingleContent object",
+                                    "Jingle content object using this transport.",
+                                    GABBLE_TYPE_JINGLE_CONTENT,
                                     G_PARAM_CONSTRUCT_ONLY |
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NICK |
                                     G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_SESSION, param_spec);
+  g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
 
-  param_spec = g_param_spec_object ("content", "GabbleJingleContent object",
-                                    "Jingle content object using this transport.",
-                                    GABBLE_TYPE_JINGLE_CONTENT,
+  param_spec = g_param_spec_string ("transport-ns", "Transport namespace",
+                                    "Namespace identifying the transport type.",
+                                    NULL,
                                     G_PARAM_CONSTRUCT_ONLY |
                                     G_PARAM_READWRITE |
                                     G_PARAM_STATIC_NICK |
                                     G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
+  g_object_class_install_property (object_class, PROP_TRANSPORT_NS, param_spec);
+
+  param_spec = g_param_spec_uint ("state",
+                                  "Connection state for the transport.",
+                                  "Enum specifying the connection state of the transport.",
+                                  JINGLE_TRANSPORT_STATE_DISCONNECTED,
+                                  JINGLE_TRANSPORT_STATE_CONNECTED,
+                                  JINGLE_TRANSPORT_STATE_DISCONNECTED,
+                                  G_PARAM_READWRITE |
+                                  G_PARAM_STATIC_NAME |
+                                  G_PARAM_STATIC_NICK |
+                                  G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_STATE, param_spec);
 
   /* signal definitions */
   signals[NEW_CANDIDATES] = g_signal_new (
@@ -234,25 +240,6 @@ gabble_jingle_transport_google_class_init (GabbleJingleTransportGoogleClass *cls
 
 }
 
-static GabbleJingleTransportIface *
-new_transport (GabbleJingleContent *content)
-{
-  GabbleJingleTransportGoogle *self;
-  GabbleJingleSession *sess;
-  GabbleConnection *conn;
-
-  g_object_get (content, "connection", &conn,
-      "session", &sess, NULL);
-
-  self = g_object_new (GABBLE_TYPE_JINGLE_TRANSPORT_GOOGLE,
-    "connection", conn,
-    "session", sess,
-    "content", content,
-    NULL);
-
-  return GABBLE_JINGLE_TRANSPORT_IFACE (self);
-}
-
 #define SET_BAD_REQ(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_BAD_REQUEST, txt)
 #define SET_OUT_ORDER(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_JINGLE_OUT_OF_ORDER, txt)
 #define SET_CONFLICT(txt...) g_set_error (error, GABBLE_XMPP_ERROR, XMPP_ERROR_CONFLICT, txt)
@@ -277,13 +264,13 @@ parse_candidates (GabbleJingleTransportIface *obj,
 
       cnode = transport_node;
 
-      g_object_get (priv->session, "dialect", &dialect, NULL);
+      g_object_get (priv->content->session, "dialect", &dialect, NULL);
 
       if (dialect == JINGLE_DIALECT_GTALK4)
         {
           /* FIXME: do we need to do anything more than retransmit
            * local candidates and mode switch? */
-          g_object_set (priv->session, "dialect",
+          g_object_set (priv->content->session, "dialect",
               JINGLE_DIALECT_GTALK3, NULL);
 
           transmit_candidates (t, priv->local_candidates);
@@ -441,10 +428,10 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
   LmMessage *msg;
   LmMessageNode *trans_node, *sess_node;
 
-  msg = jingle_session_new_message (priv->session,
+  msg = gabble_jingle_session_new_message (priv->content->session,
     JINGLE_ACTION_TRANSPORT_INFO, &sess_node);
 
-  g_object_get (priv->session, "dialect", &dialect, NULL);
+  g_object_get (priv->content->session, "dialect", &dialect, NULL);
 
   if (dialect == JINGLE_DIALECT_GTALK3)
     {
@@ -452,8 +439,8 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
     }
   else
     {
-      trans_node = lm_message_node_add_child (sess_node, "transport",
-        NS_GOOGLE_SESSION);
+      trans_node = lm_message_node_add_child (sess_node, "transport", NULL);
+      lm_message_node_set_attribute (trans_node, "xmlns", NS_GOOGLE_TRANSPORT_P2P);
     }
 
   for (li = candidates; li; li = li->next)
@@ -508,7 +495,7 @@ transmit_candidates (GabbleJingleTransportGoogle *transport, GList *candidates)
           NULL);
     }
 
-  _gabble_connection_send (priv->conn, msg, NULL);
+  _gabble_connection_send (priv->content->conn, msg, NULL);
 }
 
 static void
@@ -531,6 +518,7 @@ transport_iface_init (gpointer g_iface, gpointer iface_data)
   GabbleJingleTransportIfaceClass *klass = (GabbleJingleTransportIfaceClass *) g_iface;
 
   klass->parse_candidates = parse_candidates;
+  // FIXME: klass->produce = produce_candidates;
   klass->add_candidates = add_candidates;
 }
 
@@ -538,11 +526,12 @@ void
 jingle_transport_google_register (GabbleJingleFactory *factory)
 {
   /* GTalk libjingle0.3 dialect */
-  gabble_jingle_factory_register_transport (factory, NULL,
-      new_transport);
+  gabble_jingle_factory_register_transport (factory, "",
+      GABBLE_TYPE_JINGLE_TRANSPORT_GOOGLE);
 
   /* GTalk libjingle0.4 dialect */
   gabble_jingle_factory_register_transport (factory,
-      NS_GOOGLE_SESSION, new_transport);
+      NS_GOOGLE_TRANSPORT_P2P,
+      GABBLE_TYPE_JINGLE_TRANSPORT_GOOGLE);
 }
 
diff --git a/src/jingle-transport-iface.c b/src/jingle-transport-iface.c
index eef5a71..1388f6f 100644
--- a/src/jingle-transport-iface.c
+++ b/src/jingle-transport-iface.c
@@ -20,16 +20,17 @@
 #include "jingle-transport-iface.h"
 #include "connection.h"
 #include "jingle-session.h"
+#include "jingle-content.h"
 
 #include <glib.h>
 
 void
-gabble_jingle_transport_iface_parse (GabbleJingleTransportIface *self,
+gabble_jingle_transport_iface_parse_candidates (GabbleJingleTransportIface *self,
     LmMessageNode *node, GError **error)
 {
   void (*virtual_method)(GabbleJingleTransportIface *, 
       LmMessageNode *, GError **) =
-    GABBLE_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->parse;
+    GABBLE_JINGLE_TRANSPORT_IFACE_GET_CLASS (self)->parse_candidates;
 
   g_assert (virtual_method != NULL);
   return virtual_method (self, node, error);
@@ -70,10 +71,10 @@ gabble_jingle_transport_iface_base_init (gpointer klass)
       GParamSpec *param_spec;
 
       param_spec = g_param_spec_object (
-          "connection",
-          "GabbleConnection object",
-          "Gabble connection object that owns this jingle transport object.",
-          GABBLE_TYPE_CONNECTION,
+          "content",
+          "GabbleJingleContent object",
+          "Jingle content that's using this jingle transport object.",
+          GABBLE_TYPE_JINGLE_CONTENT,
           G_PARAM_CONSTRUCT_ONLY |
           G_PARAM_READWRITE |
           G_PARAM_STATIC_NAME |
@@ -81,11 +82,11 @@ gabble_jingle_transport_iface_base_init (gpointer klass)
           G_PARAM_STATIC_BLURB);
       g_object_interface_install_property (klass, param_spec);
 
-      param_spec = g_param_spec_object (
-          "session",
-          "GabbleJingleSession object",
-          "Jingle session that's using this jingle transport object.",
-          GABBLE_TYPE_JINGLE_SESSION,
+      param_spec = g_param_spec_string (
+          "transport-ns",
+          "Transport namespace",
+          "Namespace identifying the transport type.",
+          NULL,
           G_PARAM_CONSTRUCT_ONLY |
           G_PARAM_READWRITE |
           G_PARAM_STATIC_NAME |
@@ -93,6 +94,20 @@ gabble_jingle_transport_iface_base_init (gpointer klass)
           G_PARAM_STATIC_BLURB);
       g_object_interface_install_property (klass, param_spec);
 
+      param_spec = g_param_spec_uint (
+          "state",
+          "Connection state for the transport.",
+          "Enum specifying the connection state of the transport.",
+          JINGLE_TRANSPORT_STATE_DISCONNECTED,
+          JINGLE_TRANSPORT_STATE_CONNECTED,
+          JINGLE_TRANSPORT_STATE_DISCONNECTED,
+          G_PARAM_READWRITE |
+          G_PARAM_STATIC_NAME |
+          G_PARAM_STATIC_NICK |
+          G_PARAM_STATIC_BLURB);
+
+      g_object_interface_install_property (klass, param_spec);
+
       initialized = TRUE;
     }
 }
diff --git a/src/jingle-transport-iface.h b/src/jingle-transport-iface.h
index 9733dbd..440a54f 100644
--- a/src/jingle-transport-iface.h
+++ b/src/jingle-transport-iface.h
@@ -27,13 +27,20 @@
 
 G_BEGIN_DECLS
 
+typedef enum
+{
+  JINGLE_TRANSPORT_STATE_DISCONNECTED,
+  JINGLE_TRANSPORT_STATE_CONNECTING,
+  JINGLE_TRANSPORT_STATE_CONNECTED
+} JingleTransportState;
+
 typedef struct _GabbleJingleTransportIface GabbleJingleTransportIface;
 typedef struct _GabbleJingleTransportIfaceClass GabbleJingleTransportIfaceClass;
 
 struct _GabbleJingleTransportIfaceClass {
   GTypeInterface parent;
 
-  void (*parse) (GabbleJingleTransportIface *,
+  void (*parse_candidates) (GabbleJingleTransportIface *,
     LmMessageNode *, GError **);
   void (*produce) (GabbleJingleTransportIface *,
     LmMessageNode *);
@@ -55,7 +62,8 @@ GType gabble_jingle_transport_iface_get_type (void);
   (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GABBLE_TYPE_JINGLE_TRANSPORT_IFACE,\
                               GabbleJingleTransportIfaceClass))
 
-void gabble_jingle_transport_iface_parse (GabbleJingleTransportIface *, LmMessageNode *, GError **);
+void gabble_jingle_transport_iface_parse_candidates (GabbleJingleTransportIface *,
+    LmMessageNode *, GError **);
 void gabble_jingle_transport_iface_produce (GabbleJingleTransportIface *, LmMessageNode *);
 void gabble_jingle_transport_iface_add_candidates (GabbleJingleTransportIface *, GList *);
 
diff --git a/src/media-channel.c b/src/media-channel.c
index 43f50af..c9d96e3 100644
--- a/src/media-channel.c
+++ b/src/media-channel.c
@@ -30,6 +30,7 @@
 #include <telepathy-glib/channel-iface.h>
 #include <telepathy-glib/svc-channel.h>
 #include <telepathy-glib/svc-properties-interface.h>
+#include <telepathy-glib/svc-media-interfaces.h>
 
 #include "extensions/extensions.h"
 
@@ -45,11 +46,17 @@
 #include "presence-cache.h"
 #include "presence.h"
 
+#include "jingle-factory.h"
+#include "jingle-session.h"
+#include "jingle-content.h"
+#include "jingle-media-rtp.h"
+
 static void call_state_iface_init (gpointer, gpointer);
 static void channel_iface_init (gpointer, gpointer);
 static void hold_iface_init (gpointer, gpointer);
 static void media_signalling_iface_init (gpointer, gpointer);
 static void streamed_media_iface_init (gpointer, gpointer);
+static void session_handler_iface_init (gpointer, gpointer);
 
 G_DEFINE_TYPE_WITH_CODE (GabbleMediaChannel, gabble_media_channel,
     G_TYPE_OBJECT,
@@ -69,6 +76,8 @@ G_DEFINE_TYPE_WITH_CODE (GabbleMediaChannel, gabble_media_channel,
       tp_properties_mixin_iface_init);
     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
       tp_dbus_properties_mixin_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_SESSION_HANDLER,
+      session_handler_iface_init);
     G_IMPLEMENT_INTERFACE (GABBLE_TYPE_EXPORTABLE_CHANNEL, NULL);
     G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
 
@@ -82,6 +91,7 @@ static const gchar *gabble_media_channel_interfaces[] = {
     TP_IFACE_CHANNEL_INTERFACE_HOLD,
     TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
     TP_IFACE_PROPERTIES_INTERFACE,
+    TP_IFACE_MEDIA_SESSION_HANDLER,
     NULL
 };
 
@@ -106,6 +116,7 @@ enum
   PROP_STUN_SERVER,
   PROP_STUN_PORT,
   PROP_GTALK_P2P_RELAY_TOKEN,
+  PROP_SESSION,
   LAST_PROPERTY
 };
 
@@ -134,7 +145,7 @@ struct _GabbleMediaChannelPrivate
   TpHandle creator;
 
   GabbleMediaFactory *factory;
-  GabbleMediaSession *session;
+  GabbleJingleSession *session;
   GPtrArray *streams;
 
   guint next_stream_id;
@@ -142,6 +153,7 @@ struct _GabbleMediaChannelPrivate
   TpLocalHoldState hold_state;
   TpLocalHoldStateReason hold_state_reason;
 
+  gboolean ready:1;
   gboolean closed:1;
   gboolean dispose_has_run:1;
 };
@@ -163,6 +175,148 @@ gabble_media_channel_init (GabbleMediaChannel *self)
         GabbleMediaChannel, properties));
 }
 
+static void session_state_changed_cb (GabbleJingleSession *session,
+    GParamSpec *arg1, GabbleMediaChannel *channel);
+static void session_stream_added_cb (GabbleJingleSession *session,
+    GabbleMediaStream  *stream, GabbleMediaChannel *chan);
+static void session_terminated_cb (GabbleJingleSession *session,
+    gboolean local_terminator, gpointer user_data);
+
+
+static void
+_ensure_session (GabbleMediaChannel *chan,
+    TpHandle peer, const gchar *peer_resource, GError **error)
+{
+  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+
+  if (priv->session != NULL)
+    {
+      TpIntSet *set;
+      GList *contents, *li;
+
+      DEBUG ("%p: Latching onto incoming session %p", chan, priv->session);
+
+      /* make us local pending */
+      set = tp_intset_new ();
+      tp_intset_add (set, ((TpBaseConnection *) priv->conn)->self_handle);
+
+      tp_group_mixin_change_members (G_OBJECT (chan),
+          "", NULL, NULL, set, NULL, priv->session->peer, 0);
+
+      tp_intset_destroy (set);
+
+      /* and update flags accordingly */
+      tp_group_mixin_change_flags (G_OBJECT (chan),
+          TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE,
+          0);
+
+      priv->streams = g_ptr_array_sized_new (1);
+
+      /* FIXME: put these in separate function and execute afterwards? */
+      /* For each supported content in the session, create accompanying MediaStream. */
+      contents = gabble_jingle_session_get_contents (priv->session);
+      for (li = contents; li; li = li->next)
+        {
+          GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (li->data);
+          if (G_OBJECT_TYPE (c) == GABBLE_TYPE_JINGLE_MEDIA_RTP)
+            {
+              GabbleMediaStream *stream;
+              gchar *name;
+              guint id = _gabble_media_channel_get_stream_id (chan);
+              gchar *object_path = g_strdup_printf ("%s/MediaStream%u",
+                  priv->object_path, id);
+
+              g_object_get (c, "name", &name, NULL);
+
+              stream = g_object_new (GABBLE_TYPE_MEDIA_STREAM,
+                  "object-path", object_path,
+                  "content", c,
+                  "name", name,
+                  NULL);
+
+              /* FIXME port functions to media chan from media session
+              g_signal_connect (stream, "notify::connection-state",
+                                (GCallback) stream_connection_state_changed_cb,
+                                session);
+              g_signal_connect (stream, "notify::got-local-codecs",
+                                (GCallback) stream_got_local_codecs_changed_cb,
+                                session);
+              */
+              g_ptr_array_add (priv->streams, stream);
+
+              /* fugly hack, clean it up! */
+              session_stream_added_cb (priv->session, stream, chan);
+
+              /* all of the streams are bidirectional from farsight's point of view, it's
+               * just in the signalling they change */
+              tp_svc_media_session_handler_emit_new_stream_handler (chan,
+                  object_path, id, TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL);
+
+              g_free (object_path);
+
+              /*  FIXME copyypasted from media session
+              if (priv->ready) {
+                // _emit_new_stream (session, stream);
+              }A
+              */
+            }
+        }
+
+      g_list_free (contents);
+    }
+  else
+    {
+      GabblePresence *presence;
+#ifdef ENABLE_DEBUG
+      TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+      TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (
+          conn, TP_HANDLE_TYPE_CONTACT);
+#endif
+
+      DEBUG ("%p: Creating new outgoing session", chan);
+
+      presence = gabble_presence_cache_get (priv->conn->presence_cache, peer);
+
+      if (presence == NULL)
+        {
+          DEBUG ("failed to add contact %d (%s) to media channel: "
+              "no presence available", peer,
+              tp_handle_inspect (contact_handles, peer));
+          goto NO_CAPS;
+        }
+
+      if ((_gabble_media_channel_caps_to_typeflags (presence->caps) &
+            (TP_CHANNEL_MEDIA_CAPABILITY_AUDIO |
+             TP_CHANNEL_MEDIA_CAPABILITY_VIDEO)) == 0)
+        {
+          DEBUG ("failed to add contact %d (%s) to media channel: "
+              "caps %x aren't sufficient", peer,
+              tp_handle_inspect (contact_handles, peer),
+              presence->caps);
+          goto NO_CAPS;
+        }
+
+      priv->session = gabble_jingle_factory_create_session (
+          priv->conn->jingle_factory, peer, peer_resource);
+
+      priv->streams = g_ptr_array_sized_new (1);
+    }
+
+  g_signal_connect (priv->session, "terminated",
+    (GCallback) session_terminated_cb, chan);
+
+  /* FIXME is it complete? */
+  tp_svc_channel_interface_media_signalling_emit_new_session_handler (
+      G_OBJECT (chan), priv->object_path, "rtp");
+
+  return;
+
+NO_CAPS:
+  g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+      "handle %u has no media capabilities", peer);
+  return;
+}
+
 static GObject *
 gabble_media_channel_constructor (GType type, guint n_props,
                                   GObjectConstructParam *props)
@@ -189,6 +343,11 @@ gabble_media_channel_constructor (GType type, guint n_props,
   tp_group_mixin_init (obj, G_STRUCT_OFFSET (GabbleMediaChannel, group),
       contact_handles, conn->self_handle);
 
+  if (priv->session != NULL)
+      priv->creator = priv->session->peer;
+  else
+      priv->creator = conn->self_handle;
+
   /* automatically add creator to channel, but also ref them again (because
    * priv->creator is the InitiatorHandle) */
   g_assert (priv->creator != 0);
@@ -205,26 +364,26 @@ gabble_media_channel_constructor (GType type, guint n_props,
   tp_group_mixin_change_flags (obj,
       TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
 
+  /* act on incoming session */
+  if (priv->session != NULL)
+    {
+      _ensure_session (GABBLE_MEDIA_CHANNEL (obj), 0, NULL, NULL);
+    }
+
   return obj;
 }
 
-static void session_state_changed_cb (GabbleMediaSession *session,
-    GParamSpec *arg1, GabbleMediaChannel *channel);
-static void session_stream_added_cb (GabbleMediaSession *session,
-    GabbleMediaStream  *stream, GabbleMediaChannel *chan);
-static void session_terminated_cb (GabbleMediaSession *session,
-    guint terminator, guint reason, gpointer user_data);
 
 /**
  * create_session
  *
- * Creates a GabbleMediaSession object for given peer.
+ * Creates a GabbleJingleSession object for given peer.
  *
  * If sid is set to NULL a unique sid is generated and
  * the "initiator" property of the newly created
- * GabbleMediaSession is set to our own handle.
+ * GabbleJingleSession is set to our own handle.
  */
-static GabbleMediaSession *
+static GabbleJingleSession *
 create_session (GabbleMediaChannel *channel,
                 TpHandle peer,
                 const gchar *peer_resource,
@@ -232,7 +391,7 @@ create_session (GabbleMediaChannel *channel,
                 GError **error)
 {
   GabbleMediaChannelPrivate *priv;
-  GabbleMediaSession *session;
+  GabbleJingleSession *session;
   gchar *object_path;
   JingleInitiator initiator;
 
@@ -277,15 +436,18 @@ create_session (GabbleMediaChannel *channel,
           goto NO_CAPS;
         }
 
-      sid = _gabble_media_factory_allocate_sid (priv->factory, channel);
+      // FIXME sid = _gabble_media_factory_allocate_sid (priv->factory, channel);
+      sid = NULL;
+      g_assert_not_reached ();
     }
   else
     {
       initiator = INITIATOR_REMOTE;
-      _gabble_media_factory_register_sid (priv->factory, sid, channel);
+      g_assert_not_reached ();
+      // FIXME _gabble_media_factory_register_sid (priv->factory, sid, channel);
     }
 
-  session = g_object_new (GABBLE_TYPE_MEDIA_SESSION,
+  session = g_object_new (GABBLE_TYPE_JINGLE_SESSION,
                           "connection", priv->conn,
                           "media-channel", channel,
                           "object-path", object_path,
@@ -330,7 +492,7 @@ _gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
                                                GError **error)
 {
   GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
-  GabbleMediaSession *session = priv->session;
+  GabbleJingleSession *session = priv->session;
   gboolean session_is_new = FALSE;
 
   /* If this assertion fails, create_session() would think we're the
@@ -363,6 +525,7 @@ _gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
 
   g_object_ref (session);
 
+  /* TODO
   if (_gabble_media_session_handle_action (session, message, session_node,
         action, error))
     {
@@ -377,7 +540,9 @@ _gabble_media_channel_dispatch_session_action (GabbleMediaChannel *chan,
 
       g_object_unref (session);
       return FALSE;
-    }
+    } */
+
+  return FALSE;
 }
 
 static void
@@ -446,6 +611,9 @@ gabble_media_channel_get_property (GObject    *object,
               GABBLE_IFACE_CHANNEL_FUTURE, "Requested",
               NULL));
       break;
+    case PROP_SESSION:
+      g_value_set_object (value, priv->session);
+      break;
     default:
       param_name = g_param_spec_get_name (pspec);
 
@@ -498,6 +666,9 @@ gabble_media_channel_set_property (GObject     *object,
     case PROP_FACTORY:
       priv->factory = g_value_get_object (value);
       break;
+    case PROP_SESSION:
+      priv->session = g_value_dup_object (value);
+      break;
     default:
       param_name = g_param_spec_get_name (pspec);
 
@@ -660,6 +831,13 @@ gabble_media_channel_class_init (GabbleMediaChannelClass *gabble_media_channel_c
   g_object_class_install_property (object_class, PROP_GTALK_P2P_RELAY_TOKEN,
       param_spec);
 
+  param_spec = g_param_spec_object ("session", "GabbleJingleSession object",
+      "Jingle session associated with this media channel object.",
+      GABBLE_TYPE_JINGLE_SESSION,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_SESSION, param_spec);
+
   tp_properties_mixin_class_init (object_class,
       G_STRUCT_OFFSET (GabbleMediaChannelClass, properties_class),
       channel_property_signatures, NUM_CHAN_PROPS, NULL);
@@ -758,8 +936,9 @@ gabble_media_channel_close (GabbleMediaChannel *self)
 
   if (priv->session)
     {
+      /* TODO
       _gabble_media_session_terminate (priv->session, INITIATOR_LOCAL,
-          TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+          TP_CHANNEL_GROUP_CHANGE_REASON_NONE); */
     }
 
   tp_svc_channel_emit_closed (self);
@@ -1028,9 +1207,10 @@ gabble_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
     }
 
   /* groovy, it's all good dude, let's remove them */
+  /* TODO
   if (stream_objs->len > 0)
     _gabble_media_session_remove_streams (priv->session, (GabbleMediaStream **)
-        stream_objs->pdata, stream_objs->len);
+        stream_objs->pdata, stream_objs->len); */
 
 OUT:
   g_ptr_array_free (stream_objs, TRUE);
@@ -1090,6 +1270,7 @@ gabble_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *if
   /* streams with no session? I think not... */
   g_assert (priv->session != NULL);
 
+  /* TODO
   if (_gabble_media_session_request_stream_direction (priv->session, stream,
         stream_direction, &error))
     {
@@ -1100,7 +1281,7 @@ gabble_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *if
     {
       dbus_g_method_return_error (context, error);
       g_error_free (error);
-    }
+    } */
 }
 
 
@@ -1138,8 +1319,8 @@ gabble_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
 
   if (priv->session == NULL)
     {
-      if (create_session (self, contact_handle, NULL, NULL, &error)
-          == NULL)
+      _ensure_session (self, contact_handle, NULL, &error);
+      if (error != NULL)
         {
           dbus_g_method_return_error (context, error);
           g_error_free (error);
@@ -1165,9 +1346,11 @@ gabble_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
 
   g_assert (priv->session != NULL);
 
+  /* TODO
   if (!_gabble_media_session_request_streams (priv->session, types, &streams,
         &error))
-    goto error;
+    goto error; */
+  streams = NULL;
 
   ret = make_stream_list (self, streams);
 
@@ -1265,7 +1448,7 @@ _gabble_media_channel_add_member (GObject *obj,
               0, TP_CHANNEL_GROUP_FLAG_CAN_ADD);
 
           /* signal acceptance */
-          _gabble_media_session_accept (priv->session);
+          gabble_jingle_session_accept (priv->session);
 
           return TRUE;
         }
@@ -1305,8 +1488,9 @@ gabble_media_channel_remove_member (GObject *obj,
       return FALSE;
     }
 
+  /* TODO
   _gabble_media_session_terminate (priv->session, INITIATOR_LOCAL,
-      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+      TP_CHANNEL_GROUP_CHANGE_REASON_NONE); */
 
   /* remove the member */
   set = tp_intset_new ();
@@ -1324,14 +1508,15 @@ gabble_media_channel_remove_member (GObject *obj,
 }
 
 static void
-session_terminated_cb (GabbleMediaSession *session,
-                       guint terminator,
-                       guint reason,
+session_terminated_cb (GabbleJingleSession *session,
+                       gboolean local_terminator,
                        gpointer user_data)
 {
   GabbleMediaChannel *channel = (GabbleMediaChannel *) user_data;
   GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (channel);
   TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
+  guint reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
+  guint terminator;
   gchar *sid;
   JingleSessionState state;
   TpHandle peer;
@@ -1342,6 +1527,11 @@ session_terminated_cb (GabbleMediaSession *session,
                 "peer", &peer,
                 NULL);
 
+  if (local_terminator)
+      terminator = mixin->self_handle;
+  else
+      terminator = peer;
+
   set = tp_intset_new ();
 
   /* remove us and the peer from the member list */
@@ -1358,7 +1548,7 @@ session_terminated_cb (GabbleMediaSession *session,
 
   /* free the session ID */
   g_object_get (priv->session, "session-id", &sid, NULL);
-  _gabble_media_factory_free_sid (priv->factory, sid);
+  // FIXME _gabble_media_factory_free_sid (priv->factory, sid);
   g_free (sid);
 
   /* unref streams */
@@ -1383,7 +1573,7 @@ session_terminated_cb (GabbleMediaSession *session,
 
 
 static void
-session_state_changed_cb (GabbleMediaSession *session,
+session_state_changed_cb (GabbleJingleSession *session,
                           GParamSpec *arg1,
                           GabbleMediaChannel *channel)
 {
@@ -1646,7 +1836,7 @@ stream_error_cb (GabbleMediaStream *stream,
                  const gchar *message,
                  GabbleMediaChannel *chan)
 {
-  GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
+  // GabbleMediaChannelPrivate *priv = GABBLE_MEDIA_CHANNEL_GET_PRIVATE (chan);
   guint id;
 
   /* emit signal */
@@ -1655,7 +1845,7 @@ stream_error_cb (GabbleMediaStream *stream,
       message);
 
   /* remove stream from session */
-  _gabble_media_session_remove_streams (priv->session, &stream, 1);
+  // TODO _gabble_media_session_remove_streams (priv->session, &stream, 1);
 }
 
 static void
@@ -1698,7 +1888,7 @@ stream_direction_changed_cb (GabbleMediaStream *stream,
 }
 
 static void
-session_stream_added_cb (GabbleMediaSession *session,
+session_stream_added_cb (GabbleJingleSession *session,
                          GabbleMediaStream  *stream,
                          GabbleMediaChannel *chan)
 {
@@ -1727,6 +1917,7 @@ session_stream_added_cb (GabbleMediaSession *session,
   g_object_get (session, "peer", &handle, NULL);
   g_object_get (stream, "id", &id, "media-type", &type, NULL);
 
+  DEBUG ("emitting stream added");
   tp_svc_channel_type_streamed_media_emit_stream_added (
       chan, id, handle, type);
 
@@ -1880,6 +2071,81 @@ gabble_media_channel_request_hold (TpSvcChannelInterfaceHold *iface,
   tp_svc_channel_interface_hold_return_from_request_hold (context);
 }
 
+static void
+gabble_media_channel_ready (TpSvcMediaSessionHandler *iface,
+                            DBusGMethodInvocation *context)
+{
+  GabbleMediaChannel *self = GABBLE_MEDIA_CHANNEL (iface);
+  GabbleMediaChannelPrivate *priv =
+    GABBLE_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+  if (!priv->ready)
+    {
+      // guint i;
+
+      priv->ready = TRUE;
+
+      /* FIXME 
+      for (i = 0; i < priv->streams->len; i++)
+        _emit_new_stream (self, g_ptr_array_index (priv->streams, i));
+        */
+    }
+
+  tp_svc_media_session_handler_return_from_ready (context);
+}
+
+static void
+gabble_media_channel_error (TpSvcMediaSessionHandler *iface,
+                            guint errno,
+                            const gchar *message,
+                            DBusGMethodInvocation *context)
+{
+    // FIXME!
+#if 0 
+  GabbleMediaChannel *self = GABBLE_MEDIA_CHANNEL (iface);
+  GabbleMediaChannelPrivate *priv;
+  GPtrArray *tmp;
+  guint i;
+
+  g_assert (GABBLE_IS_MEDIA_SESSION (self));
+
+  priv = GABBLE_MEDIA_SESSION_GET_PRIVATE (self);
+
+  GMS_DEBUG_INFO (self, "Media.SessionHandler::Error called, error %u (%s) -- "
+      "emitting error on each stream", errno, message);
+
+  if (priv->state == JS_STATE_ENDED)
+    {
+      tp_svc_media_session_handler_return_from_error (context);
+      return;
+    }
+  else if (priv->state == JS_STATE_PENDING_CREATED)
+    {
+      /* shortcut to prevent sending remove actions if we haven't sent an
+       * initiate yet */
+      g_object_set (self, "state", JS_STATE_ENDED, NULL);
+      tp_svc_media_session_handler_return_from_error (context);
+      return;
+    }
+
+  g_assert (priv->streams != NULL);
+
+  tmp = priv->streams;
+  priv->streams = NULL;
+
+  for (i = 0; i < tmp->len; i++)
+    {
+      GabbleMediaStream *stream = g_ptr_array_index (tmp, i);
+
+      gabble_media_stream_error (stream, errno, message, NULL);
+    }
+
+  g_ptr_array_free (tmp, TRUE);
+#endif
+
+  tp_svc_media_session_handler_return_from_error (context);
+}
+
 
 static void
 channel_iface_init (gpointer g_iface, gpointer iface_data)
@@ -1946,3 +2212,16 @@ hold_iface_init (gpointer g_iface,
   IMPLEMENT(request_hold);
 #undef IMPLEMENT
 }
+
+static void
+session_handler_iface_init (gpointer g_iface, gpointer iface_data)
+{
+  TpSvcMediaSessionHandlerClass *klass =
+    (TpSvcMediaSessionHandlerClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_media_session_handler_implement_##x (\
+    klass, gabble_media_channel_##x)
+  IMPLEMENT(error);
+  IMPLEMENT(ready);
+#undef IMPLEMENT
+}
diff --git a/src/media-factory.c b/src/media-factory.c
index bf5bd07..9e355f0 100644
--- a/src/media-factory.c
+++ b/src/media-factory.c
@@ -36,14 +36,16 @@
 #include "channel-manager.h"
 #include "connection.h"
 #include "debug.h"
+#include "jingle-factory.h"
+#include "jingle-session.h"
 #include "media-channel.h"
 #include "namespaces.h"
 #include "text-mixin.h"
 #include "util.h"
 
+#include "jingle-media-rtp.h"
+
 static void channel_manager_iface_init (gpointer, gpointer);
-static LmHandlerResult media_factory_jingle_cb (LmMessageHandler *,
-    LmConnection *, LmMessage *, gpointer);
 
 G_DEFINE_TYPE_WITH_CODE (GabbleMediaFactory, gabble_media_factory,
     G_TYPE_OBJECT,
@@ -62,8 +64,6 @@ struct _GabbleMediaFactoryPrivate
 {
   GabbleConnection *conn;
   gulong status_changed_id;
-  LmMessageHandler *jingle_cb;
-  LmMessageHandler *jingle_info_cb;
 
   GPtrArray *channels;
   guint channel_index;
@@ -90,9 +90,6 @@ gabble_media_factory_init (GabbleMediaFactory *fac)
   priv->channels = g_ptr_array_sized_new (1);
   priv->channel_index = 0;
 
-  priv->jingle_cb = NULL;
-  priv->jingle_info_cb = NULL;
-
   priv->conn = NULL;
   priv->dispose_has_run = FALSE;
 
@@ -116,9 +113,6 @@ gabble_media_factory_dispose (GObject *object)
   DEBUG ("dispose called");
   priv->dispose_has_run = TRUE;
 
-  g_assert (priv->jingle_cb == NULL);
-  g_assert (priv->jingle_info_cb == NULL);
-
   gabble_media_factory_close_all (fac);
   g_assert (priv->channels == NULL);
 
@@ -205,238 +199,11 @@ gabble_media_factory_class_init (GabbleMediaFactoryClass *gabble_media_factory_c
 
 }
 
-static gboolean _gabble_media_factory_sid_in_use (GabbleMediaFactory *fac,
-    const gchar *sid);
 static GabbleMediaChannel *new_media_channel (GabbleMediaFactory *fac,
-    TpHandle creator);
+    GabbleJingleSession *sess);
 static void media_channel_closed_cb (GabbleMediaChannel *chan,
     gpointer user_data);
 
-/**
- * media_factory_jingle_cb
- *
- * Called by loudmouth when we get an incoming <iq>. This handler
- * is concerned only with jingle session queries, and allows other
- * handlers to be called for other queries.
- */
-static LmHandlerResult
-media_factory_jingle_cb (LmMessageHandler *handler,
-                         LmConnection *lmconn,
-                         LmMessage *message,
-                         gpointer user_data)
-{
-  GabbleMediaFactory *fac = GABBLE_MEDIA_FACTORY (user_data);
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
-      (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
-  LmMessageNode *iq_node, *session_node;
-  const gchar *from, *id, *action, *sid, *resource;
-  TpHandle handle = 0;
-  GabbleMediaChannel *chan = NULL;
-  gboolean chan_is_new = FALSE;
-  GError *error = NULL;
-
-  g_assert (lmconn == priv->conn->lmconn);
-
-  /* all jingle actions are sets */
-  if (LM_MESSAGE_SUB_TYPE_SET != lm_message_get_sub_type (message))
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-  /* is it for us? */
-  iq_node = lm_message_get_node (message);
-  session_node = lm_message_node_get_child_with_namespace (message->node,
-      "jingle", NS_JINGLE);
-
-  if (session_node != NULL)
-    {
-      action = lm_message_node_get_attribute (session_node, "action");
-    }
-  else
-    {
-      session_node = lm_message_node_get_child_with_namespace (iq_node,
-          "session", NS_GOOGLE_SESSION);
-
-      if (session_node == NULL)
-        return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-      action = lm_message_node_get_attribute (session_node, "type");
-    }
-
-  if (action == NULL)
-    {
-      NODE_DEBUG (iq_node, "session action not found");
-      goto BAD_REQUEST;
-    }
-
-  from = lm_message_node_get_attribute (iq_node, "from");
-  if (from == NULL)
-    {
-      NODE_DEBUG (iq_node, "'from' attribute not found");
-      goto BAD_REQUEST;
-    }
-
-  handle = tp_handle_ensure (contact_repo, from, NULL, NULL);
-  if (handle == 0)
-    {
-      NODE_DEBUG (iq_node, "unable to get handle for sender");
-      goto BAD_REQUEST;
-    }
-
-  resource = strchr (from, '/');
-  if (resource == NULL || *resource == '\0')
-    {
-      NODE_DEBUG (iq_node, "sender with no resource");
-      goto BAD_REQUEST;
-    }
-  resource++;
-
-  id = lm_message_node_get_attribute (iq_node, "id");
-  if (id == NULL)
-    {
-      NODE_DEBUG (iq_node, "'id' attribute not found");
-      goto BAD_REQUEST;
-    }
-
-  /* does the session exist? */
-  sid = lm_message_node_get_attribute (session_node, "sid");
-  if (sid == NULL)
-    sid = lm_message_node_get_attribute (session_node, "id");
-
-  if (sid == NULL)
-    {
-      NODE_DEBUG (iq_node, "unable to get session id");
-      goto BAD_REQUEST;
-    }
-
-  if (_gabble_media_factory_sid_in_use (fac, sid))
-    {
-      /* if it's media session, we should have it in here */
-      chan = g_hash_table_lookup (priv->session_chans, sid);
-    }
-
-  /* it's a new session */
-  if (chan == NULL)
-    {
-      /* if the session is unknown, the only allowed action is "initiate" */
-      if (tp_strdiff (action, "initiate") &&
-          tp_strdiff (action, "session-initiate"))
-        {
-          NODE_DEBUG (iq_node,
-              "action is not \"initiate\" or \"session-initiate\", rejecting");
-          goto BAD_REQUEST;
-        }
-
-      DEBUG ("creating media channel");
-
-      chan = new_media_channel (fac, handle);
-      chan_is_new = TRUE;
-    }
-
-  g_assert (chan != NULL);
-
-  DEBUG ("dispatching to session %s", sid);
-  g_object_ref (chan);
-
-  if (_gabble_media_channel_dispatch_session_action (chan, handle, resource,
-        sid, message, session_node, action, &error))
-    {
-      if (chan_is_new)
-        {
-          gabble_channel_manager_emit_new_channel (fac,
-              GABBLE_EXPORTABLE_CHANNEL (chan), NULL);
-        }
-    }
-  else
-    {
-      if (chan_is_new)
-        gabble_media_channel_close (chan);
-
-      g_assert (error != NULL);
-      _gabble_connection_send_iq_error (priv->conn, message, error->code,
-          error->message);
-    }
-
-  g_object_unref (chan);
-  if (handle)
-    tp_handle_unref (contact_repo, handle);
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-
-BAD_REQUEST:
-  _gabble_connection_send_iq_error (
-    priv->conn, message, XMPP_ERROR_BAD_REQUEST, NULL);
-
-  if (handle)
-    tp_handle_unref (contact_repo, handle);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static const gchar *
-_gabble_media_factory_get_unique_sid (GabbleMediaFactory *fac)
-{
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  guint32 val;
-  gchar *sid = NULL;
-  gboolean unique = FALSE;
-
-  while (!unique)
-    {
-      val = g_random_int_range (1000000, G_MAXINT);
-
-      g_free (sid);
-      sid = g_strdup_printf ("%u", val);
-
-      unique = !_gabble_media_factory_sid_in_use (fac, sid);
-    }
-
-  g_hash_table_insert (priv->session_chans, sid, NULL);
-
-  return sid;
-}
-
-static gboolean
-_gabble_media_factory_sid_in_use (GabbleMediaFactory *fac, const gchar *sid)
-{
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  gpointer key, value;
-
-  return g_hash_table_lookup_extended (priv->session_chans, sid, &key, &value);
-}
-
-const gchar *
-_gabble_media_factory_allocate_sid (GabbleMediaFactory *fac,
-                                    GabbleMediaChannel *chan)
-{
-  const gchar *sid = _gabble_media_factory_get_unique_sid (fac);
-
-  g_return_val_if_fail (sid, NULL);
-
-  return _gabble_media_factory_register_sid (fac, sid, chan);
-}
-
-const gchar *
-_gabble_media_factory_register_sid (GabbleMediaFactory *fac,
-                                    const gchar *sid,
-                                    GabbleMediaChannel *chan)
-{
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  gchar *sid_copy = g_strdup (sid);
-
-  g_hash_table_replace (priv->session_chans, sid_copy, chan);
-
-  return sid_copy;
-}
-
-void
-_gabble_media_factory_free_sid (GabbleMediaFactory *fac, const gchar *sid)
-{
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  if (g_hash_table_lookup (priv->session_chans, sid))
-    {
-      g_hash_table_remove (priv->session_chans, sid);
-    }
-}
-
 static gboolean
 _remove_sid_mapping (gpointer key, gpointer value, gpointer user_data)
 {
@@ -486,7 +253,7 @@ media_channel_closed_cb (GabbleMediaChannel *chan, gpointer user_data)
  */
 static GabbleMediaChannel *
 new_media_channel (GabbleMediaFactory *fac,
-                   TpHandle creator)
+                   GabbleJingleSession *sess)
 {
   GabbleMediaFactoryPrivate *priv;
   TpBaseConnection *conn;
@@ -494,7 +261,6 @@ new_media_channel (GabbleMediaFactory *fac,
   gchar *object_path;
 
   g_assert (GABBLE_IS_MEDIA_FACTORY (fac));
-  g_assert (creator != 0);
 
   priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
   conn = (TpBaseConnection *) priv->conn;
@@ -507,7 +273,7 @@ new_media_channel (GabbleMediaFactory *fac,
                        "connection", priv->conn,
                        "factory", fac,
                        "object-path", object_path,
-                       "creator", creator,
+                       "session", sess,
                        NULL);
 
   if (priv->stun_server != NULL)
@@ -535,146 +301,6 @@ new_media_channel (GabbleMediaFactory *fac,
   return chan;
 }
 
-
-static void
-jingle_info_send_request (GabbleMediaFactory *fac)
-{
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  TpBaseConnection *base = (TpBaseConnection *) priv->conn;
-  LmMessage *msg;
-  LmMessageNode *node;
-  const gchar *jid;
-  GError *error = NULL;
-  TpHandleRepoIface *contact_handles = tp_base_connection_get_handles (base,
-      TP_HANDLE_TYPE_CONTACT);
-
-  jid = tp_handle_inspect (contact_handles, base->self_handle);
-  msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ,
-      LM_MESSAGE_SUB_TYPE_GET);
-
-  node = lm_message_node_add_child (msg->node, "query", NULL);
-  lm_message_node_set_attribute (node, "xmlns", NS_GOOGLE_JINGLE_INFO);
-
-  if (!_gabble_connection_send (priv->conn, msg, &error))
-    {
-      DEBUG ("jingle info send failed: %s\n", error->message);
-      g_error_free (error);
-    }
-
-  lm_message_unref (msg);
-}
-
-
-/**
- * jingle_info_iq_callback
- *
- * Called by loudmouth when we get an incoming <iq>. This handler
- * is concerned only with Jingle info queries.
- */
-static LmHandlerResult
-jingle_info_iq_callback (LmMessageHandler *handler,
-                         LmConnection *lmconn,
-                         LmMessage *message,
-                         gpointer user_data)
-{
-  GabbleMediaFactory *fac = GABBLE_MEDIA_FACTORY (user_data);
-  GabbleMediaFactoryPrivate *priv = GABBLE_MEDIA_FACTORY_GET_PRIVATE (fac);
-  LmMessageSubType sub_type;
-  LmMessageNode *query_node, *node;
-
-  query_node = lm_message_node_get_child_with_namespace (message->node,
-      "query", NS_GOOGLE_JINGLE_INFO);
-
-  if (query_node == NULL)
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-  sub_type = lm_message_get_sub_type (message);
-
-  if (sub_type == LM_MESSAGE_SUB_TYPE_ERROR)
-    {
-      GabbleXmppError xmpp_error = XMPP_ERROR_UNDEFINED_CONDITION;
-
-      node = lm_message_node_get_child (message->node, "error");
-      if (node != NULL)
-        {
-          xmpp_error = gabble_xmpp_error_from_node (node);
-        }
-
-      DEBUG ("jingle info error: %s", gabble_xmpp_error_string (xmpp_error));
-
-      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-    }
-
-  if (sub_type != LM_MESSAGE_SUB_TYPE_RESULT &&
-      sub_type != LM_MESSAGE_SUB_TYPE_SET)
-    {
-      DEBUG ("jingle info: unexpected IQ type, ignoring");
-
-      return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-    }
-
-  if (priv->get_stun_from_jingle)
-    node = lm_message_node_get_child (query_node, "stun");
-  else
-    node = NULL;
-
-  if (node != NULL)
-    {
-      node = lm_message_node_get_child (node, "server");
-
-      if (node != NULL)
-        {
-          const gchar *server;
-          const gchar *port;
-
-          server = lm_message_node_get_attribute (node, "host");
-          port = lm_message_node_get_attribute (node, "udp");
-
-          if (server != NULL)
-            {
-              DEBUG ("jingle info: got stun server %s", server);
-              g_free (priv->stun_server);
-              priv->stun_server = g_strdup (server);
-            }
-
-          if (port != NULL)
-            {
-              DEBUG ("jingle info: got stun port %s", port);
-              priv->stun_port = atoi (port);
-            }
-        }
-    }
-
-  node = lm_message_node_get_child (query_node, "relay");
-
-  if (node != NULL)
-    {
-      node = lm_message_node_get_child (node, "token");
-
-      if (node != NULL)
-        {
-          const gchar *token;
-
-          token = lm_message_node_get_value (node);
-
-          if (token != NULL)
-            {
-              DEBUG ("jingle info: got relay token %s", token);
-              g_free (priv->relay_token);
-              priv->relay_token = g_strdup (token);
-            }
-        }
-    }
-
-  if (sub_type == LM_MESSAGE_SUB_TYPE_SET)
-    {
-      _gabble_connection_acknowledge_set_iq (priv->conn, message);
-    }
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-
 static void
 gabble_media_factory_close_all (GabbleMediaFactory *fac)
 {
@@ -714,25 +340,22 @@ gabble_media_factory_close_all (GabbleMediaFactory *fac)
           priv->status_changed_id);
       priv->status_changed_id = 0;
     }
+}
+
+static void
+new_jingle_session_cb (GabbleJingleFactory *jf, GabbleJingleSession *sess, gpointer data)
+{
+  GabbleMediaFactory *self = GABBLE_MEDIA_FACTORY (data);
 
-  if (priv->jingle_cb != NULL)
+  if (gabble_jingle_session_get_content_type (sess) ==
+      GABBLE_TYPE_JINGLE_MEDIA_RTP)
     {
-      DEBUG ("removing callbacks");
-      g_assert (priv->jingle_info_cb != NULL);
-
-      lm_connection_unregister_message_handler (priv->conn->lmconn,
-          priv->jingle_cb, LM_MESSAGE_TYPE_IQ);
-      lm_message_handler_unref (priv->jingle_cb);
-      priv->jingle_cb = NULL;
-
-      lm_connection_unregister_message_handler (priv->conn->lmconn,
-          priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ);
-      lm_message_handler_unref (priv->jingle_info_cb);
-      priv->jingle_info_cb = NULL;
+      GabbleMediaChannel *chan = new_media_channel (self, sess);
+      gabble_channel_manager_emit_new_channel (self,
+          GABBLE_EXPORTABLE_CHANNEL (chan), NULL);
     }
 }
 
-
 static void
 connection_status_changed_cb (GabbleConnection *conn,
                               guint status,
@@ -744,25 +367,8 @@ connection_status_changed_cb (GabbleConnection *conn,
   switch (status)
     {
     case TP_CONNECTION_STATUS_CONNECTING:
-      g_assert (priv->conn != NULL);
-      g_assert (priv->conn->lmconn != NULL);
-
-      DEBUG ("adding callbacks");
-      g_assert (priv->jingle_cb == NULL);
-      g_assert (priv->jingle_info_cb == NULL);
-
-      priv->jingle_cb = lm_message_handler_new (media_factory_jingle_cb,
-          self, NULL);
-      lm_connection_register_message_handler (priv->conn->lmconn,
-          priv->jingle_cb, LM_MESSAGE_TYPE_IQ,
-          LM_HANDLER_PRIORITY_NORMAL);
-
-      priv->jingle_info_cb = lm_message_handler_new (
-          jingle_info_iq_callback, self, NULL);
-      lm_connection_register_message_handler (priv->conn->lmconn,
-          priv->jingle_info_cb, LM_MESSAGE_TYPE_IQ,
-          LM_HANDLER_PRIORITY_NORMAL);
-
+      g_signal_connect (priv->conn->jingle_factory, "new-session",
+          G_CALLBACK (new_jingle_session_cb), self);
       break;
 
     case TP_CONNECTION_STATUS_CONNECTED:
@@ -789,7 +395,7 @@ connection_status_changed_cb (GabbleConnection *conn,
           if (priv->conn->features &
               GABBLE_CONNECTION_FEATURES_GOOGLE_JINGLE_INFO)
             {
-              jingle_info_send_request (self);
+              // FIXME: jingle_info_send_request (self);
             }
         }
       break;
@@ -928,7 +534,7 @@ gabble_media_factory_request_channel (GabbleChannelManager *manager,
           goto error;
         }
 
-      channel = new_media_channel (self, conn->self_handle);
+      channel = new_media_channel (self, NULL);
       break;
 
     case TP_HANDLE_TYPE_CONTACT:
@@ -937,7 +543,7 @@ gabble_media_factory_request_channel (GabbleChannelManager *manager,
             handle, &error))
         goto error;
 
-      channel = new_media_channel (self, conn->self_handle);
+      channel = new_media_channel (self, NULL);
 
       if (!_gabble_media_channel_add_member ((GObject *) channel, handle,
             "", &error))
diff --git a/src/media-stream.c b/src/media-stream.c
index d28267f..2a2690b 100644
--- a/src/media-stream.c
+++ b/src/media-stream.c
@@ -45,6 +45,8 @@
 #include "media-session.h"
 #include "namespaces.h"
 
+#include "jingle-content.h"
+
 static void stream_handler_iface_init (gpointer, gpointer);
 
 G_DEFINE_TYPE_WITH_CODE(GabbleMediaStream,
@@ -88,6 +90,7 @@ enum
   PROP_PLAYING,
   PROP_COMBINED_DIRECTION,
   PROP_LOCAL_HOLD,
+  PROP_CONTENT,
   LAST_PROPERTY
 };
 
@@ -95,6 +98,8 @@ enum
 
 struct _GabbleMediaStreamPrivate
 {
+  GabbleJingleContent *content;
+
   GabbleConnection *conn;
   GabbleMediaSession *session;
   GabbleMediaSessionMode mode;
@@ -245,6 +250,9 @@ gabble_media_stream_get_property (GObject    *object,
     case PROP_LOCAL_HOLD:
       g_value_set_boolean (value, priv->local_hold);
       break;
+    case PROP_CONTENT:
+      g_value_set_object (value, priv->content);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -288,7 +296,7 @@ gabble_media_stream_set_property (GObject      *object,
       priv->media_type = g_value_get_uint (value);
       break;
     case PROP_CONNECTION_STATE:
-      GMS_DEBUG_INFO (priv->session, "stream %s connection state %d",
+      DEBUG ("stream %s connection state %d",
           stream->name, stream->connection_state);
       stream->connection_state = g_value_get_uint (value);
       break;
@@ -302,7 +310,7 @@ gabble_media_stream_set_property (GObject      *object,
         {
           StreamSignallingState old = stream->signalling_state;
           stream->signalling_state = g_value_get_uint (value);
-          GMS_DEBUG_INFO (priv->session, "stream %s sig_state %d->%d",
+          DEBUG ("stream %s sig_state %d->%d",
               stream->name, old, stream->signalling_state);
           if (stream->signalling_state != old)
             push_native_candidates (stream);
@@ -319,6 +327,9 @@ gabble_media_stream_set_property (GObject      *object,
     case PROP_COMBINED_DIRECTION:
       stream->combined_direction = g_value_get_uint (value);
       break;
+    case PROP_CONTENT:
+      priv->content = g_value_get_object (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -504,6 +515,15 @@ gabble_media_stream_class_init (GabbleMediaStreamClass *gabble_media_stream_clas
       G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK);
   g_object_class_install_property (object_class, PROP_LOCAL_HOLD, param_spec);
 
+  param_spec = g_param_spec_object ("content", "GabbleJingleContent object",
+                                    "Jingle content signalling this media stream.",
+                                    GABBLE_TYPE_JINGLE_CONTENT,
+                                    G_PARAM_CONSTRUCT_ONLY |
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_STATIC_NICK |
+                                    G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_CONTENT, param_spec);
+
   /* signals not exported by D-Bus interface */
   signals[DESTROY] =
     g_signal_new ("destroy",
@@ -795,12 +815,14 @@ gabble_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
   guint component_id;
   const gchar *addr;
   GType candidate_struct_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE;
+  JingleCandidate *c;
+  GList *li;
 
   g_assert (GABBLE_IS_MEDIA_STREAM (self));
 
   priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
 
-  g_object_get (priv->session, "state", &state, NULL);
+  g_object_get (priv->content->session, "state", &state, NULL);
 
   /* FIXME: maybe this should be an assertion in case the channel
    * isn't closed early enough right now? */
@@ -846,18 +868,28 @@ gabble_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
   component_id = g_value_get_uint (g_value_array_get_nth (transport, 0));
   if (component_id != 1)
     {
-      GMS_DEBUG_WARNING (priv->session,
-          "%s: ignoring native candidate non-1 component", G_STRFUNC);
+      DEBUG ("%s: ignoring native candidate non-1 component", G_STRFUNC);
       tp_svc_media_stream_handler_return_from_new_native_candidate (context);
       return;
     }
 
   g_ptr_array_add (candidates, g_value_get_boxed (&candidate));
 
-  GMS_DEBUG_INFO (priv->session,
-      "put 1 native candidate from stream-engine into cache");
+  DEBUG ("put 1 native candidate from stream-engine into cache");
+
+  c = g_new0 (JingleCandidate, 1);
+  c->address = g_value_dup_string (g_value_array_get_nth (transport, 1));
+  c->port = g_value_get_uint (g_value_array_get_nth (transport, 2));
+  c->protocol = g_value_get_uint (g_value_array_get_nth (transport, 3));
+  c->preference = g_value_get_double (g_value_array_get_nth (transport, 6));
+  c->type = g_value_get_uint (g_value_array_get_nth (transport, 7));
+  c->username = g_value_dup_string (g_value_array_get_nth (transport, 8));
+  c->password = g_value_dup_string (g_value_array_get_nth (transport, 9));
 
-  push_native_candidates (self);
+  li = g_list_prepend (NULL, c);
+  gabble_jingle_content_add_candidates (priv->content, li);
+
+  // FIXME: the above instead this: push_native_candidates (self);
 
   g_signal_emit (self, signals[NEW_NATIVE_CANDIDATE], 0,
                  candidate_id, transports);
@@ -886,7 +918,7 @@ gabble_media_stream_ready (TpSvcMediaStreamHandler *iface,
 
   priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
 
-  GMS_DEBUG_INFO (priv->session, "ready called");
+  DEBUG ("ready called");
 
   g_object_set (self, "ready", TRUE, NULL);
 
@@ -918,17 +950,19 @@ gabble_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface,
 
   priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
 
-  GMS_DEBUG_INFO (priv->session, "putting list of all %d locally supported "
+  DEBUG ("putting list of all %d locally supported "
                   "codecs from stream-engine into cache", codecs->len);
 
   g_value_set_boxed (&priv->native_codecs, codecs);
 
   g_object_set (self, "got-local-codecs", TRUE, NULL);
 
+  /* FIXME: actually set them :-) */
+  gabble_jingle_content_set_local_codecs (priv->content);
+
   tp_svc_media_stream_handler_return_from_set_local_codecs (context);
 }
 
-
 /**
  * gabble_media_stream_stream_state
  *
@@ -941,10 +975,14 @@ gabble_media_stream_stream_state (TpSvcMediaStreamHandler *iface,
                                   DBusGMethodInvocation *context)
 {
   GabbleMediaStream *self = GABBLE_MEDIA_STREAM (iface);
-  g_assert (GABBLE_IS_MEDIA_STREAM (self));
+  GabbleMediaStreamPrivate *priv = GABBLE_MEDIA_STREAM_GET_PRIVATE (self);
 
   g_object_set (self, "connection-state", connection_state, NULL);
 
+  /* FIXME: we're relying on 1:1 mapping between tp and jingle enums */
+  gabble_jingle_content_set_transport_state (priv->content,
+      connection_state);
+
   tp_svc_media_stream_handler_return_from_stream_state (context);
 }
 
@@ -1599,7 +1637,7 @@ push_playing (GabbleMediaStream *stream)
   if (!priv->ready)
     return;
 
-  GMS_DEBUG_INFO (priv->session, "stream %s emitting SetStreamPlaying(%s)",
+  DEBUG ("stream %s emitting SetStreamPlaying(%s)",
       stream->name, stream->playing ? "true" : "false");
 
   tp_svc_media_stream_handler_emit_set_stream_playing (
@@ -1618,7 +1656,7 @@ push_sending (GabbleMediaStream *stream)
   if (!priv->ready)
     return;
 
-  GMS_DEBUG_INFO (priv->session, "stream %s emitting SetStreamSending(%s)",
+  DEBUG ("stream %s emitting SetStreamSending(%s)",
       stream->name, priv->sending ? "true" : "false");
 
   tp_svc_media_stream_handler_emit_set_stream_sending (
diff --git a/src/namespaces.h b/src/namespaces.h
index 0c60c6b..d46db58 100644
--- a/src/namespaces.h
+++ b/src/namespaces.h
@@ -72,4 +72,11 @@
 #define NS_X_CONFERENCE         "jabber:x:conference"
 #define NS_XMPP_STANZAS         "urn:ietf:params:xml:ns:xmpp-stanzas"
 
+#define NS_JINGLE015            "http://jabber.org/protocol/jingle"
+#define NS_JINGLE026            "urn:xmpp:tmp:jingle"
+#define NS_GOOGLE_SESSION       "http://www.google.com/session"
+#define NS_GOOGLE_SESSION_PHONE "http://www.google.com/session/phone"
+#define NS_GOOGLE_TRANSPORT_P2P "http://www.google.com/transport/p2p"
+#define NS_JINGLE_RTP_TMP       "urn:xmpp:tmp:jingle:apps:rtp"
+
 #endif /* __GABBLE_NAMESPACES__H__ */
diff --git a/src/types.h b/src/types.h
index 5a00309..557b25b 100644
--- a/src/types.h
+++ b/src/types.h
@@ -40,6 +40,14 @@ typedef struct _GabbleBytestreamFactory GabbleBytestreamFactory;
 typedef struct _GabblePrivateTubesFactory GabblePrivateTubesFactory;
 typedef struct _GabbleRequestPipeline GabbleRequestPipeline;
 
+typedef struct _GabbleJingleFactory GabbleJingleFactory;
+typedef struct _GabbleJingleSession GabbleJingleSession;
+typedef struct _GabbleJingleContent GabbleJingleContent;
+typedef struct _GabbleJingleTransportGoogle GabbleJingleTransportGoogle;
+typedef struct _GabbleJingleMediaRtp GabbleJingleMediaRtp;
+
+typedef struct _JingleCandidate JingleCandidate;
+
 typedef enum {
     INITIATOR_INVALID = -1,
     INITIATOR_LOCAL = 0,
diff --git a/tests/twisted/jingle/jingletest.py b/tests/twisted/jingle/jingletest.py
index 1cd2cc4..6704dfd 100644
--- a/tests/twisted/jingle/jingletest.py
+++ b/tests/twisted/jingle/jingletest.py
@@ -88,7 +88,7 @@ class JingleTest:
 
     def get_remote_transports_dbus(self):
         return dbus.Array([
-            (dbus.UInt32(i), host, port, proto, subtype,
+            (dbus.UInt32(1 + i), host, port, proto, subtype,
                 profile, pref, transtype, user, pwd)
                 for i, (host, port, proto, subtype, profile,
                     pref, transtype, user, pwd)
@@ -152,6 +152,7 @@ class JingleTest:
         content = domish.Element((None, 'content'))
         content['creator'] = 'initiator'
         content['name'] = 'audio1'
+        content['senders'] = 'both'
 
         desc = domish.Element((desc_ns, 'description'))
         for codec, id, rate in self.audio_codecs:
diff --git a/tests/twisted/jingle/test-incoming-call.py b/tests/twisted/jingle/test-incoming-call.py
index 456da01..e40717e 100644
--- a/tests/twisted/jingle/test-incoming-call.py
+++ b/tests/twisted/jingle/test-incoming-call.py
@@ -2,9 +2,9 @@
 Test incoming call handling.
 """
 
-print "FIXME: jingle/test-incoming-call.py disabled due to race condition"
+# print "FIXME: jingle/test-incoming-call.py disabled due to race condition"
 # exiting 77 causes automake to consider the test to have been skipped
-raise SystemExit(77)
+# raise SystemExit(77)
 
 from gabbletest import exec_test, make_result_iq, sync_stream
 from servicetest import make_channel_proxy, unwrap, tp_path_prefix, \
@@ -53,6 +53,18 @@ def test(q, bus, conn, stream):
     e = q.expect('dbus-signal', signal='MembersChanged',
              args=[u'', [remote_handle], [], [], [], 0, 0])
 
+    # We're pending because of remote_handle
+    e = q.expect('dbus-signal', signal='MembersChanged',
+             args=[u'', [], [], [1L], [], remote_handle, 0])
+
+    media_chan = make_channel_proxy(conn, tp_path_prefix + e.path, 'Channel.Interface.Group')
+
+    # S-E gets notified about a newly-created stream
+    e = q.expect('dbus-signal', signal='NewStreamHandler')
+
+    stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
+
+
     # S-E gets notified about new session handler, and calls Ready on it
     e = q.expect('dbus-signal', signal='NewSessionHandler')
     assert e.args[1] == 'rtp'
@@ -60,12 +72,7 @@ def test(q, bus, conn, stream):
     session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler')
     session_handler.Ready()
 
-    # We're pending because of remote_handle
-    e = q.expect('dbus-signal', signal='MembersChanged',
-             args=[u'', [], [], [1L], [], remote_handle, 0])
-
-    media_chan = make_channel_proxy(conn, tp_path_prefix + e.path, 'Channel.Interface.Group')
-
+    """
     # Exercise channel properties
     future_props = media_chan.GetAll(
             'org.freedesktop.Telepathy.Channel',
@@ -80,14 +87,10 @@ def test(q, bus, conn, stream):
     assert future_props['TargetID'] == ''
     assert future_props['InitiatorID'] == 'foo at bar.com'
     assert future_props['InitiatorHandle'] == remote_handle
+    """
 
     media_chan.AddMembers([dbus.UInt32(1)], 'accepted')
 
-    # S-E gets notified about a newly-created stream
-    e = q.expect('dbus-signal', signal='NewStreamHandler')
-
-    stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
-
     # We are now in members too
     e = q.expect('dbus-signal', signal='MembersChanged',
              args=[u'', [1L], [], [], [], 0, 0])
-- 
1.5.6.5




More information about the Telepathy-commits mailing list