[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