[Telepathy-commits] [telepathy-gabble/master] Fixed content-add/accept/remove, prepared twisted test to check behaviour of Request/Remove streams.

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


---
 src/jingle-content.c                               |  132 +++++++++++++---
 src/jingle-content.h                               |    1 +
 src/jingle-session.c                               |   35 +++-
 tests/twisted/Makefile.am                          |    1 +
 .../twisted/jingle/test-content-adding-removal.py  |  170 ++++++++++++++++++++
 5 files changed, 309 insertions(+), 30 deletions(-)
 create mode 100644 tests/twisted/jingle/test-content-adding-removal.py

diff --git a/src/jingle-content.c b/src/jingle-content.c
index 7444f5c..3cf9154 100644
--- a/src/jingle-content.c
+++ b/src/jingle-content.c
@@ -57,6 +57,7 @@ enum
   PROP_SENDERS,
   PROP_STATE,
   PROP_READY,
+  PROP_DISPOSITION,
   LAST_PROPERTY
 };
 
@@ -73,6 +74,7 @@ struct _GabbleJingleContentPrivate
 
   gchar *content_ns;
   gchar *transport_ns;
+  gchar *disposition;
 
   GabbleJingleTransportIface *transport;
 
@@ -142,6 +144,9 @@ gabble_jingle_content_dispose (GObject *object)
   g_free (priv->transport_ns);
   priv->transport_ns = NULL;
 
+  g_free (priv->disposition);
+  priv->disposition = NULL;
+
   if (G_OBJECT_CLASS (gabble_jingle_content_parent_class)->dispose)
     G_OBJECT_CLASS (gabble_jingle_content_parent_class)->dispose (object);
 }
@@ -172,11 +177,15 @@ gabble_jingle_content_get_property (GObject *object,
       g_value_set_uint (value, priv->state);
       break;
     case PROP_READY:
+      g_assert_not_reached ();
       g_value_set_boolean (value, priv->ready);
       break;
     case PROP_CONTENT_NS:
       g_value_set_string (value, priv->content_ns);
       break;
+    case PROP_DISPOSITION:
+      g_value_set_string (value, priv->disposition);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -239,6 +248,7 @@ gabble_jingle_content_set_property (GObject *object,
       DEBUG ("setting content state to %u", priv->state);
       break;
     case PROP_READY:
+      g_assert_not_reached ();
       DEBUG ("setting content ready from %u to %u",
           priv->ready, g_value_get_boolean (value));
 
@@ -250,9 +260,10 @@ gabble_jingle_content_set_property (GObject *object,
 
       priv->ready = g_value_get_boolean (value);
 
-      /* if we have pending local candidates, now's the time
-       * to transmit them */
-      gabble_jingle_transport_iface_retransmit_candidates (priv->transport);
+      break;
+    case PROP_DISPOSITION:
+      g_assert (priv->disposition == NULL);
+      priv->disposition = g_value_dup_string (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -345,6 +356,16 @@ gabble_jingle_content_class_init (GabbleJingleContentClass *cls)
                                      G_PARAM_STATIC_BLURB);
   g_object_class_install_property (object_class, PROP_READY, param_spec);
 
+  param_spec = g_param_spec_string ("disposition", "Content disposition",
+                                    "Distinguishes between 'session' and other "
+                                    "contents.",
+                                    NULL,
+                                    G_PARAM_READWRITE |
+                                    G_PARAM_STATIC_NAME |
+                                    G_PARAM_STATIC_BLURB);
+  g_object_class_install_property (object_class, PROP_DISPOSITION, param_spec);
+
+
   /* signal definitions */
 
   signals[READY] = g_signal_new ("ready",
@@ -390,12 +411,31 @@ parse_description (GabbleJingleContent *c, LmMessageNode *desc_node,
   virtual_method (c, desc_node, error);
 }
 
+static gboolean
+send_gtalk4_transport_accept (gpointer user_data)
+{
+  GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (user_data);
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (c);
+  LmMessageNode *sess_node, *tnode;
+  LmMessage *msg = gabble_jingle_session_new_message (c->session,
+      JINGLE_ACTION_TRANSPORT_ACCEPT, &sess_node);
+
+  DEBUG ("Sending Gtalk4 'transport-accept' message to peer");
+  tnode = lm_message_node_add_child (sess_node, "transport", NULL);
+  lm_message_node_set_attribute (tnode, "xmlns", priv->transport_ns);
+
+  _gabble_connection_send (c->conn, msg, NULL);
+  lm_message_unref (msg);
+
+  return FALSE;
+}
+
 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;
+  const gchar *name, *creator, *senders, *disposition;
   LmMessageNode *trans_node, *desc_node;
   GType transport_type = 0;
   GabbleJingleTransportIface *trans = NULL;
@@ -481,6 +521,12 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
   if (*error)
       return;
 
+  disposition = lm_message_node_get_attribute (content_node, "disposition");
+  if (disposition == NULL)
+      disposition = "session";
+
+  priv->disposition = g_strdup (disposition);
+
   DEBUG ("content creating new transport type %s", g_type_name (transport_type));
 
   trans = g_object_new (transport_type,
@@ -511,6 +557,13 @@ gabble_jingle_content_parse_add (GabbleJingleContent *c,
 
   priv->state = JINGLE_CONTENT_STATE_NEW;
 
+  /* GTALK4 seems to require "transport-accept" for acknowledging
+   * the transport type? */
+  if (dialect == JINGLE_DIALECT_GTALK4)
+    {
+      g_idle_add (send_gtalk4_transport_accept, c);
+    }
+
   return;
 }
 
@@ -669,7 +722,7 @@ gabble_jingle_content_is_ready (GabbleJingleContent *self)
 }
 
 static void
-try_content_add_or_accept (GabbleJingleContent *self)
+send_content_add_or_accept (GabbleJingleContent *self)
 {
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
   LmMessage *msg;
@@ -677,13 +730,7 @@ try_content_add_or_accept (GabbleJingleContent *self)
   JingleAction action = JINGLE_ACTION_UNKNOWN;
   JingleContentState new_state = JINGLE_CONTENT_STATE_EMPTY;
 
-  /* FIXME: we should only do this if the content-disposition != "session"
-   * so we ignore it for now */
-  DEBUG ("called, but doing nothing for now");
-  return;
-
-  if (!gabble_jingle_content_is_ready (self))
-      return;
+  g_assert (gabble_jingle_content_is_ready (self));
 
   if (priv->created_by_us)
     {
@@ -707,6 +754,46 @@ try_content_add_or_accept (GabbleJingleContent *self)
   g_object_notify (G_OBJECT (self), "state");
 }
 
+static void
+_maybe_ready (GabbleJingleContent *self)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
+
+  if (!gabble_jingle_content_is_ready (self))
+      return;
+
+  DEBUG ("called, and is ready");
+
+  if (!tp_strdiff (priv->disposition, "session"))
+    {
+      DEBUG ("disposition == 'session' signalling");
+      /* Notify the session that we're ready for
+       * session-initiate/session-accept */
+      g_signal_emit (self, signals[READY], 0);
+    }
+  else
+    {
+      JingleSessionState state;
+
+      g_object_get (self->session, "state", &state, NULL);
+
+      if (state >= JS_STATE_PENDING_INITIATE_SENT)
+        {
+          DEBUG ("disposition != 'session', sending add/accept");
+          send_content_add_or_accept (self);
+        }
+        {
+          /* non session-disposition content ready without session
+           * being initiated at all? */
+          g_assert_not_reached ();
+        }
+    }
+
+  /* if we have pending local candidates, now's the time
+   * to transmit them */
+  gabble_jingle_transport_iface_retransmit_candidates (priv->transport);
+}
+
 /* Called by a subclass when the media is ready (e.g. we got local codecs) */
 void
 _gabble_jingle_content_set_media_ready (GabbleJingleContent *self)
@@ -714,11 +801,8 @@ _gabble_jingle_content_set_media_ready (GabbleJingleContent *self)
   GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (self);
 
   priv->media_ready = TRUE;
-  try_content_add_or_accept (self);
-
-  /* FIXME we abuse this to signal to session that we might be ready */
-  g_object_notify (G_OBJECT (self), "ready");
 
+  _maybe_ready (self);
 }
 
 void
@@ -732,12 +816,8 @@ gabble_jingle_content_set_transport_state (GabbleJingleContent *self,
   if (state == JINGLE_TRANSPORT_STATE_CONNECTED)
     {
       priv->transport_ready = TRUE;
-      try_content_add_or_accept (self);
+      _maybe_ready (self);
     }
-
-  /* FIXME we abuse this to signal to session that we might be ready */
-  g_object_notify (G_OBJECT (self), "ready");
-
 }
 
 void
@@ -759,3 +839,13 @@ gabble_jingle_content_accept (GabbleJingleContent *c)
 
   g_object_set (c, "state", JINGLE_CONTENT_STATE_ACKNOWLEDGED, NULL);
 }
+
+GList *
+gabble_jingle_content_get_remote_candidates (GabbleJingleContent *c)
+{
+  GabbleJingleContentPrivate *priv = GABBLE_JINGLE_CONTENT_GET_PRIVATE (c);
+
+  return gabble_jingle_transport_iface_get_remote_candidates (priv->transport);
+}
+
+
diff --git a/src/jingle-content.h b/src/jingle-content.h
index 999f309..436eaba 100644
--- a/src/jingle-content.h
+++ b/src/jingle-content.h
@@ -109,6 +109,7 @@ gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self);
 void gabble_jingle_content_set_transport_state (GabbleJingleContent *content,
     JingleTransportState state);
 void gabble_jingle_content_accept (GabbleJingleContent *c);
+GList *gabble_jingle_content_get_remote_candidates (GabbleJingleContent *c);
 
 #endif /* __JINGLE_CONTENT_H__ */
 
diff --git a/src/jingle-session.c b/src/jingle-session.c
index aac77b5..809e8e9 100644
--- a/src/jingle-session.c
+++ b/src/jingle-session.c
@@ -117,6 +117,7 @@ static JingleAction allowed_actions[6][8] = {
   { JINGLE_ACTION_SESSION_INITIATE, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_INITIATE_SENT */
   { JINGLE_ACTION_SESSION_TERMINATE, JINGLE_ACTION_SESSION_ACCEPT,
+    JINGLE_ACTION_TRANSPORT_ACCEPT, /* required for GTalk4 */
     JINGLE_ACTION_TRANSPORT_INFO, JINGLE_ACTION_UNKNOWN },
   /* JS_STATE_PENDING_INITIATED */
   { JINGLE_ACTION_SESSION_ACCEPT, JINGLE_ACTION_SESSION_TERMINATE,
@@ -166,12 +167,12 @@ gabble_jingle_session_dispose (GObject *object)
   DEBUG ("dispose called");
   priv->dispose_has_run = TRUE;
 
-  g_hash_table_destroy (priv->contents);
-  priv->contents = NULL;
-
   g_hash_table_destroy (priv->initial_contents);
   priv->initial_contents = NULL;
 
+  g_hash_table_destroy (priv->contents);
+  priv->contents = NULL;
+
   if (sess->peer)
     {
       tp_handle_unref (contact_repo, sess->peer);
@@ -484,8 +485,7 @@ _foreach_content (GabbleJingleSession *sess, LmMessageNode *node,
     }
 }
 
-static void content_ready_cb (GabbleJingleContent *c,
-    GParamSpec *arg, GabbleJingleSession *sess);
+static void content_ready_cb (GabbleJingleContent *c, gpointer user_data);
 
 static void
 _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
@@ -545,7 +545,7 @@ _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c,
                     "content-ns", content_ns,
                     NULL);
 
-  g_signal_connect (c, "notify::ready",
+  g_signal_connect (c, "ready",
       (GCallback) content_ready_cb, sess);
 
   gabble_jingle_content_parse_add (c, content_node,
@@ -1126,10 +1126,26 @@ _try_session_accept_fill (gpointer key, gpointer data, gpointer user_data)
 {
   GabbleJingleContent *c = GABBLE_JINGLE_CONTENT (data);
   LmMessageNode *sess_node = user_data;
+  JingleContentState state;
 
   /* We can safely add every content, because we're only
    * considering the initial ones anyways. */
   gabble_jingle_content_produce_node (c, sess_node, TRUE);
+
+  g_object_get (c, "state", &state, NULL);
+
+  if (state == JINGLE_CONTENT_STATE_EMPTY)
+    {
+      g_object_set (c, "state", JINGLE_CONTENT_STATE_SENT, NULL);
+    }
+  else if (state == JINGLE_CONTENT_STATE_NEW)
+    {
+      g_object_set (c, "state", JINGLE_CONTENT_STATE_ACKNOWLEDGED, NULL);
+    }
+  else
+    {
+      DEBUG ("weird, content %p is in stata %u", c, state);
+    }
 }
 
 static void
@@ -1312,12 +1328,13 @@ gabble_jingle_session_add_content (GabbleJingleSession *sess, JingleMediaType mt
                     "content-ns", content_ns,
                     "transport-ns", transport_ns,
                     "name", name,
+                    "disposition", "session",
                     "senders", JINGLE_CONTENT_SENDERS_BOTH,
                     NULL);
 
   DEBUG ("here");
 
-  g_signal_connect (c, "notify::ready",
+  g_signal_connect (c, "ready",
       (GCallback) content_ready_cb, sess);
 
   g_hash_table_insert (priv->contents, g_strdup (name), c);
@@ -1418,9 +1435,9 @@ gabble_jingle_session_get_contents (GabbleJingleSession *sess)
 }
 
 static void
-content_ready_cb (GabbleJingleContent *c,
-    GParamSpec *arg, GabbleJingleSession *sess)
+content_ready_cb (GabbleJingleContent *c, gpointer user_data)
 {
+  GabbleJingleSession *sess = GABBLE_JINGLE_SESSION (user_data);
   GabbleJingleSessionPrivate *priv = GABBLE_JINGLE_SESSION_GET_PRIVATE (sess);
   const gchar *name;
 
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index 5bdffe6..8c9657c 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -41,6 +41,7 @@ TWISTED_TESTS = \
 	vcard/test-vcard-set-and-get.py \
 	jingle/hold-audio.py \
 	jingle/hold-av.py \
+	jingle/test-content-adding-removal.py \
 	jingle/test-incoming-call.py \
 	jingle/test-incoming-call-reject.py \
 	jingle/test-outgoing-call.py \
diff --git a/tests/twisted/jingle/test-content-adding-removal.py b/tests/twisted/jingle/test-content-adding-removal.py
new file mode 100644
index 0000000..edd2660
--- /dev/null
+++ b/tests/twisted/jingle/test-content-adding-removal.py
@@ -0,0 +1,170 @@
+"""
+Test content adding and removal during the session. We start
+session with only one stream, then add one more, then remove
+the first one and lastly remove the second stream, which
+closes the session.
+"""
+
+from gabbletest import exec_test, make_result_iq, sync_stream, \
+        send_error_reply
+from servicetest import make_channel_proxy, unwrap, tp_path_prefix, \
+        call_async, EventPattern
+from twisted.words.xish import domish
+import jingletest
+import gabbletest
+import dbus
+import time
+
+
+def test(q, bus, conn, stream):
+    jt = jingletest.JingleTest(stream, 'test at localhost', 'foo at bar.com/Foo')
+
+    # Connecting
+    conn.Connect()
+
+    q.expect('dbus-signal', signal='StatusChanged', args=[1, 1])
+
+    q.expect('stream-authenticated')
+    q.expect('dbus-signal', signal='PresenceUpdate',
+        args=[{1L: (0L, {u'available': {}})}])
+    q.expect('dbus-signal', signal='StatusChanged', args=[0, 1])
+
+    self_handle = conn.GetSelfHandle()
+
+    # We need remote end's presence for capabilities
+    jt.send_remote_presence()
+
+    # Gabble doesn't trust it, so makes a disco
+    event = q.expect('stream-iq', query_ns='http://jabber.org/protocol/disco#info',
+             to='foo at bar.com/Foo')
+
+    jt.send_remote_disco_reply(event.stanza)
+
+    # Force Gabble to process the caps before calling RequestChannel
+    sync_stream(q, stream)
+
+    handle = conn.RequestHandles(1, [jt.remote_jid])[0]
+
+    path = conn.RequestChannel(
+        'org.freedesktop.Telepathy.Channel.Type.StreamedMedia',
+        1, handle, True)
+
+    signalling_iface = make_channel_proxy(conn, path, 'Channel.Interface.MediaSignalling')
+    media_iface = make_channel_proxy(conn, path, 'Channel.Type.StreamedMedia')
+    group_iface = make_channel_proxy(conn, path, 'Channel.Interface.Group')
+
+    # FIXME: Hack to make sure the disco info has been processed - we need to
+    # send Gabble some XML that will cause an event when processed, and
+    # wait for that event (until
+    # https://bugs.freedesktop.org/show_bug.cgi?id=15769 is fixed)
+    el = domish.Element(('jabber.client', 'presence'))
+    el['from'] = 'bob at example.com/Bar'
+    stream.send(el.toXml())
+    q.expect('dbus-signal', signal='PresenceUpdate')
+    # OK, now we can continue. End of hack
+
+    # This is the interesting part of this test
+
+    media_iface.RequestStreams(handle, [0]) # 0 == MEDIA_STREAM_TYPE_AUDIO
+
+    # 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'
+
+    session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler')
+    session_handler.Ready()
+
+    e = q.expect('dbus-signal', signal='NewStreamHandler')
+    stream_id = e.args[1]
+
+    stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
+
+    stream_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus())
+    stream_handler.Ready(jt.get_audio_codecs_dbus())
+    stream_handler.StreamState(2)
+
+    e = q.expect('stream-iq')
+    assert e.query.name == 'jingle'
+    assert e.query['action'] == 'session-initiate'
+    stream.send(gabbletest.make_result_iq(stream, e.stanza))
+    # send_error_reply(stream, e.stanza)
+
+    jt.outgoing_call_reply(e.query['sid'], True)
+
+    q.expect('stream-iq', iq_type='result')
+
+    # Now we want another stream!
+
+    media_iface.RequestStreams(handle, [1]) # 1 == MEDIA_STREAM_TYPE_VIDEO
+
+    e = q.expect('dbus-signal', signal='NewStreamHandler')
+    stream2_id = e.args[1]
+
+    stream_handler2 = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler')
+
+    stream_handler2.NewNativeCandidate("fake", jt.get_remote_transports_dbus())
+    stream_handler2.Ready(jt.get_audio_codecs_dbus())
+    stream_handler2.StreamState(2)
+
+    e = q.expect('stream-iq')
+    assert e.query.name == 'jingle'
+    assert e.query['action'] == 'content-add'
+    stream.send(gabbletest.make_result_iq(stream, e.stanza))
+
+    iq, jingle = jt._jingle_stanza('content-accept')
+
+    content = domish.Element((None, 'content'))
+    content['creator'] = 'initiator'
+    content['name'] = 'stream2'
+    content['senders'] = 'both'
+    jingle.addChild(content)
+
+    desc = domish.Element(("http://jabber.org/protocol/jingle/description/audio", 'description'))
+    for codec, id, rate in jt.audio_codecs:
+        p = domish.Element((None, 'payload-type'))
+        p['name'] = codec
+        p['id'] = str(id)
+        p['rate'] = str(rate)
+        desc.addChild(p)
+
+    content.addChild(desc)
+
+    xport = domish.Element(("http://www.google.com/transport/p2p", 'transport'))
+    content.addChild(xport)
+
+    stream.send(iq.toXml())
+
+    e = q.expect('dbus-signal', signal='SetStreamPlaying', args=[1])
+
+    # We first remove the original stream
+    media_iface.RemoveStreams([stream_id])
+
+    e = q.expect('stream-iq')
+    assert e.query.name == 'jingle'
+    assert e.query['action'] == 'content-remove'
+    stream.send(gabbletest.make_result_iq(stream, e.stanza))
+
+    # Then we remove the second stream
+    media_iface.RemoveStreams([stream2_id])
+
+    e = q.expect('stream-iq')
+    assert e.query.name == 'jingle'
+    assert e.query['action'] == 'content-remove'
+    stream.send(gabbletest.make_result_iq(stream, e.stanza))
+
+    # Now the session should be terminated
+
+    e = q.expect('dbus-signal', signal='Closed')
+    assert (tp_path_prefix + e.path) == path
+
+    # Test completed, close the connection
+
+    conn.Disconnect()
+    q.expect('dbus-signal', signal='StatusChanged', args=[2, 1])
+
+    return True
+
+
+if __name__ == '__main__':
+    exec_test(test)
+
-- 
1.5.6.5




More information about the Telepathy-commits mailing list