diff options
-rw-r--r-- | src/Makefile.am | 12 | ||||
-rw-r--r-- | src/call-member.c | 2 | ||||
-rw-r--r-- | src/call-muc-channel.c | 2 | ||||
-rw-r--r-- | src/gtalk-file-collection.c | 8 | ||||
-rw-r--r-- | src/jingle-content.c | 33 | ||||
-rw-r--r-- | src/jingle-content.h | 3 | ||||
-rw-r--r-- | src/jingle-factory.c | 3 | ||||
-rw-r--r-- | src/jingle-factory.h | 36 | ||||
-rw-r--r-- | src/jingle-session.c | 244 | ||||
-rw-r--r-- | src/jingle-session.h | 4 | ||||
-rw-r--r-- | src/media-channel.c | 210 | ||||
-rw-r--r-- | src/protocol.c | 12 | ||||
-rw-r--r-- | tests/twisted/Makefile.am | 2 | ||||
-rw-r--r-- | tests/twisted/constants.py | 9 | ||||
-rw-r--r-- | tests/twisted/jingle/incoming-call-stream-error.py | 61 | ||||
-rw-r--r-- | tests/twisted/jingle/stream-errors-on-content-reject.py | 240 | ||||
-rw-r--r-- | tests/twisted/jingle/stream-errors-on-terminate.py | 134 | ||||
-rw-r--r-- | tests/twisted/jingle/test-outgoing-call-rejected.py | 21 | ||||
-rw-r--r-- | tools/telepathy.am | 13 |
19 files changed, 866 insertions, 183 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 7217a0b6a..b8bdf00af 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -190,6 +190,10 @@ libgabble_convenience_la_SOURCES = \ vcard-manager.h \ vcard-manager.c +enumtype_sources = \ + jingle-factory.h \ + presence.h + libgabble_convenience_la_LIBADD = \ $(top_builddir)/extensions/libgabble-extensions.la \ $(top_builddir)/lib/gibber/libgibber.la \ @@ -257,19 +261,19 @@ gabble-signals-marshal.list: $(libgabble_convenience_la_SOURCES) Makefile.am } > $@ # rules for making the glib enum objects -gabble-enumtypes.h: presence.h Makefile.in +gabble-enumtypes.h: $(enumtype_sources) Makefile.in $(AM_V_GEN)glib-mkenums \ --fhead "#ifndef __GABBLE_ENUM_TYPES_H__\n#define __GABBLE_ENUM_TYPES_H__\n\n#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ --fprod "/* enumerations from \"@filename@\" */\n" \ --vhead "GType @enum_name@_get_type (void);\n#define GABBLE_TYPE_@ENUMSHORT@ (@enum_name@_get_type())\n" \ --ftail "G_END_DECLS\n\n#endif /* __GABBLE_ENUM_TYPES_H__ */" \ - $< > $@ + $(enumtype_sources) > $@ -gabble-enumtypes.c: presence.h Makefile.in +gabble-enumtypes.c: $(enumtype_sources) Makefile.in $(AM_V_GEN)glib-mkenums \ --fhead "#include <$*.h>" \ --fprod "\n/* enumerations from \"@filename@\" */\n#include \"@filename@\"" \ --vhead "GType\n@enum_name@_get_type (void)\n{\n static GType etype = 0;\n if (etype == 0) {\n static const G@Type@Value values[] = {" \ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \ --vtail " { 0, NULL, NULL }\n };\n etype = g_@type@_register_static (\"@EnumName@\", values);\n }\n return etype;\n}\n" \ - $< > $@ + $(enumtype_sources) > $@ diff --git a/src/call-member.c b/src/call-member.c index 33cda7cdd..beb73d139 100644 --- a/src/call-member.c +++ b/src/call-member.c @@ -592,7 +592,7 @@ gabble_call_member_shutdown (GabbleCallMember *self) if (priv->session != NULL) { gabble_jingle_session_terminate (priv->session, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, NULL, NULL); + JINGLE_REASON_UNKNOWN, NULL, NULL); } /* removing the content will remove it from our list */ diff --git a/src/call-muc-channel.c b/src/call-muc-channel.c index bd888d9e1..09c588af5 100644 --- a/src/call-muc-channel.c +++ b/src/call-muc-channel.c @@ -1062,7 +1062,7 @@ gabble_call_muc_channel_incoming_session (GabbleCallMucChannel *self, if (member == NULL || gabble_call_member_get_session (member) != NULL) { gabble_jingle_session_terminate (session, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + JINGLE_REASON_UNKNOWN, "Muji jingle session initiated while there already was one", NULL); } diff --git a/src/gtalk-file-collection.c b/src/gtalk-file-collection.c index 57bcd3f17..21a3dd128 100644 --- a/src/gtalk-file-collection.c +++ b/src/gtalk-file-collection.c @@ -242,7 +242,7 @@ gtalk_file_collection_dispose (GObject *object) if (self->priv->jingle != NULL) gabble_jingle_session_terminate (self->priv->jingle, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, NULL, NULL); + JINGLE_REASON_UNKNOWN, NULL, NULL); tp_clear_object (&self->priv->jingle); @@ -464,7 +464,7 @@ jingle_session_state_changed_cb (GabbleJingleSession *session, static void jingle_session_terminated_cb (GabbleJingleSession *session, gboolean local_terminator, - TpChannelGroupChangeReason reason, + JingleReason reason, const gchar *text, gpointer user_data) { @@ -1714,7 +1714,7 @@ gtalk_file_collection_terminate (GTalkFileCollection *self, jingle session */ self->priv->status = GTALK_FT_STATUS_TERMINATED; gabble_jingle_session_terminate (self->priv->jingle, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, NULL, NULL); + JINGLE_REASON_UNKNOWN, NULL, NULL); return; } return; @@ -1755,7 +1755,7 @@ channel_disposed (gpointer data, GObject *object) jingle session */ self->priv->status = GTALK_FT_STATUS_TERMINATED; gabble_jingle_session_terminate (self->priv->jingle, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, NULL, NULL); + JINGLE_REASON_UNKNOWN, NULL, NULL); return; } } diff --git a/src/jingle-content.c b/src/jingle-content.c index 9d5a80a2f..1db1c3c52 100644 --- a/src/jingle-content.c +++ b/src/jingle-content.c @@ -1167,8 +1167,10 @@ _on_remove_reply (GObject *c_as_obj, g_signal_emit (c, signals[REMOVED], 0); } -void -gabble_jingle_content_remove (GabbleJingleContent *c, gboolean signal_peer) +static void +_content_remove (GabbleJingleContent *c, + gboolean signal_peer, + JingleReason reason) { GabbleJingleContentPrivate *priv = c->priv; LmMessage *msg; @@ -1191,7 +1193,18 @@ gabble_jingle_content_remove (GabbleJingleContent *c, gboolean signal_peer) g_object_notify ((GObject *) c, "state"); msg = gabble_jingle_session_new_message (c->session, - JINGLE_ACTION_CONTENT_REMOVE, &sess_node); + reason == JINGLE_REASON_UNKNOWN ? + JINGLE_ACTION_CONTENT_REMOVE : JINGLE_ACTION_CONTENT_REJECT, + &sess_node); + + if (reason != JINGLE_REASON_UNKNOWN) + { + LmMessageNode *reason_node = lm_message_node_add_child (sess_node, + "reason", NULL); + lm_message_node_add_child (reason_node, + gabble_jingle_session_get_reason_name (reason), NULL); + } + gabble_jingle_content_produce_node (c, sess_node, FALSE, FALSE, NULL); gabble_jingle_session_send (c->session, msg, _on_remove_reply, (GObject *) c); @@ -1206,6 +1219,20 @@ gabble_jingle_content_remove (GabbleJingleContent *c, gboolean signal_peer) } } +void +gabble_jingle_content_remove (GabbleJingleContent *c, + gboolean signal_peer) +{ + _content_remove (c, signal_peer, JINGLE_REASON_UNKNOWN); +} + +void +gabble_jingle_content_reject (GabbleJingleContent *c, + JingleReason reason) +{ + _content_remove (c, TRUE, reason); +} + gboolean gabble_jingle_content_is_created_by_us (GabbleJingleContent *c) { diff --git a/src/jingle-content.h b/src/jingle-content.h index 4f04d551b..9305ff638 100644 --- a/src/jingle-content.h +++ b/src/jingle-content.h @@ -126,6 +126,9 @@ gboolean gabble_jingle_content_is_ready (GabbleJingleContent *self); void gabble_jingle_content_set_transport_state (GabbleJingleContent *content, JingleTransportState state); void gabble_jingle_content_remove (GabbleJingleContent *c, gboolean signal_peer); +void gabble_jingle_content_reject (GabbleJingleContent *c, + JingleReason reason); + GList *gabble_jingle_content_get_remote_candidates (GabbleJingleContent *c); GList *gabble_jingle_content_get_local_candidates (GabbleJingleContent *c); gboolean gabble_jingle_content_change_direction (GabbleJingleContent *c, diff --git a/src/jingle-factory.c b/src/jingle-factory.c index 9b5fb7f5a..13075e4c2 100644 --- a/src/jingle-factory.c +++ b/src/jingle-factory.c @@ -810,8 +810,7 @@ REQUEST_ERROR: g_error_free (error); if (sess != NULL && new_session) - gabble_jingle_session_terminate (sess, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, - NULL, NULL); + gabble_jingle_session_terminate (sess, JINGLE_REASON_UNKNOWN, NULL, NULL); return LM_HANDLER_RESULT_REMOVE_MESSAGE; } diff --git a/src/jingle-factory.h b/src/jingle-factory.h index ac6c4be4e..62563057d 100644 --- a/src/jingle-factory.h +++ b/src/jingle-factory.h @@ -26,7 +26,7 @@ G_BEGIN_DECLS -typedef enum { +typedef enum { /*< skip >*/ /* not a jingle message */ JINGLE_DIALECT_ERROR, /* old libjingle3 gtalk variant */ @@ -42,7 +42,7 @@ typedef enum { #define JINGLE_IS_GOOGLE_DIALECT(d)\ ((d == JINGLE_DIALECT_GTALK3) || (d == JINGLE_DIALECT_GTALK4)) -typedef enum { +typedef enum { /*< skip >*/ JINGLE_STATE_INVALID = -1, JINGLE_STATE_PENDING_CREATED = 0, JINGLE_STATE_PENDING_INITIATE_SENT, @@ -53,7 +53,7 @@ typedef enum { MAX_JINGLE_STATES } JingleState; -typedef enum { +typedef enum { /*< skip >*/ JINGLE_ACTION_UNKNOWN, JINGLE_ACTION_CONTENT_ACCEPT, JINGLE_ACTION_CONTENT_ADD, @@ -71,31 +71,53 @@ typedef enum { JINGLE_ACTION_INFO } JingleAction; -typedef enum { +typedef enum { /*< skip >*/ JINGLE_CONTENT_SENDERS_NONE, JINGLE_CONTENT_SENDERS_INITIATOR, JINGLE_CONTENT_SENDERS_RESPONDER, JINGLE_CONTENT_SENDERS_BOTH } JingleContentSenders; -typedef enum { +typedef enum { /*< skip >*/ JINGLE_TRANSPORT_UNKNOWN, JINGLE_TRANSPORT_GOOGLE_P2P, JINGLE_TRANSPORT_RAW_UDP, JINGLE_TRANSPORT_ICE_UDP, } JingleTransportType; -typedef enum { +typedef enum { /*< skip >*/ JINGLE_TRANSPORT_PROTOCOL_UDP, JINGLE_TRANSPORT_PROTOCOL_TCP } JingleTransportProtocol; -typedef enum { +typedef enum { /*< skip >*/ JINGLE_CANDIDATE_TYPE_LOCAL, JINGLE_CANDIDATE_TYPE_STUN, JINGLE_CANDIDATE_TYPE_RELAY } JingleCandidateType; +typedef enum +{ + JINGLE_REASON_UNKNOWN, + JINGLE_REASON_ALTERNATIVE_SESSION, + JINGLE_REASON_BUSY, + JINGLE_REASON_CANCEL, + JINGLE_REASON_CONNECTIVITY_ERROR, + JINGLE_REASON_DECLINE, + JINGLE_REASON_EXPIRED, + JINGLE_REASON_FAILED_APPLICATION, + JINGLE_REASON_FAILED_TRANSPORT, + JINGLE_REASON_GENERAL_ERROR, + JINGLE_REASON_GONE, + JINGLE_REASON_INCOMPATIBLE_PARAMETERS, + JINGLE_REASON_MEDIA_ERROR, + JINGLE_REASON_SECURITY_ERROR, + JINGLE_REASON_SUCCESS, + JINGLE_REASON_TIMEOUT, + JINGLE_REASON_UNSUPPORTED_APPLICATIONS, + JINGLE_REASON_UNSUPPORTED_TRANSPORTS +} JingleReason; + typedef struct _GabbleJingleFactoryClass GabbleJingleFactoryClass; GType gabble_jingle_factory_get_type (void); diff --git a/src/jingle-session.c b/src/jingle-session.c index 97e81aaa3..b4701fccc 100644 --- a/src/jingle-session.c +++ b/src/jingle-session.c @@ -26,6 +26,7 @@ #include <loudmouth/loudmouth.h> #include <telepathy-glib/handle-repo-dynamic.h> +#include <wocky/wocky-utils.h> #define DEBUG_FLAG GABBLE_DEBUG_MEDIA @@ -34,6 +35,7 @@ #include "conn-presence.h" #include "debug.h" #include "gabble-signals-marshal.h" +#include "gabble-enumtypes.h" #include "jingle-content.h" #include "jingle-factory.h" /* FIXME: the RTP-specific bits of this file should be separated from the @@ -52,6 +54,7 @@ enum NEW_CONTENT, REMOTE_STATE_CHANGED, TERMINATED, + CONTENT_REJECTED, LAST_SIGNAL }; @@ -479,12 +482,45 @@ gabble_jingle_session_class_init (GabbleJingleSessionClass *cls) G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals[CONTENT_REJECTED] = g_signal_new ("content-rejected", + G_TYPE_FROM_CLASS (cls), G_SIGNAL_RUN_LAST, + 0, NULL, NULL, gabble_marshal_VOID__OBJECT_UINT_STRING, + G_TYPE_NONE, 3, G_TYPE_OBJECT, G_TYPE_UINT, G_TYPE_STRING); } typedef void (*HandlerFunc)(GabbleJingleSession *sess, - LmMessageNode *node, GError **error); + LmMessageNode *node, GError **error); typedef void (*ContentHandlerFunc)(GabbleJingleSession *sess, - GabbleJingleContent *c, LmMessageNode *content_node, GError **error); + GabbleJingleContent *c, LmMessageNode *content_node, gpointer user_data, + GError **error); + +static gboolean +extract_reason (WockyNode *node, JingleReason *reason, gchar **message) +{ + JingleReason _reason = JINGLE_REASON_UNKNOWN; + WockyNode *child; + WockyNodeIter iter; + + g_return_val_if_fail (node != NULL, FALSE); + + if (message != NULL) + *message = g_strdup (wocky_node_get_content_from_child (node, "text")); + + wocky_node_iter_init (&iter, node, NULL, NULL); + + while (wocky_node_iter_next (&iter, &child)) + { + if (wocky_enum_from_nick ( + jingle_reason_get_type (), child->name, (gint *) &_reason)) + { + if (reason != NULL) + *reason = _reason; + return TRUE; + } + } + + return FALSE; +} static JingleAction parse_action (const gchar *txt) @@ -592,9 +628,7 @@ action_is_allowed (JingleAction action, JingleState state) static void gabble_jingle_session_send_rtp_info (GabbleJingleSession *sess, const gchar *name); static void set_state (GabbleJingleSession *sess, - JingleState state, - TpChannelGroupChangeReason termination_reason, - const gchar *text); + JingleState state, JingleReason termination_reason, const gchar *text); static GabbleJingleContent *_get_any_content (GabbleJingleSession *session); static gboolean @@ -682,6 +716,7 @@ _foreach_content (GabbleJingleSession *sess, LmMessageNode *node, gboolean fail_if_missing, ContentHandlerFunc func, + gpointer user_data, GError **error) { GabbleJingleContent *c; @@ -700,7 +735,7 @@ _foreach_content (GabbleJingleSession *sess, fail_if_missing, &c, error)) return; - func (sess, c, content_node, error); + func (sess, c, content_node, user_data, error); if (*error != NULL) return; } @@ -829,7 +864,7 @@ create_content (GabbleJingleSession *sess, GType content_type, static void _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c, - LmMessageNode *content_node, GError **error) + LmMessageNode *content_node, gpointer user_data, GError **error) { GabbleJingleSessionPrivate *priv = sess->priv; const gchar *name = lm_message_node_get_attribute (content_node, "name"); @@ -873,7 +908,7 @@ _each_content_add (GabbleJingleSession *sess, GabbleJingleContent *c, static void _each_content_remove (GabbleJingleSession *sess, GabbleJingleContent *c, - LmMessageNode *content_node, GError **error) + LmMessageNode *content_node, gpointer user_data, GError **error) { g_assert (c != NULL); @@ -881,8 +916,20 @@ _each_content_remove (GabbleJingleSession *sess, GabbleJingleContent *c, } static void +_each_content_rejected (GabbleJingleSession *sess, GabbleJingleContent *c, + LmMessageNode *content_node, gpointer user_data, GError **error) +{ + JingleReason reason = GPOINTER_TO_UINT (user_data); + g_assert (c != NULL); + + g_signal_emit (sess, signals[CONTENT_REJECTED], 0, c, reason, ""); + + gabble_jingle_content_remove (c, FALSE); +} + +static void _each_content_modify (GabbleJingleSession *sess, GabbleJingleContent *c, - LmMessageNode *content_node, GError **error) + LmMessageNode *content_node, gpointer user_data, GError **error) { g_assert (c != NULL); @@ -894,19 +941,19 @@ _each_content_modify (GabbleJingleSession *sess, GabbleJingleContent *c, static void _each_content_replace (GabbleJingleSession *sess, GabbleJingleContent *c, - LmMessageNode *content_node, GError **error) + LmMessageNode *content_node, gpointer user_data, GError **error) { - _each_content_remove (sess, c, content_node, error); + _each_content_remove (sess, c, content_node, NULL, error); if (*error != NULL) return; - _each_content_add (sess, c, content_node, error); + _each_content_add (sess, c, content_node, NULL, error); } static void _each_content_accept (GabbleJingleSession *sess, GabbleJingleContent *c, - LmMessageNode *content_node ,GError **error) + LmMessageNode *content_node, gpointer user_data, GError **error) { GabbleJingleSessionPrivate *priv = sess->priv; JingleContentState state; @@ -929,7 +976,7 @@ _each_content_accept (GabbleJingleSession *sess, GabbleJingleContent *c, static void _each_description_info (GabbleJingleSession *sess, GabbleJingleContent *c, - LmMessageNode *content_node, GError **error) + LmMessageNode *content_node, gpointer user_data, GError **error) { gabble_jingle_content_parse_description_info (c, content_node, error); } @@ -945,8 +992,7 @@ on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node, { /* We ignore initiate from us, and terminate the session immediately * afterwards */ - gabble_jingle_session_terminate (sess, - TP_CHANNEL_GROUP_CHANGE_REASON_BUSY, NULL, NULL); + gabble_jingle_session_terminate (sess, JINGLE_REASON_BUSY, NULL, NULL); return; } @@ -976,17 +1022,17 @@ on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node, } else { - _each_content_add (sess, NULL, node, error); + _each_content_add (sess, NULL, node, NULL, error); } } else if (priv->dialect == JINGLE_DIALECT_GTALK4) { /* in this case we implicitly have just one content */ - _each_content_add (sess, NULL, node, error); + _each_content_add (sess, NULL, node, NULL, error); } else { - _foreach_content (sess, node, FALSE, _each_content_add, error); + _foreach_content (sess, node, FALSE, _each_content_add, NULL, error); } if (*error == NULL) @@ -995,7 +1041,8 @@ on_session_initiate (GabbleJingleSession *sess, LmMessageNode *node, * disposition; resolve this as soon as the proper procedure is defined * in XEP-0166. */ - set_state (sess, JINGLE_STATE_PENDING_INITIATED, 0, NULL); + set_state (sess, JINGLE_STATE_PENDING_INITIATED, JINGLE_REASON_UNKNOWN, + NULL); gabble_jingle_session_send_rtp_info (sess, "ringing"); } @@ -1005,45 +1052,54 @@ static void on_content_add (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - _foreach_content (sess, node, FALSE, _each_content_add, error); + _foreach_content (sess, node, FALSE, _each_content_add, NULL, error); } static void on_content_modify (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - _foreach_content (sess, node, TRUE, _each_content_modify, error); + _foreach_content (sess, node, TRUE, _each_content_modify, NULL, error); } static void on_content_remove (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - _foreach_content (sess, node, TRUE, _each_content_remove, error); + _foreach_content (sess, node, TRUE, _each_content_remove, NULL, error); } static void on_content_replace (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - _foreach_content (sess, node, TRUE, _each_content_replace, error); + _foreach_content (sess, node, TRUE, _each_content_replace, NULL, error); } static void on_content_reject (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - /* FIXME: reject is different from remove - remove is for - * acknowledged contents, reject is for pending; but the result - * is the same. */ - _foreach_content (sess, node, TRUE, _each_content_remove, error); + LmMessageNode *n = lm_message_node_get_child (node, "reason"); + JingleReason reason = JINGLE_REASON_UNKNOWN; + + DEBUG (" "); + + if (n != NULL) + extract_reason (n, &reason, NULL); + + if (reason == JINGLE_REASON_UNKNOWN) + reason = JINGLE_REASON_GENERAL_ERROR; + + _foreach_content (sess, node, TRUE, _each_content_rejected, + GUINT_TO_POINTER (reason), error); } static void on_content_accept (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - _foreach_content (sess, node, TRUE, _each_content_accept, error); + _foreach_content (sess, node, TRUE, _each_content_accept, NULL, error); } static void @@ -1066,19 +1122,19 @@ on_session_accept (GabbleJingleSession *sess, LmMessageNode *node, GList *l; for (l = cs; l != NULL; l = l->next) - _each_content_accept (sess, l->data, node, error); + _each_content_accept (sess, l->data, node, NULL, error); g_list_free (cs); } else { - _foreach_content (sess, node, TRUE, _each_content_accept, error); + _foreach_content (sess, node, TRUE, _each_content_accept, NULL, error); } if (*error != NULL) return; - set_state (sess, JINGLE_STATE_ACTIVE, 0, NULL); + set_state (sess, JINGLE_STATE_ACTIVE, JINGLE_REASON_UNKNOWN, NULL); if (priv->dialect != JINGLE_DIALECT_V032) { @@ -1259,76 +1315,24 @@ on_session_info (GabbleJingleSession *sess, "no recognized session-info payloads"); } -typedef struct { - const gchar *element; - TpChannelGroupChangeReason reason; -} ReasonMapping; - -/* Taken from the schema in XEP 0166 */ -ReasonMapping reasons[] = { - { "alternative-session", TP_CHANNEL_GROUP_CHANGE_REASON_NONE }, - { "busy", TP_CHANNEL_GROUP_CHANGE_REASON_BUSY }, - { "cancel", TP_CHANNEL_GROUP_CHANGE_REASON_NONE }, - { "connectivity-error", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "decline", TP_CHANNEL_GROUP_CHANGE_REASON_NONE }, - { "expired", TP_CHANNEL_GROUP_CHANGE_REASON_NONE }, - { "failed-application", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "failed-transport", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "general-error", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "gone", TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE }, - { "incompatible-parameters", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "media-error", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "security-error", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "success", TP_CHANNEL_GROUP_CHANGE_REASON_NONE }, - { "timeout", TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER }, - { "unsupported-applications", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { "unsupported-transports", TP_CHANNEL_GROUP_CHANGE_REASON_ERROR }, - { NULL, } -}; - static void on_session_terminate (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - TpChannelGroupChangeReason reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE; - const gchar *text = NULL; + gchar *text = NULL; LmMessageNode *n = lm_message_node_get_child (node, "reason"); - ReasonMapping *m = NULL; - NodeIter i; + JingleReason jingle_reason = JINGLE_REASON_UNKNOWN; - /* If the session-terminate stanza has a <reason> child, then iterate across - * its children, looking for a child whose name we recognise as a - * machine-readable reason for the call ending (looked up from the table - * above), and a <text> node containing a human-readable message. - */ if (n != NULL) - for (i = node_iter (n); i; i = node_iter_next (i)) - { - const gchar *name; + extract_reason (n, &jingle_reason, &text); - n = node_iter_data (i); - - name = lm_message_node_get_name (n); + DEBUG ("remote end terminated the session with reason %s and text '%s'", + gabble_jingle_session_get_reason_name (jingle_reason), + (text != NULL ? text : "(none)")); - if (!tp_strdiff (name, "text")) - { - text = lm_message_node_get_value (n); - continue; - } + set_state (sess, JINGLE_STATE_ENDED, jingle_reason, text); - for (m = reasons; m->element != NULL; m++) - if (!tp_strdiff (m->element, name)) - { - reason = m->reason; - break; - } - } - - DEBUG ("remote end terminated the session with reason %s (%u) " - "and text '%s'", - (m != NULL && m->element != NULL ? m->element : "(none)"), reason, - (text != NULL ? text : "(none)")); - set_state (sess, JINGLE_STATE_ENDED, reason, text); + g_free (text); } static void @@ -1409,7 +1413,7 @@ static void on_description_info (GabbleJingleSession *sess, LmMessageNode *node, GError **error) { - _foreach_content (sess, node, TRUE, _each_description_info, error); + _foreach_content (sess, node, TRUE, _each_description_info, NULL, error); } static void @@ -1816,7 +1820,7 @@ _on_initiate_reply (GObject *sess_as_obj, } else { - set_state (sess, JINGLE_STATE_ENDED, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + set_state (sess, JINGLE_STATE_ENDED, JINGLE_REASON_UNKNOWN, NULL); } } @@ -1835,7 +1839,7 @@ _on_accept_reply (GObject *sess_as_obj, } else { - set_state (sess, JINGLE_STATE_ENDED, TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + set_state (sess, JINGLE_STATE_ENDED, JINGLE_REASON_UNKNOWN, NULL); } } @@ -1966,14 +1970,14 @@ try_session_initiate_or_accept (GabbleJingleSession *sess) * @sess: a jingle session * @state: the new state for the session * @termination_reason: if @state is JINGLE_STATE_ENDED, the reason the session - * ended. Otherwise, must be 0. + * ended. Otherwise, must be JINGLE_REASON_UNKNOWN. * @text: if @state is JINGLE_STATE_ENDED, the human-readable reason the session * ended. */ static void set_state (GabbleJingleSession *sess, JingleState state, - TpChannelGroupChangeReason termination_reason, + JingleReason termination_reason, const gchar *text) { GabbleJingleSessionPrivate *priv = sess->priv; @@ -1985,7 +1989,7 @@ set_state (GabbleJingleSession *sess, } if (state != JINGLE_STATE_ENDED) - g_assert (termination_reason == 0); + g_assert (termination_reason == JINGLE_REASON_UNKNOWN); DEBUG ("Setting state of JingleSession: %p (priv = %p) from %u to %u", sess, priv, priv->state, state); @@ -2013,33 +2017,20 @@ gabble_jingle_session_accept (GabbleJingleSession *sess) try_session_initiate_or_accept (sess); } -static const gchar * -_get_jingle_reason (GabbleJingleSession *sess, - TpChannelGroupChangeReason reason) +const gchar * +gabble_jingle_session_get_reason_name (JingleReason reason) { - switch (reason) - { - case TP_CHANNEL_GROUP_CHANGE_REASON_NONE: - if (sess->priv->state == JINGLE_STATE_ACTIVE) - return "success"; - else - return "cancel"; - case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE: - return "gone"; - case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY: - return "busy"; - case TP_CHANNEL_GROUP_CHANGE_REASON_ERROR: - return "general-error"; - case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: - return "timeout"; - default: - return NULL; - } + GEnumClass *klass = g_type_class_ref (jingle_reason_get_type ()); + GEnumValue *enum_value = g_enum_get_value (klass, (gint) reason); + + g_return_val_if_fail (enum_value != NULL, NULL); + + return enum_value->value_nick; } gboolean gabble_jingle_session_terminate (GabbleJingleSession *sess, - TpChannelGroupChangeReason reason, + JingleReason reason, const gchar *text, GError **error) { @@ -2052,14 +2043,11 @@ gabble_jingle_session_terminate (GabbleJingleSession *sess, return TRUE; } - reason_elt = _get_jingle_reason (sess, reason); + if (reason == JINGLE_REASON_UNKNOWN) + reason = (priv->state == JINGLE_STATE_ACTIVE) ? + JINGLE_REASON_SUCCESS : JINGLE_REASON_CANCEL; - if (reason_elt == NULL) - { - g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, - "%u doesn't make sense as a reason to end a call", reason); - return FALSE; - } + reason_elt = gabble_jingle_session_get_reason_name (reason); if (priv->state != JINGLE_STATE_PENDING_CREATED) { @@ -2139,7 +2127,7 @@ content_removed_cb (GabbleJingleContent *c, gpointer user_data) if (count_active_contents (sess) == 0) { gabble_jingle_session_terminate (sess, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, NULL, NULL); + JINGLE_REASON_UNKNOWN, NULL, NULL); } else { diff --git a/src/jingle-session.h b/src/jingle-session.h index 446f66fde..f2a1fb83a 100644 --- a/src/jingle-session.h +++ b/src/jingle-session.h @@ -84,7 +84,7 @@ LmMessage *gabble_jingle_session_new_message (GabbleJingleSession *sess, void gabble_jingle_session_accept (GabbleJingleSession *sess); gboolean gabble_jingle_session_terminate (GabbleJingleSession *sess, - TpChannelGroupChangeReason reason, + JingleReason reason, const gchar *text, GError **error); void gabble_jingle_session_remove_content (GabbleJingleSession *sess, @@ -127,5 +127,7 @@ gboolean gabble_jingle_session_defines_action (GabbleJingleSession *sess, const gchar *gabble_jingle_session_get_peer_jid (GabbleJingleSession *sess); +const gchar *gabble_jingle_session_get_reason_name (JingleReason reason); + #endif /* __JINGLE_SESSION_H__ */ diff --git a/src/media-channel.c b/src/media-channel.c index 57f27681f..eef53ea5d 100644 --- a/src/media-channel.c +++ b/src/media-channel.c @@ -212,9 +212,7 @@ gabble_media_channel_init (GabbleMediaChannel *self) static void session_state_changed_cb (GabbleJingleSession *session, GParamSpec *arg1, GabbleMediaChannel *channel); static void session_terminated_cb (GabbleJingleSession *session, - gboolean local_terminator, - TpChannelGroupChangeReason reason, - const gchar *text, + gboolean local_terminator, JingleReason reason, const gchar *text, gpointer user_data); static void session_new_content_cb (GabbleJingleSession *session, GabbleJingleContent *c, gpointer user_data); @@ -223,6 +221,9 @@ static void create_stream_from_content (GabbleMediaChannel *chan, static gboolean contact_is_media_capable (GabbleMediaChannel *chan, TpHandle peer, gboolean *wait, GError **error); static void stream_creation_data_cancel (gpointer p, gpointer unused); +static void session_content_rejected_cb (GabbleJingleSession *session, + GabbleJingleContent *c, JingleReason reason, const gchar *message, + gpointer user_data); static void create_initial_streams (GabbleMediaChannel *chan) @@ -289,6 +290,9 @@ _latch_to_session (GabbleMediaChannel *chan) g_signal_connect (priv->session, "terminated", (GCallback) session_terminated_cb, chan); + g_signal_connect (priv->session, "content-rejected", + (GCallback) session_content_rejected_cb, chan); + gabble_media_channel_hold_latch_to_session (chan); g_assert (priv->streams->len == 0); @@ -1055,7 +1059,7 @@ gabble_media_channel_close (GabbleMediaChannel *self) if (priv->session != NULL) gabble_jingle_session_terminate (priv->session, - TP_CHANNEL_GROUP_CHANGE_REASON_NONE, NULL, NULL); + JINGLE_REASON_UNKNOWN, NULL, NULL); tp_svc_channel_emit_closed (self); } @@ -1284,6 +1288,30 @@ _find_stream_by_id (GabbleMediaChannel *chan, return NULL; } +static GabbleMediaStream * +_find_stream_by_content (GabbleMediaChannel *chan, + GabbleJingleContent *content) +{ + GabbleMediaChannelPrivate *priv; + guint i; + + g_assert (GABBLE_IS_MEDIA_CHANNEL (chan)); + + priv = chan->priv; + + for (i = 0; i < priv->streams->len; i++) + { + GabbleMediaStream *stream = g_ptr_array_index (priv->streams, i); + GabbleJingleContent *c = GABBLE_JINGLE_CONTENT ( + gabble_media_stream_get_content (stream)); + + if (content == c) + return stream; + } + + return NULL; +} + /** * gabble_media_channel_remove_streams * @@ -2184,7 +2212,7 @@ static gboolean gabble_media_channel_remove_member (GObject *obj, TpHandle handle, const gchar *message, - guint reason, + TpChannelGroupChangeReason reason, GError **error) { GabbleMediaChannel *chan = GABBLE_MEDIA_CHANNEL (obj); @@ -2206,15 +2234,34 @@ gabble_media_channel_remove_member (GObject *obj, } else { - /* Terminate can fail if the UI provides a reason that makes no sense, - * like Invited. - */ - if (!gabble_jingle_session_terminate (priv->session, reason, message, - error)) + JingleReason jingle_reason = JINGLE_REASON_UNKNOWN; + + switch (reason) { + case TP_CHANNEL_GROUP_CHANGE_REASON_NONE: + jingle_reason = JINGLE_REASON_UNKNOWN; + break; + case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE: + jingle_reason = JINGLE_REASON_GONE; + break; + case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY: + jingle_reason = JINGLE_REASON_BUSY; + break; + case TP_CHANNEL_GROUP_CHANGE_REASON_ERROR: + jingle_reason = JINGLE_REASON_GENERAL_ERROR; + break; + case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: + jingle_reason = JINGLE_REASON_TIMEOUT; + break; + default: + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "%u doesn't make sense as a reason to end a call", reason); g_object_unref (chan); return FALSE; } + + gabble_jingle_session_terminate (priv->session, jingle_reason, message, + error); } /* Remove CanAdd if it was there for the deprecated anonymous channel @@ -2239,10 +2286,91 @@ copy_stream_list (GabbleMediaChannel *channel) return gabble_g_ptr_array_copy (channel->priv->streams); } + +/* return TRUE when the jingle reason is reason enough to raise a + * StreamError */ +static gboolean +extract_media_stream_error_from_jingle_reason (JingleReason jingle_reason, + TpMediaStreamError *stream_error) +{ + TpMediaStreamError _stream_error; + + /* TODO: Make a better mapping with more distinction of possible errors */ + switch (jingle_reason) + { + case JINGLE_REASON_CONNECTIVITY_ERROR: + _stream_error = TP_MEDIA_STREAM_ERROR_NETWORK_ERROR; + break; + case JINGLE_REASON_MEDIA_ERROR: + _stream_error = TP_MEDIA_STREAM_ERROR_MEDIA_ERROR; + break; + case JINGLE_REASON_FAILED_APPLICATION: + _stream_error = TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED; + break; + case JINGLE_REASON_GENERAL_ERROR: + _stream_error = TP_MEDIA_STREAM_ERROR_UNKNOWN; + break; + default: + { + if (stream_error != NULL) + *stream_error = TP_MEDIA_STREAM_ERROR_UNKNOWN; + + return FALSE; + } + } + + if (stream_error != NULL) + *stream_error = _stream_error; + + return TRUE; +} + +static JingleReason +media_stream_error_to_jingle_reason (TpMediaStreamError stream_error) +{ + switch (stream_error) + { + case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR: + return JINGLE_REASON_CONNECTIVITY_ERROR; + case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR: + return JINGLE_REASON_MEDIA_ERROR; + case TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED: + return JINGLE_REASON_FAILED_APPLICATION; + default: + return JINGLE_REASON_GENERAL_ERROR; + } +} + +static TpChannelGroupChangeReason +jingle_reason_to_group_change_reason (JingleReason jingle_reason) +{ + switch (jingle_reason) + { + case JINGLE_REASON_BUSY: + return TP_CHANNEL_GROUP_CHANGE_REASON_BUSY; + case JINGLE_REASON_GONE: + return TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE; + case JINGLE_REASON_TIMEOUT: + return TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER; + case JINGLE_REASON_CONNECTIVITY_ERROR: + case JINGLE_REASON_FAILED_APPLICATION: + case JINGLE_REASON_FAILED_TRANSPORT: + case JINGLE_REASON_GENERAL_ERROR: + case JINGLE_REASON_MEDIA_ERROR: + case JINGLE_REASON_SECURITY_ERROR: + case JINGLE_REASON_INCOMPATIBLE_PARAMETERS: + case JINGLE_REASON_UNSUPPORTED_APPLICATIONS: + case JINGLE_REASON_UNSUPPORTED_TRANSPORTS: + return TP_CHANNEL_GROUP_CHANGE_REASON_ERROR; + default: + return TP_CHANNEL_GROUP_CHANGE_REASON_NONE; + } +} + static void session_terminated_cb (GabbleJingleSession *session, gboolean local_terminator, - TpChannelGroupChangeReason reason, + JingleReason jingle_reason, const gchar *text, gpointer user_data) { @@ -2273,7 +2401,8 @@ session_terminated_cb (GabbleJingleSession *session, tp_intset_add (set, peer); tp_group_mixin_change_members ((GObject *) channel, - text, NULL, set, NULL, NULL, terminator, reason); + text, NULL, set, NULL, NULL, terminator, + jingle_reason_to_group_change_reason (jingle_reason)); tp_intset_destroy (set); @@ -2289,8 +2418,28 @@ session_terminated_cb (GabbleJingleSession *session, { GPtrArray *tmp = copy_stream_list (channel); + guint i; + TpMediaStreamError stream_error = TP_MEDIA_STREAM_ERROR_UNKNOWN; + gboolean is_error = extract_media_stream_error_from_jingle_reason ( + jingle_reason, &stream_error); - g_ptr_array_foreach (tmp, (GFunc) gabble_media_stream_close, NULL); + for (i = 0; i < tmp->len; i++) + { + GabbleMediaStream *stream = tmp->pdata[i]; + + if (is_error) + { + guint id; + + DEBUG ("emitting stream error"); + + g_object_get (stream, "id", &id, NULL); + tp_svc_channel_type_streamed_media_emit_stream_error (channel, id, + stream_error, text); + } + + gabble_media_stream_close (stream); + } /* All the streams should have closed. */ g_assert (priv->streams->len == 0); @@ -2430,8 +2579,13 @@ stream_error_cb (GabbleMediaStream *stream, * so we can dispose of the stream) */ c = gabble_media_stream_get_content (stream); - gabble_jingle_session_remove_content (priv->session, - (GabbleJingleContent *) c); + + if (errno == TP_MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED) + gabble_jingle_content_reject ((GabbleJingleContent *) c, + JINGLE_REASON_FAILED_APPLICATION); + else + gabble_jingle_session_remove_content (priv->session, + (GabbleJingleContent *) c); } else { @@ -2442,7 +2596,7 @@ stream_error_cb (GabbleMediaStream *stream, */ DEBUG ("Terminating call in response to stream error"); gabble_jingle_session_terminate (priv->session, - TP_CHANNEL_GROUP_CHANGE_REASON_ERROR, message, NULL); + media_stream_error_to_jingle_reason (errno), message, NULL); } g_list_free (contents); @@ -2759,6 +2913,30 @@ create_stream_from_content (GabbleMediaChannel *self, } static void +session_content_rejected_cb (GabbleJingleSession *session, + GabbleJingleContent *c, JingleReason reason, const gchar *message, + gpointer user_data) +{ + GabbleMediaChannel *chan = GABBLE_MEDIA_CHANNEL (user_data); + GabbleMediaStream *stream = _find_stream_by_content (chan, c); + TpMediaStreamError stream_error = TP_MEDIA_STREAM_ERROR_UNKNOWN; + guint id = 0; + + DEBUG (" "); + + g_return_if_fail (stream != NULL); + + g_object_get (stream, + "id", &id, + NULL); + + extract_media_stream_error_from_jingle_reason (reason, &stream_error); + + tp_svc_channel_type_streamed_media_emit_stream_error (chan, id, stream_error, + message); +} + +static void session_new_content_cb (GabbleJingleSession *session, GabbleJingleContent *c, gpointer user_data) { diff --git a/src/protocol.c b/src/protocol.c index c8b4c83a5..03eefa7c3 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -346,6 +346,17 @@ get_connection_details (TpBaseProtocol *self, } } +static GStrv +dup_authentication_types (TpBaseProtocol *self) +{ + const gchar * const types[] = { + TP_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION, + TP_IFACE_CHANNEL_INTERFACE_SASL_AUTHENTICATION, + NULL }; + + return g_strdupv ((GStrv) types); +} + static void gabble_jabber_protocol_class_init (GabbleJabberProtocolClass *klass) { @@ -359,6 +370,7 @@ gabble_jabber_protocol_class_init (GabbleJabberProtocolClass *klass) base_class->get_interfaces = get_interfaces; base_class->get_connection_details = get_connection_details; base_class->get_statuses = get_presence_statuses; + base_class->dup_authentication_types = dup_authentication_types; } TpBaseProtocol * diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index e0dec8ebf..7278cd882 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -159,6 +159,8 @@ TWISTED_JINGLE_TESTS = \ jingle/payload-types.py \ jingle/preload-caps-crash.py \ jingle/session-id-collision.py \ + jingle/stream-errors-on-terminate.py \ + jingle/stream-errors-on-content-reject.py \ jingle/stream-handler-error.py \ jingle/test-content-adding-removal.py \ jingle/test-description-info.py \ diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py index 8d8db88ef..e8d476b7e 100644 --- a/tests/twisted/constants.py +++ b/tests/twisted/constants.py @@ -421,3 +421,12 @@ DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_FAILURES = 1 DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_SUCCESSES = 2 DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_READ = 4 DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_DELETED = 8 + +MEDIA_STREAM_ERROR_UNKNOWN = 0 +MEDIA_STREAM_ERROR_EOS = 1 +MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED = 2 +MEDIA_STREAM_ERROR_CONNECTION_FAILED = 3 +MEDIA_STREAM_ERROR_NETWORK_ERROR = 4 +MEDIA_STREAM_ERROR_NO_CODECS = 5 +MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR = 6 +MEDIA_STREAM_ERROR_MEDIA_ERROR = 7 diff --git a/tests/twisted/jingle/incoming-call-stream-error.py b/tests/twisted/jingle/incoming-call-stream-error.py index b41f6f32a..969b9ddda 100644 --- a/tests/twisted/jingle/incoming-call-stream-error.py +++ b/tests/twisted/jingle/incoming-call-stream-error.py @@ -10,9 +10,25 @@ from servicetest import EventPattern, assertEquals, make_channel_proxy from jingletest2 import JingleTest2, test_all_dialects import constants as cs -MEDIA_STREAM_ERROR_CONNECTION_FAILED = 3 +from twisted.words.xish import xpath -def test(jp, q, bus, conn, stream): +def _session_terminate_predicate(event, reason, msg, jp): + matches = jp.match_jingle_action(event.query, 'session-terminate') + + if matches and jp.is_modern_jingle(): + reason = xpath.queryForNodes("/iq" + "/jingle[@action='session-terminate']" + "/reason/%s" % reason, + event.stanza) + reason_text = xpath.queryForString("/iq/jingle/reason/text", + event.stanza) + + return bool(reason) and reason_text == msg + + return matches + +def _test(jp, q, bus, conn, stream, + jingle_reason, group_change_reason, stream_error): jt = JingleTest2(jp, conn, q, stream, 'test@localhost', 'foo@bar.com/Foo') jt.prepare() self_handle = conn.GetSelfHandle() @@ -29,6 +45,8 @@ def test(jp, q, bus, conn, stream): assertEquals(remote_handle, new_channel.args[3]) assertEquals('rtp', new_session_handler.args[1]) + channel_path = new_channel.args[0] + # Client calls Ready on new session handler. session_handler = make_channel_proxy( conn, new_session_handler.args[0], 'Media.SessionHandler') @@ -36,21 +54,48 @@ def test(jp, q, bus, conn, stream): # Client gets notified about a newly created stream... new_stream_handler = q.expect('dbus-signal', signal='NewStreamHandler') + stream_id = new_stream_handler.args[1] stream_handler = make_channel_proxy( conn, new_stream_handler.args[0], 'Media.StreamHandler') + stream_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus()) + stream_handler.Ready(jt.dbusify_codecs([("FOO", 5, 8000, {})])) + + q.expect('dbus-signal', signal='SetRemoteCodecs') + + msg = u"o noes" + # ...but something goes wrong. - stream_handler.Error(MEDIA_STREAM_ERROR_CONNECTION_FAILED, "o noes") + stream_handler.Error(stream_error, msg) + q.expect("stream-iq", iq_type="set", + predicate=lambda x: _session_terminate_predicate(x, jingle_reason, + msg, jp)) # Bye bye members. - q.expect('dbus-signal', signal='MembersChanged', - args=[u'o noes', [], [self_handle, remote_handle], [], [], self_handle, - cs.GC_REASON_ERROR]) - # Bye bye stream. + mc = q.expect('dbus-signal', signal='MembersChanged', + interface=cs.CHANNEL_IFACE_GROUP, path=channel_path, + args=[msg, [], [self_handle, remote_handle], [], + [], self_handle, group_change_reason]) + + q.expect('dbus-signal', signal='StreamError', + interface=cs.CHANNEL_TYPE_STREAMED_MEDIA, + args=[stream_id, stream_error, msg]) + + # Bye bye stream q.expect('dbus-signal', signal='Close') q.expect('dbus-signal', signal='StreamRemoved') + # Bye bye channel. q.expect('dbus-signal', signal='Closed') q.expect('dbus-signal', signal='ChannelClosed') +def test_connection_error(jp, q, bus, conn, stream): + _test(jp, q, bus, conn, stream, "connectivity-error", cs.GC_REASON_ERROR, + cs.MEDIA_STREAM_ERROR_NETWORK_ERROR) + +def test_codec_negotiation_fail(jp, q, bus, conn, stream): + _test(jp, q, bus, conn, stream, "failed-application", cs.GC_REASON_ERROR, + cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED) + if __name__ == '__main__': - test_all_dialects(test) + test_all_dialects(test_connection_error) + test_all_dialects(test_codec_negotiation_fail) diff --git a/tests/twisted/jingle/stream-errors-on-content-reject.py b/tests/twisted/jingle/stream-errors-on-content-reject.py new file mode 100644 index 000000000..27ea726f9 --- /dev/null +++ b/tests/twisted/jingle/stream-errors-on-content-reject.py @@ -0,0 +1,240 @@ +""" +Test StreamError events when new content is rejected in-call. +""" + +import dbus + +from gabbletest import make_result_iq, sync_stream, exec_test +from servicetest import ( + make_channel_proxy, unwrap, EventPattern, assertEquals, assertLength) +from jingletest2 import JingleTest2, JingleProtocol031 +import constants as cs + +from twisted.words.xish import xpath + +def _content_reject_predicate(event): + reason = xpath.queryForNodes("/iq" + "/jingle[@action='content-reject']" + "/reason/failed-application", + event.stanza) + + return bool(reason) + +def _start_audio_session(jp, q, bus, conn, stream, incoming): + jt = JingleTest2(jp, conn, q, stream, 'test@localhost', 'foo@bar.com/Foo') + jt.prepare() + + self_handle = conn.GetSelfHandle() + remote_handle = conn.RequestHandles(cs.HT_CONTACT, [jt.peer])[0] + + if incoming: + jt.incoming_call() + else: + ret = conn.Requests.CreateChannel( + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: remote_handle, + cs.INITIAL_AUDIO: True + }) + + nc, e = q.expect_many( + EventPattern('dbus-signal', signal='NewChannel', + predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args), + EventPattern('dbus-signal', signal='NewSessionHandler')) + + path = nc.args[0] + + media_chan = make_channel_proxy(conn, path, 'Channel.Interface.Group') + media_iface = make_channel_proxy(conn, path, 'Channel.Type.StreamedMedia') + + # S-E was notified about new session handler, and calls Ready on it + session_handler = make_channel_proxy(conn, e.args[0], + 'Media.SessionHandler') + session_handler.Ready() + + nsh_event = q.expect('dbus-signal', signal='NewStreamHandler') + + # S-E gets notified about a newly-created stream + stream_handler = make_channel_proxy(conn, nsh_event.args[0], + 'Media.StreamHandler') + + group_props = media_chan.GetAll( + cs.CHANNEL_IFACE_GROUP, dbus_interface=dbus.PROPERTIES_IFACE) + + if incoming: + assertEquals([remote_handle], group_props['Members']) + assertEquals(unwrap(group_props['LocalPendingMembers']), + [(self_handle, remote_handle, cs.GC_REASON_INVITED, '')]) + else: + assertEquals([self_handle], group_props['Members']) + + streams = media_chan.ListStreams( + dbus_interface=cs.CHANNEL_TYPE_STREAMED_MEDIA) + + stream_id = streams[0][0] + + stream_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus()) + stream_handler.Ready(jt.dbusify_codecs([("FOO", 5, 8000, {})])) + + msg = u"None of the codecs are good for us, damn!" + + expected_events = [] + + if incoming: + stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) + stream_handler.SupportedCodecs(jt.get_audio_codecs_dbus()) + + e = q.expect('stream-iq', predicate=jp.action_predicate('transport-info')) + assertEquals(jt.peer, e.query['initiator']) + content = xpath.queryForNodes('/iq/jingle/content', e.stanza)[0] + assertEquals('initiator', content['creator']) + + stream.send(make_result_iq(stream, e.stanza)) + + media_chan.AddMembers([self_handle], 'accepted') + + memb, acc, _, _, _ = q.expect_many( + EventPattern('dbus-signal', signal='MembersChanged', + args=[u'', [self_handle], [], [], [], self_handle, + cs.GC_REASON_NONE]), + EventPattern('stream-iq', + predicate=jp.action_predicate('session-accept')), + EventPattern('dbus-signal', signal='SetStreamSending', + args=[True]), + EventPattern('dbus-signal', signal='SetStreamPlaying', + args=[True]), + EventPattern('dbus-signal', signal='StreamDirectionChanged', + args=[stream_id, + cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, 0])) + + stream.send(make_result_iq(stream, acc.stanza)) + + active_event = jp.rtp_info_event("active") + if active_event is not None: + q.expect_many(active_event) + + members = media_chan.GetMembers() + assert set(members) == set([self_handle, remote_handle]), members + else: + stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) + session_initiate = q.expect( + 'stream-iq', + predicate=jp.action_predicate('session-initiate')) + + q.expect('dbus-signal', signal='MembersChanged', path=path, + args=['', [], [], [], [remote_handle], self_handle, + cs.GC_REASON_INVITED]) + + jt.parse_session_initiate(session_initiate.query) + stream.send(jp.xml(jp.ResultIq('test@localhost', + session_initiate.stanza, []))) + + jt.accept() + + q.expect_many( + EventPattern('stream-iq', iq_type='result'), + # Call accepted + EventPattern('dbus-signal', signal='MembersChanged', + args=['', [remote_handle], [], [], [], remote_handle, + cs.GC_REASON_NONE]), + ) + return jt, media_iface + +def _start_audio_session_outgoing(jp, q, bus, conn, stream): + return _start_audio_session(jp, q, bus, conn, stream, False) + +def _start_audio_session_incoming(jp, q, bus, conn, stream): + return _start_audio_session(jp, q, bus, conn, stream, True) + +def _remote_content_add(jp, q, bus, conn, stream, initiate_call_func): + jt, chan = initiate_call_func(jp, q, bus, conn, stream) + + video_codecs = [ + jp.PayloadType(name, str(rate), str(id), parameters) \ + for (name, id, rate, parameters) in jt.video_codecs] + + node = jp.SetIq(jt.peer, jt.jid, [ + jp.Jingle(jt.sid, jt.peer, 'content-add', [ + jp.Content( + 'videostream', 'initiator', 'both', + jp.Description('video', video_codecs), + jp.TransportGoogleP2P()) ]) ]) + stream.send(jp.xml(node)) + + _, nsh = q.expect_many( + EventPattern('dbus-signal', signal='StreamAdded'), + EventPattern('dbus-signal', signal='NewStreamHandler')) + + stream_handler_path, stream_id, media_type, direction = nsh.args + + video_handler = make_channel_proxy(conn, stream_handler_path, + 'Media.StreamHandler') + + video_handler.NewNativeCandidate("fake", + jt.get_remote_transports_dbus()) + video_handler.Ready(jt.dbusify_codecs([("FOO", 5, 8000, {})])) + + msg = u"None of the codecs are good for us, damn!" + + video_handler.Error(cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED, msg) + + q.expect_many( + EventPattern('dbus-signal', signal='StreamError', + args=[stream_id, + cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED, + msg]), + EventPattern('stream-iq', predicate=_content_reject_predicate)) + +def _local_content_add(jp, q, bus, conn, stream, initiate_call_func): + jt, chan = initiate_call_func(jp, q, bus, conn, stream) + + remote_handle = conn.RequestHandles(cs.HT_CONTACT, [jt.peer])[0] + + chan.RequestStreams(remote_handle, [cs.MEDIA_STREAM_TYPE_VIDEO]) + + nsh = q.expect('dbus-signal', signal='NewStreamHandler') + stream_handler_path, stream_id, media_type, direction = nsh.args + video_handler = make_channel_proxy(conn, stream_handler_path, + 'Media.StreamHandler') + + video_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus()) + video_handler.Ready(jt.get_audio_codecs_dbus()) + video_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) + + e = q.expect('stream-iq', predicate=jp.action_predicate('content-add')) + c = e.query.firstChildElement() + stream.send(make_result_iq(stream, e.stanza)) + + node = jp.SetIq(jt.peer, jt.jid, [ + jp.Jingle(jt.sid, jt.peer, 'content-reject', [ + ('reason', None, {}, [ + ('failed-application', None, {}, [])]), + jp.Content(c['name'], c['creator'], c['senders']) ]) ]) + stream.send(jp.xml(node)) + + q.expect('dbus-signal', signal='StreamError', + args=[stream_id, + cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED, + ""]), + +def test_remote_content_add_incoming(jp, q, bus, conn, stream): + _remote_content_add(jp, q, bus, conn, stream, + _start_audio_session_incoming) + +def test_remote_content_add_outgoing(jp, q, bus, conn, stream): + _remote_content_add(jp, q, bus, conn, stream, + _start_audio_session_outgoing) + +def test_local_content_add_incoming(jp, q, bus, conn, stream): + _local_content_add(jp, q, bus, conn, stream, _start_audio_session_incoming) + +def test_local_content_add_outgoing(jp, q, bus, conn, stream): + _local_content_add(jp, q, bus, conn, stream, _start_audio_session_outgoing) + +if __name__ == '__main__': + for f in (test_local_content_add_incoming, + test_local_content_add_outgoing, + test_remote_content_add_incoming, + test_remote_content_add_outgoing): + exec_test( + lambda q, b, c, s: f(JingleProtocol031(), q, b, c, s)) diff --git a/tests/twisted/jingle/stream-errors-on-terminate.py b/tests/twisted/jingle/stream-errors-on-terminate.py new file mode 100644 index 000000000..8e46473bc --- /dev/null +++ b/tests/twisted/jingle/stream-errors-on-terminate.py @@ -0,0 +1,134 @@ +""" +Test StreamError events and on session terminate, both directions. +""" + +import dbus + +from gabbletest import make_result_iq, sync_stream, exec_test +from servicetest import ( + make_channel_proxy, unwrap, EventPattern, assertEquals, assertLength) +from jingletest2 import JingleTest2, JingleProtocol031 +import constants as cs + +from twisted.words.xish import xpath + +def _session_terminate_predicate(event, msg): + reason = xpath.queryForNodes("/iq" + "/jingle[@action='session-terminate']" + "/reason/failed-application", + event.stanza) + reason_text = xpath.queryForString("/iq/jingle/reason/text", + event.stanza) + + return reason is not None and reason_text == msg + +def _test_terminate_reason(jp, q, bus, conn, stream, incoming): + jt = JingleTest2(jp, conn, q, stream, 'test@localhost', 'foo@bar.com/Foo') + jt.prepare() + + self_handle = conn.GetSelfHandle() + remote_handle = conn.RequestHandles(cs.HT_CONTACT, [jt.peer])[0] + + if incoming: + jt.incoming_call() + else: + ret = conn.Requests.CreateChannel( + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_HANDLE: remote_handle, + cs.INITIAL_AUDIO: True + }) + + nc, e = q.expect_many( + EventPattern('dbus-signal', signal='NewChannel', + predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args), + EventPattern('dbus-signal', signal='NewSessionHandler')) + + path = nc.args[0] + + media_chan = make_channel_proxy(conn, path, 'Channel.Interface.Group') + media_iface = make_channel_proxy(conn, path, 'Channel.Type.StreamedMedia') + + # S-E was notified about new session handler, and calls Ready on it + session_handler = make_channel_proxy(conn, e.args[0], + 'Media.SessionHandler') + session_handler.Ready() + + nsh_event = q.expect('dbus-signal', signal='NewStreamHandler') + + # S-E gets notified about a newly-created stream + stream_handler = make_channel_proxy(conn, nsh_event.args[0], + 'Media.StreamHandler') + + group_props = media_chan.GetAll( + cs.CHANNEL_IFACE_GROUP, dbus_interface=dbus.PROPERTIES_IFACE) + + if incoming: + assertEquals([remote_handle], group_props['Members']) + assertEquals(unwrap(group_props['LocalPendingMembers']), + [(self_handle, remote_handle, cs.GC_REASON_INVITED, '')]) + else: + assertEquals([self_handle], group_props['Members']) + + streams = media_chan.ListStreams( + dbus_interface=cs.CHANNEL_TYPE_STREAMED_MEDIA) + + stream_id = streams[0][0] + + stream_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus()) + stream_handler.Ready(jt.dbusify_codecs([("FOO", 5, 8000, {})])) + + msg = u"None of the codecs are good for us, damn!" + + expected_events = [] + + if incoming: + q.expect('dbus-signal', signal='SetRemoteCodecs') + stream_handler.Error(cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED, + msg) + expected_events = [EventPattern( + "stream-iq", iq_type="set", + predicate=lambda x: _session_terminate_predicate(x, msg))] + rejector = self_handle + else: + stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) + session_initiate = q.expect( + 'stream-iq', + predicate=jp.action_predicate('session-initiate')) + + q.expect('dbus-signal', signal='MembersChanged', path=path, + args=['', [], [], [], [remote_handle], self_handle, + cs.GC_REASON_INVITED]) + + jt.parse_session_initiate(session_initiate.query) + stream.send(jp.xml(jp.ResultIq('test@localhost', + session_initiate.stanza, []))) + jt.terminate('failed-application', msg) + rejector = remote_handle + + expected_events += [ + EventPattern('dbus-signal', signal='StreamError', + interface=cs.CHANNEL_TYPE_STREAMED_MEDIA, path=path, + args=[stream_id, + cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED, + msg]), + EventPattern('dbus-signal', signal='MembersChanged', + interface=cs.CHANNEL_IFACE_GROUP, path=path, + args=[msg, [], [self_handle, remote_handle], [], [], + rejector, cs.GC_REASON_ERROR])] + + + q.expect_many(*expected_events) + + q.expect('dbus-signal', signal='Closed', path=path) + +def test_terminate_outgoing(jp, q, bus, conn, stream): + _test_terminate_reason(jp, q, bus, conn, stream, False) + +def test_terminate_incoming(jp, q, bus, conn, stream): + _test_terminate_reason(jp, q, bus, conn, stream, True) + +if __name__ == '__main__': + for f in (test_terminate_incoming, test_terminate_outgoing): + exec_test( + lambda q, b, c, s: f(JingleProtocol031(), q, b, c, s)) diff --git a/tests/twisted/jingle/test-outgoing-call-rejected.py b/tests/twisted/jingle/test-outgoing-call-rejected.py index d629b2af9..3f0ef4ad4 100644 --- a/tests/twisted/jingle/test-outgoing-call-rejected.py +++ b/tests/twisted/jingle/test-outgoing-call-rejected.py @@ -8,7 +8,8 @@ from servicetest import make_channel_proxy, assertEquals import constants as cs from jingletest2 import JingleTest2, test_all_dialects -def test(jp, q, bus, conn, stream): +def _test(jp, q, bus, conn, stream, + jingle_reason, group_change_reason, stream_error): remote_jid = 'foo@bar.com/Foo' jt = JingleTest2(jp, conn, q, stream, 'test@localhost', remote_jid) @@ -47,7 +48,7 @@ def test(jp, q, bus, conn, stream): text = u"begone!" jt.parse_session_initiate(e.query) - jt.terminate(reason="busy", text=text) + jt.terminate(reason=jingle_reason, text=text) mc = q.expect('dbus-signal', signal='MembersChanged') message, added, removed, lp, rp, actor, reason = mc.args @@ -59,9 +60,21 @@ def test(jp, q, bus, conn, stream): assert actor == remote_handle, (actor, remote_handle) if jp.is_modern_jingle(): assertEquals(text, message) - assertEquals(cs.GC_REASON_BUSY, reason) + assertEquals(group_change_reason, reason) + + if jp.is_modern_jingle() and stream_error: + se = q.expect('dbus-signal', signal='StreamError') + assertEquals(stream_error, se.args[1]) q.expect('dbus-signal', signal='Close') #XXX - match against the path +def test_busy(jp, q, bus, conn, stream): + _test(jp, q, bus, conn, stream, "busy", cs.GC_REASON_BUSY, None) + +def test_codec_fail(jp, q, bus, conn, stream): + _test(jp, q, bus, conn, stream, "failed-application", cs.GC_REASON_ERROR, + cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED) + if __name__ == '__main__': - test_all_dialects(test) + test_all_dialects(test_busy) + test_all_dialects(test_codec_fail) diff --git a/tools/telepathy.am b/tools/telepathy.am index 1ed13078f..971502639 100644 --- a/tools/telepathy.am +++ b/tools/telepathy.am @@ -7,8 +7,9 @@ dist-hook: fi distcheck-hook: - @case @VERSION@ in \ - *.*.*.*) ;; \ + @test "z$(CHECK_FOR_UNRELEASED)" = z || \ + case @VERSION@ in \ + *.*.*.*|*+) ;; \ *) \ if grep -r UNRELEASED $(CHECK_FOR_UNRELEASED); \ then \ @@ -20,7 +21,7 @@ distcheck-hook: _is-release-check: @case @VERSION@ in \ - (*.*.*.*) \ + (*.*.*.*|*+) \ echo "Hey! @VERSION@ is not a release!" >&2; \ exit 2; \ ;; \ @@ -29,6 +30,10 @@ _is-release-check: echo "Hey! Your tree is dirty! No release for you." >&2; \ exit 2; \ fi + @if ! git diff --cached --no-ext-diff --quiet --exit-code; then \ + echo "Hey! You have changes staged! No release for you." >&2; \ + exit 2; \ + fi %.tar.gz.asc: %.tar.gz $(AM_V_GEN)gpg --detach-sign --armor $@ @@ -59,6 +64,6 @@ maintainer-make-release: maintainer-prepare-release maintainer-upload-release @echo "Now:" @echo " • bump the nano-version;" @echo " • push the branch and tags upstream; and" - @echo " • send release-mail to <telepathy@freedesktop.org>." + @echo " • send release-mail to <telepathy@lists.freedesktop.org>." ## vim:set ft=automake: |