summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Crête <olivier.crete@collabora.com>2012-05-08 12:51:51 -0400
committerOlivier Crête <olivier.crete@collabora.com>2012-05-08 12:51:51 -0400
commit0d032e114d76461f33dfd1138f3be5f5e9a4403c (patch)
tree96d01c71ef6227d85009cc2f28ee7cbad9b69cf0
parent8cb2b73e711e86dd2efeb73b20fc3973ef8683c0 (diff)
parent86f1d77943a377053f06d898767535f36a3f0a24 (diff)
Merge branch 'call1'
-rw-r--r--configure.ac2
-rw-r--r--rakia/Makefile.am18
-rw-r--r--rakia/call-channel.c829
-rw-r--r--rakia/call-channel.h71
-rw-r--r--rakia/call-content.c401
-rw-r--r--rakia/call-content.h84
-rw-r--r--rakia/call-stream.c650
-rw-r--r--rakia/call-stream.h76
-rw-r--r--rakia/codec-param-formats.c197
-rw-r--r--rakia/codec-param-formats.h50
-rw-r--r--rakia/debug.c3
-rw-r--r--rakia/debug.h1
-rw-r--r--rakia/media-channel.c2297
-rw-r--r--rakia/media-channel.h94
-rw-r--r--rakia/media-manager.c349
-rw-r--r--rakia/media-manager.h3
-rw-r--r--rakia/media-session.c2265
-rw-r--r--rakia/media-session.h133
-rw-r--r--rakia/media-stream.c2001
-rw-r--r--rakia/media-stream.h94
-rw-r--r--rakia/sip-media.c1236
-rw-r--r--rakia/sip-media.h182
-rw-r--r--rakia/sip-session.c1826
-rw-r--r--rakia/sip-session.h142
-rw-r--r--tests/suppressions/rakia.supp47
-rw-r--r--tests/suppressions/tp-glib.supp250
-rw-r--r--tests/twisted/Makefile.am8
-rw-r--r--tests/twisted/constants.py88
-rw-r--r--tests/twisted/sofiatest.py5
-rw-r--r--tests/twisted/tools/Makefile.am3
-rw-r--r--tests/twisted/tools/exec-with-log.sh.in3
-rw-r--r--tests/twisted/voip/add-remove-content.py212
-rw-r--r--tests/twisted/voip/calltest.py507
-rw-r--r--tests/twisted/voip/direction-change.py771
-rw-r--r--tests/twisted/voip/dtmf.py160
-rw-r--r--tests/twisted/voip/incoming-basics.py163
-rw-r--r--tests/twisted/voip/outgoing-basics.py284
-rw-r--r--tests/twisted/voip/requestable-classes.py59
-rw-r--r--tests/twisted/voip/ringing-queued.py44
-rw-r--r--tests/twisted/voip/voip_test.py189
40 files changed, 7907 insertions, 7890 deletions
diff --git a/configure.ac b/configure.ac
index 14621e3..83640a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,7 +86,7 @@ AC_SUBST(SOFIA_SIP_UA_CFLAGS)
AC_SUBST(SOFIA_SIP_UA_VERSION)
dnl Check for telepathy-glib
-PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.13.13])
+PKG_CHECK_MODULES(TELEPATHY_GLIB, [telepathy-glib >= 0.17.6])
AC_SUBST(TELEPATHY_GLIB_CFLAGS)
AC_SUBST(TELEPATHY_GLIB_LIBS)
diff --git a/rakia/Makefile.am b/rakia/Makefile.am
index 3e8f03d..48e5786 100644
--- a/rakia/Makefile.am
+++ b/rakia/Makefile.am
@@ -34,18 +34,22 @@ BUILT_SOURCES = \
librakia_la_SOURCES = \
base-connection.c \
base-connection-sofia.c \
- connection-aliasing.c \
+ connection-aliasing.c \
+ call-channel.h \
+ call-channel.c \
+ call-content.h \
+ call-content.c \
+ call-stream.h \
+ call-stream.c \
codec-param-formats.c \
event-target.c \
handles.c \
debug.c \
- media-channel.h \
- media-channel.c \
media-manager.c \
- media-session.h \
- media-session.c \
- media-stream.h \
- media-stream.c \
+ sip-media.c \
+ sip-media.h \
+ sip-session.c \
+ sip-session.h \
text-channel.h \
text-channel.c \
text-manager.c \
diff --git a/rakia/call-channel.c b/rakia/call-channel.c
new file mode 100644
index 0000000..51267f2
--- /dev/null
+++ b/rakia/call-channel.c
@@ -0,0 +1,829 @@
+/*
+ * call-channel.c - RakiaCallChannel
+ * Copyright © 2011-2012 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "rakia/call-channel.h"
+
+#include <string.h>
+
+#include "rakia/call-content.h"
+#include "rakia/sip-session.h"
+
+#define DEBUG_FLAG RAKIA_DEBUG_CALL
+#include "rakia/debug.h"
+
+
+#include <telepathy-glib/exportable-channel.h>
+
+
+G_DEFINE_TYPE (RakiaCallChannel, rakia_call_channel,
+ TP_TYPE_BASE_MEDIA_CALL_CHANNEL)
+
+static void rakia_call_channel_constructed (GObject *obj);
+static void rakia_call_channel_set_property (GObject *object,
+ guint property_id, const GValue *value, GParamSpec *pspec);
+static void rakia_call_channel_get_property (GObject *object,
+ guint property_id, GValue *value, GParamSpec *pspec);
+static void rakia_call_channel_dispose (GObject *object);
+static void rakia_call_channel_finalize (GObject *object);
+
+
+static void rakia_call_channel_close (TpBaseChannel *base);
+
+static TpBaseCallContent * rakia_call_channel_add_content (
+ TpBaseCallChannel *base,
+ const gchar *name,
+ TpMediaStreamType type,
+ TpMediaStreamDirection initial_direction,
+ GError **error);
+static void rakia_call_channel_hangup (
+ TpBaseCallChannel *base,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message);
+static void rakia_call_channel_set_ringing (TpBaseCallChannel *base);
+static void rakia_call_channel_set_queued (TpBaseCallChannel *base);
+
+static void rakia_call_channel_accept (TpBaseMediaCallChannel *channel);
+static void rakia_call_channel_hold_state_changed (TpBaseMediaCallChannel *self,
+ TpLocalHoldState hold_state, TpLocalHoldStateReason hold_state_reason);
+
+static gboolean rakia_call_channel_is_connected (TpBaseCallChannel *self);
+
+static void ended_cb (RakiaSipSession *session, gboolean self_actor,
+ guint status, gchar *message, RakiaCallChannel *self);
+static void ringing_cb (RakiaSipSession *session, RakiaCallChannel *self);
+static void queued_cb (RakiaSipSession *session, RakiaCallChannel *self);
+static void in_progress_cb (RakiaSipSession *session, RakiaCallChannel *self);
+static void media_added_cb (RakiaSipSession *session, RakiaSipMedia *media,
+ RakiaCallChannel *self);
+static void media_removed_cb (RakiaSipSession *session, RakiaSipMedia *media,
+ RakiaCallChannel *self);
+static void state_changed_cb (RakiaSipSession *session,
+ RakiaSipSessionState old_state, RakiaSipSessionState new_state,
+ RakiaCallChannel *self);
+static void remote_held_changed_cb (RakiaSipSession *session, GParamSpec *pspec,
+ RakiaCallChannel *self);
+
+
+static RakiaCallContent *rakia_call_channel_get_content_by_media (RakiaCallChannel *self,
+ RakiaSipMedia *media);
+
+static void new_content (RakiaCallChannel *self,
+ const gchar *name,
+ RakiaSipMedia *media,
+ TpCallContentDisposition disposition);
+
+
+/* properties */
+enum
+{
+ PROP_SIP_SESSION = 1,
+ PROP_STUN_SERVER,
+ PROP_STUN_PORT,
+ LAST_PROPERTY
+};
+
+
+/* private structure */
+struct _RakiaCallChannelPrivate
+{
+ RakiaSipSession *session;
+
+ gchar *stun_server;
+ guint stun_port;
+
+ guint last_content_no;
+
+};
+
+
+
+static void
+rakia_call_channel_init (RakiaCallChannel *self)
+{
+ RakiaCallChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ RAKIA_TYPE_CALL_CHANNEL, RakiaCallChannelPrivate);
+
+ self->priv = priv;
+}
+
+
+static gchar *
+rakia_call_channel_get_object_path_suffix (TpBaseChannel *base)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (base);
+
+ return g_strdup_printf ("CallChannel%p", self);
+}
+
+
+
+static void
+rakia_call_channel_class_init (
+ RakiaCallChannelClass *rakia_call_channel_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (rakia_call_channel_class);
+ TpBaseChannelClass *base_channel_class =
+ TP_BASE_CHANNEL_CLASS (rakia_call_channel_class);
+ TpBaseCallChannelClass *base_call_class =
+ TP_BASE_CALL_CHANNEL_CLASS (rakia_call_channel_class);
+ TpBaseMediaCallChannelClass *base_media_call_class =
+ TP_BASE_MEDIA_CALL_CHANNEL_CLASS (rakia_call_channel_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (rakia_call_channel_class,
+ sizeof (RakiaCallChannelPrivate));
+
+ object_class->constructed = rakia_call_channel_constructed;
+ object_class->get_property = rakia_call_channel_get_property;
+ object_class->set_property = rakia_call_channel_set_property;
+
+ object_class->dispose = rakia_call_channel_dispose;
+ object_class->finalize = rakia_call_channel_finalize;
+
+
+ base_channel_class->target_handle_type = TP_HANDLE_TYPE_CONTACT;
+ base_channel_class->get_object_path_suffix =
+ rakia_call_channel_get_object_path_suffix;
+ base_channel_class->close = rakia_call_channel_close;
+
+ base_call_class->add_content = rakia_call_channel_add_content;
+ base_call_class->hangup = rakia_call_channel_hangup;
+ base_call_class->set_ringing = rakia_call_channel_set_ringing;
+ base_call_class->set_queued = rakia_call_channel_set_queued;
+ base_call_class->is_connected = rakia_call_channel_is_connected;
+
+ base_media_call_class->accept = rakia_call_channel_accept;
+ base_media_call_class->hold_state_changed =
+ rakia_call_channel_hold_state_changed;
+
+ param_spec = g_param_spec_object ("sip-session", "RakiaSipSession object",
+ "SIP session object that is used for this SIP media channel object.",
+ RAKIA_TYPE_SIP_SESSION,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIP_SESSION, param_spec);
+
+ param_spec = g_param_spec_string ("stun-server", "STUN server",
+ "IP or address of STUN server.", NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STUN_SERVER, param_spec);
+
+ param_spec = g_param_spec_uint ("stun-port", "STUN port",
+ "UDP port of STUN server.", 0, G_MAXUINT16, 0,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STUN_PORT, param_spec);
+}
+
+
+static void
+rakia_call_channel_constructed (GObject *obj)
+{
+ TpBaseChannel *bc = TP_BASE_CHANNEL (obj);
+ TpBaseCallChannel *bcc = TP_BASE_CALL_CHANNEL (obj);
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (obj);
+ GObjectClass *parent_object_class =
+ G_OBJECT_CLASS (rakia_call_channel_parent_class);
+ TpHandle actor;
+ TpCallStateChangeReason reason;
+
+ g_signal_connect_object (self->priv->session, "ended",
+ G_CALLBACK (ended_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "ringing",
+ G_CALLBACK (ringing_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "queued",
+ G_CALLBACK (queued_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "in-progress",
+ G_CALLBACK (in_progress_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "media-added",
+ G_CALLBACK (media_added_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "media-removed",
+ G_CALLBACK (media_removed_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "state-changed",
+ G_CALLBACK (state_changed_cb), self, 0);
+ g_signal_connect_object (self->priv->session, "notify::remote-held",
+ G_CALLBACK (remote_held_changed_cb), self, 0);
+
+ if (tp_base_channel_is_requested (bc))
+ {
+ const gchar *initial_audio_name;
+ const gchar *initial_video_name;
+
+ if (tp_base_call_channel_has_initial_audio (bcc, &initial_audio_name))
+ rakia_sip_session_add_media (self->priv->session,
+ TP_MEDIA_STREAM_TYPE_AUDIO, initial_audio_name,
+ RAKIA_DIRECTION_BIDIRECTIONAL);
+
+ if (tp_base_call_channel_has_initial_video (bcc, &initial_video_name))
+ rakia_sip_session_add_media (self->priv->session,
+ TP_MEDIA_STREAM_TYPE_VIDEO, initial_video_name,
+ RAKIA_DIRECTION_BIDIRECTIONAL);
+
+ actor = tp_base_channel_get_self_handle (bc);
+ reason = TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED;
+ }
+ else
+ {
+ guint i;
+ GPtrArray *medias = rakia_sip_session_get_medias (self->priv->session);
+
+ for (i = 0; i < medias->len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (medias, i);
+ gchar *name;
+
+ if (media)
+ {
+ name = g_strdup_printf ("initial_%s_%u",
+ sip_media_get_media_type_str (media), i + 1);
+ new_content (self, name, media,
+ TP_CALL_CONTENT_DISPOSITION_INITIAL);
+ g_free (name);
+ }
+ }
+
+
+ actor = tp_base_channel_get_target_handle (bc);
+ reason = TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE;
+ }
+
+ tp_base_call_channel_update_member_flags (bcc,
+ tp_base_channel_get_target_handle (bc), 0,
+ actor, reason, "", "Call Created");
+
+ if (parent_object_class->constructed != NULL)
+ parent_object_class->constructed (obj);
+}
+
+static void
+rakia_call_channel_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (object);
+ RakiaCallChannelPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_SIP_SESSION:
+ g_value_set_object (value, priv->session);
+ break;
+ case PROP_STUN_SERVER:
+ g_value_set_string (value, priv->stun_server);
+ break;
+ case PROP_STUN_PORT:
+ g_value_set_uint (value, priv->stun_port);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+
+static void
+rakia_call_channel_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (object);
+ RakiaCallChannelPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_SIP_SESSION:
+ priv->session = g_value_dup_object (value);
+ break;
+ case PROP_STUN_SERVER:
+ priv->stun_server = g_value_dup_string (value);
+ break;
+ case PROP_STUN_PORT:
+ priv->stun_port = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+
+static void
+rakia_call_channel_dispose (GObject *object)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (object);
+ RakiaCallChannelPrivate *priv = self->priv;
+
+ DEBUG ("disposing");
+
+ tp_clear_object (&priv->session);
+
+ if (G_OBJECT_CLASS (rakia_call_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_call_channel_parent_class)->dispose (object);
+}
+
+static void
+rakia_call_channel_finalize (GObject *object)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (object);
+ RakiaCallChannelPrivate *priv = self->priv;
+
+ g_free (priv->stun_server);
+
+ G_OBJECT_CLASS (rakia_call_channel_parent_class)->finalize (object);
+}
+
+
+static void
+rakia_call_channel_close (TpBaseChannel *base)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (base);
+ RakiaCallChannelPrivate *priv = self->priv;
+
+ if (priv->session)
+ rakia_sip_session_terminate (priv->session, 480, "Terminated");
+
+ DEBUG ("Closed: %s", tp_base_channel_get_object_path (base));
+
+ TP_BASE_CHANNEL_CLASS (rakia_call_channel_parent_class)->close (base);
+}
+
+static TpBaseCallContent *
+rakia_call_channel_add_content (
+ TpBaseCallChannel *base,
+ const gchar *name,
+ TpMediaStreamType type,
+ TpMediaStreamDirection initial_direction,
+ GError **error)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (base);
+ RakiaCallChannelPrivate *priv = self->priv;
+ RakiaCallContent *content;
+ RakiaSipMedia *media;
+
+ media = rakia_sip_session_add_media (priv->session,
+ type, name, initial_direction);
+
+ content = rakia_call_channel_get_content_by_media (self, media);
+
+ return TP_BASE_CALL_CONTENT (content);
+
+}
+static void
+rakia_call_channel_hangup (
+ TpBaseCallChannel *base,
+ TpCallStateChangeReason reason,
+ const gchar *detailed_reason,
+ const gchar *message)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (base);
+ RakiaCallChannelPrivate *priv = self->priv;
+ guint status = 486;
+ const gchar *sipmessage;
+
+ switch (reason) {
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_NO_ANSWER:
+ status = 480;
+ sipmessage = "No answer";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_INVALID_CONTACT:
+ sipmessage = "Not Found";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_PERMISSION_DENIED:
+ sipmessage = "Permission denied";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_REJECTED:
+ sipmessage = "Rejected";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_BUSY:
+ sipmessage = "Busy";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR:
+ status = 480;
+ sipmessage = "Internal Error";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_SERVICE_ERROR:
+ sipmessage = "Service Error";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_NETWORK_ERROR:
+ status = 480;
+ sipmessage = "Network error";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR:
+ status = 488;
+ if (!strcmp (detailed_reason, TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE))
+ sipmessage = "Unsupported type";
+ else if (!strcmp (detailed_reason, TP_ERROR_STR_MEDIA_CODECS_INCOMPATIBLE))
+ sipmessage = "Codecs Incompatible";
+ else
+ sipmessage = "Media error";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_CONNECTIVITY_ERROR:
+ sipmessage = "Connectivity Error";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED:
+ sipmessage = "User ended call";
+ break;
+ case TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE:
+ case TP_CALL_STATE_CHANGE_REASON_UNKNOWN:
+ default:
+ sipmessage = "Terminated by unknown reason";
+ }
+
+ rakia_sip_session_terminate (priv->session, status, sipmessage);
+}
+
+static void
+rakia_call_channel_set_ringing (TpBaseCallChannel *base)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (base);
+
+ rakia_sip_session_ringing (self->priv->session);
+}
+
+static void
+rakia_call_channel_set_queued (TpBaseCallChannel *base)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (base);
+
+ rakia_sip_session_queued (self->priv->session);
+
+}
+
+static void
+rakia_call_channel_accept (TpBaseMediaCallChannel *channel)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (channel);
+
+ rakia_sip_session_accept (self->priv->session);
+}
+
+static void
+rakia_call_channel_hold_state_changed (TpBaseMediaCallChannel *bmcc,
+ TpLocalHoldState hold_state,
+ TpLocalHoldStateReason hold_state_reason)
+{
+ RakiaCallChannel *self = RAKIA_CALL_CHANNEL (bmcc);
+
+ DEBUG ("hold state changed to %d", hold_state);
+
+ switch (hold_state)
+ {
+ case TP_LOCAL_HOLD_STATE_PENDING_HOLD:
+ case TP_LOCAL_HOLD_STATE_HELD:
+ case TP_LOCAL_HOLD_STATE_PENDING_UNHOLD:
+ rakia_sip_session_set_hold_requested (self->priv->session, TRUE);
+ break;
+ case TP_LOCAL_HOLD_STATE_UNHELD:
+ rakia_sip_session_set_hold_requested (self->priv->session, FALSE);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+ended_cb (RakiaSipSession *session, gboolean self_actor, guint status,
+ gchar *message, RakiaCallChannel *self)
+{
+ TpHandle actor;
+ TpCallStateChangeReason reason;
+ const gchar *detailed_reason = "";
+ gchar *free_message = NULL;
+
+ if (self_actor)
+ actor = tp_base_channel_get_self_handle (TP_BASE_CHANNEL (self));
+ else
+ actor = tp_base_channel_get_target_handle (TP_BASE_CHANNEL (self));
+
+ switch (status)
+ {
+ case 400: /* Bad Request */
+ case 405: /* Method Not Allowed */
+ case 406: /* Not Acceptable */
+ case 413: /* Request Entity Too Large */
+ case 414: /* Request-URI Too Long */
+ case 415: /* Unsupported Media Type */
+ case 416: /* Unsupported URI Scheme */
+ case 420: /* Bad Extension */
+ case 421: /* Extension Required */
+ case 483: /* Too Many Hops */
+ case 484: /* Address incomplete */
+ case 485: /* Ambiguous */
+ case 493: /* Undecipherable */
+ case 606: /* Not Acceptable */
+ reason = TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR;
+ detailed_reason = TP_ERROR_STR_CONFUSED;
+ break;
+ case 500: /* Server Internal Error */
+ case 501: /* Not Implemented */
+ case 502: /* Bad Gateway */
+ case 503: /* Service Unavailable */
+ case 504: /* Server Time-out */
+ case 505: /* Version Not Supported */
+ case 513: /* Message Too Large */
+ reason = TP_CALL_STATE_CHANGE_REASON_SERVICE_ERROR;
+ detailed_reason = TP_ERROR_STR_SERVICE_CONFUSED;
+ break;
+ case 401: /* Unauthorized */
+ case 403: /* Forbidden */
+ reason = TP_CALL_STATE_CHANGE_REASON_PERMISSION_DENIED;
+ detailed_reason = TP_ERROR_STR_PERMISSION_DENIED;
+ break;
+ case 404: /* Not Found */
+ case 410: /* Gone */
+ case 604: /* Does Not Exist Anywhere */
+ reason = TP_CALL_STATE_CHANGE_REASON_INVALID_CONTACT;
+ detailed_reason = TP_ERROR_STR_DOES_NOT_EXIST;
+ break;
+ case 480: /* Temporarily Unavaible */
+ case 408: /* Request Timeout */
+ reason = TP_CALL_STATE_CHANGE_REASON_NO_ANSWER;
+ detailed_reason = TP_ERROR_STR_NO_ANSWER;
+ break;
+ case 486: /* Busy Here */
+ case 600: /* Busy Everywhere */
+ reason = TP_CALL_STATE_CHANGE_REASON_BUSY;
+ detailed_reason = TP_ERROR_STR_BUSY;
+ break;
+ case 603: /* Decline */
+ reason = TP_CALL_STATE_CHANGE_REASON_REJECTED;
+ detailed_reason = TP_ERROR_STR_REJECTED;
+ break;
+
+ default:
+ reason = TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED;
+ break;
+ }
+
+ if (message[0] == 0 && reason != 0)
+ free_message = message = g_strdup_printf ("SIP status code %d", status);
+
+ tp_base_call_channel_set_state (TP_BASE_CALL_CHANNEL (self),
+ TP_CALL_STATE_ENDED, actor, reason, detailed_reason, message);
+ g_free (free_message);
+}
+
+static void
+ringing_cb (RakiaSipSession *session, RakiaCallChannel *self)
+{
+
+ tp_base_call_channel_update_member_flags (TP_BASE_CALL_CHANNEL (self),
+ tp_base_channel_get_target_handle (TP_BASE_CHANNEL (self)),
+ TP_CALL_MEMBER_FLAG_RINGING,
+ tp_base_channel_get_target_handle (TP_BASE_CHANNEL (self)),
+ TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE,
+ "", "Remote side has started ringing");
+}
+
+static void
+queued_cb (RakiaSipSession *session, RakiaCallChannel *self)
+{
+}
+
+static void
+in_progress_cb (RakiaSipSession *session, RakiaCallChannel *self)
+{
+}
+
+static RakiaCallContent *
+rakia_call_channel_get_content_by_media (RakiaCallChannel *self,
+ RakiaSipMedia *media)
+{
+ GList *e;
+
+ for (e = tp_base_call_channel_get_contents (TP_BASE_CALL_CHANNEL (self));
+ e != NULL;
+ e = e->next)
+ {
+ RakiaCallContent *content = e->data;
+ if (rakia_call_content_get_media (content) == media)
+ return content;
+ }
+
+ return NULL;
+}
+
+static void
+new_content (RakiaCallChannel *self,
+ const gchar *name,
+ RakiaSipMedia *media,
+ TpCallContentDisposition disposition)
+{
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (self);
+ RakiaCallContent *content;
+ TpMediaStreamType media_type;
+ TpHandle creator;
+ gchar *object_path;
+ gchar *free_name = NULL;
+ const gchar *media_type_name;
+
+ switch (rakia_sip_media_get_media_type (media))
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ media_type = TP_MEDIA_STREAM_TYPE_AUDIO;
+ media_type_name = "Audio";
+ break;
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ media_type = TP_MEDIA_STREAM_TYPE_VIDEO;
+ media_type_name = "Video";
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (rakia_sip_media_is_created_locally (media))
+ creator = tp_base_channel_get_self_handle (bchan);
+ else
+ creator = tp_base_channel_get_target_handle (bchan);
+
+ object_path = g_strdup_printf ("%s/Content%u",
+ tp_base_channel_get_object_path (bchan), ++self->priv->last_content_no);
+
+ if (name == NULL)
+ name = free_name = g_strdup_printf ("%s %u", media_type_name,
+ self->priv->last_content_no);
+
+ /* We already request bidi for initial media,
+ * the client can change it before accepting.
+ */
+ if (disposition == TP_CALL_CONTENT_DISPOSITION_INITIAL)
+ rakia_sip_media_set_requested_direction (media,
+ RAKIA_DIRECTION_BIDIRECTIONAL);
+
+
+ content = rakia_call_content_new (self, media, object_path,
+ tp_base_channel_get_connection (bchan), name,
+ media_type, creator, disposition);
+
+ g_free (free_name);
+ g_free (object_path);
+
+ tp_base_call_channel_add_content (TP_BASE_CALL_CHANNEL (self),
+ TP_BASE_CALL_CONTENT (content));
+
+ rakia_call_content_add_stream (content);
+}
+
+static void
+media_added_cb (RakiaSipSession *session, RakiaSipMedia *media,
+ RakiaCallChannel *self)
+{
+ TpCallContentDisposition disposition;
+ const gchar *name;
+
+ DEBUG ("Adding media");
+
+ /* Ignore new medias that we have created ourselves */
+ g_assert (rakia_call_channel_get_content_by_media (self, media) == NULL);
+
+ switch (rakia_sip_session_get_state (session))
+ {
+ case RAKIA_SIP_SESSION_STATE_CREATED:
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ disposition = TP_CALL_CONTENT_DISPOSITION_INITIAL;
+ break;
+ default:
+ disposition = TP_CALL_CONTENT_DISPOSITION_NONE;
+ }
+
+ name = rakia_sip_media_get_name (media);
+
+ new_content (self, name, media, disposition);
+}
+
+static void
+media_removed_cb (RakiaSipSession *session, RakiaSipMedia *media,
+ RakiaCallChannel *self)
+{
+ RakiaCallContent *content;
+
+ content = rakia_call_channel_get_content_by_media (self, media);
+ if (content == NULL)
+ return;
+
+ tp_base_call_channel_remove_content (TP_BASE_CALL_CHANNEL (self),
+ TP_BASE_CALL_CONTENT (content),
+ tp_base_channel_get_target_handle (TP_BASE_CHANNEL (self)),
+ TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "Removed by remote side");
+}
+
+static void
+state_changed_cb (RakiaSipSession *session, RakiaSipSessionState old_state,
+ RakiaSipSessionState new_state, RakiaCallChannel *self)
+{
+ switch (new_state)
+ {
+ case RAKIA_SIP_SESSION_STATE_INVITE_SENT:
+ /* Do nothing.. we don't have a TP state for this */
+ break;
+
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ /* This will never be received here because this is what
+ * triggers RakiaMediaManager to create a Channel in the first place
+ */
+ break;
+
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+
+ if (tp_base_channel_is_requested (TP_BASE_CHANNEL (self)))
+ {
+ GList *e;
+
+ tp_base_call_channel_remote_accept (TP_BASE_CALL_CHANNEL (self));
+
+ for (e = tp_base_call_channel_get_contents (
+ TP_BASE_CALL_CHANNEL (self));
+ e != NULL;
+ e = e->next)
+ {
+ RakiaCallContent *content = e->data;
+ if (content)
+ rakia_call_content_remote_accept (content);
+ }
+ }
+
+ case RAKIA_SIP_SESSION_STATE_ENDED:
+ /* the ended callback is used to get more information */
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+remote_held_changed_cb (RakiaSipSession *session, GParamSpec *pspec,
+ RakiaCallChannel *self)
+{
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (self);
+ TpBaseCallChannel *bcc = TP_BASE_CALL_CHANNEL (self);
+ gboolean remote_held;
+ GHashTable *members;
+ TpCallMemberFlags member_flags;
+ TpHandle remote_contact = tp_base_channel_get_target_handle (bchan);
+
+ g_object_get (session, "remote-held", &remote_held, NULL);
+
+ members = tp_base_call_channel_get_call_members (bcc);
+
+ member_flags = GPOINTER_TO_UINT (g_hash_table_lookup (members,
+ GUINT_TO_POINTER (remote_contact)));
+
+ if (!!(member_flags & TP_CALL_MEMBER_FLAG_HELD) == remote_held)
+ return;
+
+ if (remote_held)
+ member_flags |= TP_CALL_MEMBER_FLAG_HELD;
+ else
+ member_flags &= ~TP_CALL_MEMBER_FLAG_HELD;
+
+ tp_base_call_channel_update_member_flags (bcc, remote_contact, member_flags,
+ remote_contact, TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ remote_held ? "Held by remote side" : "Unheld by remote side");
+}
+
+static gboolean
+rakia_call_channel_is_connected (TpBaseCallChannel *self)
+{
+ /* We don't support ICE, so we don'T have the concept of connected-ness
+ * yet.
+ */
+ return TRUE;
+}
+
+void
+rakia_call_channel_hangup_error (RakiaCallChannel *self,
+ TpCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ const gchar *message)
+{
+ TpHandle self_handle = tp_base_channel_get_self_handle (
+ TP_BASE_CHANNEL (self));
+
+ rakia_call_channel_hangup (TP_BASE_CALL_CHANNEL (self),
+ reason, dbus_reason, message);
+
+ tp_base_call_channel_set_state (TP_BASE_CALL_CHANNEL (self),
+ TP_CALL_STATE_ENDED, self_handle, reason, dbus_reason, message);
+}
diff --git a/rakia/call-channel.h b/rakia/call-channel.h
new file mode 100644
index 0000000..06c2eaf
--- /dev/null
+++ b/rakia/call-channel.h
@@ -0,0 +1,71 @@
+/*
+ * call-channel.h - Header for RakiaCallChannel
+ * Copyright © 2011 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __RAKIA_CALL_CHANNEL_H__
+#define __RAKIA_CALL_CHANNEL_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-media-call-channel.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaCallChannel RakiaCallChannel;
+typedef struct _RakiaCallChannelPrivate RakiaCallChannelPrivate;
+typedef struct _RakiaCallChannelClass RakiaCallChannelClass;
+
+struct _RakiaCallChannelClass {
+ TpBaseMediaCallChannelClass parent_class;
+};
+
+struct _RakiaCallChannel {
+ TpBaseMediaCallChannel parent;
+
+ RakiaCallChannelPrivate *priv;
+};
+
+GType rakia_call_channel_get_type (void);
+
+/* TYPE MACROS */
+#define RAKIA_TYPE_CALL_CHANNEL \
+ (rakia_call_channel_get_type ())
+#define RAKIA_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ RAKIA_TYPE_CALL_CHANNEL, RakiaCallChannel))
+#define RAKIA_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ RAKIA_TYPE_CALL_CHANNEL, RakiaCallChannelClass))
+#define RAKIA_IS_CALL_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_CALL_CHANNEL))
+#define RAKIA_IS_CALL_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_CALL_CHANNEL))
+#define RAKIA_CALL_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ RAKIA_TYPE_CALL_CHANNEL, RakiaCallChannelClass))
+
+void
+rakia_call_channel_hangup_error (RakiaCallChannel *self,
+ TpCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ const gchar *message);
+
+G_END_DECLS
+
+#endif /* #ifndef __RAKIA_CALL_CHANNEL_H__*/
diff --git a/rakia/call-content.c b/rakia/call-content.c
new file mode 100644
index 0000000..314b04a
--- /dev/null
+++ b/rakia/call-content.c
@@ -0,0 +1,401 @@
+/*
+ * call-content.c - RakiaCallContent
+ * Copyright (C) 2011-2012 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "rakia/call-content.h"
+
+#include "rakia/call-stream.h"
+
+#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
+#include "debug.h"
+
+
+static void rakia_call_content_constructed (GObject *object);
+static void rakia_call_content_dispose (GObject *object);
+static void rakia_call_content_deinit (TpBaseCallContent *base);
+
+static void media_remote_codecs_updated_cb (RakiaSipMedia *media,
+ gboolean is_offer, RakiaCallContent *self);
+static void local_media_description_updated (RakiaCallContent *self,
+ TpHandle contact, GHashTable *properties, gpointer user_data);
+
+static void md_offer_cb (GObject *obj, GAsyncResult *res, gpointer user_data);
+
+
+G_DEFINE_TYPE (RakiaCallContent, rakia_call_content,
+ TP_TYPE_BASE_MEDIA_CALL_CONTENT);
+
+
+/* properties */
+enum
+{
+ PROP_CHANNEL = 1,
+ PROP_SIP_MEDIA,
+ LAST_PROPERTY
+};
+
+
+/* private structure */
+struct _RakiaCallContentPrivate
+{
+ RakiaCallChannel *channel;
+
+ RakiaSipMedia *media;
+
+ RakiaCallStream *stream;
+
+ guint codec_offer_id;
+};
+
+static void
+rakia_call_content_init (RakiaCallContent *self)
+{
+ RakiaCallContentPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ RAKIA_TYPE_CALL_CONTENT, RakiaCallContentPrivate);
+
+ self->priv = priv;
+}
+
+static void
+rakia_call_content_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaCallContent *self = RAKIA_CALL_CONTENT (object);
+ RakiaCallContentPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CHANNEL:
+ priv->channel = g_value_dup_object (value);
+ break;
+ case PROP_SIP_MEDIA:
+ priv->media = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_call_content_class_init (
+ RakiaCallContentClass *rakia_call_content_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (rakia_call_content_class);
+ TpBaseCallContentClass *bcc_class =
+ TP_BASE_CALL_CONTENT_CLASS (rakia_call_content_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (rakia_call_content_class,
+ sizeof (RakiaCallContentPrivate));
+
+ object_class->constructed = rakia_call_content_constructed;
+ object_class->dispose = rakia_call_content_dispose;
+ object_class->set_property = rakia_call_content_set_property;
+
+ bcc_class->deinit = rakia_call_content_deinit;
+
+ param_spec = g_param_spec_object ("channel", "RakiaCallChannel object",
+ "Call Channel.",
+ RAKIA_TYPE_CALL_CHANNEL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_CHANNEL, param_spec);
+
+ param_spec = g_param_spec_object ("sip-media", "RakiaSipMedia object",
+ "SIP media object that is used for this SIP media channel object.",
+ RAKIA_TYPE_SIP_MEDIA,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIP_MEDIA, param_spec);
+}
+
+static void
+rakia_call_content_constructed (GObject *object)
+{
+ RakiaCallContent *self = RAKIA_CALL_CONTENT (object);
+ RakiaCallContentPrivate *priv = self->priv;
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (priv->channel);
+ TpBaseCallContent *bcc = TP_BASE_CALL_CONTENT (self);
+ TpBaseMediaCallContent *bmcc = TP_BASE_MEDIA_CALL_CONTENT (self);
+ TpHandle creator;
+ gchar *object_path;
+
+ g_signal_connect_object (priv->media, "remote-codec-offer-updated",
+ G_CALLBACK (media_remote_codecs_updated_cb), self, 0);
+
+ g_signal_connect (self, "local-media-description-updated",
+ G_CALLBACK (local_media_description_updated), NULL);
+
+ g_object_get (object, "creator", &creator, NULL);
+
+ if (creator == tp_base_channel_get_self_handle (bchan))
+ {
+ TpCallContentMediaDescription *md;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ tp_base_call_content_get_connection (bcc));
+
+ object_path = g_strdup_printf ("%s/InitialOffer",
+ tp_base_call_content_get_object_path (bcc));
+
+ md = tp_call_content_media_description_new (bus, object_path,
+ tp_base_channel_get_target_handle (bchan), FALSE, TRUE);
+ g_free (object_path);
+
+ tp_base_media_call_content_offer_media_description_async (bmcc,
+ md, md_offer_cb, GUINT_TO_POINTER (TRUE));
+ }
+ else
+ {
+ media_remote_codecs_updated_cb (priv->media, TRUE, self);
+ }
+
+ G_OBJECT_CLASS (rakia_call_content_parent_class)->constructed (object);
+}
+
+static void
+rakia_call_content_dispose (GObject *object)
+{
+ RakiaCallContent *self = RAKIA_CALL_CONTENT (object);
+ RakiaCallContentPrivate *priv = self->priv;
+
+ tp_clear_object (&priv->media);
+
+ if (G_OBJECT_CLASS (rakia_call_content_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_call_content_parent_class)->dispose (object);
+}
+
+static void
+rakia_call_content_deinit (TpBaseCallContent *base)
+{
+ RakiaCallContent *self = RAKIA_CALL_CONTENT (base);
+ RakiaCallContentPrivate *priv = self->priv;
+ RakiaSipSession *session;
+
+ session = rakia_sip_media_get_session (priv->media);
+
+ /* If the media was removed, it means it's by user request, so we must
+ * do a re-invite
+ */
+ if (rakia_sip_session_remove_media (session, priv->media, 0, NULL))
+ rakia_sip_session_media_changed (session);
+
+
+ tp_clear_object (&priv->stream);
+ tp_clear_object (&priv->channel);
+
+ TP_BASE_CALL_CONTENT_CLASS (
+ rakia_call_content_parent_class)->deinit (base);
+}
+
+RakiaCallContent *
+rakia_call_content_new (RakiaCallChannel *channel,
+ RakiaSipMedia *media,
+ const gchar *object_path,
+ TpBaseConnection *connection,
+ const gchar *name,
+ TpMediaStreamType media_type,
+ TpHandle creator,
+ TpCallContentDisposition disposition)
+{
+ RakiaCallContent *content;
+
+ content = g_object_new (RAKIA_TYPE_CALL_CONTENT,
+ "channel", channel,
+ "sip-media", media,
+ "object-path", object_path,
+ "connection", connection,
+ "name", name,
+ "media-type", media_type,
+ "creator", creator,
+ "disposition", disposition,
+ "packetization", TP_CALL_CONTENT_PACKETIZATION_TYPE_RTP,
+ NULL);
+
+ return content;
+}
+
+RakiaSipMedia *
+rakia_call_content_get_media (RakiaCallContent *self)
+{
+ return self->priv->media;
+}
+
+static void
+set_telepathy_codecs (RakiaCallContent *self, GHashTable *md_properties)
+{
+ RakiaCallContentPrivate *priv = self->priv;
+ guint i;
+ GPtrArray *tpcodecs = tp_asv_get_boxed (md_properties,
+ TP_PROP_CALL_CONTENT_MEDIA_DESCRIPTION_CODECS,
+ TP_ARRAY_TYPE_CODEC_LIST);
+ GPtrArray *sipcodecs = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) rakia_sip_codec_free);
+
+ for (i = 0; i < tpcodecs->len; i++)
+ {
+ GValueArray *tpcodec = g_ptr_array_index (tpcodecs, i);
+ guint id;
+ const gchar *name;
+ guint clock_rate;
+ guint channels;
+ gboolean updated;
+ GHashTable *extra_params;
+ RakiaSipCodec *sipcodec;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ tp_value_array_unpack (tpcodec, 6, &id, &name, &clock_rate,
+ &channels, &updated, &extra_params);
+
+ sipcodec = rakia_sip_codec_new (id, name, clock_rate, channels);
+
+ g_hash_table_iter_init (&iter, extra_params);
+
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ rakia_sip_codec_add_param (sipcodec, key, value);
+
+ g_ptr_array_add (sipcodecs, sipcodec);
+ }
+
+ rakia_sip_media_take_local_codecs (priv->media, sipcodecs);
+}
+
+static void
+md_offer_cb (GObject *obj, GAsyncResult *res, gpointer user_data)
+{
+ RakiaCallContent *self = RAKIA_CALL_CONTENT (obj);
+ RakiaCallContentPrivate *priv = self->priv;
+ TpBaseMediaCallContent *bmcc = TP_BASE_MEDIA_CALL_CONTENT (self);
+ GError *error = NULL;
+ gboolean is_initial_offer = GPOINTER_TO_UINT (user_data);
+
+ if (tp_base_media_call_content_offer_media_description_finish (bmcc,
+ res, &error))
+ {
+ GHashTable *local_md =
+ tp_base_media_call_content_get_local_media_description (bmcc,
+ tp_base_channel_get_target_handle (TP_BASE_CHANNEL (priv->channel)));
+
+ set_telepathy_codecs (self, local_md);
+ }
+ else
+ {
+ /* Only reject if the codecs where rejected */
+ if (error->domain == TP_ERRORS &&
+ error->code == TP_ERROR_MEDIA_CODECS_INCOMPATIBLE)
+ {
+ g_assert (!is_initial_offer);
+
+ rakia_sip_media_codecs_rejected (priv->media);
+
+ DEBUG ("Codecs rejected: %s", error->message);
+
+ }
+
+ /* FIXME: We need to allow for partial failures */
+ g_clear_error (&error);
+ }
+}
+
+static void
+media_remote_codecs_updated_cb (RakiaSipMedia *media, gboolean is_offer,
+ RakiaCallContent *self)
+{
+ TpBaseCallContent *bcc = TP_BASE_CALL_CONTENT (self);
+ TpBaseMediaCallContent *bmcc = TP_BASE_MEDIA_CALL_CONTENT (self);
+ RakiaCallContentPrivate *priv = self->priv;
+ GPtrArray *remote_codecs =
+ rakia_sip_media_get_remote_codec_offer (priv->media);
+ TpCallContentMediaDescription *md;
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ tp_base_call_content_get_connection (bcc));
+ gchar *object_path;
+ guint i, j;
+
+ if (remote_codecs == NULL)
+ return;
+
+ object_path = g_strdup_printf ("%s/Offer%u",
+ tp_base_call_content_get_object_path (bcc), ++priv->codec_offer_id);
+
+ md = tp_call_content_media_description_new (bus, object_path,
+ tp_base_channel_get_target_handle (TP_BASE_CHANNEL (priv->channel)),
+ TRUE, is_offer);
+
+ g_free (object_path);
+
+ for (i = 0; i < remote_codecs->len; i++)
+ {
+ RakiaSipCodec *codec = g_ptr_array_index (remote_codecs, i);
+ GHashTable *parameters = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* No need to copy the values as .._append_codec() will */
+ if (codec->params)
+ for (j = 0; j < codec->params->len; j++)
+ {
+ RakiaSipCodecParam *param = g_ptr_array_index (codec->params, j);
+
+ g_hash_table_insert (parameters, param->name, param->value);
+ }
+
+ tp_call_content_media_description_append_codec (md, codec->id,
+ codec->encoding_name, codec->clock_rate, codec->channels, TRUE,
+ parameters);
+
+ g_hash_table_unref (parameters);
+ }
+
+ tp_base_media_call_content_offer_media_description_async (bmcc,
+ md, md_offer_cb, GUINT_TO_POINTER (FALSE));
+
+ g_object_unref (md);
+}
+
+void
+rakia_call_content_add_stream (RakiaCallContent *self)
+{
+ RakiaCallContentPrivate *priv = self->priv;
+ TpBaseCallContent *bcc = TP_BASE_CALL_CONTENT (self);
+ gchar *object_path;
+
+ object_path = g_strdup_printf ("%s/Stream",
+ tp_base_call_content_get_object_path (bcc));
+ priv->stream = rakia_call_stream_new (self, priv->media,
+ object_path, TP_STREAM_TRANSPORT_TYPE_RAW_UDP,
+ tp_base_call_content_get_connection (bcc));
+ g_free (object_path);
+
+ tp_base_call_content_add_stream (bcc, TP_BASE_CALL_STREAM (priv->stream));
+}
+
+static void
+local_media_description_updated (RakiaCallContent *self, TpHandle contact,
+ GHashTable *properties, gpointer user_data)
+{
+ set_telepathy_codecs (self, properties);
+}
+
+void
+rakia_call_content_remote_accept (RakiaCallContent *content)
+{
+ rakia_call_stream_update_direction (content->priv->stream);
+}
diff --git a/rakia/call-content.h b/rakia/call-content.h
new file mode 100644
index 0000000..65dd415
--- /dev/null
+++ b/rakia/call-content.h
@@ -0,0 +1,84 @@
+/*
+ * call-content.h - Header for RakiaCallContent
+ * Copyright (C) 2011 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __RAKIA_CALL_CONTENT_H__
+#define __RAKIA_CALL_CONTENT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-media-call-content.h>
+
+#include "rakia/call-channel.h"
+#include "rakia/sip-session.h"
+#include "rakia/sip-media.h"
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaCallContent RakiaCallContent;
+typedef struct _RakiaCallContentPrivate RakiaCallContentPrivate;
+typedef struct _RakiaCallContentClass RakiaCallContentClass;
+
+struct _RakiaCallContentClass {
+ TpBaseMediaCallContentClass parent_class;
+};
+
+struct _RakiaCallContent {
+ TpBaseMediaCallContent parent;
+
+ RakiaCallContentPrivate *priv;
+};
+
+GType rakia_call_content_get_type (void);
+
+/* TYPE MACROS */
+#define RAKIA_TYPE_CALL_CONTENT \
+ (rakia_call_content_get_type ())
+#define RAKIA_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ RAKIA_TYPE_CALL_CONTENT, RakiaCallContent))
+#define RAKIA_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ RAKIA_TYPE_CALL_CONTENT, RakiaCallContentClass))
+#define RAKIA_IS_CALL_CONTENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_CALL_CONTENT))
+#define RAKIA_IS_CALL_CONTENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_CALL_CONTENT))
+#define RAKIA_CALL_CONTENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ RAKIA_TYPE_CALL_CONTENT, RakiaCallContentClass))
+
+RakiaCallContent *rakia_call_content_new (RakiaCallChannel *channel,
+ RakiaSipMedia *media,
+ const gchar *object_path,
+ TpBaseConnection *connection,
+ const gchar *name,
+ TpMediaStreamType media_type,
+ TpHandle creator,
+ TpCallContentDisposition disposition);
+
+RakiaSipMedia *rakia_call_content_get_media (RakiaCallContent *self);
+
+void rakia_call_content_add_stream (RakiaCallContent *self);
+
+void rakia_call_content_remote_accept (RakiaCallContent *content);
+
+G_END_DECLS
+
+#endif /* #ifndef __RAKIA_CALL_CONTENT_H__*/
diff --git a/rakia/call-stream.c b/rakia/call-stream.c
new file mode 100644
index 0000000..cc558f2
--- /dev/null
+++ b/rakia/call-stream.c
@@ -0,0 +1,650 @@
+/*
+ * call-stream.c - RakiaCallStream
+ * Copyright (C) 2011-2012 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include "config.h"
+
+#include "rakia/call-stream.h"
+
+#include "rakia/sip-media.h"
+
+#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
+#include "debug.h"
+
+
+static void rakia_call_stream_report_sending_failure (
+ TpBaseMediaCallStream *self,
+ TpStreamFlowState old_state,
+ TpCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ const gchar *message);
+static void rakia_call_stream_report_receiving_failure (
+ TpBaseMediaCallStream *self,
+ TpStreamFlowState old_state,
+ TpCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ const gchar *message);
+static gboolean rakia_call_stream_finish_initial_candidates (
+ TpBaseMediaCallStream *stream, GError **error);
+static GPtrArray *rakia_call_stream_add_local_candidates (
+ TpBaseMediaCallStream *stream,
+ const GPtrArray *candidates,
+ GError **error);
+static gboolean rakia_call_stream_set_sending (TpBaseMediaCallStream *stream,
+ gboolean sending,
+ GError **error);
+static void rakia_call_stream_request_receiving (TpBaseMediaCallStream *stream,
+ TpHandle contact, gboolean receive);
+
+
+static void rakia_call_stream_constructed (GObject *object);
+static void rakia_call_stream_dispose (GObject *object);
+static void rakia_call_stream_finalize (GObject *object);
+
+
+static void media_remote_candidates_updated_cb (RakiaSipMedia *media,
+ RakiaCallStream *self);
+static void receiving_updated_cb (RakiaCallStream *self);
+
+
+G_DEFINE_TYPE (RakiaCallStream, rakia_call_stream,
+ TP_TYPE_BASE_MEDIA_CALL_STREAM)
+
+
+/* properties */
+enum
+{
+ PROP_SIP_MEDIA = 1,
+ PROP_CAN_REQUEST_RECEIVING,
+ LAST_PROPERTY
+};
+
+/* private structure */
+struct _RakiaCallStreamPrivate
+{
+ RakiaCallChannel *channel;
+
+ RakiaSipMedia *media;
+
+ TpCallStreamEndpoint *endpoint;
+
+ guint last_endpoint_no;
+};
+
+static void
+rakia_call_stream_init (RakiaCallStream *self)
+{
+ RakiaCallStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ RAKIA_TYPE_CALL_STREAM, RakiaCallStreamPrivate);
+
+ self->priv = priv;
+}
+
+
+
+static void
+rakia_call_stream_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (object);
+ RakiaCallStreamPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_SIP_MEDIA:
+ priv->media = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_call_stream_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (object);
+ RakiaCallStreamPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_CAN_REQUEST_RECEIVING:
+ {
+ gboolean mutable_contents;
+
+ g_object_get (priv->channel,
+ "mutable-contents", &mutable_contents,
+ NULL);
+ g_value_set_boolean (value, mutable_contents);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_call_stream_class_init (RakiaCallStreamClass *rakia_call_stream_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (rakia_call_stream_class);
+ TpBaseMediaCallStreamClass *bmcs_class =
+ TP_BASE_MEDIA_CALL_STREAM_CLASS (rakia_call_stream_class);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (rakia_call_stream_class,
+ sizeof (RakiaCallStreamPrivate));
+
+ object_class->constructed = rakia_call_stream_constructed;
+ object_class->set_property = rakia_call_stream_set_property;
+ object_class->get_property = rakia_call_stream_get_property;
+ object_class->dispose = rakia_call_stream_dispose;
+ object_class->finalize = rakia_call_stream_finalize;
+
+ bmcs_class->report_sending_failure = rakia_call_stream_report_sending_failure;
+ bmcs_class->report_receiving_failure =
+ rakia_call_stream_report_receiving_failure;
+ bmcs_class->add_local_candidates = rakia_call_stream_add_local_candidates;
+ bmcs_class->finish_initial_candidates =
+ rakia_call_stream_finish_initial_candidates;
+ bmcs_class->request_receiving = rakia_call_stream_request_receiving;
+ bmcs_class->set_sending = rakia_call_stream_set_sending;
+
+ g_object_class_override_property (object_class, PROP_CAN_REQUEST_RECEIVING,
+ "can-request-receiving");
+
+ param_spec = g_param_spec_object ("sip-media", "RakiaSipMedia object",
+ "SIP media object that is used for this SIP media channel object.",
+ RAKIA_TYPE_SIP_MEDIA,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_SIP_MEDIA, param_spec);
+}
+
+static void
+rakia_call_stream_constructed (GObject *object)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (object);
+ RakiaCallStreamPrivate *priv = self->priv;
+ TpBaseCallStream *bcs = TP_BASE_CALL_STREAM (object);
+ TpBaseMediaCallStream *bmcs = TP_BASE_MEDIA_CALL_STREAM (object);
+ TpHandle contact;
+ GPtrArray *stun_array;
+ GPtrArray *relay_array;
+ gchar *stun_server = NULL;
+ guint stun_port = 0;
+
+ g_object_get (self, "channel", &priv->channel, NULL);
+ contact = tp_base_channel_get_target_handle (TP_BASE_CHANNEL (priv->channel));
+
+ g_signal_connect_object (priv->media, "remote-candidates-updated",
+ G_CALLBACK (media_remote_candidates_updated_cb), self, 0);
+ g_signal_connect_object (priv->media, "direction-changed",
+ G_CALLBACK (rakia_call_stream_update_direction), self, G_CONNECT_SWAPPED);
+
+ if (!rakia_sip_media_is_created_locally (priv->media))
+ media_remote_candidates_updated_cb (priv->media, self);
+
+
+ stun_array = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) g_value_array_free);
+
+ g_object_get (priv->channel, "stun-server", &stun_server,
+ "stun-port", &stun_port, NULL);
+ if (stun_server && stun_port)
+ {
+ g_ptr_array_add (stun_array, tp_value_array_build (2,
+ G_TYPE_STRING, stun_server,
+ G_TYPE_UINT, stun_port,
+ G_TYPE_INVALID));
+ }
+ g_free (stun_server);
+ tp_base_media_call_stream_set_stun_servers (bmcs, stun_array);
+ g_ptr_array_unref (stun_array);
+
+
+ relay_array = g_ptr_array_new ();
+ tp_base_media_call_stream_set_relay_info (bmcs, relay_array);
+ g_ptr_array_unref (relay_array);
+
+ g_signal_connect (self, "notify::receiving-state",
+ G_CALLBACK (receiving_updated_cb), NULL);
+ receiving_updated_cb (self);
+
+ /* Put the initial value */
+ if (rakia_sip_media_get_requested_direction (priv->media) &
+ RAKIA_DIRECTION_RECEIVE)
+ tp_base_call_stream_update_remote_sending_state (bcs, contact,
+ TP_SENDING_STATE_PENDING_SEND, 0,
+ TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", "");
+ else
+ tp_base_call_stream_update_remote_sending_state (bcs, contact,
+ TP_SENDING_STATE_NONE, 0,
+ TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", "");
+
+ rakia_call_stream_update_direction (self);
+ tp_base_media_call_stream_update_receiving_state (
+ TP_BASE_MEDIA_CALL_STREAM (self));
+
+ G_OBJECT_CLASS (rakia_call_stream_parent_class)->constructed (object);
+}
+
+void
+rakia_call_stream_dispose (GObject *object)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (object);
+ RakiaCallStreamPrivate *priv = self->priv;
+
+ tp_clear_object (&priv->endpoint);
+ tp_clear_object (&priv->media);
+
+ G_OBJECT_CLASS (rakia_call_stream_parent_class)->dispose (object);
+}
+
+void
+rakia_call_stream_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (rakia_call_stream_parent_class)->finalize (object);
+}
+
+static void
+rakia_call_stream_report_sending_failure (TpBaseMediaCallStream *bmcs,
+ TpStreamFlowState old_state,
+ TpCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ const gchar *message)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (bmcs);
+ TpBaseCallStream *bcs = TP_BASE_CALL_STREAM (self);
+ RakiaCallStreamPrivate *priv = self->priv;
+ RakiaDirection current_direction =
+ rakia_sip_media_get_requested_direction (priv->media);
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (priv->channel);
+ TpHandle self_handle = tp_base_channel_get_self_handle (bchan);
+
+ tp_base_call_stream_update_local_sending_state (bcs,
+ TP_SENDING_STATE_NONE, self_handle, reason, dbus_reason, message);
+
+ if (!(current_direction & RAKIA_DIRECTION_SEND))
+ return;
+
+ rakia_sip_media_set_requested_direction (priv->media,
+ current_direction & ~RAKIA_DIRECTION_SEND);
+}
+
+static void
+rakia_call_stream_report_receiving_failure (TpBaseMediaCallStream *bmcs,
+ TpStreamFlowState old_state,
+ TpCallStateChangeReason reason,
+ const gchar *dbus_reason,
+ const gchar *message)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (bmcs);
+ TpBaseCallStream *bcs = TP_BASE_CALL_STREAM (self);
+ RakiaCallStreamPrivate *priv = self->priv;
+ gboolean can_request_receiving;
+ RakiaDirection current_requested_direction =
+ rakia_sip_media_get_requested_direction (priv->media);
+ RakiaDirection current_direction =
+ rakia_sip_media_get_direction (priv->media);
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (priv->channel);
+
+ g_object_get (self, "can-request-receiving", &can_request_receiving, NULL);
+ if (!can_request_receiving)
+ {
+ /* Failing the whole call because we can't stop receivingt */
+ DEBUG ("Closing channel because of error: %s", message);
+ rakia_call_channel_hangup_error (priv->channel, reason, dbus_reason,
+ message);
+ return;
+ }
+
+ if (!(current_requested_direction & RAKIA_DIRECTION_RECEIVE))
+ return;
+
+ if (current_direction & RAKIA_DIRECTION_RECEIVE)
+ tp_base_call_stream_update_remote_sending_state (bcs,
+ tp_base_channel_get_target_handle (bchan),
+ TP_SENDING_STATE_PENDING_STOP_SENDING,
+ tp_base_channel_get_self_handle (bchan),
+ reason, dbus_reason, message);
+
+ rakia_sip_media_set_requested_direction (priv->media,
+ current_requested_direction & ~RAKIA_DIRECTION_RECEIVE);
+}
+
+static GPtrArray *
+rakia_call_stream_add_local_candidates (TpBaseMediaCallStream *stream,
+ const GPtrArray *candidates,
+ GError **error)
+{
+ GPtrArray *accepted_candidates = g_ptr_array_sized_new (candidates->len);
+ guint i;
+
+ for (i = 0; i < candidates->len; i ++)
+ {
+ GValueArray *candidate = g_ptr_array_index (candidates, i);
+ guint component;
+ const gchar *ip;
+ guint port;
+ GHashTable *info;
+ GInetAddress *inetaddr;
+ gboolean valid;
+ TpMediaStreamBaseProto proto;
+
+ tp_value_array_unpack (candidate, 4, &component, &ip, &port, &info);
+
+ if (component != 1 && component != 2)
+ continue;
+
+ if (port > 65535)
+ continue;
+
+ proto = tp_asv_get_uint32 (info, "protocol", &valid);
+ if (valid && proto != TP_MEDIA_STREAM_BASE_PROTO_UDP)
+ continue;
+
+ inetaddr = g_inet_address_new_from_string (ip);
+ if (inetaddr == NULL)
+ continue;
+ g_object_unref (inetaddr);
+
+ g_ptr_array_add (accepted_candidates, candidate);
+ }
+
+ if (accepted_candidates->len == 0)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "No valid candidate passed");
+ g_ptr_array_unref (accepted_candidates);
+ return NULL;
+ }
+
+ return accepted_candidates;
+}
+
+static gboolean
+rakia_call_stream_finish_initial_candidates (TpBaseMediaCallStream *stream,
+ GError **error)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (stream);
+ RakiaCallStreamPrivate *priv = self->priv;
+ GPtrArray *candidates = tp_base_media_call_stream_get_local_candidates (
+ stream);
+ guint i;
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ GValueArray *candidate = g_ptr_array_index (candidates, i);
+ guint component;
+ gchar *ip;
+ guint port;
+ GHashTable *info;
+ const gchar *foundation;
+ guint priority;
+ gboolean valid;
+
+ tp_value_array_unpack (candidate, 4, &component, &ip, &port, &info);
+
+ foundation = tp_asv_get_string (info, "foundation");
+ if (!foundation)
+ foundation = "";
+
+ priority = tp_asv_get_uint32 (info, "priority", &valid);
+ if (!valid)
+ priority = 0;
+
+ rakia_sip_media_take_local_candidate (priv->media,
+ rakia_sip_candidate_new (component, ip, port, foundation, priority));
+ }
+
+ if (!rakia_sip_media_local_candidates_prepared (priv->media))
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "You need to set a candidate on component 1 first.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void rakia_call_stream_request_receiving (
+ TpBaseMediaCallStream *stream,
+ TpHandle contact,
+ gboolean receive)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (stream);
+ TpBaseCallStream *bcs = TP_BASE_CALL_STREAM (stream);
+ RakiaCallStreamPrivate *priv = self->priv;
+ RakiaDirection current_requested_direction =
+ rakia_sip_media_get_requested_direction (priv->media);
+ RakiaDirection current_direction =
+ rakia_sip_media_get_direction (priv->media);
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (priv->channel);
+
+ if ((!!(current_requested_direction & RAKIA_DIRECTION_RECEIVE)) == receive)
+ return;
+
+ if (receive)
+ {
+ rakia_sip_media_set_requested_direction (priv->media,
+ current_requested_direction | RAKIA_DIRECTION_RECEIVE);
+
+ if (current_direction & RAKIA_DIRECTION_RECEIVE)
+ tp_base_call_stream_update_remote_sending_state (bcs,
+ tp_base_channel_get_target_handle (bchan),
+ TP_SENDING_STATE_SENDING,
+ tp_base_channel_get_self_handle (bchan),
+ TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ "User requested to start receiving");
+ }
+ else
+ {
+ rakia_sip_media_set_requested_direction (priv->media,
+ current_requested_direction & ~RAKIA_DIRECTION_RECEIVE);
+
+ if (!(current_direction & RAKIA_DIRECTION_RECEIVE))
+ tp_base_call_stream_update_remote_sending_state (bcs,
+ tp_base_channel_get_target_handle (bchan),
+ TP_SENDING_STATE_NONE,
+ tp_base_channel_get_self_handle (bchan),
+ TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
+ "User requested to stop receiving");
+ }
+}
+
+static gboolean
+rakia_call_stream_set_sending (TpBaseMediaCallStream *stream,
+ gboolean sending,
+ GError **error)
+{
+ RakiaCallStream *self = RAKIA_CALL_STREAM (stream);
+ RakiaCallStreamPrivate *priv = self->priv;
+ RakiaDirection current_direction =
+ rakia_sip_media_get_requested_direction (priv->media);
+ gboolean mutable_contents;
+
+ if (!!(current_direction & RAKIA_DIRECTION_SEND) == sending)
+ return TRUE;
+
+ /* Can't change the actual direction of a stream if we disable modifying
+ * the SDP, except if we're starting a call.
+ * In that case, we stop sending, but we don't inform the other side.
+ */
+ g_object_get (priv->channel,
+ "mutable-contents", &mutable_contents,
+ NULL);
+
+ if (!mutable_contents &&
+ !(tp_base_channel_is_requested (TP_BASE_CHANNEL (priv->channel)) &&
+ tp_base_call_channel_get_state (
+ TP_BASE_CALL_CHANNEL (priv->channel)) ==
+ TP_CALL_STATE_PENDING_INITIATOR))
+ return TRUE;
+
+ if (sending)
+ rakia_sip_media_set_requested_direction (priv->media,
+ current_direction | RAKIA_DIRECTION_SEND);
+ else
+ rakia_sip_media_set_requested_direction (priv->media,
+ current_direction & ~RAKIA_DIRECTION_SEND);
+
+ return TRUE;
+}
+
+static void
+media_remote_candidates_updated_cb (RakiaSipMedia *media, RakiaCallStream *self)
+{
+ RakiaCallStreamPrivate *priv = self->priv;
+ TpBaseCallStream *bcs = TP_BASE_CALL_STREAM (self);
+ TpBaseMediaCallStream *bmcs = TP_BASE_MEDIA_CALL_STREAM (self);
+ GPtrArray *candidates = rakia_sip_media_get_remote_candidates (media);
+ TpDBusDaemon *bus = tp_base_connection_get_dbus_daemon (
+ tp_base_call_stream_get_connection (bcs));
+ gchar *object_path;
+ guint i;
+
+ if (priv->endpoint)
+ {
+ tp_base_media_call_stream_remove_endpoint (bmcs, priv->endpoint);
+ tp_clear_object (&priv->endpoint);
+ }
+
+ if (candidates == NULL)
+ return;
+
+ object_path = g_strdup_printf ("%s/Endpoint%u",
+ tp_base_call_stream_get_object_path (bcs),
+ ++priv->last_endpoint_no);
+ priv->endpoint = tp_call_stream_endpoint_new (bus, object_path,
+ TP_STREAM_TRANSPORT_TYPE_RAW_UDP, FALSE);
+ g_free (object_path);
+
+ for (i = 0; i < candidates->len; i++)
+ {
+ RakiaSipCandidate *candidate = g_ptr_array_index (candidates, i);
+ GHashTable *info = tp_asv_new (
+ "priority", G_TYPE_UINT, candidate->priority,
+ "protocol", G_TYPE_UINT, TP_MEDIA_STREAM_BASE_PROTO_UDP,
+ NULL);
+
+ tp_call_stream_endpoint_add_new_candidate (priv->endpoint,
+ candidate->component, candidate->ip, candidate->port, info);
+ g_hash_table_unref (info);
+ }
+
+ tp_base_media_call_stream_add_endpoint (bmcs, priv->endpoint);
+}
+
+RakiaCallStream *
+rakia_call_stream_new (
+ RakiaCallContent *content,
+ RakiaSipMedia *media,
+ const gchar *object_path,
+ TpStreamTransportType transport,
+ TpBaseConnection *connection)
+{
+ return g_object_new (RAKIA_TYPE_CALL_STREAM,
+ "content", content,
+ "sip-media", media,
+ "object-path", object_path,
+ "transport", transport,
+ "connection", connection,
+ NULL);
+}
+
+void
+rakia_call_stream_update_direction (RakiaCallStream *self)
+{
+ TpBaseCallStream *bcs = TP_BASE_CALL_STREAM (self);
+ TpBaseMediaCallStream *bmcs = TP_BASE_MEDIA_CALL_STREAM (self);
+ RakiaCallStreamPrivate *priv = self->priv;
+ TpBaseChannel *bchan = TP_BASE_CHANNEL (priv->channel);
+ TpHandle contact = tp_base_channel_get_target_handle (bchan);
+ TpHandle self_handle = tp_base_channel_get_self_handle (bchan);
+ RakiaDirection direction = rakia_sip_media_get_direction (priv->media);
+ RakiaDirection remote_direction =
+ rakia_sip_media_get_remote_direction (priv->media);
+ RakiaDirection requested_direction =
+ rakia_sip_media_get_requested_direction (priv->media);
+ TpLocalHoldState hold_state =
+ tp_base_media_call_channel_get_local_hold_state (
+ TP_BASE_MEDIA_CALL_CHANNEL (priv->channel), NULL);
+
+ DEBUG ("direction: %s requested: %s remote: %s hold: %d",
+ rakia_direction_to_string (direction),
+ rakia_direction_to_string (requested_direction),
+ rakia_direction_to_string (remote_direction),
+ hold_state != TP_LOCAL_HOLD_STATE_UNHELD);
+
+ if ((direction & RAKIA_DIRECTION_SEND ||
+ !rakia_sip_media_has_remote_media (priv->media) ||
+ hold_state != TP_LOCAL_HOLD_STATE_UNHELD) &&
+ requested_direction & RAKIA_DIRECTION_SEND)
+ {
+ tp_base_call_stream_update_local_sending_state (bcs,
+ TP_SENDING_STATE_SENDING, self_handle,
+ TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "User requested");
+ if (rakia_sip_media_has_remote_media (priv->media))
+ tp_base_media_call_stream_set_local_sending (bmcs, TRUE);
+ }
+ else if (remote_direction & RAKIA_DIRECTION_SEND)
+ {
+ if (tp_base_call_stream_get_local_sending_state (bcs) !=
+ TP_SENDING_STATE_SENDING)
+ tp_base_call_stream_update_local_sending_state (bcs,
+ TP_SENDING_STATE_PENDING_SEND, contact,
+ TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "",
+ "Remote requested that we start sending");
+ }
+ else
+ {
+ tp_base_call_stream_update_local_sending_state (bcs,
+ TP_SENDING_STATE_NONE, self_handle,
+ TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "User requested");
+ }
+
+
+ if ((direction & RAKIA_DIRECTION_RECEIVE) &&
+ (requested_direction & RAKIA_DIRECTION_RECEIVE))
+ tp_base_call_stream_update_remote_sending_state (bcs, contact,
+ TP_SENDING_STATE_SENDING, 0,
+ TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", "");
+ else if (!(direction & RAKIA_DIRECTION_RECEIVE) &&
+ !(requested_direction & RAKIA_DIRECTION_RECEIVE))
+ tp_base_call_stream_update_remote_sending_state (bcs, contact,
+ TP_SENDING_STATE_NONE, 0,
+ TP_CALL_STATE_CHANGE_REASON_PROGRESS_MADE, "", "");
+}
+
+static void
+receiving_updated_cb (RakiaCallStream *self)
+{
+ RakiaCallStreamPrivate *priv = self->priv;
+ TpBaseMediaCallStream *bmcs = TP_BASE_MEDIA_CALL_STREAM (self);
+
+ rakia_sip_media_set_can_receive (priv->media,
+ tp_base_media_call_stream_get_receiving_state (bmcs) ==
+ TP_STREAM_FLOW_STATE_STARTED);
+}
diff --git a/rakia/call-stream.h b/rakia/call-stream.h
new file mode 100644
index 0000000..ccc15ab
--- /dev/null
+++ b/rakia/call-stream.h
@@ -0,0 +1,76 @@
+/*
+ * call-stream.h - Header for RakiaCallStream
+ * Copyright (C) 2011 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __RAKIA_CALL_STREAM_H__
+#define __RAKIA_CALL_STREAM_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-media-call-stream.h>
+
+#include "rakia/call-content.h"
+#include "rakia/sip-media.h"
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaCallStream RakiaCallStream;
+typedef struct _RakiaCallStreamPrivate RakiaCallStreamPrivate;
+typedef struct _RakiaCallStreamClass RakiaCallStreamClass;
+
+struct _RakiaCallStreamClass {
+ TpBaseMediaCallStreamClass parent_class;
+};
+
+struct _RakiaCallStream {
+ TpBaseMediaCallStream parent;
+
+ RakiaCallStreamPrivate *priv;
+};
+
+GType rakia_call_stream_get_type (void);
+
+/* TYPE MACROS */
+#define RAKIA_TYPE_CALL_STREAM \
+ (rakia_call_stream_get_type ())
+#define RAKIA_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), RAKIA_TYPE_CALL_STREAM, RakiaCallStream))
+#define RAKIA_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), RAKIA_TYPE_CALL_STREAM, \
+ RakiaCallStreamClass))
+#define RAKIA_IS_CALL_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_CALL_STREAM))
+#define RAKIA_IS_CALL_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_CALL_STREAM))
+#define RAKIA_CALL_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), RAKIA_TYPE_CALL_STREAM, \
+ RakiaCallStreamClass))
+
+RakiaCallStream * rakia_call_stream_new (
+ RakiaCallContent *content,
+ RakiaSipMedia *media,
+ const gchar *object_path,
+ TpStreamTransportType transport,
+ TpBaseConnection *connection);
+
+void rakia_call_stream_update_direction (RakiaCallStream *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __RAKIA_CALL_STREAM_H__*/
diff --git a/rakia/codec-param-formats.c b/rakia/codec-param-formats.c
index 969eeca..c73aed1 100644
--- a/rakia/codec-param-formats.c
+++ b/rakia/codec-param-formats.c
@@ -27,6 +27,43 @@
#define DEBUG_FLAG RAKIA_DEBUG_UTILITIES
#include "debug.h"
+
+/**
+ * RakiaCodecParamFormatFunc:
+ * @params: the map of codec parameters
+ * @out: a #GString for the output
+ *
+ * Defines the function pointer signature for codec parameter formatters.
+ * A formatter takes a codec parameter map as passed in
+ * a org.freedesktop.Telepathy.Media.StreamHandler codec structure,
+ * and outputs its SDP representation, as per the value for an
+ * <literal>a=fmtp</literal> attribute, into the string buffer @out.
+ *
+ * <note>
+ * <para>The function is allowed to delete pairs from the @params hash table.
+ * This is useful to implement a custom formatter that processes the
+ * few parameters treated specially, removes them from the map, and
+ * calls a more generic formatter such as rakia_codec_param_format_generic().
+ * </para>
+ * </note>
+ */
+typedef void (* RakiaCodecParamFormatFunc) (RakiaSipCodec *codec,
+ TpMediaStreamType media_type,
+ GString *out);
+
+/**
+ * RakiaCodecParamParseFunc:
+ * @str: a string value with format-specific parameter description
+ * @out: the parameter map to populate
+ *
+ * Defines the function pointer signature for codec parameter parsers.
+ * A parser takes the string value coming from an <literal>a=fmtp</literal>
+ * SDP attribute, and populates the parameter hash table.
+ */
+typedef void (* RakiaCodecParamParseFunc) (const gchar *str,
+ TpMediaStreamType media_type,
+ RakiaSipCodec *codec);
+
/* Regexps for the name and the value parts of the parameter syntax */
#define FMTP_TOKEN_PARAM "[-A-Za-z0-9!#$%&'*+.^_`{|}~]+"
#define FMTP_TOKEN_VALUE "[^;\"\\s]+|\"([^\"\\\\]|\\\\.)*\""
@@ -46,9 +83,25 @@ static GHashTable *codec_param_formats[NUM_TP_MEDIA_STREAM_TYPES];
static void rakia_codec_param_formats_init ();
+
+static void rakia_codec_param_format_generic (RakiaSipCodec *codec,
+ TpMediaStreamType media_type,
+ GString *out);
+
+static void rakia_codec_param_parse_generic (const gchar *str,
+ TpMediaStreamType media_type,
+ RakiaSipCodec *codec);
+
+
+static void rakia_codec_param_register_format (
+ TpMediaStreamType media,
+ const char *name,
+ RakiaCodecParamFormatFunc format,
+ RakiaCodecParamParseFunc parse);
+
/**
* rakia_codec_param_format:
- * @media: the media type
+ * @media_type: the media type
* @name: name of the codec, as per its MIME subtype registration
* @params: the map of codec parameters
* @out: a #GString for the output
@@ -58,36 +111,37 @@ static void rakia_codec_param_formats_init ();
* as specified for the media type defined by @media and @name.
*/
void
-rakia_codec_param_format (TpMediaStreamType media, const char *name,
- GHashTable *params, GString *out)
+rakia_codec_param_format (TpMediaStreamType media_type, RakiaSipCodec *codec,
+ GString *out)
{
RakiaCodecParamFormatting *fmt;
rakia_codec_param_formats_init ();
/* XXX: thread unsafe, we don't care for now */
- fmt = g_hash_table_lookup (codec_param_formats[media], name);
+ fmt = g_hash_table_lookup (codec_param_formats[media_type],
+ codec->encoding_name);
if (fmt != NULL && fmt->format != NULL)
- fmt->format (params, out);
+ fmt->format (codec, media_type, out);
else
- rakia_codec_param_format_generic (params, out);
+ rakia_codec_param_format_generic (codec, media_type, out);
}
/**
* rakia_codec_param_parse:
- * @media: the media type
+ * @media_type: the media type
* @name: name of the codec, as per its MIME subtype registration
* @fmtp: a string with the codec-specific parameter data. May be #NULL.
* @out: the parameter map to populate
*
* Parses the payload-specific parameter description as coming from an
* <literal>a=fmtp</literal> attribute of an RTP payload description.
- * The media type is defined by @media and @name.
+ * The media type is defined by @media_type and @name.
*/
void
-rakia_codec_param_parse (TpMediaStreamType media, const char *name,
- const gchar *fmtp, GHashTable *out)
+rakia_codec_param_parse (TpMediaStreamType media_type, RakiaSipCodec *codec,
+ const gchar *fmtp)
{
RakiaCodecParamFormatting *fmt;
@@ -97,17 +151,18 @@ rakia_codec_param_parse (TpMediaStreamType media, const char *name,
rakia_codec_param_formats_init ();
/* XXX: thread unsafe, we don't care for now */
- fmt = g_hash_table_lookup (codec_param_formats[media], name);
+ fmt = g_hash_table_lookup (codec_param_formats[media_type],
+ codec->encoding_name);
if (fmt != NULL && fmt->parse != NULL)
- fmt->parse (fmtp, out);
+ fmt->parse (fmtp, media_type, codec);
else
- rakia_codec_param_parse_generic (fmtp, out);
+ rakia_codec_param_parse_generic (fmtp, media_type, codec);
}
/**
* rakia_codec_param_register_format:
- * @media: the media type
+ * @media_type: the media type
* @name: name of the codec, as per its MIME subtype registration. Must be a static string.
* @format: pointer to the formatting function
* @parse: pointer to the parsing function
@@ -115,8 +170,8 @@ rakia_codec_param_parse (TpMediaStreamType media, const char *name,
* Registers custom SDP payload parameter formatting routines for a media
* type.
*/
-void
-rakia_codec_param_register_format (TpMediaStreamType media, const char *name,
+static void
+rakia_codec_param_register_format (TpMediaStreamType media_type, const char *name,
RakiaCodecParamFormatFunc format,
RakiaCodecParamParseFunc parse)
{
@@ -129,31 +184,7 @@ rakia_codec_param_register_format (TpMediaStreamType media, const char *name,
fmt->parse = parse;
/* XXX: thread unsafe, we don't care for now */
- g_hash_table_insert (codec_param_formats[media], (gpointer) name, fmt);
-}
-
-static void
-format_param_generic (gpointer key, gpointer val, gpointer user_data)
-{
- const gchar *name = key;
- const gchar *value = val;
- GString *out = user_data;
-
- /* Ignore freaky parameters */
- g_return_if_fail (name != NULL && name[0]);
- g_return_if_fail (value != NULL && value[0]);
-
- if (out->len != 0)
- g_string_append_c (out, ';');
-
- if (strpbrk (value, "; \t") == NULL)
- g_string_append_printf (out, "%s=%s", name, value);
- else
- {
- g_string_append (out, name);
- g_string_append_c (out, '=');
- rakia_string_append_quoted (out, value);
- }
+ g_hash_table_insert (codec_param_formats[media_type], (gpointer) name, fmt);
}
/**
@@ -165,10 +196,38 @@ format_param_generic (gpointer key, gpointer val, gpointer user_data)
* <replaceable>parameter</replaceable><literal>=</literal><replaceable>value</replaceable>
* pairs, as recommended in IETF RFC 4855 Section 3.
*/
-void
-rakia_codec_param_format_generic (GHashTable *params, GString *out)
+static void
+rakia_codec_param_format_generic (RakiaSipCodec *codec,
+ TpMediaStreamType media_type, GString *out)
{
- g_hash_table_foreach (params, format_param_generic, out);
+ guint i;
+
+ if (codec->params == NULL)
+ return;
+
+ for (i = 0; i < codec->params->len; i++)
+ {
+ RakiaSipCodecParam *param = g_ptr_array_index (codec->params, i);
+ RakiaCodecParamFormatting *fmt;
+
+ /* Ignore the ones with special functions */
+ fmt = g_hash_table_lookup (codec_param_formats[media_type],
+ codec->encoding_name);
+ if (fmt != NULL && fmt->format != NULL)
+ continue;
+
+ if (out->len != 0)
+ g_string_append_c (out, ';');
+
+ if (strpbrk (param->value, "; \t") == NULL)
+ g_string_append_printf (out, "%s=%s", param->name, param->value);
+ else
+ {
+ g_string_append (out, param->name);
+ g_string_append_c (out, '=');
+ rakia_string_append_quoted (out, param->value);
+ }
+ }
}
/**
@@ -180,8 +239,9 @@ rakia_codec_param_format_generic (GHashTable *params, GString *out)
* <replaceable>parameter</replaceable><literal>=</literal><replaceable>value</replaceable>
* pairs, as recommended in IETF RFC 4855 Section 3.
*/
-void
-rakia_codec_param_parse_generic (const gchar *fmtp, GHashTable *out)
+static void
+rakia_codec_param_parse_generic (const gchar *fmtp, TpMediaStreamType media_type,
+ RakiaSipCodec *codec)
{
GMatchInfo *match = NULL;
gint pos;
@@ -226,7 +286,7 @@ rakia_codec_param_parse_generic (const gchar *fmtp, GHashTable *out)
value_end - value_start);
}
- g_hash_table_insert (out, name, value);
+ rakia_sip_codec_add_param (codec, name, value);
g_match_info_fetch_pos (match, 0, NULL, &pos);
if (!fmtp[pos])
@@ -242,27 +302,50 @@ rakia_codec_param_parse_generic (const gchar *fmtp, GHashTable *out)
" as an attribute-value list: %s", &fmtp[pos]);
}
+RakiaSipCodecParam *
+find_param_by_name (RakiaSipCodec *codec, const gchar *name)
+{
+ guint i;
+
+ if (codec->params == NULL)
+ return NULL;
+
+ for (i = 0; i < codec->params->len; i++)
+ {
+ RakiaSipCodecParam *param = g_ptr_array_index (codec->params, i);
+
+ if (!strcmp (param->name, name))
+ return param;
+ }
+
+ return NULL;
+}
+
+
/* Custom format for audio/telephone-event */
static void
-rakia_codec_param_format_telephone_event (GHashTable *params, GString *out)
+rakia_codec_param_format_telephone_event (RakiaSipCodec *codec,
+ TpMediaStreamType media_type,
+ GString *out)
{
- const gchar *events;
+ RakiaSipCodecParam *events;
/* events parameter value comes first without the parameter name */
- events = g_hash_table_lookup (params, "events");
+ events = find_param_by_name (codec, "events");
if (events != NULL)
{
- g_string_append (out, events);
- g_hash_table_remove (params, "events");
+ g_string_append (out, events->value);
}
/* format the rest of the parameters, if any */
- rakia_codec_param_format_generic (params, out);
+ rakia_codec_param_format_generic (codec, media_type, out);
}
static void
-rakia_codec_param_parse_telephone_event (const gchar *fmtp, GHashTable *out)
+rakia_codec_param_parse_telephone_event (const gchar *fmtp,
+ TpMediaStreamType media_type,
+ RakiaSipCodec *codec)
{
GMatchInfo *match = NULL;
gint end_pos = 0;
@@ -278,15 +361,15 @@ rakia_codec_param_parse_telephone_event (const gchar *fmtp, GHashTable *out)
gchar *events;
events = g_match_info_fetch (match, 1);
- g_hash_table_insert (out, g_strdup ("events"), events);
-
+ rakia_sip_codec_add_param (codec, "events", events);
+ g_free (events);
g_match_info_fetch_pos (match, 0, NULL, &end_pos);
}
g_match_info_free (match);
/* Parse the remaining parameters, if any */
- rakia_codec_param_parse_generic (fmtp + end_pos, out);
+ rakia_codec_param_parse_generic (fmtp + end_pos, media_type, codec);
}
/*
diff --git a/rakia/codec-param-formats.h b/rakia/codec-param-formats.h
index feb0f86..668ee4f 100644
--- a/rakia/codec-param-formats.h
+++ b/rakia/codec-param-formats.h
@@ -25,55 +25,17 @@
#include <telepathy-glib/enums.h>
-G_BEGIN_DECLS
-
-/**
- * RakiaCodecParamFormatFunc:
- * @params: the map of codec parameters
- * @out: a #GString for the output
- *
- * Defines the function pointer signature for codec parameter formatters.
- * A formatter takes a codec parameter map as passed in
- * a org.freedesktop.Telepathy.Media.StreamHandler codec structure,
- * and outputs its SDP representation, as per the value for an
- * <literal>a=fmtp</literal> attribute, into the string buffer @out.
- *
- * <note>
- * <para>The function is allowed to delete pairs from the @params hash table.
- * This is useful to implement a custom formatter that processes the
- * few parameters treated specially, removes them from the map, and
- * calls a more generic formatter such as rakia_codec_param_format_generic().
- * </para>
- * </note>
- */
-typedef void (* RakiaCodecParamFormatFunc) (GHashTable *params, GString *out);
+#include <rakia/sip-media.h>
-/**
- * RakiaCodecParamParseFunc:
- * @str: a string value with format-specific parameter description
- * @out: the parameter map to populate
- *
- * Defines the function pointer signature for codec parameter parsers.
- * A parser takes the string value coming from an <literal>a=fmtp</literal>
- * SDP attribute, and populates the parameter hash table.
- */
-typedef void (* RakiaCodecParamParseFunc) (const gchar *str, GHashTable *out);
-
-void rakia_codec_param_format (TpMediaStreamType media, const char *name,
- GHashTable *params, GString *out);
+G_BEGIN_DECLS
-void rakia_codec_param_parse (TpMediaStreamType media, const char *name,
- const gchar *fmtp, GHashTable *out);
-void rakia_codec_param_register_format (
- TpMediaStreamType media,
- const char *name,
- RakiaCodecParamFormatFunc format,
- RakiaCodecParamParseFunc parse);
+void rakia_codec_param_format (TpMediaStreamType media_type, RakiaSipCodec *codec,
+ GString *out);
-void rakia_codec_param_format_generic (GHashTable *params, GString *out);
+void rakia_codec_param_parse (TpMediaStreamType media_type, RakiaSipCodec *codec,
+ const gchar *fmtp);
-void rakia_codec_param_parse_generic (const gchar *str, GHashTable *out);
G_END_DECLS
diff --git a/rakia/debug.c b/rakia/debug.c
index a608389..f82f975 100644
--- a/rakia/debug.c
+++ b/rakia/debug.c
@@ -29,12 +29,13 @@
static RakiaDebugFlags rakia_debug_flags = 0;
static const GDebugKey rakia_debug_keys[] = {
- { "media-channel", RAKIA_DEBUG_MEDIA },
+ { "media", RAKIA_DEBUG_MEDIA },
{ "connection", RAKIA_DEBUG_CONNECTION },
{ "im", RAKIA_DEBUG_IM },
{ "events", RAKIA_DEBUG_EVENTS },
{ "sofia", RAKIA_DEBUG_SOFIA },
{ "utilities", RAKIA_DEBUG_UTILITIES },
+ { "call", RAKIA_DEBUG_CALL },
};
static GHashTable *flag_to_domains = NULL;
diff --git a/rakia/debug.h b/rakia/debug.h
index e3ff1b3..3e62739 100644
--- a/rakia/debug.h
+++ b/rakia/debug.h
@@ -34,6 +34,7 @@ typedef enum
RAKIA_DEBUG_EVENTS = 1 << 3,
RAKIA_DEBUG_SOFIA = 1 << 4,
RAKIA_DEBUG_UTILITIES = 1 << 5,
+ RAKIA_DEBUG_CALL = 1 << 6,
} RakiaDebugFlags;
void rakia_debug_set_flags_from_env ();
diff --git a/rakia/media-channel.c b/rakia/media-channel.c
deleted file mode 100644
index ef6db85..0000000
--- a/rakia/media-channel.c
+++ /dev/null
@@ -1,2297 +0,0 @@
-/*
- * sip-media-channel.c - Source for RakiaMediaChannel
- * Copyright (C) 2005-2008 Collabora Ltd.
- * Copyright (C) 2005-2010 Nokia Corporation
- * @author Kai Vehmanen <first.surname@nokia.com>
- * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
- *
- * Based on telepathy-gabble implementation (gabble-media-channel).
- * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
- *
- * This work is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This work is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this work; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "config.h"
-
-#include "rakia/media-channel.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <telepathy-glib/channel-iface.h>
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/dtmf.h>
-#include <telepathy-glib/errors.h>
-#include <telepathy-glib/exportable-channel.h>
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/svc-channel.h>
-
-#include <rakia/event-target.h>
-
-#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
-#include "rakia/debug.h"
-
-#include <rakia/media-session.h>
-#include <rakia/base-connection.h>
-
-#define RAKIA_CHANNEL_CALL_STATE_PROCEEDING_MASK \
- (TP_CHANNEL_CALL_STATE_RINGING | \
- TP_CHANNEL_CALL_STATE_QUEUED | \
- TP_CHANNEL_CALL_STATE_IN_PROGRESS)
-
-/* DTMF dialstring playback durations in milliseconds */
-#define RAKIA_DTMF_TONE_DURATION 250
-#define RAKIA_DTMF_GAP_DURATION 100
-#define RAKIA_DTMF_PAUSE_DURATION 3000
-
-static void event_target_init (gpointer, gpointer);
-static void channel_iface_init (gpointer, gpointer);
-static void media_signalling_iface_init (gpointer, gpointer);
-static void streamed_media_iface_init (gpointer, gpointer);
-static void dtmf_iface_init (gpointer, gpointer);
-static void call_state_iface_init (gpointer, gpointer);
-static void hold_iface_init (gpointer, gpointer);
-
-static void priv_session_dtmf_ready_cb (RakiaMediaSession *session,
- RakiaMediaChannel *channel);
-
-G_DEFINE_TYPE_WITH_CODE (RakiaMediaChannel, rakia_media_channel,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (RAKIA_TYPE_EVENT_TARGET, event_target_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
- tp_dbus_properties_mixin_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE,
- tp_properties_mixin_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
- tp_group_mixin_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
- media_signalling_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DTMF,
- dtmf_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CALL_STATE,
- call_state_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
- hold_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA,
- streamed_media_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
- G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
-
-static const gchar *rakia_media_channel_interfaces[] = {
- TP_IFACE_CHANNEL_INTERFACE_GROUP,
- TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING,
- TP_IFACE_CHANNEL_INTERFACE_DTMF,
- TP_IFACE_CHANNEL_INTERFACE_CALL_STATE,
- TP_IFACE_CHANNEL_INTERFACE_HOLD,
- TP_IFACE_PROPERTIES_INTERFACE,
- NULL
-};
-
-/* properties */
-enum
-{
- PROP_CONNECTION = 1,
- PROP_OBJECT_PATH,
- PROP_CHANNEL_TYPE,
- PROP_HANDLE_TYPE,
- PROP_HANDLE,
- PROP_TARGET_ID,
- PROP_INITIATOR,
- PROP_INITIATOR_ID,
- PROP_REQUESTED,
- PROP_INTERFACES,
- PROP_CHANNEL_DESTROYED,
- PROP_CHANNEL_PROPERTIES,
- PROP_INITIAL_AUDIO,
- PROP_INITIAL_VIDEO,
- PROP_IMMUTABLE_STREAMS,
- PROP_CURRENTLY_SENDING_TONES,
- PROP_INITIAL_TONES,
- PROP_DEFERRED_TONES,
- /* Telepathy properties (see below too) */
- PROP_NAT_TRAVERSAL,
- PROP_STUN_SERVER,
- PROP_STUN_PORT,
- LAST_PROPERTY
-};
-
-/* TP channel properties */
-enum
-{
- TP_PROP_NAT_TRAVERSAL = 0,
- TP_PROP_STUN_SERVER,
- TP_PROP_STUN_PORT,
- NUM_TP_PROPS
-};
-
-static const TpPropertySignature media_channel_property_signatures[NUM_TP_PROPS] =
-{
- { "nat-traversal", G_TYPE_STRING },
- { "stun-server", G_TYPE_STRING },
- { "stun-port", G_TYPE_UINT },
-};
-
-/* signals */
-enum
-{
- SIG_INCOMING_CALL,
- NUM_SIGNALS
-};
-
-static guint signals[NUM_SIGNALS] = { 0 };
-
-
-/* private structure */
-struct _RakiaMediaChannelPrivate
-{
- RakiaBaseConnection *conn;
- RakiaMediaSession *session;
- gchar *object_path;
- TpHandle handle;
- TpHandle initiator;
- GHashTable *call_states;
- gchar *stun_server;
- guint stun_port;
- TpDTMFPlayer *dtmf_player;
- gchar *initial_tones;
- gchar *deferred_tones;
-
- gboolean initial_audio;
- gboolean initial_video;
- gboolean immutable_streams;
- gboolean closed;
- gboolean dispose_has_run;
-};
-
-#define RAKIA_MEDIA_CHANNEL_GET_PRIVATE(chan) ((chan)->priv)
-
-/***********************************************************************
- * Set: Gobject interface
- ***********************************************************************/
-
-static void
-rakia_media_channel_init (RakiaMediaChannel *self)
-{
- RakiaMediaChannelPrivate *priv =
- G_TYPE_INSTANCE_GET_PRIVATE (self,
- RAKIA_TYPE_MEDIA_CHANNEL, RakiaMediaChannelPrivate);
-
- self->priv = priv;
-
- /* allocate any data required by the object here */
- priv->call_states = g_hash_table_new (NULL, NULL);
-
- /* initialise the properties mixin *before* GObject
- * sets the construct-time properties */
- tp_properties_mixin_init (G_OBJECT (self),
- G_STRUCT_OFFSET (RakiaMediaChannel, properties));
-}
-
-static void
-rakia_media_channel_constructed (GObject *obj)
-{
- RakiaMediaChannel *chan = RAKIA_MEDIA_CHANNEL (obj);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (chan);
- TpBaseConnection *conn = (TpBaseConnection *)(priv->conn);
- GObjectClass *parent_object_class =
- G_OBJECT_CLASS (rakia_media_channel_parent_class);
- TpDBusDaemon *bus;
- TpHandleRepoIface *contact_repo;
- TpIntSet *add;
-
- if (parent_object_class->constructed != NULL)
- parent_object_class->constructed (obj);
-
- contact_repo = tp_base_connection_get_handles (conn,
- TP_HANDLE_TYPE_CONTACT);
-
- if (priv->handle != 0)
- tp_handle_ref (contact_repo, priv->handle);
-
- /* register object on the bus */
- bus = tp_base_connection_get_dbus_daemon (conn);
-
- DEBUG("registering object to dbus path=%s", priv->object_path);
- tp_dbus_daemon_register_object (bus, priv->object_path, obj);
-
- /* initialize group mixin */
- tp_group_mixin_init (obj,
- G_STRUCT_OFFSET (RakiaMediaChannel, group),
- contact_repo,
- conn->self_handle);
-
- /* automatically add initiator to channel, but also ref them again (because
- * priv->initiator is the InitiatorHandle) */
- g_assert (priv->initiator != 0);
- tp_handle_ref (contact_repo, priv->initiator);
-
- add = tp_intset_new_containing (priv->initiator);
- tp_group_mixin_change_members (obj, "", add, NULL, NULL, NULL, 0, 0);
- tp_intset_destroy (add);
-
- /* We start off with lots of flags, and then delete them as we work out what
- * kind of channel we are, rather than trying to track what we need to
- * add/remove over time. We should always have the right flags before we are
- * advertised on the bus. */
- tp_group_mixin_change_flags (obj,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
- TP_CHANNEL_GROUP_FLAG_CAN_RESCIND | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
-}
-
-static void rakia_media_channel_dispose (GObject *object);
-static void rakia_media_channel_finalize (GObject *object);
-static void rakia_media_channel_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec);
-static void rakia_media_channel_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec);
-
-static void priv_create_session (RakiaMediaChannel *channel,
- nua_handle_t *nh,
- TpHandle peer);
-static void priv_destroy_session(RakiaMediaChannel *channel);
-
-static void priv_outbound_call (RakiaMediaChannel *channel,
- TpHandle peer);
-
-static gboolean rakia_media_channel_remove_with_reason (
- GObject *iface,
- TpHandle handle,
- const gchar *message,
- guint reason,
- GError **error);
-
-static void
-rakia_media_channel_class_init (RakiaMediaChannelClass *klass)
-{
- static TpDBusPropertiesMixinPropImpl channel_props[] = {
- { "ChannelType", "channel-type", NULL },
- { "Interfaces", "interfaces", NULL },
- { "TargetHandleType", "handle-type", NULL },
- { "TargetHandle", "handle", NULL },
- { "TargetID", "target-id", NULL },
- { "InitiatorHandle", "initiator", NULL },
- { "InitiatorID", "initiator-id", NULL },
- { "Requested", "requested", NULL },
- { NULL }
- };
- static TpDBusPropertiesMixinPropImpl streamed_media_props[] = {
- { "InitialAudio", "initial-audio", NULL },
- { "InitialVideo", "initial-video", NULL },
- { "ImmutableStreams", "immutable-streams", NULL },
- { NULL }
- };
- static TpDBusPropertiesMixinPropImpl dtmf_props[] = {
- { "CurrentlySendingTones", "currently-sending-tones", NULL },
- { "InitialTones", "initial-tones", NULL },
- { "DeferredTones", "deferred-tones", NULL },
- { NULL }
- };
-
- static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
- { TP_IFACE_CHANNEL,
- tp_dbus_properties_mixin_getter_gobject_properties,
- NULL,
- channel_props,
- },
- { TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
- tp_dbus_properties_mixin_getter_gobject_properties,
- NULL,
- streamed_media_props,
- },
- { TP_IFACE_CHANNEL_INTERFACE_DTMF,
- tp_dbus_properties_mixin_getter_gobject_properties,
- NULL,
- dtmf_props,
- },
- { NULL }
- };
-
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GParamSpec *param_spec;
-
- DEBUG("enter");
-
- g_type_class_add_private (klass, sizeof (RakiaMediaChannelPrivate));
-
- object_class->constructed = rakia_media_channel_constructed;
- object_class->dispose = rakia_media_channel_dispose;
- object_class->finalize = rakia_media_channel_finalize;
-
- object_class->get_property = rakia_media_channel_get_property;
- object_class->set_property = rakia_media_channel_set_property;
-
- g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
- "handle-type");
- g_object_class_override_property (object_class, PROP_HANDLE, "handle");
- g_object_class_override_property (object_class, PROP_OBJECT_PATH,
- "object-path");
- g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
- "channel-type");
-
- g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED,
- "channel-destroyed");
- g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES,
- "channel-properties");
-
- param_spec = g_param_spec_object ("connection", "RakiaConnection object",
- "SIP connection object that owns this SIP media channel object.",
- RAKIA_TYPE_BASE_CONNECTION,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
- param_spec = g_param_spec_string ("nat-traversal", "NAT traversal mechanism",
- "A string representing the type of NAT traversal that should be "
- "performed for streams on this channel.",
- "none",
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_NAT_TRAVERSAL, param_spec);
-
- param_spec = g_param_spec_string ("stun-server", "STUN server",
- "IP or address of STUN server.", NULL,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_STUN_SERVER, param_spec);
-
- param_spec = g_param_spec_uint ("stun-port", "STUN port",
- "UDP port of STUN server.", 0, G_MAXUINT16, 0,
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_STUN_PORT, param_spec);
-
- param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
- "Addition Channel.Interface.* interfaces", G_TYPE_STRV,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
-
- param_spec = g_param_spec_string ("target-id", "Target SIP URI",
- "Currently empty, because this channel always has handle 0.",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
-
- param_spec = g_param_spec_uint ("initiator", "Channel initiator",
- "The TpHandle representing the contact who created the channel.",
- 0, G_MAXUINT32, 0,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIATOR, param_spec);
-
- param_spec = g_param_spec_string ("initiator-id", "Creator URI",
- "The URI obtained by inspecting the initiator handle.",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIATOR_ID, param_spec);
-
- param_spec = g_param_spec_boolean ("requested", "Requested?",
- "True if this channel was requested by the local user",
- FALSE,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
-
- param_spec = g_param_spec_boolean ("initial-audio", "InitialAudio",
- "Whether the channel initially contained an audio stream",
- FALSE,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIAL_AUDIO,
- param_spec);
-
- param_spec = g_param_spec_boolean ("initial-video", "InitialVideo",
- "Whether the channel initially contained a video stream",
- FALSE,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIAL_VIDEO,
- param_spec);
-
- param_spec = g_param_spec_boolean ("immutable-streams", "ImmutableStreams",
- "Whether the set of streams on this channel are fixed once requested",
- FALSE,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_IMMUTABLE_STREAMS,
- param_spec);
-
- param_spec = g_param_spec_boolean ("currently-sending-tones",
- "Currently sending tones",
- "True if the channel is currently sending DTMF tones",
- FALSE,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_CURRENTLY_SENDING_TONES,
- param_spec);
-
- param_spec = g_param_spec_string ("initial-tones", "Initial tones",
- "The initial DTMF tones to send after audio stream(s) are established.",
- NULL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_INITIAL_TONES,
- param_spec);
-
- param_spec = g_param_spec_string ("deferred-tones", "Deferred tones",
- "The DTMF tones deferred waiting for user input.",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_DEFERRED_TONES,
- param_spec);
-
- signals[SIG_INCOMING_CALL] =
- g_signal_new ("incoming-call",
- G_OBJECT_CLASS_TYPE (klass),
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- tp_properties_mixin_class_init (object_class,
- G_STRUCT_OFFSET (RakiaMediaChannelClass, properties_class),
- media_channel_property_signatures, NUM_TP_PROPS, NULL);
-
- klass->dbus_props_class.interfaces =
- prop_interfaces;
- tp_dbus_properties_mixin_class_init (object_class,
- G_STRUCT_OFFSET (RakiaMediaChannelClass, dbus_props_class));
-
- tp_group_mixin_class_init (object_class,
- G_STRUCT_OFFSET (RakiaMediaChannelClass, group_class),
- _rakia_media_channel_add_member,
- NULL);
- tp_group_mixin_class_allow_self_removal (object_class);
- tp_group_mixin_class_set_remove_with_reason_func(object_class,
- rakia_media_channel_remove_with_reason);
- tp_group_mixin_init_dbus_properties (object_class);
-
-}
-
-static void
-rakia_media_channel_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- RakiaMediaChannel *chan = RAKIA_MEDIA_CHANNEL (object);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (chan);
- TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn);
-
- switch (property_id) {
- case PROP_CONNECTION:
- g_value_set_object (value, priv->conn);
- break;
- case PROP_OBJECT_PATH:
- g_value_set_string (value, priv->object_path);
- break;
- case PROP_CHANNEL_TYPE:
- g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
- break;
- case PROP_HANDLE:
- g_value_set_uint (value, priv->handle);
- break;
- case PROP_HANDLE_TYPE:
- g_value_set_uint (value, priv->handle?
- TP_HANDLE_TYPE_CONTACT : TP_HANDLE_TYPE_NONE);
- break;
- case PROP_TARGET_ID:
- if (priv->handle != 0)
- {
- TpHandleRepoIface *repo = tp_base_connection_get_handles (
- base_conn, TP_HANDLE_TYPE_CONTACT);
-
- g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
- }
- else
- g_value_set_static_string (value, "");
- break;
- case PROP_INITIATOR:
- g_value_set_uint (value, priv->initiator);
- break;
- case PROP_INITIATOR_ID:
- {
- TpHandleRepoIface *repo = tp_base_connection_get_handles (
- base_conn, TP_HANDLE_TYPE_CONTACT);
-
- g_value_set_string (value, tp_handle_inspect (repo, priv->initiator));
- }
- break;
- case PROP_REQUESTED:
- g_value_set_boolean (value, (priv->initiator == base_conn->self_handle));
- break;
- case PROP_INTERFACES:
- g_value_set_static_boxed (value, rakia_media_channel_interfaces);
- break;
- case PROP_CHANNEL_DESTROYED:
- g_value_set_boolean (value, priv->closed);
- break;
- case PROP_CHANNEL_PROPERTIES:
- g_value_take_boxed (value,
- tp_dbus_properties_mixin_make_properties_hash (object,
- TP_IFACE_CHANNEL, "ChannelType",
- TP_IFACE_CHANNEL, "TargetHandleType",
- TP_IFACE_CHANNEL, "TargetHandle",
- TP_IFACE_CHANNEL, "TargetID",
- TP_IFACE_CHANNEL, "InitiatorHandle",
- TP_IFACE_CHANNEL, "InitiatorID",
- TP_IFACE_CHANNEL, "Requested",
- TP_IFACE_CHANNEL, "Interfaces",
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialAudio",
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialVideo",
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "ImmutableStreams",
- NULL));
- break;
- case PROP_INITIAL_AUDIO:
- g_value_set_boolean (value, priv->initial_audio);
- break;
- case PROP_INITIAL_VIDEO:
- g_value_set_boolean (value, priv->initial_video);
- break;
- case PROP_IMMUTABLE_STREAMS:
- g_value_set_boolean (value, priv->immutable_streams);
- break;
- case PROP_STUN_SERVER:
- g_value_set_string (value, priv->stun_server);
- break;
- case PROP_STUN_PORT:
- g_value_set_uint (value, priv->stun_port);
- break;
- case PROP_CURRENTLY_SENDING_TONES:
- g_value_set_boolean (value,
- priv->dtmf_player != NULL
- && tp_dtmf_player_is_active (priv->dtmf_player));
- break;
- case PROP_INITIAL_TONES:
- if (priv->initial_tones == NULL)
- g_value_set_static_string (value, "");
- else
- g_value_set_string (value, priv->initial_tones);
- break;
- case PROP_DEFERRED_TONES:
- if (priv->deferred_tones == NULL)
- g_value_set_static_string (value, "");
- else
- g_value_set_string (value, priv->deferred_tones);
- break;
- default:
- /* Some properties live in the mixin */
- {
- const gchar *param_name;
- guint tp_property_id;
- GValue *tp_property_value;
-
- param_name = g_param_spec_get_name (pspec);
- if (G_LIKELY (tp_properties_mixin_has_property (object, param_name,
- &tp_property_id)))
- {
- tp_property_value =
- chan->properties.properties[tp_property_id].value;
-
- if (G_LIKELY (tp_property_value != NULL))
- {
- g_value_copy (tp_property_value, value);
- return;
- }
- }
- }
-
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static gboolean
-rakia_media_channel_set_tp_property (RakiaMediaChannel *chan,
- const GValue *value,
- GParamSpec *pspec)
-{
- GObject *obj = (GObject *) chan;
- const gchar *param_name = g_param_spec_get_name (pspec);
- guint tp_property_id;
-
- if (G_LIKELY (tp_properties_mixin_has_property (obj, param_name,
- &tp_property_id)))
- {
- tp_properties_mixin_change_value (obj, tp_property_id,
- value, NULL);
- tp_properties_mixin_change_flags (obj, tp_property_id,
- TP_PROPERTY_FLAG_READ, 0, NULL);
- return TRUE;
- }
- else
- {
- WARNING("Telepathy property '%s' is not defined for media channels",
- param_name);
- return FALSE;
- }
-}
-
-static void
-rakia_media_channel_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- RakiaMediaChannel *chan = RAKIA_MEDIA_CHANNEL (object);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (chan);
-
- switch (property_id) {
- case PROP_HANDLE_TYPE:
- case PROP_CHANNEL_TYPE:
- /* this property is writable in the interface, but not actually
- * meaningfully changable on this channel, so we do nothing */
- break;
- case PROP_CONNECTION:
- priv->conn = g_value_dup_object (value);
- break;
- case PROP_OBJECT_PATH:
- g_free (priv->object_path);
- priv->object_path = g_value_dup_string (value);
- break;
- case PROP_HANDLE:
- /* XXX: this property is defined as writable,
- * but don't set it after construction, mmkay? */
- /* we don't ref it here because we don't necessarily have access to the
- * contact repo yet - instead we ref it in constructed. */
- priv->handle = g_value_get_uint (value);
- break;
- case PROP_INITIATOR:
- /* similarly we can't ref this yet */
- priv->initiator = g_value_get_uint (value);
- break;
- case PROP_INITIAL_AUDIO:
- priv->initial_audio = g_value_get_boolean (value);
- break;
- case PROP_INITIAL_VIDEO:
- priv->initial_video = g_value_get_boolean (value);
- break;
- case PROP_IMMUTABLE_STREAMS:
- priv->immutable_streams = g_value_get_boolean (value);
- break;
- case PROP_STUN_SERVER:
- priv->stun_server = g_value_dup_string (value);
- /* Also expose as a legacy Telepathy property */
- rakia_media_channel_set_tp_property (chan, value, pspec);
- break;
- case PROP_STUN_PORT:
- priv->stun_port = g_value_get_uint (value);
- /* Also expose as a legacy Telepathy property */
- rakia_media_channel_set_tp_property (chan, value, pspec);
- break;
- case PROP_INITIAL_TONES:
- priv->initial_tones = g_value_dup_string (value);
- break;
- default:
- /* some properties live in the mixin */
- if (rakia_media_channel_set_tp_property (chan, value, pspec))
- return;
-
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void
-rakia_media_channel_dispose (GObject *object)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (object);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpHandleRepoIface *contact_handles;
-
- if (priv->dispose_has_run)
- return;
-
- DEBUG("enter");
-
- priv->dispose_has_run = TRUE;
-
- if (!priv->closed)
- rakia_media_channel_close (self);
-
- if (priv->dtmf_player != NULL)
- g_object_unref (priv->dtmf_player);
-
- contact_handles = tp_base_connection_get_handles (
- TP_BASE_CONNECTION (priv->conn), TP_HANDLE_TYPE_CONTACT);
-
- tp_handle_unref (contact_handles, priv->initiator);
- priv->initiator = 0;
-
- g_object_unref (priv->conn);
-
- if (G_OBJECT_CLASS (rakia_media_channel_parent_class)->dispose)
- G_OBJECT_CLASS (rakia_media_channel_parent_class)->dispose (object);
-
- DEBUG("exit");
-}
-
-static void
-rakia_media_channel_finalize (GObject *object)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (object);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- g_hash_table_unref (priv->call_states);
-
- g_free (priv->initial_tones);
- g_free (priv->deferred_tones);
-
- g_free (priv->stun_server);
-
- g_free (priv->object_path);
-
- tp_group_mixin_finalize (object);
-
- tp_properties_mixin_finalize (object);
-
- G_OBJECT_CLASS (rakia_media_channel_parent_class)->finalize (object);
-
- DEBUG("exit");
-}
-
-/***********************************************************************
- * Set: Channel interface implementation (same for 0.12/0.13)
- ***********************************************************************/
-
-/**
- * rakia_media_channel_close_async
- *
- * Implements DBus method Close
- * on interface org.freedesktop.Telepathy.Channel
- */
-static void
-rakia_media_channel_dbus_close (TpSvcChannel *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
-
- rakia_media_channel_close (self);
- tp_svc_channel_return_from_close (context);
-}
-
-void
-rakia_media_channel_close (RakiaMediaChannel *obj)
-{
- RakiaMediaChannelPrivate *priv;
-
- DEBUG("enter");
-
- g_assert (RAKIA_IS_MEDIA_CHANNEL (obj));
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (obj);
-
- if (priv->closed)
- return;
-
- priv->closed = TRUE;
-
- if (priv->session) {
- rakia_media_session_terminate (priv->session);
- g_assert (priv->session == NULL);
- }
-
- tp_svc_channel_emit_closed ((TpSvcChannel *)obj);
-
- return;
-}
-
-/**
- * rakia_media_channel_get_channel_type
- *
- * Implements DBus method GetChannelType
- * on interface org.freedesktop.Telepathy.Channel
- */
-static void
-rakia_media_channel_get_channel_type (TpSvcChannel *obj,
- DBusGMethodInvocation *context)
-{
- tp_svc_channel_return_from_get_channel_type (context,
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
-}
-
-
-/**
- * rakia_media_channel_get_handle
- *
- * Implements DBus method GetHandle
- * on interface org.freedesktop.Telepathy.Channel
- */
-static void
-rakia_media_channel_get_handle (TpSvcChannel *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- if (priv->handle != 0)
- tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
- priv->handle);
- else
- tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_NONE, 0);
-}
-
-/**
- * rakia_media_channel_get_interfaces
- *
- * Implements DBus method GetInterfaces
- * on interface org.freedesktop.Telepathy.Channel
- */
-static void
-rakia_media_channel_get_interfaces (TpSvcChannel *iface,
- DBusGMethodInvocation *context)
-{
- tp_svc_channel_return_from_get_interfaces (context,
- rakia_media_channel_interfaces);
-}
-
-/***********************************************************************
- * Set: Channel.Interface.MediaSignalling Telepathy-0.13 interface
- ***********************************************************************/
-
-/**
- * rakia_media_channel_get_session_handlers
- *
- * Implements DBus method GetSessionHandlers
- * on interface org.freedesktop.Telepathy.Channel.Interface.MediaSignalling
- *
- * @error: Used to return a pointer to a GError detailing any error
- * that occured, DBus will throw the error only if this
- * function returns false.
- *
- * Returns: TRUE if successful, FALSE if an error was thrown.
- */
-static void
-rakia_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv;
- GPtrArray *ret;
- GValue handler = { 0 };
-
- DEBUG("enter");
-
- g_assert (RAKIA_IS_MEDIA_CHANNEL (self));
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- ret = g_ptr_array_new ();
-
- if (priv->session)
- {
- GType handler_type;
- gchar *path;
-
- g_object_get (priv->session,
- "object-path", &path,
- NULL);
-
- handler_type = dbus_g_type_get_struct ("GValueArray",
- DBUS_TYPE_G_OBJECT_PATH,
- G_TYPE_STRING,
- G_TYPE_INVALID);
-
- g_value_init (&handler, handler_type);
- g_value_take_boxed (&handler,
- dbus_g_type_specialized_construct (handler_type));
-
- dbus_g_type_struct_set (&handler,
- 0, path,
- 1, "rtp",
- G_MAXUINT);
-
- g_free (path);
-
- g_ptr_array_add (ret, g_value_get_boxed (&handler));
- }
-
- tp_svc_channel_interface_media_signalling_return_from_get_session_handlers (
- context, ret);
-
- if (G_IS_VALUE(&handler))
- g_value_unset (&handler);
-
- g_ptr_array_unref (ret);
-}
-
-
-/***********************************************************************
- * Set: Channel.Type.StreamedMedia Telepathy-0.13 interface
- ***********************************************************************/
-
-/**
- * rakia_media_channel_list_streams
- *
- * Implements D-Bus method ListStreams
- * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
- */
-static void
-rakia_media_channel_list_streams (TpSvcChannelTypeStreamedMedia *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv;
- GPtrArray *ret = NULL;
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- ret = g_ptr_array_new ();
-
- if (priv->session != NULL)
- rakia_media_session_list_streams (priv->session, ret);
-
- tp_svc_channel_type_streamed_media_return_from_list_streams (context, ret);
-
- g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, ret);
-}
-
-/**
- * rakia_media_channel_remove_streams
- *
- * Implements D-Bus method RemoveStreams
- * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
- */
-static void
-rakia_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface,
- const GArray *streams,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv;
- GError *error = NULL;
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- if (priv->immutable_streams)
- {
- error = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
- "Cannot remove streams from the existing channel");
- }
- else if (priv->session != NULL)
- {
- rakia_media_session_remove_streams(priv->session,
- streams,
- &error);
- }
- else
- {
- error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "No session is available");
- }
-
- if (error != NULL)
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- return;
- }
-
- tp_svc_channel_type_streamed_media_return_from_remove_streams (context);
-}
-
-/**
- * rakia_media_channel_request_stream_direction
- *
- * Implements D-Bus method RequestStreamDirection
- * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
- */
-static void
-rakia_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface,
- guint stream_id,
- guint stream_direction,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv;
- GError *error = NULL;
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- if (priv->immutable_streams)
- {
- GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Cannot change directions on an immutable channel" };
- dbus_g_method_return_error (context, &e);
- return;
- }
-
- if (priv->session != NULL)
- {
- rakia_media_session_request_stream_direction (priv->session,
- stream_id,
- stream_direction,
- &error);
- }
- else
- {
- error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "The media session is not available");
- }
-
- if (error == NULL)
- {
- tp_svc_channel_type_streamed_media_return_from_request_stream_direction (context);
- }
- else
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- }
-}
-
-
-/**
- * rakia_media_channel_request_streams
- *
- * Implements D-Bus method RequestStreams
- * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia
- */
-static void
-rakia_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface,
- guint contact_handle,
- const GArray *types,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- GError *error = NULL;
- GPtrArray *ret = NULL;
- RakiaMediaChannelPrivate *priv;
- TpHandleRepoIface *contact_repo;
-
- DEBUG("enter");
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- if (priv->immutable_streams)
- {
- GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Cannot add streams to the immutable channel" };
- dbus_g_method_return_error (context, &e);
- return;
- }
-
- contact_repo = tp_base_connection_get_handles (
- (TpBaseConnection *)(priv->conn), TP_HANDLE_TYPE_CONTACT);
-
- if (!tp_handle_is_valid (contact_repo, contact_handle, &error))
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- return;
- }
-
- priv_outbound_call (self, contact_handle);
-
- ret = g_ptr_array_sized_new (types->len);
-
- if (rakia_media_session_request_streams (priv->session, types, ret, &error))
- {
- g_assert (types->len == ret->len);
- tp_svc_channel_type_streamed_media_return_from_request_streams (context,
- ret);
- }
- else
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- }
-
- g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, ret);
-
- DEBUG ("exit");
-}
-
-/***********************************************************************
- * Set: sip-media-channel API towards sip-connection
- ***********************************************************************/
-
-void
-rakia_media_channel_create_initial_streams (RakiaMediaChannel *self)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- g_assert (priv->initiator != priv->handle);
-
- /* RequestChannel(None, 0) => channel is anonymous:
- * caller uses RequestStreams to set the peer and start the call. */
- if (priv->handle == 0)
- return;
-
- priv_outbound_call (self, priv->handle);
-
- g_assert (priv->session != NULL);
-
- if (priv->initial_audio)
- rakia_media_session_add_stream (priv->session,
- TP_MEDIA_STREAM_TYPE_AUDIO,
- TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
- TRUE);
-
- if (priv->initial_video)
- rakia_media_session_add_stream (priv->session,
- TP_MEDIA_STREAM_TYPE_VIDEO,
- TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
- TRUE);
-}
-
-/*
- * Handles an incoming call, called shortly after the channel
- * has been created with initiator handle of the sender, when remote SDP
- * session data are reported by the NUA stack.
- */
-static void
-rakia_media_channel_handle_incoming_call (RakiaMediaChannel *self,
- nua_handle_t *nh,
- const sdp_session_t *sdp)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpBaseConnection *conn = TP_BASE_CONNECTION (priv->conn);
-
- g_assert (priv->initiator != conn->self_handle);
- g_assert (priv->session == NULL);
-
- if (sdp != NULL)
- {
- /* Get the initial media properties from the session offer */
- const sdp_media_t *media;
-
- for (media = sdp->sdp_media; media != NULL; media = media->m_next)
- {
- if (media->m_rejected || media->m_port == 0)
- continue;
-
- switch (media->m_type)
- {
- case sdp_media_audio:
- priv->initial_audio = TRUE;
- DEBUG("has initial audio");
- break;
- case sdp_media_video:
- priv->initial_video = TRUE;
- DEBUG("has initial video");
- break;
- default:
- break;
- }
- }
- }
-
- /* Tell the factory to emit NewChannel(s) */
- g_signal_emit (self, signals[SIG_INCOMING_CALL], 0);
-
- /* Offer the session handler to the client */
- priv_create_session (self, nh, priv->initiator);
-
- g_assert (priv->session != NULL);
- rakia_media_session_receive_invite (priv->session);
-}
-
-static gboolean
-priv_nua_i_invite_cb (RakiaMediaChannel *self,
- const RakiaNuaEvent *ev,
- tagi_t tags[],
- gpointer foo)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- /* nua_i_invite delivered for a bound handle means a re-INVITE */
-
- g_return_val_if_fail (priv->session != NULL, FALSE);
-
- rakia_media_session_receive_reinvite (priv->session);
-
- return TRUE;
-}
-
-static guint
-rakia_media_channel_get_call_state (RakiaMediaChannel *self,
- TpHandle peer)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- return GPOINTER_TO_UINT (g_hash_table_lookup (priv->call_states,
- GUINT_TO_POINTER (peer)));
-}
-
-static void
-rakia_media_channel_peer_error (RakiaMediaChannel *self,
- TpHandle peer,
- guint status,
- const char* message)
-{
- TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
- TpIntSet *remove;
- guint reason = TP_CHANNEL_GROUP_CHANGE_REASON_ERROR;
-
- switch (status)
- {
- case 410:
- case 604:
- reason = TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT;
- break;
- case 486:
- case 600:
- reason = TP_CHANNEL_GROUP_CHANGE_REASON_BUSY;
- break;
- case 408:
- reason = TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER;
- break;
- case 404:
- case 480:
- reason = (rakia_media_channel_get_call_state (self, peer)
- & RAKIA_CHANNEL_CALL_STATE_PROCEEDING_MASK)
- ? TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER
- : TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE;
- break;
- case 603:
- /* No reason means roughly "rejected" */
- reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE;
- break;
- case 403:
- case 401:
- case 407:
- reason = TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED;
- break;
- }
-
- if (message == NULL || !g_utf8_validate (message, -1, NULL))
- message = "";
-
- remove = tp_intset_new ();
- tp_intset_add (remove, peer);
- tp_intset_add (remove, mixin->self_handle);
- tp_group_mixin_change_members ((GObject *)self, message,
- NULL, remove, NULL, NULL, peer, reason);
- tp_intset_destroy (remove);
-}
-
-guint
-rakia_media_channel_change_call_state (RakiaMediaChannel *self,
- TpHandle peer,
- guint flags_add,
- guint flags_remove)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- gpointer key = GUINT_TO_POINTER (peer);
- guint old_state;
- guint new_state;
-
- /* XXX: check if the peer is a member? */
-
- old_state = GPOINTER_TO_UINT (g_hash_table_lookup (priv->call_states, key));
- new_state = (old_state | flags_add) & ~flags_remove;
-
- if (new_state != old_state)
- {
- DEBUG ("setting call state %u for peer %u", new_state, peer);
- if (new_state == 0)
- g_hash_table_remove (priv->call_states, key);
- else
- g_hash_table_replace (priv->call_states, key,
- GUINT_TO_POINTER (new_state));
-
- tp_svc_channel_interface_call_state_emit_call_state_changed (self,
- peer,
- new_state);
- }
-
- return new_state;
-}
-
-static gboolean
-priv_nua_i_bye_cb (RakiaMediaChannel *self,
- const RakiaNuaEvent *ev,
- tagi_t tags[],
- gpointer foo)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
- TpIntSet *remove;
- TpHandle peer;
-
- g_return_val_if_fail (priv->session != NULL, FALSE);
-
- peer = rakia_media_session_get_peer (priv->session);
- remove = tp_intset_new ();
- tp_intset_add (remove, peer);
- tp_intset_add (remove, mixin->self_handle);
-
- tp_group_mixin_change_members ((GObject *) self, "",
- NULL, remove, NULL, NULL,
- peer, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
- tp_intset_destroy (remove);
-
- return TRUE;
-}
-
-static gboolean
-priv_nua_i_cancel_cb (RakiaMediaChannel *self,
- const RakiaNuaEvent *ev,
- tagi_t tags[],
- gpointer foo)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpGroupMixin *mixin = TP_GROUP_MIXIN (self);
- TpIntSet *remove;
- TpHandle actor = 0;
- TpHandle peer;
- const sip_reason_t *reason;
- guint cause = 0;
- const gchar *message = NULL;
-
- g_return_val_if_fail (priv->session != NULL, FALSE);
-
- /* FIXME: implement cancellation of an incoming re-INVITE, if ever
- * found in real usage and not caused by a request timeout */
-
- if (ev->sip != NULL)
- for (reason = ev->sip->sip_reason;
- reason != NULL;
- reason = reason->re_next)
- {
- const char *protocol = reason->re_protocol;
- if (protocol == NULL || strcmp (protocol, "SIP") != 0)
- continue;
- if (reason->re_cause != NULL)
- {
- cause = (guint) g_ascii_strtoull (reason->re_cause, NULL, 10);
- message = reason->re_text;
- break;
- }
- }
-
- peer = rakia_media_session_get_peer (priv->session);
-
- switch (cause)
- {
- case 200:
- case 603:
- /* The user must have acted on another branch of the forked call */
- actor = mixin->self_handle;
- break;
- default:
- actor = peer;
- }
-
- if (message == NULL || !g_utf8_validate (message, -1, NULL))
- message = "";
-
- remove = tp_intset_new ();
- tp_intset_add (remove, peer);
- tp_intset_add (remove, mixin->self_handle);
-
- tp_group_mixin_change_members ((GObject *) self, message,
- NULL, remove, NULL, NULL,
- actor, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
- tp_intset_destroy (remove);
-
- return TRUE;
-}
-
-static gboolean
-priv_nua_i_state_cb (RakiaMediaChannel *self,
- const RakiaNuaEvent *ev,
- tagi_t tags[],
- gpointer foo)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- const sdp_session_t *r_sdp = NULL;
- int offer_recv = 0;
- int answer_recv = 0;
- int ss_state = nua_callstate_init;
- gint status = ev->status;
- TpHandle peer;
-
- tl_gets(tags,
- NUTAG_CALLSTATE_REF(ss_state),
- NUTAG_OFFER_RECV_REF(offer_recv),
- NUTAG_ANSWER_RECV_REF(answer_recv),
- SOATAG_REMOTE_SDP_REF(r_sdp),
- TAG_END());
-
- DEBUG("call with handle %p is %s", ev->nua_handle, nua_callstate_name (ss_state));
-
- if (ss_state == nua_callstate_received && priv->session == NULL)
- {
- /* We get the session data for initial media properties with this event;
- * initialize the session before we can create any streams below.
- */
- rakia_media_channel_handle_incoming_call (self, ev->nua_handle, r_sdp);
- }
-
- g_return_val_if_fail (priv->session != NULL, FALSE);
-
- if (r_sdp)
- {
- g_return_val_if_fail (answer_recv || offer_recv, FALSE);
- if (!rakia_media_session_set_remote_media (priv->session, r_sdp))
- {
- rakia_media_channel_close (self);
- return TRUE;
- }
- }
-
- peer = rakia_media_session_get_peer (priv->session);
-
- switch ((enum nua_callstate)ss_state)
- {
- case nua_callstate_proceeding:
- switch (status)
- {
- case 180:
- rakia_media_channel_change_call_state (self, peer,
- TP_CHANNEL_CALL_STATE_RINGING, 0);
- break;
- case 182:
- rakia_media_channel_change_call_state (self, peer,
- TP_CHANNEL_CALL_STATE_QUEUED, 0);
- break;
- case 183:
- rakia_media_channel_change_call_state (self, peer,
- TP_CHANNEL_CALL_STATE_IN_PROGRESS, 0);
- break;
- }
- break;
-
- case nua_callstate_completing:
- /* In auto-ack mode, we don't need to call nua_ack(), see NUTAG_AUTOACK() */
- break;
-
- case nua_callstate_ready:
-
- /* Clear any pre-establishment call states */
- rakia_media_channel_change_call_state (self, peer, 0,
- RAKIA_CHANNEL_CALL_STATE_PROCEEDING_MASK);
-
- if (status < 300)
- {
- TpIntSet *add = tp_intset_new_containing (peer);
-
- tp_group_mixin_change_members ((GObject *) self,
- "",
- add, /* add */
- NULL, /* remove */
- NULL,
- NULL,
- peer,
- TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
- tp_intset_destroy (add);
-
- rakia_media_session_accept (priv->session);
- }
- else if (status == 491)
- rakia_media_session_resolve_glare (priv->session);
- else
- {
- /* Was something wrong with our re-INVITE? We can't cope anyway. */
- MESSAGE ("can't handle non-fatal response %d %s", status, ev->text);
- rakia_media_session_terminate (priv->session);
- }
- break;
-
- case nua_callstate_terminated:
- /* In cases of self-inflicted termination,
- * we should have already gone through the moves */
- if (rakia_media_session_get_state (priv->session)
- == RAKIA_MEDIA_SESSION_STATE_ENDED)
- break;
-
- if (status >= 300)
- {
- rakia_media_channel_peer_error (
- self, peer, status, ev->text);
- }
-
- rakia_media_session_change_state (priv->session,
- RAKIA_MEDIA_SESSION_STATE_ENDED);
- break;
-
- default:
- break;
- }
-
- return TRUE;
-}
-
-static void priv_session_state_changed_cb (RakiaMediaSession *session,
- guint old_state,
- guint state,
- RakiaMediaChannel *channel)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel);
- TpGroupMixin *mixin = TP_GROUP_MIXIN (channel);
- TpHandle self_handle;
- TpHandle peer;
- TpIntSet *set = NULL;
-
- DEBUG("enter");
-
- self_handle = mixin->self_handle;
- peer = rakia_media_session_get_peer (session);
-
- switch (state)
- {
- case RAKIA_MEDIA_SESSION_STATE_INVITE_SENT:
- g_assert (priv->initiator == self_handle);
-
- /* add the peer to remote pending */
- set = tp_intset_new_containing (peer);
- tp_group_mixin_change_members ((GObject *)channel,
- "",
- NULL, /* add */
- NULL, /* remove */
- NULL, /* local pending */
- set, /* remote pending */
- self_handle, /* actor */
- TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
-
- /* update flags: no more adding */
- tp_group_mixin_change_flags ((GObject *)channel, 0,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD);
-
- break;
-
- case RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED:
- /* add ourself to local pending */
- set = tp_intset_new_containing (self_handle);
- tp_group_mixin_change_members ((GObject *) channel, "",
- NULL, /* add */
- NULL, /* remove */
- set, /* local pending */
- NULL, /* remote pending */
- priv->initiator, /* actor */
- TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
-
- /* No adding more members to the incoming call. Therefore also not
- * possible to add anyone to remote-pending, so rescinding would make
- * utterly no sense. We also disallow removing the remote peer if
- * we are not the initiator, so disallow that too.
- * Removing yourself to end the call is not represented by group flags.
- */
- tp_group_mixin_change_flags ((GObject *) channel, 0,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE |
- TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
-
- break;
-
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- if (priv->initiator == self_handle)
- {
- if (!tp_handle_set_is_member (mixin->remote_pending, peer))
- break; /* no-op */
-
- /* the peer has promoted itself to members */
- set = tp_intset_new_containing (peer);
- tp_group_mixin_change_members ((GObject *)channel, "",
- set, /* add */
- NULL, /* remove */
- NULL,
- NULL,
- peer, 0);
- }
- else
- {
- if (!tp_handle_set_is_member (mixin->local_pending, self_handle))
- break; /* no-op */
-
- /* promote ourselves to members */
- set = tp_intset_new_containing (self_handle);
- tp_group_mixin_change_members ((GObject *)channel, "",
- set, /* add */
- NULL, /* remove */
- NULL,
- NULL,
- self_handle, 0);
- }
-
- /* update flags: deny adding and rescinding. Removing the remote peer is
- * still allowed.
- * Removing yourself to end the call is not represented by group flags.
- */
- tp_group_mixin_change_flags ((GObject *)channel, 0,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND);
-
- break;
-
- case RAKIA_MEDIA_SESSION_STATE_ENDED:
- set = tp_intset_new ();
-
- /* remove us and the peer from the member list */
- tp_intset_add (set, self_handle);
- tp_intset_add (set, peer);
- tp_group_mixin_change_members ((GObject *)channel, "",
- NULL, /* add */
- set, /* remove */
- NULL,
- NULL,
- 0, 0);
-
- /* Close the channel; destroy the session first to avoid
- * the rakia_media_session_terminate() path in this case */
- priv_destroy_session (channel);
- rakia_media_channel_close (channel);
- break;
- }
-
- if (set != NULL)
- tp_intset_destroy (set);
-}
-
-void
-rakia_media_channel_attach_to_nua_handle (RakiaMediaChannel *self,
- nua_handle_t *nh)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- rakia_event_target_attach (nh, (GObject *) self);
-
- /* have the connection handle authentication, before all other
- * response callbacks */
- rakia_base_connection_add_auth_handler (priv->conn, RAKIA_EVENT_TARGET (self));
-
- g_signal_connect (self,
- "nua-event::nua_i_invite",
- G_CALLBACK (priv_nua_i_invite_cb),
- NULL);
- g_signal_connect (self,
- "nua-event::nua_i_bye",
- G_CALLBACK (priv_nua_i_bye_cb),
- NULL);
- g_signal_connect (self,
- "nua-event::nua_i_cancel",
- G_CALLBACK (priv_nua_i_cancel_cb),
- NULL);
- g_signal_connect (self,
- "nua-event::nua_i_state",
- G_CALLBACK (priv_nua_i_state_cb),
- NULL);
-
-}
-
-/**
- * priv_create_session:
- *
- * Creates a RakiaMediaSession object for given peer.
- **/
-static void
-priv_create_session (RakiaMediaChannel *channel,
- nua_handle_t *nh,
- TpHandle peer)
-{
- RakiaMediaChannelPrivate *priv;
- RakiaMediaSession *session;
- TpBaseConnection *conn;
- TpHandleRepoIface *contact_repo;
- gchar *object_path;
- gchar *local_ip_address = NULL;
-
- DEBUG("enter");
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel);
- conn = (TpBaseConnection *)(priv->conn);
- contact_repo = tp_base_connection_get_handles (conn,
- TP_HANDLE_TYPE_CONTACT);
-
- g_assert (priv->session == NULL);
-
- object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer);
-
- DEBUG("allocating session, peer=%u", peer);
-
- /* The channel manages references to the peer handle for the session */
- tp_handle_ref (contact_repo, peer);
-
- g_object_get (priv->conn,
- "local-ip-address", &local_ip_address,
- NULL);
-
- session = g_object_new (RAKIA_TYPE_MEDIA_SESSION,
- "dbus-daemon",
- tp_base_connection_get_dbus_daemon (conn),
- "media-channel", channel,
- "object-path", object_path,
- "nua-handle", nh,
- "peer", peer,
- "local-ip-address", local_ip_address,
- NULL);
-
- g_free (local_ip_address);
-
- g_signal_connect_object (session,
- "state-changed",
- G_CALLBACK(priv_session_state_changed_cb),
- channel,
- 0);
- g_signal_connect_object (session,
- "dtmf-ready",
- G_CALLBACK (priv_session_dtmf_ready_cb),
- channel,
- 0);
-
- priv->session = session;
-
- tp_svc_channel_interface_media_signalling_emit_new_session_handler (
- (TpSvcChannelInterfaceMediaSignalling *)channel, object_path, "rtp");
-
- g_free (object_path);
-
- DEBUG ("exit");
-}
-
-static void
-priv_destroy_session(RakiaMediaChannel *channel)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel);
- RakiaMediaSession *session;
- TpBaseConnection *conn;
- TpHandleRepoIface *contact_repo;
-
- session = priv->session;
- if (session == NULL)
- return;
-
- DEBUG("enter");
-
- /* Release the peer handle */
- conn = (TpBaseConnection *)(priv->conn);
- contact_repo = tp_base_connection_get_handles (conn,
- TP_HANDLE_TYPE_CONTACT);
- tp_handle_unref (contact_repo, rakia_media_session_get_peer (session));
-
- priv->session = NULL;
- g_object_unref (session);
-
- DEBUG("exit");
-}
-
-/*
- * Creates an outbound call session if a session does not exist
- */
-static void
-priv_outbound_call (RakiaMediaChannel *channel,
- TpHandle peer)
-{
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel);
- nua_handle_t *nh;
-
- if (priv->session == NULL)
- {
- DEBUG("making outbound call - setting peer handle to %u", peer);
-
- nh = rakia_base_connection_create_handle (priv->conn, peer);
- priv_create_session (channel, nh, peer);
-
- /* Bind the channel object to the handle to handle NUA events */
- rakia_media_channel_attach_to_nua_handle (channel, nh);
-
- nua_handle_unref (nh);
- }
- else
- DEBUG("session already exists");
-
- g_assert (priv->session != NULL);
-}
-
-gboolean
-_rakia_media_channel_add_member (GObject *iface,
- TpHandle handle,
- const gchar *message,
- GError **error)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpGroupMixin *mixin = TP_GROUP_MIXIN (iface);
-
- DEBUG("mixin->self_handle=%d, handle=%d", mixin->self_handle, handle);
-
- if (priv->initiator == mixin->self_handle)
- {
- TpIntSet *remote_pending;
-
- /* case a: an old-school outbound call
- * (we are the initiator, a new handle added with AddMembers) */
-
- priv_outbound_call (self, handle);
-
- /* Backwards compatible behavior:
- * add the peer to remote pending without waiting for the actual request
- * to be sent */
- remote_pending = tp_intset_new_containing (handle);
- tp_group_mixin_change_members (iface,
- "",
- NULL, /* add */
- NULL, /* remove */
- NULL, /* local pending */
- remote_pending, /* remote pending */
- mixin->self_handle, /* actor */
- TP_CHANNEL_GROUP_CHANGE_REASON_INVITED);
- tp_intset_destroy (remote_pending);
-
- /* update flags: no more adding.
- * Removal and rescinding are still allowed. */
- tp_group_mixin_change_flags (iface, 0,
- TP_CHANNEL_GROUP_FLAG_CAN_ADD);
-
- return TRUE;
- }
- if (priv->session &&
- handle == mixin->self_handle &&
- tp_handle_set_is_member (mixin->local_pending, handle))
- {
- /* case b: an incoming invite */
- DEBUG("accepting an incoming invite");
- g_return_val_if_fail (priv->session != NULL, FALSE);
-
- rakia_media_session_accept (priv->session);
-
- return TRUE;
- }
-
- MESSAGE ("unsupported member change requested for a media channel");
-
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "handle %u cannot be added in the current state", handle);
- return FALSE;
-}
-
-static gint
-rakia_status_from_tp_reason (TpChannelGroupChangeReason reason)
-{
- switch (reason)
- {
- case TP_CHANNEL_GROUP_CHANGE_REASON_NONE:
- return 603; /* Decline */
- case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER:
- case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
- return 480; /* Temporarily Unavailable */
- case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY:
- return 486; /* Busy Here */
- case TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED:
- case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
- return 403; /* Forbidden */
- case TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT:
- return 404; /* Not Found */
- default:
- return 500; /* Server Internal Error */
- }
-}
-
-static gboolean
-rakia_media_channel_remove_with_reason (GObject *obj,
- TpHandle handle,
- const gchar *message,
- guint reason,
- GError **error)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (obj);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpGroupMixin *mixin = TP_GROUP_MIXIN (obj);
- TpIntSet *set = NULL;
- TpHandle self_handle;
- gboolean rejected;
-
- self_handle = mixin->self_handle;
-
- if (priv->initiator != self_handle && handle != self_handle)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED,
- "handle %u cannot be removed because you are not the initiator of the"
- " channel", handle);
-
- return FALSE;
- }
-
- if (priv->session == NULL)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "handle %u cannot be removed in the current state", handle);
-
- return FALSE;
- }
-
- rejected = (handle == self_handle
- && tp_handle_set_is_member (mixin->local_pending, handle));
-
- /* We have excluded all the problem cases.
- * Now we always want to remove both members on behalf of the local user */
- set = tp_intset_new ();
- tp_intset_add (set, self_handle);
- tp_intset_add (set, rakia_media_session_get_peer (priv->session));
- tp_group_mixin_change_members (obj, "",
- NULL, /* add */
- set, /* remove */
- NULL,
- NULL,
- self_handle, 0);
- tp_intset_destroy (set);
-
- if (rejected)
- {
- /* The user has rejected the call */
-
- gint status;
-
- status = rakia_status_from_tp_reason (reason);
-
- /* XXX: raise NotAvailable if it's the wrong state? */
- rakia_media_session_respond (priv->session, status, message);
-
- /* This session is effectively ended, prevent the nua_i_state handler
- * from useless work */
- rakia_media_session_change_state (priv->session,
- RAKIA_MEDIA_SESSION_STATE_ENDED);
- }
- else
- {
- /* Want to terminate the call in whatever other situation;
- * rescinding is handled by sending CANCEL */
- rakia_media_session_terminate (priv->session);
- }
-
- return TRUE;
-}
-
-static void
-rakia_media_channel_get_call_states (TpSvcChannelInterfaceCallState *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- tp_svc_channel_interface_call_state_return_from_get_call_states (
- context,
- priv->call_states);
-}
-
-static void
-rakia_media_channel_get_hold_state (TpSvcChannelInterfaceHold *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
- TpLocalHoldState hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
- TpLocalHoldStateReason hold_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
-
- if (priv->session == NULL)
- {
- GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "The media session is not available"};
- dbus_g_method_return_error (context, &e);
- }
-
- g_object_get (priv->session,
- "hold-state", &hold_state,
- "hold-state-reason", &hold_reason,
- NULL);
-
- tp_svc_channel_interface_hold_return_from_get_hold_state (context,
- hold_state,
- hold_reason);
-}
-
-static void
-rakia_media_channel_request_hold (TpSvcChannelInterfaceHold *iface,
- gboolean hold,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv;
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- if (priv->immutable_streams)
- {
- GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "Session modification disabled"};
- dbus_g_method_return_error (context, &e);
- return;
- }
- else if (priv->session != NULL)
- {
- rakia_media_session_request_hold (priv->session, hold);
- }
- else
- {
- GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "The media session is not available"};
- dbus_g_method_return_error (context, &e);
- return;
- }
-
- tp_svc_channel_interface_hold_return_from_request_hold (context);
-}
-
-static void
-dtmf_player_started_tone_cb (TpDTMFPlayer *dtmf_player,
- guint event,
- gpointer user_data)
-{
- RakiaMediaChannel *self = user_data;
- RakiaMediaChannelPrivate *priv = self->priv;
-
- if (priv->session != NULL)
- rakia_media_session_start_telephony_event (priv->session, event);
-}
-
-static void
-dtmf_player_stopped_tone_cb (TpDTMFPlayer *dtmf_player,
- gpointer user_data)
-{
- RakiaMediaChannel *self = user_data;
- RakiaMediaChannelPrivate *priv = self->priv;
-
- if (priv->session != NULL)
- rakia_media_session_stop_telephony_event (priv->session);
-}
-
-static void
-dtmf_player_finished_cb (TpDTMFPlayer *dtmf_player,
- gboolean cancelled,
- gpointer user_data)
-{
- RakiaMediaChannel *self = user_data;
-
- tp_svc_channel_interface_dtmf_emit_stopped_tones (self, cancelled);
-}
-
-static void
-dtmf_player_tones_deferred_cb (TpDTMFPlayer *dtmf_player,
- gchar *tones,
- gpointer user_data)
-{
- RakiaMediaChannel *self = user_data;
- RakiaMediaChannelPrivate *priv = self->priv;
-
- g_free (priv->deferred_tones);
- priv->deferred_tones = g_strdup (tones);
-
- tp_svc_channel_interface_dtmf_emit_tones_deferred (self, tones);
-}
-
-static void
-priv_ensure_dtmf_player (RakiaMediaChannel *self)
-{
- RakiaMediaChannelPrivate *priv = self->priv;
- if (priv->dtmf_player != NULL)
- return;
-
- priv->dtmf_player = tp_dtmf_player_new ();
-
- g_signal_connect (priv->dtmf_player, "started-tone",
- G_CALLBACK (dtmf_player_started_tone_cb), self);
- g_signal_connect (priv->dtmf_player, "stopped-tone",
- G_CALLBACK (dtmf_player_stopped_tone_cb), self);
- g_signal_connect (priv->dtmf_player, "finished",
- G_CALLBACK (dtmf_player_finished_cb), self);
- g_signal_connect (priv->dtmf_player, "tones-deferred",
- G_CALLBACK (dtmf_player_tones_deferred_cb), self);
-}
-
-static gboolean
-rakia_media_channel_send_dtmf_tones (RakiaMediaChannel *self,
- const gchar *tones,
- guint tone_duration,
- GError **error)
-{
- RakiaMediaChannelPrivate *priv = self->priv;
-
- /* Perform sanity checks on the session */
- if (priv->session == NULL)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "the media session is not available, has the channel been closed?");
- return FALSE;
- }
- if (!rakia_media_session_has_media (priv->session,
- TP_MEDIA_STREAM_TYPE_AUDIO))
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "no audio streams are available");
- return FALSE;
- }
-
- priv_ensure_dtmf_player (self);
-
- if (!tp_dtmf_player_play (priv->dtmf_player, tones, tone_duration,
- RAKIA_DTMF_GAP_DURATION, RAKIA_DTMF_PAUSE_DURATION, error))
- return FALSE;
-
- g_free (priv->deferred_tones);
- priv->deferred_tones = NULL;
-
- tp_svc_channel_interface_dtmf_emit_sending_tones (self, tones);
-
- return TRUE;
-}
-
-static void
-rakia_media_channel_start_tone (TpSvcChannelInterfaceDTMF *iface,
- guint stream_id,
- guchar event,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- GError *error = NULL;
- gchar tone[2];
-
- DEBUG("enter");
-
- if (event >= NUM_TP_DTMF_EVENTS)
- {
- g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
- "event %u is not a known DTMF event", event);
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- return;
- }
-
- tone[0] = tp_dtmf_event_to_char (event);
- tone[1] = '\0';
-
- if (!rakia_media_channel_send_dtmf_tones (self, tone, G_MAXUINT, &error))
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- return;
- }
-
- tp_svc_channel_interface_dtmf_return_from_start_tone (context);
-}
-
-static void
-rakia_media_channel_stop_tone (TpSvcChannelInterfaceDTMF *iface,
- guint stream_id,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface);
- RakiaMediaChannelPrivate *priv;
-
- DEBUG("enter");
-
- priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self);
-
- if (priv->dtmf_player != NULL)
- tp_dtmf_player_cancel (priv->dtmf_player);
-
- tp_svc_channel_interface_dtmf_return_from_stop_tone (context);
-}
-
-static void
-rakia_media_channel_multiple_tones (TpSvcChannelInterfaceDTMF *iface,
- const gchar *tones,
- DBusGMethodInvocation *context)
-{
- RakiaMediaChannel *self = (RakiaMediaChannel *) iface;
- GError *error = NULL;
-
- if (!rakia_media_channel_send_dtmf_tones (self, tones,
- RAKIA_DTMF_TONE_DURATION, &error))
- {
- dbus_g_method_return_error (context, error);
- g_error_free (error);
- return;
- }
-
- tp_svc_channel_interface_dtmf_return_from_multiple_tones (context);
-}
-
-static void
-priv_session_dtmf_ready_cb (RakiaMediaSession *session,
- RakiaMediaChannel *channel)
-{
- RakiaMediaChannelPrivate *priv = channel->priv;
- if (!tp_str_empty (priv->initial_tones))
- rakia_media_channel_send_dtmf_tones (channel, priv->initial_tones,
- RAKIA_DTMF_TONE_DURATION, NULL);
-}
-
-static void
-event_target_init(gpointer g_iface, gpointer iface_data)
-{
-}
-
-static void
-channel_iface_init(gpointer g_iface, gpointer iface_data)
-{
- TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
-
- tp_svc_channel_implement_close (
- klass, rakia_media_channel_dbus_close);
-#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
- klass, rakia_media_channel_##x)
- IMPLEMENT(get_channel_type);
- IMPLEMENT(get_handle);
- IMPLEMENT(get_interfaces);
-#undef IMPLEMENT
-}
-
-static void
-streamed_media_iface_init(gpointer g_iface, gpointer iface_data)
-{
- TpSvcChannelTypeStreamedMediaClass *klass = (TpSvcChannelTypeStreamedMediaClass *)g_iface;
-
-#define IMPLEMENT(x) tp_svc_channel_type_streamed_media_implement_##x (\
- klass, rakia_media_channel_##x)
- IMPLEMENT(list_streams);
- IMPLEMENT(remove_streams);
- IMPLEMENT(request_stream_direction);
- IMPLEMENT(request_streams);
-#undef IMPLEMENT
-}
-
-static void
-media_signalling_iface_init(gpointer g_iface, gpointer iface_data)
-{
- TpSvcChannelInterfaceMediaSignallingClass *klass = (TpSvcChannelInterfaceMediaSignallingClass *)g_iface;
-
-#define IMPLEMENT(x) tp_svc_channel_interface_media_signalling_implement_##x (\
- klass, rakia_media_channel_##x)
- IMPLEMENT(get_session_handlers);
-#undef IMPLEMENT
-}
-
-static void
-dtmf_iface_init (gpointer g_iface, gpointer iface_data)
-{
- TpSvcChannelInterfaceDTMFClass *klass = (TpSvcChannelInterfaceDTMFClass *)g_iface;
-
-#define IMPLEMENT(x) tp_svc_channel_interface_dtmf_implement_##x (\
- klass, rakia_media_channel_##x)
- IMPLEMENT(start_tone);
- IMPLEMENT(stop_tone);
- IMPLEMENT(multiple_tones);
-#undef IMPLEMENT
-}
-
-static void
-call_state_iface_init (gpointer g_iface,
- gpointer iface_data)
-{
- TpSvcChannelInterfaceCallStateClass *klass = g_iface;
-#define IMPLEMENT(x) tp_svc_channel_interface_call_state_implement_##x (\
- klass, rakia_media_channel_##x)
- IMPLEMENT (get_call_states);
-#undef IMPLEMENT
-}
-
-static void
-hold_iface_init (gpointer g_iface,
- gpointer iface_data)
-{
- TpSvcChannelInterfaceHoldClass *klass = g_iface;
-
-#define IMPLEMENT(x) tp_svc_channel_interface_hold_implement_##x (\
- klass, rakia_media_channel_##x)
- IMPLEMENT (get_hold_state);
- IMPLEMENT (request_hold);
-#undef IMPLEMENT
-}
diff --git a/rakia/media-channel.h b/rakia/media-channel.h
deleted file mode 100644
index cbd42ec..0000000
--- a/rakia/media-channel.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * sip-media-channel.h - Header for RakiaMediaChannel
- * Copyright (C) 2005 Collabora Ltd.
- * Copyright (C) 2005-2009 Nokia Corporation
- *
- * This work is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This work is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this work; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef __RAKIA_MEDIA_CHANNEL_H__
-#define __RAKIA_MEDIA_CHANNEL_H__
-
-#include <glib-object.h>
-#include <sofia-sip/sdp.h>
-#include <telepathy-glib/dbus-properties-mixin.h>
-#include <telepathy-glib/group-mixin.h>
-#include <telepathy-glib/handle.h>
-#include <telepathy-glib/properties-mixin.h>
-
-#include <rakia/sofia-decls.h>
-
-
-G_BEGIN_DECLS
-
-typedef struct _RakiaMediaChannel RakiaMediaChannel;
-typedef struct _RakiaMediaChannelClass RakiaMediaChannelClass;
-typedef struct _RakiaMediaChannelPrivate RakiaMediaChannelPrivate;
-
-struct _RakiaMediaChannelClass {
- GObjectClass parent_class;
- TpGroupMixinClass group_class;
- TpPropertiesMixinClass properties_class;
- TpDBusPropertiesMixinClass dbus_props_class;
-};
-
-struct _RakiaMediaChannel {
- GObject parent;
- TpGroupMixin group;
- TpPropertiesMixin properties;
- RakiaMediaChannelPrivate *priv;
-};
-
-GType rakia_media_channel_get_type(void);
-
-/* TYPE MACROS */
-#define RAKIA_TYPE_MEDIA_CHANNEL \
- (rakia_media_channel_get_type())
-#define RAKIA_MEDIA_CHANNEL(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST((obj), RAKIA_TYPE_MEDIA_CHANNEL, RakiaMediaChannel))
-#define RAKIA_MEDIA_CHANNEL_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), RAKIA_TYPE_MEDIA_CHANNEL, RakiaMediaChannelClass))
-#define RAKIA_IS_MEDIA_CHANNEL(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_MEDIA_CHANNEL))
-#define RAKIA_IS_MEDIA_CHANNEL_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_MEDIA_CHANNEL))
-#define RAKIA_MEDIA_CHANNEL_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), RAKIA_TYPE_MEDIA_CHANNEL, RakiaMediaChannelClass))
-
-/***********************************************************************
- * Additional declarations (not based on generated templates)
- ***********************************************************************/
-
-void rakia_media_channel_close (RakiaMediaChannel *self);
-
-gboolean _rakia_media_channel_add_member (GObject *iface,
- TpHandle handle,
- const gchar *message,
- GError **error);
-
-void rakia_media_channel_create_initial_streams (RakiaMediaChannel *self);
-
-void rakia_media_channel_attach_to_nua_handle (RakiaMediaChannel *self,
- nua_handle_t *nh);
-
-guint
-rakia_media_channel_change_call_state (RakiaMediaChannel *self,
- TpHandle peer,
- guint flags_add,
- guint flags_remove);
-
-G_END_DECLS
-
-#endif /* #ifndef __RAKIA_MEDIA_CHANNEL_H__*/
diff --git a/rakia/media-manager.c b/rakia/media-manager.c
index a630926..db4631e 100644
--- a/rakia/media-manager.c
+++ b/rakia/media-manager.c
@@ -28,12 +28,14 @@
#include <telepathy-glib/dbus.h>
#include <telepathy-glib/interfaces.h>
-#include "rakia/media-channel.h"
#include "rakia/base-connection.h"
+#include "rakia/call-channel.h"
#include "rakia/handles.h"
+#include "rakia/sip-session.h"
#include <sofia-sip/sip_status.h>
+
#define DEBUG_FLAG RAKIA_DEBUG_CONNECTION
#include "rakia/debug.h"
@@ -41,6 +43,10 @@ static void channel_manager_iface_init (gpointer, gpointer);
static void rakia_media_manager_constructed (GObject *object);
static void rakia_media_manager_close_all (RakiaMediaManager *fac);
+
+static RakiaSipSession * new_session (RakiaMediaManager *fac, nua_handle_t *nh,
+ TpHandle handle);
+
G_DEFINE_TYPE_WITH_CODE (RakiaMediaManager, rakia_media_manager,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
@@ -54,12 +60,11 @@ enum
LAST_PROPERTY
};
-typedef struct _RakiaMediaManagerPrivate RakiaMediaManagerPrivate;
struct _RakiaMediaManagerPrivate
{
/* unreferenced (since it owns this manager) */
TpBaseConnection *conn;
- /* array of referenced (RakiaMediaChannel *) */
+ /* array of referenced (RakiaCallChannel *) */
GPtrArray *channels;
/* for unique channel object paths, currently always increments */
guint channel_index;
@@ -73,15 +78,26 @@ struct _RakiaMediaManagerPrivate
gboolean dispose_has_run;
};
-#define RAKIA_MEDIA_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RAKIA_TYPE_MEDIA_MANAGER, RakiaMediaManagerPrivate))
+#define RAKIA_MEDIA_MANAGER_GET_PRIVATE(fac) ((fac)->priv)
+
+static void
+close_channel_and_unref (gpointer data)
+{
+ TpBaseChannel *chan = data;
+ tp_base_channel_close (chan);
+ g_object_unref (chan);
+}
static void
rakia_media_manager_init (RakiaMediaManager *fac)
{
- RakiaMediaManagerPrivate *priv = RAKIA_MEDIA_MANAGER_GET_PRIVATE (fac);
+ RakiaMediaManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (fac,
+ RAKIA_TYPE_MEDIA_MANAGER, RakiaMediaManagerPrivate);
+
+ fac->priv = priv;
priv->conn = NULL;
- priv->channels = g_ptr_array_sized_new (1);
+ priv->channels = g_ptr_array_new_with_free_func (close_channel_and_unref);
priv->channel_index = 0;
priv->dispose_has_run = FALSE;
}
@@ -214,17 +230,10 @@ rakia_media_manager_close_all (RakiaMediaManager *fac)
if (priv->channels != NULL)
{
GPtrArray *channels;
- guint i;
channels = priv->channels;
priv->channels = NULL;
- for (i = 0; i < channels->len; i++)
- {
- RakiaMediaChannel *chan = g_ptr_array_index (channels, i);
- g_object_unref (chan);
- }
-
g_ptr_array_unref (channels);
}
}
@@ -235,7 +244,7 @@ rakia_media_manager_close_all (RakiaMediaManager *fac)
* that #RakiaMediaManager holds to them.
*/
static void
-media_channel_closed_cb (RakiaMediaChannel *chan, gpointer user_data)
+call_channel_closed_cb (RakiaCallChannel *chan, gpointer user_data)
{
RakiaMediaManager *fac = RAKIA_MEDIA_MANAGER (user_data);
RakiaMediaManagerPrivate *priv = RAKIA_MEDIA_MANAGER_GET_PRIVATE (fac);
@@ -246,35 +255,36 @@ media_channel_closed_cb (RakiaMediaChannel *chan, gpointer user_data)
if (priv->channels)
{
g_ptr_array_remove_fast (priv->channels, chan);
- g_object_unref (chan);
}
}
/**
- * new_media_channel
+ * new_call_channel
*
- * Creates a new empty RakiaMediaChannel.
+ * Creates a new empty RakiaCallChannel.
*/
-static RakiaMediaChannel *
-new_media_channel (RakiaMediaManager *fac,
+static RakiaCallChannel *
+new_call_channel (RakiaMediaManager *fac,
TpHandle initiator,
TpHandle maybe_peer,
- GHashTable *request_properties)
+ GHashTable *request_properties,
+ RakiaSipSession *session)
{
RakiaMediaManagerPrivate *priv;
- RakiaMediaChannel *chan = NULL;
+ RakiaCallChannel *chan = NULL;
gchar *object_path;
- const gchar *nat_traversal = "none";
gboolean initial_audio = FALSE;
gboolean initial_video = FALSE;
gboolean immutable_streams = FALSE;
const gchar *dtmf_initial_tones = NULL;
+ const gchar *initial_audio_name = NULL;
+ const gchar *initial_video_name = NULL;
g_assert (initiator != 0);
priv = RAKIA_MEDIA_MANAGER_GET_PRIVATE (fac);
- object_path = g_strdup_printf ("%s/MediaChannel%u", priv->conn->object_path,
+ object_path = g_strdup_printf ("%s/CallChannel%u", priv->conn->object_path,
priv->channel_index++);
DEBUG("channel object path %s", object_path);
@@ -282,68 +292,122 @@ new_media_channel (RakiaMediaManager *fac,
if (request_properties != NULL)
{
initial_audio = tp_asv_get_boolean (request_properties,
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio", NULL);
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL);
initial_video = tp_asv_get_boolean (request_properties,
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo", NULL);
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL);
+ initial_audio_name = tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO_NAME);
+ initial_video_name = tp_asv_get_string (request_properties,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO_NAME);
dtmf_initial_tones = tp_asv_get_string (request_properties,
- TP_IFACE_CHANNEL_INTERFACE_DTMF ".InitialTones");
+ TP_PROP_CHANNEL_INTERFACE_DTMF_INITIAL_TONES);
}
g_object_get (priv->conn,
"immutable-streams", &immutable_streams,
NULL);
- if (priv->stun_server != NULL)
- {
- nat_traversal = "stun";
- }
-
- chan = g_object_new (RAKIA_TYPE_MEDIA_CHANNEL,
+ chan = g_object_new (RAKIA_TYPE_CALL_CHANNEL,
"connection", priv->conn,
"object-path", object_path,
"handle", maybe_peer,
- "initiator", initiator,
+ "initiator-handle", initiator,
"initial-audio", initial_audio,
"initial-video", initial_video,
- "immutable-streams", immutable_streams,
- "nat-traversal", nat_traversal,
+ "initial-audio-name", initial_audio_name,
+ "initial-video-name", initial_video_name,
+ "initial-transport", TP_STREAM_TRANSPORT_TYPE_RAW_UDP,
+ "mutable-contents", !immutable_streams,
"initial-tones", dtmf_initial_tones,
+ "sip-session", session,
+ "stun-server", priv->stun_server ? priv->stun_server :
+ "",
+ "stun-port", priv->stun_port,
+ "requested", (initiator == priv->conn->self_handle),
NULL);
g_free (object_path);
- if (priv->stun_server != NULL)
- {
- g_object_set ((GObject *) chan, "stun-server", priv->stun_server, NULL);
- if (priv->stun_port != 0)
- g_object_set ((GObject *) chan, "stun-port", priv->stun_port, NULL);
- }
- g_signal_connect (chan, "closed", G_CALLBACK (media_channel_closed_cb), fac);
+ g_signal_connect (chan, "closed", G_CALLBACK (call_channel_closed_cb), fac);
g_ptr_array_add (priv->channels, chan);
+ tp_base_channel_register (TP_BASE_CHANNEL (chan));
+
return chan;
}
+
+static RakiaSipSession *
+new_session (RakiaMediaManager *fac,
+ nua_handle_t *nh,
+ TpHandle handle)
+{
+ RakiaMediaManagerPrivate *priv = RAKIA_MEDIA_MANAGER_GET_PRIVATE (fac);
+ RakiaSipSession *session;
+ gboolean outgoing = (nh == NULL);
+ gboolean immutable_streams = FALSE;
+
+ g_object_get (priv->conn,
+ "immutable-streams", &immutable_streams,
+ NULL);
+
+ if (outgoing)
+ {
+ nh = rakia_base_connection_create_handle (
+ RAKIA_BASE_CONNECTION (priv->conn), handle);
+ }
+
+ session = rakia_sip_session_new (nh, RAKIA_BASE_CONNECTION (priv->conn),
+ !outgoing, immutable_streams);
+
+ if (outgoing)
+ {
+ nua_handle_unref (nh);
+ }
+
+ return session;
+}
+
+
+struct InviteData {
+ RakiaMediaManager *fac;
+ TpHandle handle;
+};
+
static void
-incoming_call_cb (RakiaMediaChannel *channel,
- RakiaMediaManager *fac)
+incoming_call_cb (RakiaSipSession *session,
+ struct InviteData *idata)
{
- g_signal_handlers_disconnect_by_func (channel,
- G_CALLBACK (incoming_call_cb), fac);
- tp_channel_manager_emit_new_channel (fac,
+ RakiaCallChannel *channel;
+ RakiaMediaManagerPrivate *priv =
+ RAKIA_MEDIA_MANAGER_GET_PRIVATE (idata->fac);
+
+ g_signal_handlers_disconnect_by_func (session,
+ G_CALLBACK (incoming_call_cb), idata);
+
+ channel = new_call_channel (idata->fac, idata->handle, idata->handle, NULL,
+ session);
+
+ tp_channel_manager_emit_new_channel (idata->fac,
TP_EXPORTABLE_CHANNEL (channel), NULL);
+
+ g_object_unref (session);
+ rakia_handle_unref (priv->conn, idata->handle);
+ g_slice_free (struct InviteData, idata);
}
+
static gboolean
rakia_nua_i_invite_cb (TpBaseConnection *conn,
const RakiaNuaEvent *ev,
tagi_t tags[],
RakiaMediaManager *fac)
{
- RakiaMediaChannel *channel;
TpHandle handle;
+ RakiaSipSession *session;
+ struct InviteData *idata;
/* figure out a handle for the identity */
@@ -362,19 +426,20 @@ rakia_nua_i_invite_cb (TpBaseConnection *conn,
{
DEBUG("cannot handle calls from self");
nua_respond (ev->nua_handle, 501, "Calls from self are not supported", TAG_END());
+ /* FIXME: Possible handle leak.. needs double checking ? */
return TRUE;
}
- channel = new_media_channel (fac, handle, handle, NULL);
-
- rakia_handle_unref (conn, handle);
+ session = new_session (fac, ev->nua_handle, 0);
/* We delay emission of NewChannel(s) until we have the data on
* initial media */
- g_signal_connect (channel, "incoming-call",
- G_CALLBACK (incoming_call_cb), fac);
+ idata = g_slice_new (struct InviteData);
+ idata->fac = fac;
+ idata->handle = handle;
- rakia_media_channel_attach_to_nua_handle (channel, ev->nua_handle);
+ g_signal_connect (session, "incoming-call",
+ G_CALLBACK (incoming_call_cb), idata);
return TRUE;
}
@@ -446,17 +511,20 @@ rakia_media_manager_foreach_channel (TpChannelManager *manager,
}
static const gchar * const media_channel_fixed_properties[] = {
- TP_IFACE_CHANNEL ".ChannelType",
- TP_IFACE_CHANNEL ".TargetHandleType",
+ TP_PROP_CHANNEL_CHANNEL_TYPE,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
NULL
};
static const gchar * const named_channel_allowed_properties[] = {
- TP_IFACE_CHANNEL ".TargetHandle",
- TP_IFACE_CHANNEL ".TargetID",
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio",
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo",
- TP_IFACE_CHANNEL_INTERFACE_DTMF ".InitialTones",
+ TP_PROP_CHANNEL_TARGET_HANDLE,
+ TP_PROP_CHANNEL_TARGET_ID,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO_NAME,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO_NAME,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_TRANSPORT,
+ TP_PROP_CHANNEL_INTERFACE_DTMF_INITIAL_TONES,
NULL
};
@@ -476,15 +544,26 @@ rakia_media_manager_type_foreach_channel_class (GType type,
GValue *value, *handle_type_value;
value = tp_g_value_slice_new (G_TYPE_STRING);
- g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA);
- g_hash_table_insert (table, TP_IFACE_CHANNEL ".ChannelType", value);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_CALL);
+ g_hash_table_insert (table, TP_PROP_CHANNEL_CHANNEL_TYPE, value);
handle_type_value = tp_g_value_slice_new (G_TYPE_UINT);
/* no uint value yet - we'll change it for each channel class */
- g_hash_table_insert (table, TP_IFACE_CHANNEL ".TargetHandleType",
+ g_hash_table_insert (table, TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
handle_type_value);
g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_CONTACT);
+
+ g_hash_table_insert (table, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO,
+ tp_g_value_slice_new_boolean (TRUE));
+
+ func (type, table, named_channel_allowed_properties, user_data);
+
+ g_hash_table_remove (table, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO);
+
+ g_hash_table_insert (table, TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO,
+ tp_g_value_slice_new_boolean (TRUE));
+
func (type, table, named_channel_allowed_properties, user_data);
g_hash_table_unref (table);
@@ -508,16 +587,15 @@ rakia_media_manager_requestotron (TpChannelManager *manager,
TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
TpHandleType handle_type;
TpHandle handle;
- RakiaMediaChannel *channel = NULL;
+ RakiaSipSession *session;
+ RakiaCallChannel *channel = NULL;
GError *error = NULL;
GSList *request_tokens;
- gboolean require_target_handle;
- gboolean add_peer_to_remote_pending;
+ gboolean valid = FALSE;
+ gboolean initial_audio = FALSE;
+ gboolean initial_video = FALSE;
/* Supported modes of operation:
- * - RequestChannel(None, 0):
- * channel is anonymous;
- * caller uses RequestStreams to set the peer and start the call.
* - RequestChannel(Contact, n) where n != 0:
* channel has TargetHandle=n;
* n is in remote pending;
@@ -531,24 +609,20 @@ rakia_media_manager_requestotron (TpChannelManager *manager,
* whatever properties and group membership it has;
* otherwise the same as into CreateChannel
*/
- switch (method)
- {
- case METHOD_REQUEST:
- require_target_handle = FALSE;
- add_peer_to_remote_pending = TRUE;
- break;
- case METHOD_CREATE:
- case METHOD_ENSURE:
- require_target_handle = TRUE;
- add_peer_to_remote_pending = FALSE;
- break;
- default:
- g_assert_not_reached ();
- }
if (tp_strdiff (tp_asv_get_string (request_properties,
- TP_IFACE_CHANNEL ".ChannelType"),
- TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA))
+ TP_PROP_CHANNEL_CHANNEL_TYPE),
+ TP_IFACE_CHANNEL_TYPE_CALL))
+ return FALSE;
+
+ if (tp_asv_get_boolean (request_properties,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, &valid) && valid)
+ initial_audio = TRUE;
+ if (tp_asv_get_boolean (request_properties,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, &valid) && valid)
+ initial_audio = TRUE;
+
+ if (!initial_audio && !initial_video)
return FALSE;
handle_type = tp_asv_get_uint32 (request_properties,
@@ -557,95 +631,56 @@ rakia_media_manager_requestotron (TpChannelManager *manager,
handle = tp_asv_get_uint32 (request_properties,
TP_IFACE_CHANNEL ".TargetHandle", NULL);
- switch (handle_type)
- {
- case TP_HANDLE_TYPE_NONE:
- g_assert (handle == 0);
-
- if (require_target_handle)
- {
- g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
- "A valid Contact handle must be provided when requesting a media "
- "channel");
- goto error;
- }
-
- if (tp_channel_manager_asv_has_unknown_properties (request_properties,
- media_channel_fixed_properties, anon_channel_allowed_properties,
- &error))
- goto error;
+ if (handle_type != TP_HANDLE_TYPE_CONTACT)
+ return FALSE;
- channel = new_media_channel (self, conn->self_handle, 0, NULL);
- break;
+ g_assert (handle != 0);
- case TP_HANDLE_TYPE_CONTACT:
- g_assert (handle != 0);
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ media_channel_fixed_properties, named_channel_allowed_properties,
+ &error))
+ goto error;
- if (tp_channel_manager_asv_has_unknown_properties (request_properties,
- media_channel_fixed_properties, named_channel_allowed_properties,
- &error))
- goto error;
+ /* Calls to self are problematic in terms of StreamedMedia channel
+ * interface and its semantically required Group member changes;
+ * we disable them until a better API is available through
+ * Call channel type */
+ if (handle == conn->self_handle)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
+ "Cannot call self");
+ goto error;
+ }
- /* Calls to self are problematic in terms of StreamedMedia channel
- * interface and its semantically required Group member changes;
- * we disable them until a better API is available through
- * Call channel type */
- if (handle == conn->self_handle)
- {
- g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED,
- "Cannot call self");
- goto error;
- }
+ if (method == METHOD_ENSURE)
+ {
+ guint i;
+ TpHandle peer = 0;
- if (method == METHOD_ENSURE)
+ for (i = 0; i < priv->channels->len; i++)
{
- guint i;
- TpHandle peer = 0;
+ channel = g_ptr_array_index (priv->channels, i);
+ g_object_get (channel, "peer", &peer, NULL);
- for (i = 0; i < priv->channels->len; i++)
+ if (peer == handle)
{
- channel = g_ptr_array_index (priv->channels, i);
- g_object_get (channel, "peer", &peer, NULL);
-
- if (peer == handle)
- {
- tp_channel_manager_emit_request_already_satisfied (self,
- request_token, TP_EXPORTABLE_CHANNEL (channel));
- return TRUE;
- }
+ tp_channel_manager_emit_request_already_satisfied (self,
+ request_token, TP_EXPORTABLE_CHANNEL (channel));
+ return TRUE;
}
}
-
- channel = new_media_channel (self, conn->self_handle, handle,
- request_properties);
-
- if (add_peer_to_remote_pending)
- {
- if (!_rakia_media_channel_add_member ((GObject *) channel, handle,
- "", &error))
- {
- /* FIXME: do we really want to emit Closed in this case?
- * There wasn't a NewChannel/NewChannels emission */
- rakia_media_channel_close (channel);
- goto error;
- }
- }
-
- break;
-
- default:
- return FALSE;
}
- g_assert (channel != NULL);
+ session = new_session (self, NULL, handle);
+ channel = new_call_channel (self, conn->self_handle, handle,
+ request_properties, session);
+ g_object_unref (session);
request_tokens = g_slist_prepend (NULL, request_token);
tp_channel_manager_emit_new_channel (self,
TP_EXPORTABLE_CHANNEL (channel), request_tokens);
g_slist_free (request_tokens);
- rakia_media_channel_create_initial_streams (channel);
-
return TRUE;
error:
diff --git a/rakia/media-manager.h b/rakia/media-manager.h
index ab8338c..f2bcecd 100644
--- a/rakia/media-manager.h
+++ b/rakia/media-manager.h
@@ -27,6 +27,7 @@ G_BEGIN_DECLS
typedef struct _RakiaMediaManager RakiaMediaManager;
typedef struct _RakiaMediaManagerClass RakiaMediaManagerClass;
+typedef struct _RakiaMediaManagerPrivate RakiaMediaManagerPrivate;
struct _RakiaMediaManagerClass {
GObjectClass parent_class;
@@ -34,6 +35,8 @@ struct _RakiaMediaManagerClass {
struct _RakiaMediaManager {
GObject parent;
+
+ RakiaMediaManagerPrivate *priv;
};
GType rakia_media_manager_get_type(void);
diff --git a/rakia/media-session.c b/rakia/media-session.c
deleted file mode 100644
index 8bc1588..0000000
--- a/rakia/media-session.c
+++ /dev/null
@@ -1,2265 +0,0 @@
-/*
- * sip-media-session.c - Source for RakiaMediaSession
- * Copyright (C) 2005 Collabora Ltd.
- * Copyright (C) 2005-2010 Nokia Corporation
- * @author Kai Vehmanen <first.surname@nokia.com>
- * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
- *
- * Based on telepathy-gabble implementation (gabble-media-session).
- * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
- *
- * This work is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This work is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this work; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "config.h"
-
-#include "rakia/media-session.h"
-
-#include <dbus/dbus-glib.h>
-#include <stdlib.h>
-#include <time.h>
-#include <string.h>
-
-#include <sofia-sip/sip_status.h>
-
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/errors.h>
-#include <telepathy-glib/gtypes.h>
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/svc-media-interfaces.h>
-
-#include "config.h"
-
-#include <rakia/base-connection.h>
-
-#include "rakia/media-channel.h"
-#include "rakia/media-stream.h"
-
-#include "signals-marshal.h"
-
-#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
-#include "rakia/debug.h"
-
-/* The timeout for outstanding re-INVITE transactions in seconds.
- * Chosen to match the allowed cancellation timeout for proxies
- * described in RFC 3261 Section 13.3.1.1 */
-#define RAKIA_REINVITE_TIMEOUT 180
-
-static void session_handler_iface_init (gpointer, gpointer);
-
-G_DEFINE_TYPE_WITH_CODE(RakiaMediaSession,
- rakia_media_session,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_SESSION_HANDLER,
- session_handler_iface_init)
- )
-
-/* signal enum */
-enum
-{
- SIG_STATE_CHANGED,
- SIG_DTMF_READY,
- SIG_LAST_SIGNAL
-};
-
-/* properties */
-enum
-{
- PROP_MEDIA_CHANNEL = 1,
- PROP_DBUS_DAEMON,
- PROP_OBJECT_PATH,
- PROP_NUA_OP,
- PROP_PEER,
- PROP_HOLD_STATE,
- PROP_HOLD_STATE_REASON,
- PROP_REMOTE_PTIME,
- PROP_REMOTE_MAX_PTIME,
- PROP_RTCP_ENABLED,
- PROP_LOCAL_IP_ADDRESS,
- PROP_STUN_SERVERS,
- LAST_PROPERTY
-};
-
-static guint signals[SIG_LAST_SIGNAL] = {0};
-
-#ifdef ENABLE_DEBUG
-
-/**
- * Media session states:
- * - created, objects created, local cand/codec query ongoing
- * - invite-sent, an INVITE with local SDP sent, awaiting response
- * - invite-received, a remote INVITE received, response is pending
- * - response-received, a 200 OK received, codec intersection is in progress
- * - active, codecs and candidate pairs have been negotiated (note,
- * SteamEngine might still fail to verify connectivity and report
- * an error)
- * - reinvite-sent, a local re-INVITE sent, response is pending
- * - reinvite-received, a remote re-INVITE received, response is pending
- * - ended, session has ended
- */
-static const char *const session_states[NUM_RAKIA_MEDIA_SESSION_STATES] =
-{
- "created",
- "invite-sent",
- "invite-received",
- "response-received",
- "active",
- "reinvite-sent",
- "reinvite-received",
- "reinvite-pending",
- "ended"
-};
-
-#define SESSION_DEBUG(session, format, ...) \
- rakia_log (DEBUG_FLAG, G_LOG_LEVEL_DEBUG, "session [%-17s]: " format, \
- session_states[(session)->priv->state],##__VA_ARGS__)
-
-#define SESSION_MESSAGE(session, format, ...) \
- rakia_log (DEBUG_FLAG, G_LOG_LEVEL_MESSAGE, "session [%-17s]: " format, \
- session_states[(session)->priv->state],##__VA_ARGS__)
-
-#else /* !ENABLE_DEBUG */
-
-#define SESSION_DEBUG(session, format, ...) G_STMT_START { } G_STMT_END
-#define SESSION_MESSAGE(session, format, ...) G_STMT_START { } G_STMT_END
-
-#endif /* ENABLE_DEBUG */
-
-/* private structure */
-struct _RakiaMediaSessionPrivate
-{
- TpDBusDaemon *dbus_daemon;
- RakiaMediaChannel *channel; /* see gobj. prop. 'media-channel' */
- gchar *object_path; /* see gobj. prop. 'object-path' */
- nua_handle_t *nua_op; /* see gobj. prop. 'nua-handle' */
- TpHandle peer; /* see gobj. prop. 'peer' */
- gchar *local_ip_address; /* see gobj. prop. 'local-ip-address' */
- gchar *remote_ptime; /* see gobj. prop. 'remote-ptime' */
- gchar *remote_max_ptime; /* see gobj. prop. 'remote-max-ptime' */
- gboolean rtcp_enabled; /* see gobj. prop. 'rtcp-enabled' */
- RakiaMediaSessionState state; /* session state */
- TpLocalHoldState hold_state; /* local hold state aggregated from stream directions */
- TpLocalHoldStateReason hold_reason; /* last used hold state change reason */
- nua_saved_event_t saved_event[1]; /* Saved incoming request event */
- gint local_non_ready; /* number of streams with local information update pending */
- guint remote_stream_count; /* number of streams last seen in a remote offer */
- guint glare_timer_id;
- su_home_t *home; /* Sofia memory home for remote SDP session structure */
- su_home_t *backup_home; /* Sofia memory home for previous generation remote SDP session*/
- sdp_session_t *remote_sdp; /* last received remote session */
- sdp_session_t *backup_remote_sdp; /* previous remote session */
- gchar *local_sdp; /* local session as SDP string */
- GPtrArray *streams;
- gboolean remote_initiated; /*< session is remotely intiated */
- gboolean accepted; /*< session has been locally accepted for use */
- gboolean se_ready; /*< connection established with stream-engine */
- gboolean audio_connected; /*< an audio stream has reached connected state */
- gboolean pending_offer; /*< local media have been changed, but a re-INVITE is pending */
- gboolean dispose_has_run;
-};
-
-#define RAKIA_MEDIA_SESSION_GET_PRIVATE(session) ((session)->priv)
-
-static void rakia_media_session_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec);
-static void rakia_media_session_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec);
-
-static RakiaMediaStream *
-rakia_media_session_get_stream (RakiaMediaSession *self,
- guint stream_id,
- GError **error);
-
-static void priv_request_response_step (RakiaMediaSession *session);
-static void priv_session_invite (RakiaMediaSession *session, gboolean reinvite);
-static void priv_local_media_changed (RakiaMediaSession *session);
-static gboolean priv_update_remote_media (RakiaMediaSession *session,
- gboolean authoritative);
-static void priv_save_event (RakiaMediaSession *self);
-static void priv_zap_event (RakiaMediaSession *self);
-
-static void rakia_media_session_init (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- RAKIA_TYPE_MEDIA_SESSION, RakiaMediaSessionPrivate);
-
- self->priv = priv;
-
- priv->state = RAKIA_MEDIA_SESSION_STATE_CREATED;
- priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
- priv->hold_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
- priv->rtcp_enabled = TRUE;
-
- /* allocate any data required by the object here */
- priv->streams = g_ptr_array_new ();
-}
-
-static GObject *
-rakia_media_session_constructor (GType type, guint n_props,
- GObjectConstructParam *props)
-{
- GObject *obj;
- RakiaMediaSessionPrivate *priv;
-
- obj = G_OBJECT_CLASS (rakia_media_session_parent_class)->
- constructor (type, n_props, props);
- priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (RAKIA_MEDIA_SESSION (obj));
-
- g_assert (TP_IS_DBUS_DAEMON (priv->dbus_daemon));
- tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj);
-
- return obj;
-}
-
-static void rakia_media_session_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- RakiaMediaSession *session = RAKIA_MEDIA_SESSION (object);
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- switch (property_id)
- {
- case PROP_DBUS_DAEMON:
- g_value_set_object (value, priv->dbus_daemon);
- break;
- case PROP_MEDIA_CHANNEL:
- g_value_set_object (value, priv->channel);
- break;
- case PROP_OBJECT_PATH:
- g_value_set_string (value, priv->object_path);
- break;
- case PROP_NUA_OP:
- g_value_set_pointer (value, priv->nua_op);
- break;
- case PROP_PEER:
- g_value_set_uint (value, priv->peer);
- break;
- case PROP_HOLD_STATE:
- g_value_set_uint (value, priv->hold_state);
- break;
- case PROP_HOLD_STATE_REASON:
- g_value_set_uint (value, priv->hold_reason);
- break;
- case PROP_REMOTE_PTIME:
- g_value_set_string (value, priv->remote_ptime);
- break;
- case PROP_REMOTE_MAX_PTIME:
- g_value_set_string (value, priv->remote_max_ptime);
- break;
- case PROP_LOCAL_IP_ADDRESS:
- g_value_set_string (value, priv->local_ip_address);
- break;
- case PROP_RTCP_ENABLED:
- g_value_set_boolean (value, priv->rtcp_enabled);
- break;
- case PROP_STUN_SERVERS:
- {
- /* TODO: should be able to get all entries from the DNS lookup(s).
- * At the moment, rawudp ignores all servers except the first one. */
- GPtrArray *servers;
- gchar *stun_server = NULL;
- guint stun_port = RAKIA_DEFAULT_STUN_PORT;
-
- g_return_if_fail (priv->channel != NULL);
-
- g_object_get (priv->channel,
- "stun-server", &stun_server,
- "stun-port", &stun_port,
- NULL);
-
- servers = g_ptr_array_new ();
-
- if (stun_server != NULL)
- {
- GValue addr = { 0 };
- const GType addr_type = TP_STRUCT_TYPE_SOCKET_ADDRESS_IP;
-
- g_value_init (&addr, addr_type);
- g_value_take_boxed (&addr,
- dbus_g_type_specialized_construct (addr_type));
-
- dbus_g_type_struct_set (&addr,
- 0, stun_server,
- 1, (guint16) stun_port,
- G_MAXUINT);
-
- g_ptr_array_add (servers, g_value_get_boxed (&addr));
-
- g_free (stun_server);
- }
-
- g_value_take_boxed (value, servers);
- }
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void rakia_media_session_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- RakiaMediaSession *session = RAKIA_MEDIA_SESSION (object);
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- switch (property_id)
- {
- case PROP_DBUS_DAEMON:
- g_assert (priv->dbus_daemon == NULL); /* construct-only */
- priv->dbus_daemon = g_value_dup_object (value);
- break;
- case PROP_MEDIA_CHANNEL:
- priv->channel = RAKIA_MEDIA_CHANNEL (g_value_get_object (value));
- break;
- case PROP_OBJECT_PATH:
- g_assert (priv->object_path == NULL);
- priv->object_path = g_value_dup_string (value);
- break;
- case PROP_NUA_OP:
- /* you can only set the NUA handle once - migrating a media session
- * between two NUA handles makes no sense */
- g_return_if_fail (priv->nua_op == NULL);
- priv->nua_op = g_value_get_pointer (value);
- nua_handle_ref (priv->nua_op);
- break;
- case PROP_PEER:
- priv->peer = g_value_get_uint (value);
- break;
- case PROP_LOCAL_IP_ADDRESS:
- g_assert (priv->local_ip_address == NULL);
- priv->local_ip_address = g_value_dup_string (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
-
-static void rakia_media_session_dispose (GObject *object);
-static void rakia_media_session_finalize (GObject *object);
-
-static void
-rakia_media_session_class_init (RakiaMediaSessionClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GParamSpec *param_spec;
-
- g_type_class_add_private (klass, sizeof (RakiaMediaSessionPrivate));
-
- object_class->constructor = rakia_media_session_constructor;
-
- object_class->get_property = rakia_media_session_get_property;
- object_class->set_property = rakia_media_session_set_property;
-
- object_class->dispose = rakia_media_session_dispose;
- object_class->finalize = rakia_media_session_finalize;
-
- param_spec = g_param_spec_object ("dbus-daemon", "TpDBusDaemon",
- "Connection to D-Bus.", TP_TYPE_DBUS_DAEMON,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec);
-
- param_spec = g_param_spec_object ("media-channel", "RakiaMediaChannel object",
- "SIP media channel object that owns this media session object"
- " (not reference counted).",
- RAKIA_TYPE_MEDIA_CHANNEL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_MEDIA_CHANNEL, param_spec);
-
- param_spec = g_param_spec_string ("object-path", "D-Bus object path",
- "The D-Bus object path used for this object on the bus.",
- NULL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
-
- param_spec = g_param_spec_pointer ("nua-handle", "NUA handle",
- "NUA stack operation handle associated with this media session.",
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_NUA_OP, param_spec);
-
- param_spec = g_param_spec_uint ("peer", "Session peer",
- "The TpHandle representing the contact with whom this session communicates.",
- 0, G_MAXUINT32,
- 0,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_PEER, param_spec);
-
- param_spec = g_param_spec_uint ("hold-state", "Local hold state",
- "The current Local_Hold_State value as reported by the Hold interface",
- TP_LOCAL_HOLD_STATE_UNHELD, TP_LOCAL_HOLD_STATE_PENDING_UNHOLD,
- TP_LOCAL_HOLD_STATE_UNHELD,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_HOLD_STATE, param_spec);
-
- param_spec = g_param_spec_uint ("hold-state-reason",
- "Local hold state change reason",
- "The last Local_Hold_State_Reason value as reported by the Hold interface",
- TP_LOCAL_HOLD_STATE_REASON_NONE,
- TP_LOCAL_HOLD_STATE_REASON_RESOURCE_NOT_AVAILABLE,
- TP_LOCAL_HOLD_STATE_REASON_NONE,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_HOLD_STATE_REASON, param_spec);
-
- param_spec = g_param_spec_string ("remote-ptime",
- "a=ptime value of remote media session",
- "Value of the a=ptime attribute of the remote media session, or NULL",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_REMOTE_PTIME, param_spec);
-
- param_spec = g_param_spec_string ("remote-max-ptime",
- "a=maxptime value of remote media session",
- "Value of the a=maxptime attribute of the remote media session, or NULL",
- NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_REMOTE_MAX_PTIME, param_spec);
-
- param_spec = g_param_spec_string ("local-ip-address", "Local IP address",
- "The local IP address preferred for media streams",
- NULL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_LOCAL_IP_ADDRESS, param_spec);
-
- param_spec = g_param_spec_boolean ("rtcp-enabled", "RTCP enabled",
- "Is RTCP enabled session-wide",
- TRUE,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_RTCP_ENABLED, param_spec);
-
- param_spec = g_param_spec_boxed ("stun-servers", "STUN servers",
- "Array of IP address-port pairs for available STUN servers",
- TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_STUN_SERVERS, param_spec);
-
- signals[SIG_STATE_CHANGED] =
- g_signal_new ("state-changed",
- G_OBJECT_CLASS_TYPE (klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- _rakia_marshal_VOID__UINT_UINT,
- G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-
- signals[SIG_DTMF_READY] =
- g_signal_new ("dtmf-ready",
- G_OBJECT_CLASS_TYPE (klass),
- G_SIGNAL_RUN_LAST,
- 0, NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-}
-
-static void
-rakia_media_session_dispose (GObject *object)
-{
- RakiaMediaSession *self = RAKIA_MEDIA_SESSION (object);
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- if (priv->dispose_has_run)
- return;
-
- DEBUG("enter");
-
- priv->dispose_has_run = TRUE;
-
- if (priv->glare_timer_id)
- g_source_remove (priv->glare_timer_id);
-
- tp_clear_object (&priv->dbus_daemon);
-
- if (G_OBJECT_CLASS (rakia_media_session_parent_class)->dispose)
- G_OBJECT_CLASS (rakia_media_session_parent_class)->dispose (object);
-
- DEBUG("exit");
-}
-
-static void
-rakia_media_session_finalize (GObject *object)
-{
- RakiaMediaSession *self = RAKIA_MEDIA_SESSION (object);
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- guint i;
-
- /* terminating the session should have discarded the NUA handle */
- g_assert (priv->nua_op == NULL);
-
- /* free any data held directly by the object here */
-
- for (i = 0; i < priv->streams->len; i++) {
- RakiaMediaStream *stream = g_ptr_array_index (priv->streams, i);
- if (stream != NULL)
- {
- WARNING ("stream %u (%p) left over, reaping", i, stream);
- g_object_unref (stream);
- }
- }
- g_ptr_array_free(priv->streams, TRUE);
-
- priv_zap_event (self);
-
- if (priv->home != NULL)
- su_home_unref (priv->home);
- if (priv->backup_home != NULL)
- su_home_unref (priv->backup_home);
-
- g_free (priv->local_sdp);
- g_free (priv->remote_ptime);
- g_free (priv->remote_max_ptime);
- g_free (priv->local_ip_address);
- g_free (priv->object_path);
-
- G_OBJECT_CLASS (rakia_media_session_parent_class)->finalize (object);
-
- DEBUG("exit");
-}
-
-
-
-/**
- * rakia_media_session_error
- *
- * Implements DBus method Error
- * on interface org.freedesktop.Telepathy.Media.SessionHandler
- */
-static void
-rakia_media_session_error (TpSvcMediaSessionHandler *iface,
- guint errno,
- const gchar *message,
- DBusGMethodInvocation *context)
-{
- RakiaMediaSession *obj = RAKIA_MEDIA_SESSION (iface);
-
- SESSION_DEBUG (obj, "Media.SessionHandler::Error called (%s), terminating session", message);
-
- rakia_media_session_terminate (obj);
-
- tp_svc_media_session_handler_return_from_error (context);
-}
-
-static void priv_emit_new_stream (RakiaMediaSession *self,
- RakiaMediaStream *stream)
-{
- gchar *object_path;
- guint id;
- guint media_type;
- guint direction;
-
- g_object_get (stream,
- "object-path", &object_path,
- "id", &id,
- "media-type", &media_type,
- "direction", &direction,
- NULL);
-
- /* note: all of the streams are bidirectional from farsight's point of view, it's
- * just in the signalling they change */
-
- tp_svc_media_session_handler_emit_new_stream_handler (
- (TpSvcMediaSessionHandler *)self, object_path, id, media_type,
- direction);
-
- g_free (object_path);
-}
-
-
-/**
- * rakia_media_session_ready
- *
- * Implements DBus method Ready
- * on interface org.freedesktop.Telepathy.Media.SessionHandler
- */
-static void
-rakia_media_session_ready (TpSvcMediaSessionHandler *iface,
- DBusGMethodInvocation *context)
-{
- RakiaMediaSession *self = RAKIA_MEDIA_SESSION (iface);
- RakiaMediaSessionPrivate *priv = self->priv;
- guint i;
-
- SESSION_DEBUG (self, "Media.SessionHandler.Ready called");
-
- if (!priv->se_ready)
- {
- priv->se_ready = TRUE;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- RakiaMediaStream *stream = g_ptr_array_index (priv->streams, i);
- if (stream)
- priv_emit_new_stream (self, stream);
- }
- }
-
- tp_svc_media_session_handler_return_from_ready (context);
-}
-
-/***********************************************************************
- * Helper functions follow (not based on generated templates)
- ***********************************************************************/
-
-TpHandle
-rakia_media_session_get_peer (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- return priv->peer;
-}
-
-RakiaMediaSessionState
-rakia_media_session_get_state (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- return priv->state;
-}
-
-static gboolean
-rakia_media_session_supports_media_type (guint media_type)
-{
- switch (media_type)
- {
- case TP_MEDIA_STREAM_TYPE_AUDIO:
- case TP_MEDIA_STREAM_TYPE_VIDEO:
- return TRUE;
- }
- return FALSE;
-}
-
-static void
-priv_close_all_streams (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
- for (i = 0; i < priv->streams->len; i++)
- {
- RakiaMediaStream *stream;
- stream = g_ptr_array_index (priv->streams, i);
- if (stream != NULL)
- rakia_media_stream_close (stream);
- g_assert (g_ptr_array_index (priv->streams, i) == NULL);
- }
-}
-
-static void
-priv_apply_streams_pending_direction (RakiaMediaSession *session,
- guint pending_send_mask)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- RakiaMediaStream *stream;
- guint i;
-
- /* If there has been a local change pending a re-INVITE,
- * suspend remote approval until the next transaction */
- if (priv->pending_offer)
- pending_send_mask &= ~(guint)TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
-
- /* Apply the pending direction changes */
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL)
- rakia_media_stream_apply_pending_direction (stream, pending_send_mask);
- }
-}
-
-void
-rakia_media_session_change_state (RakiaMediaSession *session,
- RakiaMediaSessionState new_state)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- guint old_state;
-
- if (priv->state == new_state)
- return;
-
- SESSION_DEBUG (session, "changing state to %s", session_states[new_state]);
-
- old_state = priv->state;
- priv->state = new_state;
-
- switch (new_state)
- {
- case RAKIA_MEDIA_SESSION_STATE_CREATED:
- case RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
- case RAKIA_MEDIA_SESSION_STATE_INVITE_SENT:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT:
- case RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING:
- break;
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- /* Apply any pending remote send after outgoing INVITEs.
- * We don't want automatic removal of pending local send after
- * responding to incoming re-INVITEs, however */
- priv_apply_streams_pending_direction (session,
- TP_MEDIA_STREAM_PENDING_REMOTE_SEND);
- break;
- case RAKIA_MEDIA_SESSION_STATE_ENDED:
- priv_close_all_streams (session);
- SESSION_DEBUG (session, "destroying the NUA handle %p", priv->nua_op);
- if (priv->nua_op != NULL)
- {
- nua_handle_destroy (priv->nua_op);
- priv->nua_op = NULL;
- }
- break;
- case NUM_RAKIA_MEDIA_SESSION_STATES:
- g_assert_not_reached();
-
- /* Don't add default because we want to be warned by the compiler
- * about unhandled states */
- }
-
- g_signal_emit (session, signals[SIG_STATE_CHANGED], 0, old_state, new_state);
-
- if (new_state == RAKIA_MEDIA_SESSION_STATE_ACTIVE && priv->pending_offer)
- priv_session_invite (session, TRUE);
-}
-
-void rakia_media_session_terminate (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- DEBUG ("enter");
-
- if (priv->state == RAKIA_MEDIA_SESSION_STATE_ENDED)
- return;
-
- /* XXX: taken care of by the state change? */
- priv_close_all_streams (session);
-
- if (priv->nua_op != NULL)
- {
- /* XXX: should the stack do pretty much the same
- * (except freeing the saved event) upon nua_handle_destroy()? */
- switch (priv->state)
- {
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- case RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING:
- SESSION_DEBUG (session, "sending BYE");
- nua_bye (priv->nua_op, TAG_END());
- break;
- case RAKIA_MEDIA_SESSION_STATE_INVITE_SENT:
- SESSION_DEBUG (session, "sending CANCEL");
- nua_cancel (priv->nua_op, TAG_END());
- break;
- case RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED:
- SESSION_DEBUG (session, "sending the 480 response to an incoming INVITE");
- nua_respond (priv->nua_op, 480, "Terminated", TAG_END());
- break;
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
- if (priv->saved_event[0])
- {
- SESSION_DEBUG (session, "sending the 480 response to an incoming re-INVITE");
- nua_respond (priv->nua_op, 480, "Terminated",
- NUTAG_WITH(nua_saved_event_request (priv->saved_event)),
- TAG_END());
- nua_destroy_event (priv->saved_event);
- }
- SESSION_DEBUG (session, "sending BYE to terminate the call itself");
- nua_bye (priv->nua_op, TAG_END());
- break;
- default:
- /* let the Sofia stack decide what do to */;
- }
- }
-
- rakia_media_session_change_state (session, RAKIA_MEDIA_SESSION_STATE_ENDED);
-}
-
-gboolean
-rakia_media_session_set_remote_media (RakiaMediaSession *session,
- const sdp_session_t* sdp)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- gboolean authoritative;
-
- DEBUG ("enter");
-
- if (priv->state == RAKIA_MEDIA_SESSION_STATE_INVITE_SENT
- || priv->state == RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT)
- {
- rakia_media_session_change_state (
- session,
- RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED);
- }
- else
- {
- /* Remember the m= line count in the remote offer,
- * to match it with exactly this number of answer lines */
- sdp_media_t *media;
- guint count = 0;
-
- for (media = sdp->sdp_media; media != NULL; media = media->m_next)
- ++count;
-
- priv->remote_stream_count = count;
- }
-
- /* Shortcut session non-updates */
- if (!sdp_session_cmp (priv->remote_sdp, sdp))
- goto finally;
-
- /* Delete a backup session structure, if any */
- if (priv->backup_remote_sdp != NULL)
- {
- priv->backup_remote_sdp = NULL;
- g_assert (priv->backup_home != NULL);
- su_home_unref (priv->backup_home);
- priv->backup_home = NULL;
- }
- /* Back up the old session.
- * The streams still need the old media descriptions */
- if (priv->remote_sdp != NULL)
- {
- g_assert (priv->home != NULL);
- g_assert (priv->backup_home == NULL);
- priv->backup_home = priv->home;
- priv->backup_remote_sdp = priv->remote_sdp;
- }
-
- /* Store the session description structure */
- priv->home = su_home_create ();
- priv->remote_sdp = sdp_session_dup (priv->home, sdp);
- g_return_val_if_fail (priv->remote_sdp != NULL, FALSE);
-
- authoritative = (priv->state == RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED
- || priv->state == RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED);
- if (!priv_update_remote_media (session, authoritative))
- return FALSE;
-
-finally:
- /* Make sure to always transition states and send out the response,
- * even if no stream-engine roundtrips were initiated */
- priv_request_response_step (session);
- return TRUE;
-}
-
-void
-priv_add_stream_list_entry (GPtrArray *list,
- RakiaMediaStream *stream,
- RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- GValue entry = { 0 };
- GType stream_type;
- guint id;
- TpMediaStreamType type = TP_MEDIA_STREAM_TYPE_AUDIO;
- TpMediaStreamState connection_state = TP_MEDIA_STREAM_STATE_CONNECTED;
- TpMediaStreamDirection direction = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
- guint pending_send_flags = 0;
-
- g_assert(stream != NULL);
-
- g_object_get (stream,
- "id", &id,
- "media-type", &type,
- "state", &connection_state,
- "direction", &direction,
- "pending-send-flags", &pending_send_flags,
- NULL);
-
- stream_type = TP_STRUCT_TYPE_MEDIA_STREAM_INFO;
-
- g_value_init (&entry, stream_type);
- g_value_take_boxed (&entry,
- dbus_g_type_specialized_construct (stream_type));
-
- dbus_g_type_struct_set (&entry,
- 0, id,
- 1, priv->peer,
- 2, type,
- 3, connection_state,
- 4, direction,
- 5, pending_send_flags,
- G_MAXUINT);
-
- g_ptr_array_add (list, g_value_get_boxed (&entry));
-}
-
-gboolean rakia_media_session_request_streams (RakiaMediaSession *session,
- const GArray *media_types,
- GPtrArray *ret,
- GError **error)
-{
- guint i;
-
- DEBUG ("enter");
-
- /* Validate the media types before creating any streams */
- for (i = 0; i < media_types->len; i++) {
- guint media_type = g_array_index (media_types, guint, i);
- if (!rakia_media_session_supports_media_type (media_type))
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "media type #%u is not supported", i);
- return FALSE;
- }
- }
-
- for (i = 0; i < media_types->len; i++) {
- guint media_type = g_array_index (media_types, guint, i);
- RakiaMediaStream *stream;
-
- stream = rakia_media_session_add_stream (session,
- media_type,
- TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
- TRUE);
-
- if (stream == NULL)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "creation of stream %u failed", i);
- /* XXX: should we close the streams already created as part of
- * this request, despite having emitted signals about them? */
- return FALSE;
- }
-
- priv_add_stream_list_entry (ret, stream, session);
- }
-
- priv_local_media_changed (session);
-
- return TRUE;
-}
-
-gboolean
-rakia_media_session_remove_streams (RakiaMediaSession *self,
- const GArray *stream_ids,
- GError **error)
-{
- RakiaMediaStream *stream;
- guint stream_id;
- guint i;
-
- DEBUG ("enter");
-
- for (i = 0; i < stream_ids->len; i++)
- {
- stream_id = g_array_index (stream_ids, guint, i);
- stream = rakia_media_session_get_stream (self, stream_id, error);
- if (stream == NULL)
- return FALSE;
- rakia_media_stream_close (stream);
- }
-
- priv_local_media_changed (self);
-
- return TRUE;
-}
-
-void rakia_media_session_list_streams (RakiaMediaSession *session,
- GPtrArray *ret)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- RakiaMediaStream *stream;
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream)
- priv_add_stream_list_entry (ret, stream, session);
- }
-}
-
-gboolean
-rakia_media_session_request_stream_direction (RakiaMediaSession *self,
- guint stream_id,
- guint direction,
- GError **error)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream;
-
- stream = rakia_media_session_get_stream (self, stream_id, error);
- if (stream == NULL)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
- "stream %u does not exist", stream_id);
- return FALSE;
- }
-
- SESSION_DEBUG (self, "direction %u requested for stream %u",
- direction, stream_id);
-
- if (priv->state == RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED
- || priv->state == RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED)
- {
- /* While processing a session offer, we can only mask out direction
- * requested by the remote peer */
- direction &= rakia_media_stream_get_requested_direction (stream);
- }
-
- rakia_media_stream_set_direction (stream,
- direction,
- TP_MEDIA_STREAM_PENDING_REMOTE_SEND);
-
- return TRUE;
-}
-
-static void
-priv_save_event (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaBaseConnection *conn = NULL;
-
- priv_zap_event (self);
-
- g_object_get (priv->channel, "connection", &conn, NULL);
-
- g_return_if_fail (conn != NULL);
-
- rakia_base_connection_save_event (conn, priv->saved_event);
-
- g_object_unref (conn);
-
-#ifdef ENABLE_DEBUG
- {
- nua_event_data_t const *ev_data = nua_event_data (priv->saved_event);
- g_assert (ev_data != NULL);
- DEBUG("saved the last event: %s %hd %s", nua_event_name (ev_data->e_event), ev_data->e_status, ev_data->e_phrase);
- }
-#endif
-}
-
-static void
-priv_zap_event (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- if (priv->saved_event[0])
- {
- nua_event_data_t const *ev_data = nua_event_data (priv->saved_event);
- g_assert (ev_data != NULL);
- WARNING ("zapping unhandled saved event '%s'", nua_event_name (ev_data->e_event));
- nua_destroy_event (priv->saved_event);
- }
-}
-
-void
-rakia_media_session_receive_invite (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- g_return_if_fail (priv->state == RAKIA_MEDIA_SESSION_STATE_CREATED);
- g_return_if_fail (priv->nua_op != NULL);
-
- priv->remote_initiated = TRUE;
-
- nua_respond (priv->nua_op, SIP_180_RINGING, TAG_END());
-
- rakia_media_session_change_state (self, RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED);
-}
-
-void
-rakia_media_session_receive_reinvite (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- /* Check for permitted state transitions */
- switch (priv->state)
- {
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- case RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
- break;
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING:
- g_source_remove (priv->glare_timer_id);
- break;
- default:
- g_return_if_reached ();
- }
-
- priv_save_event (self);
-
- rakia_media_session_change_state (self, RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED);
-}
-
-void
-rakia_media_session_accept (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- if (priv->accepted)
- return;
-
- SESSION_DEBUG (self, "accepting the session");
-
- priv->accepted = TRUE;
-
- /* Apply the pending send flags */
- priv_apply_streams_pending_direction (self,
- TP_MEDIA_STREAM_PENDING_LOCAL_SEND |
- TP_MEDIA_STREAM_PENDING_REMOTE_SEND);
-
- /* Will change session state to active when streams are ready */
- priv_request_response_step (self);
-
- /* Can play the DTMF dialstring if an audio stream is connected */
- if (priv->audio_connected)
- g_signal_emit (self, signals[SIG_DTMF_READY], 0);
-}
-
-void
-rakia_media_session_respond (RakiaMediaSession *self,
- gint status,
- const char *message)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- SESSION_DEBUG (self, "responding: %03d %s", status, message ? message : "");
-
- if (message != NULL && !message[0])
- message = NULL;
-
- if (priv->nua_op)
- nua_respond (priv->nua_op, status, message, TAG_END());
-}
-
-gboolean rakia_media_session_is_accepted (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- return priv->accepted;
-}
-
-static gboolean
-priv_glare_retry (gpointer session)
-{
- RakiaMediaSession *self = session;
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
-
- SESSION_DEBUG (self, "glare resolution interval is over");
-
- if (priv->state == RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING)
- priv_session_invite (self, TRUE);
-
- /* Reap the timer */
- priv->glare_timer_id = 0;
- return FALSE;
-}
-
-void
-rakia_media_session_resolve_glare (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- guint interval;
-
- if (priv->state != RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT)
- {
- SESSION_DEBUG (self, "glare resolution triggered in unexpected state");
- return;
- }
-
- /*
- * Set the grace interval accordinlgly to RFC 3261 section 14.1:
- *
- * 1. If the UAC is the owner of the Call-ID of the dialog ID
- * (meaning it generated the value), T has a randomly chosen value
- * between 2.1 and 4 seconds in units of 10 ms.
- * 2. If the UAC is not the owner of the Call-ID of the dialog ID, T
- * has a randomly chosen value of between 0 and 2 seconds in units
- * of 10 ms.
- */
- if (priv->pending_offer)
- interval = 0; /* cut short, we have new things to negotiate */
- else if (priv->remote_initiated)
- interval = g_random_int_range (0, 200) * 10;
- else
- interval = g_random_int_range (210, 400) * 10;
-
- if (priv->glare_timer_id != 0)
- g_source_remove (priv->glare_timer_id);
-
- priv->glare_timer_id = g_timeout_add (interval, priv_glare_retry, self);
-
- SESSION_DEBUG (self, "glare resolution interval %u msec", interval);
-
- rakia_media_session_change_state (
- self, RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING);
-}
-
-static RakiaMediaStream *
-rakia_media_session_get_stream (RakiaMediaSession *self,
- guint stream_id,
- GError **error)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream;
-
- g_assert (priv->streams != NULL);
-
- if (stream_id >= priv->streams->len)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
- "stream ID %u is invalid", stream_id);
- return NULL;
- }
-
- stream = g_ptr_array_index (priv->streams, stream_id);
-
- if (stream == NULL)
- {
- g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
- "stream %u does not exist", stream_id);
- return NULL;
- }
-
- return stream;
-}
-
-TpLocalHoldState
-rakia_media_session_get_hold_state (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- return priv->hold_state;
-}
-
-static gboolean
-rakia_media_session_is_local_hold_ongoing (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- return (priv->hold_state == TP_LOCAL_HOLD_STATE_HELD
- || priv->hold_state == TP_LOCAL_HOLD_STATE_PENDING_HOLD);
-}
-
-static void
-priv_initiate_hold (RakiaMediaSession *self,
- gboolean hold,
- TpLocalHoldStateReason reason)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- gboolean stream_hold_requested = FALSE;
- RakiaMediaStream *stream;
- guint i;
-
- DEBUG("enter");
-
- if (hold)
- {
- if (priv->hold_state == TP_LOCAL_HOLD_STATE_HELD
- || priv->hold_state == TP_LOCAL_HOLD_STATE_PENDING_HOLD)
- {
- MESSAGE ("redundant hold request");
- return;
- }
- }
- else
- {
- if (priv->hold_state == TP_LOCAL_HOLD_STATE_UNHELD
- || priv->hold_state == TP_LOCAL_HOLD_STATE_PENDING_UNHOLD)
- {
- MESSAGE ("redundant unhold request");
- return;
- }
- }
-
- /* Emit the hold notification for every stream that needs it */
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL
- && rakia_media_stream_request_hold_state (stream, hold))
- stream_hold_requested = TRUE;
- }
-
- if (stream_hold_requested)
- {
- priv->hold_state = hold? TP_LOCAL_HOLD_STATE_PENDING_HOLD
- : TP_LOCAL_HOLD_STATE_PENDING_UNHOLD;
- }
- else
- {
- /* There were no streams to flip, short cut to the final state */
- priv->hold_state = hold? TP_LOCAL_HOLD_STATE_HELD
- : TP_LOCAL_HOLD_STATE_UNHELD;
- }
- priv->hold_reason = reason;
-
- tp_svc_channel_interface_hold_emit_hold_state_changed (priv->channel,
- priv->hold_state, reason);
-}
-
-static void
-priv_finalize_hold (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream;
- TpLocalHoldState final_hold_state;
- guint hold_mask;
- guint unhold_mask;
- guint i;
- gboolean held = FALSE;
-
- DEBUG("enter");
-
- switch (priv->hold_state)
- {
- case TP_LOCAL_HOLD_STATE_PENDING_HOLD:
- held = TRUE;
- break;
- case TP_LOCAL_HOLD_STATE_PENDING_UNHOLD:
- held = FALSE;
- break;
- default:
- /* Streams changed state without request, signal this to the client.
- * All streams should have the same hold state at this point,
- * so just query one of them for the current hold state */
- stream = NULL;
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL)
- break;
- }
- g_return_if_fail (stream != NULL);
-
- g_object_get (stream, "hold-state", &held, NULL);
- }
-
- if (held)
- {
- final_hold_state = TP_LOCAL_HOLD_STATE_HELD;
- hold_mask = TP_MEDIA_STREAM_DIRECTION_SEND;
- unhold_mask = 0;
- }
- else
- {
- final_hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
- hold_mask = TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
- unhold_mask = TP_MEDIA_STREAM_DIRECTION_RECEIVE;
- }
-
- priv->hold_state = final_hold_state;
- tp_svc_channel_interface_hold_emit_hold_state_changed (priv->channel,
- final_hold_state, priv->hold_reason);
-
- /* Set stream directions accordingly to the achieved hold state */
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL)
- {
- guint direction = rakia_media_stream_get_requested_direction (stream);
- direction &= hold_mask;
- direction |= unhold_mask;
- rakia_media_stream_set_direction (stream,
- direction,
- TP_MEDIA_STREAM_PENDING_REMOTE_SEND
- | TP_MEDIA_STREAM_PENDING_LOCAL_SEND);
- }
- }
-}
-
-void
-rakia_media_session_request_hold (RakiaMediaSession *self,
- gboolean hold)
-{
- priv_initiate_hold (self,
- hold,
- TP_LOCAL_HOLD_STATE_REASON_REQUESTED);
-}
-
-gboolean
-rakia_media_session_has_media (RakiaMediaSession *self,
- TpMediaStreamType type)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream;
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream == NULL)
- continue;
- if (rakia_media_stream_get_media_type (stream) == type)
- return TRUE;
- }
-
- return FALSE;
-}
-
-void
-rakia_media_session_start_telephony_event (RakiaMediaSession *self,
- guchar event)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream;
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream == NULL)
- continue;
- if (rakia_media_stream_get_media_type (stream)
- != TP_MEDIA_STREAM_TYPE_AUDIO)
- continue;
-
- SESSION_DEBUG (self, "starting telephony event %u on stream %u",
- (guint) event, i);
-
- rakia_media_stream_start_telephony_event (stream, event);
- }
-}
-
-void
-rakia_media_session_stop_telephony_event (RakiaMediaSession *self)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream;
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream == NULL)
- continue;
- if (rakia_media_stream_get_media_type (stream)
- != TP_MEDIA_STREAM_TYPE_AUDIO)
- continue;
-
- SESSION_DEBUG (self, "stopping the telephony event on stream %u", i);
-
- rakia_media_stream_stop_telephony_event (stream);
- }
-}
-
-gint
-rakia_media_session_rate_native_transport (RakiaMediaSession *session,
- const GValue *transport)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- gint result = 0;
- gchar *address = NULL;
- guint proto = TP_MEDIA_STREAM_BASE_PROTO_UDP;
-
- dbus_g_type_struct_get (transport,
- 1, &address,
- 3, &proto,
- G_MAXUINT);
-
- g_assert (address != NULL);
-
- if (proto != TP_MEDIA_STREAM_BASE_PROTO_UDP)
- result = -1;
- /* XXX: this will not work properly when IPv6 support comes */
- else if (priv->local_ip_address != NULL
- && strcmp (address, priv->local_ip_address) == 0)
- result = 1;
-
- g_free (address);
-
- return result;
-}
-
-static void
-priv_session_set_streams_playing (RakiaMediaSession *session, gboolean playing)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- RakiaMediaStream *stream;
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL)
- rakia_media_stream_set_playing (stream, playing);
- }
-}
-
-static void
-priv_local_media_changed (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- switch (priv->state)
- {
- case RAKIA_MEDIA_SESSION_STATE_CREATED:
- /* If all streams are ready, send an offer now */
- priv_request_response_step (session);
- break;
- case RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
- /* The changes to existing streams will be included in the
- * eventual answer (FIXME: implement postponed direction changes,
- * which are applied after the remote offer has been processed).
- * Check, however, if there are new streams not present in the
- * remote offer, that will need another offer-answer round */
- if (priv->remote_stream_count < priv->streams->len)
- priv->pending_offer = TRUE;
- break;
- case RAKIA_MEDIA_SESSION_STATE_INVITE_SENT:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT:
- case RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
- /* Cannot send another offer right now */
- priv->pending_offer = TRUE;
- break;
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- /* Check if we are allowed to send re-INVITES */
- {
- gboolean immutable_streams = FALSE;
- g_object_get (priv->channel,
- "immutable-streams", &immutable_streams,
- NULL);
- if (immutable_streams) {
- SESSION_MESSAGE (session, "sending of a local media update disabled by parameter 'immutable-streams'");
- break;
- }
- }
- /* Fall through to the next case */
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING:
- if (priv->local_non_ready == 0)
- priv_session_invite (session, TRUE);
- else
- priv->pending_offer = TRUE;
- break;
- default:
- g_assert_not_reached();
- }
-}
-
-static void
-priv_update_remote_hold (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- RakiaMediaStream *stream;
- gboolean has_streams = FALSE;
- gboolean remote_held = TRUE;
- guint direction;
- guint i;
-
- /* The call is remotely unheld if there's at least one sending stream */
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL)
- {
- direction = rakia_media_stream_get_requested_direction (stream);
-
- if ((direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0)
- remote_held = FALSE;
-
- has_streams = TRUE;
- }
- }
-
- if (!has_streams)
- return;
-
- SESSION_DEBUG (session, "is remotely %s", remote_held? "held" : "unheld");
-
- if (remote_held)
- rakia_media_channel_change_call_state (priv->channel,
- priv->peer,
- TP_CHANNEL_CALL_STATE_HELD,
- 0);
- else
- rakia_media_channel_change_call_state (priv->channel,
- priv->peer,
- 0,
- TP_CHANNEL_CALL_STATE_HELD);
-}
-
-gchar *
-rakia_sdp_get_string_attribute (const sdp_attribute_t *attrs, const char *name)
-{
- sdp_attribute_t *attr;
-
- attr = sdp_attribute_find (attrs, name);
- if (attr == NULL)
- return NULL;
-
- return g_strdup (attr->a_value);
-}
-
-static gboolean
-priv_update_remote_media (RakiaMediaSession *session, gboolean authoritative)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- const sdp_session_t *sdp = priv->remote_sdp;
- const sdp_media_t *media;
- gboolean has_supported_media = FALSE;
- guint direction_up_mask;
- guint pending_send_mask;
- guint i;
-
- g_return_val_if_fail (sdp != NULL, FALSE);
-
- /* Update the session-wide parameters
- * before updating streams' media */
-
- priv->remote_ptime = rakia_sdp_get_string_attribute (
- sdp->sdp_attributes, "ptime");
- priv->remote_max_ptime = rakia_sdp_get_string_attribute (
- sdp->sdp_attributes, "maxptime");
-
- priv->rtcp_enabled = !rakia_sdp_rtcp_bandwidth_throttled (
- sdp->sdp_bandwidths);
-
- /*
- * Do not allow:
- * 1) an answer to bump up directions beyond what's been offered;
- * 2) an offer to remove the local hold.
- */
- if (authoritative)
- direction_up_mask
- = rakia_media_session_is_local_hold_ongoing (session)
- ? TP_MEDIA_STREAM_DIRECTION_SEND
- : TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL;
- else
- direction_up_mask = 0;
-
- /* A remote media requesting to enable sending would need local approval.
- * Also, if there have been any local media updates pending a re-INVITE,
- * keep or bump the pending remote send flag on the streams: it will
- * be resolved in the next re-INVITE transaction */
- pending_send_mask = TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
- if (priv->pending_offer)
- pending_send_mask |= TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
-
- media = sdp->sdp_media;
-
- /* note: for each session, we maintain an ordered list of
- * streams (SDP m-lines) which are matched 1:1 to
- * the streams of the remote SDP */
-
- for (i = 0; media != NULL; media = media->m_next, i++)
- {
- RakiaMediaStream *stream = NULL;
- guint media_type;
-
- media_type = rakia_tp_media_type (media->m_type);
-
- if (i >= priv->streams->len)
- stream = rakia_media_session_add_stream (
- session,
- media_type,
- rakia_media_stream_direction_from_remote_media (media),
- FALSE);
- else
- stream = g_ptr_array_index(priv->streams, i);
-
- /* note: it is ok for the stream to be NULL (unsupported media type) */
- if (stream == NULL)
- continue;
-
- SESSION_DEBUG (session, "setting remote SDP for stream %u", i);
-
- if (media->m_rejected)
- {
- SESSION_DEBUG (session, "the stream has been rejected, closing");
- }
- else if (rakia_media_stream_get_media_type (stream) != media_type)
- {
- /* XXX: close this stream and create a new one in its place? */
- WARNING ("The peer has changed the media type, don't know what to do");
- }
- else if (rakia_media_stream_set_remote_media (stream,
- media,
- direction_up_mask,
- pending_send_mask))
- {
- has_supported_media = TRUE;
- continue;
- }
-
- /* There have been problems with the stream update, kill the stream */
- rakia_media_stream_close (stream);
- }
- g_assert(media == NULL);
- g_assert(i <= priv->streams->len);
- g_assert(!authoritative || i == priv->remote_stream_count);
-
- if (i < priv->streams->len && !priv->pending_offer)
- {
- /*
- * It's not defined what we should do if there are previously offered
- * streams not accounted in the remote SDP, in violation of RFC 3264.
- * Closing them off serves resource preservation and gives better
- * clue to the client as to the real state of the session.
- * Note that this situation is masked if any local media updates
- * have been requested and are pending until the present remote session
- * answer is received and applied. In such a case, we'll issue a new offer
- * at the closest available time, with the "overhanging" stream entries
- * intact.
- */
- do
- {
- RakiaMediaStream *stream;
- stream = g_ptr_array_index(priv->streams, i);
- if (stream != NULL)
- {
- SESSION_MESSAGE (session, "closing a mismatched stream %u", i);
- rakia_media_stream_close (stream);
- }
- }
- while (++i < priv->streams->len);
- }
-
- if (has_supported_media)
- priv_update_remote_hold (session);
-
- DEBUG("exit");
-
- return has_supported_media;
-}
-
-static void
-priv_session_rollback (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- DEBUG("enter");
-
- if (priv->remote_sdp != NULL)
- {
- priv->remote_sdp = NULL;
- g_assert (priv->home != NULL);
- su_home_unref (priv->home);
- priv->home = NULL;
- }
- if (priv->backup_remote_sdp == NULL)
- {
- rakia_media_session_terminate (session);
- return;
- }
-
- /* restore remote SDP from the backup */
- priv->remote_sdp = priv->backup_remote_sdp;
- g_assert (priv->backup_home != NULL);
- priv->home = priv->backup_home;
- priv->backup_remote_sdp = NULL;
- priv->backup_home = NULL;
-
- priv_update_remote_media (session, FALSE);
-
- if (priv->saved_event[0])
- {
- nua_respond (priv->nua_op, SIP_488_NOT_ACCEPTABLE,
- NUTAG_WITH(nua_saved_event_request (priv->saved_event)),
- TAG_END());
- nua_destroy_event (priv->saved_event);
- }
- else
- {
- nua_respond (priv->nua_op, SIP_488_NOT_ACCEPTABLE,
- TAG_END());
- }
-
- rakia_media_session_change_state (session, RAKIA_MEDIA_SESSION_STATE_ACTIVE);
-}
-
-static GString *
-priv_session_generate_sdp (RakiaMediaSession *session,
- gboolean authoritative)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- GString *user_sdp;
- guint len;
- guint i;
-
- g_return_val_if_fail (priv->local_non_ready == 0, NULL);
-
- user_sdp = g_string_new ("v=0\r\n");
-
- len = priv->streams->len;
- if (!authoritative && len > priv->remote_stream_count)
- {
- len = priv->remote_stream_count;
- SESSION_DEBUG (session, "clamped response to %u streams seen in the offer", len);
- }
-
- for (i = 0; i < len; i++)
- {
- RakiaMediaStream *stream = g_ptr_array_index (priv->streams, i);
- if (stream)
- rakia_media_stream_generate_sdp (stream, user_sdp);
- else
- g_string_append (user_sdp, "m=audio 0 RTP/AVP 0\r\n");
- }
-
- return user_sdp;
-}
-
-static void
-priv_session_invite (RakiaMediaSession *session, gboolean reinvite)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- GString *user_sdp;
-
- DEBUG("enter");
-
- g_return_if_fail (priv->nua_op != NULL);
-
- user_sdp = priv_session_generate_sdp (session, TRUE);
-
- g_return_if_fail (user_sdp != NULL);
-
- if (!reinvite
- || priv->state == RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING
- || tp_strdiff (priv->local_sdp, user_sdp->str))
- {
- g_free (priv->local_sdp);
- priv->local_sdp = g_string_free (user_sdp, FALSE);
-
- /* We need to be prepared to receive media right after the
- * offer is sent, so we must set the streams to playing */
- priv_session_set_streams_playing (session, TRUE);
-
- nua_invite (priv->nua_op,
- SOATAG_USER_SDP_STR(priv->local_sdp),
- SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
- SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
- NUTAG_AUTOANSWER(0),
- TAG_IF(reinvite,
- NUTAG_INVITE_TIMER (RAKIA_REINVITE_TIMEOUT)),
- TAG_END());
- priv->pending_offer = FALSE;
-
- rakia_media_session_change_state (
- session,
- reinvite? RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT
- : RAKIA_MEDIA_SESSION_STATE_INVITE_SENT);
- }
- else
- {
- SESSION_DEBUG (session, "SDP unchanged, not sending a re-INVITE");
- g_string_free (user_sdp, TRUE);
- }
-}
-
-static void
-priv_session_respond (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- msg_t *msg;
-
- g_return_if_fail (priv->nua_op != NULL);
-
- {
- GString *user_sdp = priv_session_generate_sdp (session, FALSE);
-
- g_free (priv->local_sdp);
- priv->local_sdp = g_string_free (user_sdp, FALSE);
- }
-
- /* We need to be prepared to receive media right after the
- * answer is sent, so we must set the streams to playing */
- priv_session_set_streams_playing (session, TRUE);
-
- msg = (priv->saved_event[0])
- ? nua_saved_event_request (priv->saved_event) : NULL;
-
- nua_respond (priv->nua_op, SIP_200_OK,
- TAG_IF(msg, NUTAG_WITH(msg)),
- SOATAG_USER_SDP_STR(priv->local_sdp),
- SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
- SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
- NUTAG_AUTOANSWER(0),
- TAG_END());
-
- if (priv->saved_event[0])
- nua_destroy_event (priv->saved_event);
-
- rakia_media_session_change_state (session, RAKIA_MEDIA_SESSION_STATE_ACTIVE);
-}
-
-static gboolean
-priv_is_codec_intersect_pending (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- guint i;
-
- for (i = 0; i < priv->streams->len; i++)
- {
- RakiaMediaStream *stream = g_ptr_array_index (priv->streams, i);
- if (stream != NULL
- && rakia_media_stream_is_codec_intersect_pending (stream))
- return TRUE;
- }
-
- return FALSE;
-}
-
-/**
- * Sends requests and responses with an outbound offer/answer
- * if all streams of the session are prepared.
- *
- * Following inputs are considered in decision making:
- * - state of the session (is remote INVITE being handled)
- * - status of local streams (set up with stream-engine)
- * - whether session is locally accepted
- */
-static void
-priv_request_response_step (RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- if (priv->local_non_ready != 0)
- {
- SESSION_DEBUG (session, "there are local streams not ready, postponed");
- return;
- }
-
- switch (priv->state)
- {
- case RAKIA_MEDIA_SESSION_STATE_CREATED:
- priv_session_invite (session, FALSE);
- break;
- case RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
- if (priv->accepted
- && !priv_is_codec_intersect_pending (session))
- rakia_media_session_change_state (session,
- RAKIA_MEDIA_SESSION_STATE_ACTIVE);
- break;
- case RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED:
- /* TODO: if the call has not yet been accepted locally
- * and the remote endpoint supports 100rel, send them
- * an early session answer in a reliable 183 response */
- if (priv->accepted
- && !priv_is_codec_intersect_pending (session))
- priv_session_respond (session);
- break;
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
- if (!priv_is_codec_intersect_pending (session))
- priv_session_respond (session);
- break;
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING:
- if (priv->pending_offer)
- priv_session_invite (session, TRUE);
- break;
- default:
- SESSION_DEBUG (session, "no action taken in the current state");
- }
-}
-
-static void
-priv_stream_close_cb (RakiaMediaStream *stream,
- RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv;
- guint id;
-
- DEBUG("enter");
-
- priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- id = rakia_media_stream_get_id (stream);
- g_return_if_fail (g_ptr_array_index(priv->streams, id) == stream);
-
- if (!rakia_media_stream_is_local_ready (stream))
- {
- g_assert (priv->local_non_ready > 0);
- --priv->local_non_ready;
- SESSION_DEBUG(session, "stream wasn't ready, decrement the local non ready counter to %d", priv->local_non_ready);
- }
-
- g_object_unref (stream);
-
- g_ptr_array_index(priv->streams, id) = NULL;
-
- tp_svc_channel_type_streamed_media_emit_stream_removed (priv->channel, id);
-}
-
-static void priv_stream_ready_cb (RakiaMediaStream *stream,
- RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv;
-
- DEBUG ("enter");
-
- priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- g_assert (priv->local_non_ready > 0);
- --priv->local_non_ready;
-
- priv_request_response_step (session);
-}
-
-static void priv_stream_supported_codecs_cb (RakiaMediaStream *stream,
- guint num_codecs,
- RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv;
-
- priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
-
- g_assert (!rakia_media_stream_is_codec_intersect_pending (stream));
-
- if (num_codecs == 0)
- {
- /* This remote media description got no codec intersection. */
- switch (priv->state)
- {
- case RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
- case RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED:
- SESSION_DEBUG (session, "no codec intersection, closing the stream");
- rakia_media_stream_close (stream);
- break;
- case RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
- /* In this case, we have the stream negotiated already,
- * and we don't want to close it just because the remote party
- * offers a different set of codecs.
- * Roll back the whole session to the previously negotiated state. */
- priv_session_rollback (session);
- return;
- case RAKIA_MEDIA_SESSION_STATE_ACTIVE:
- /* We've most likely rolled back from
- * RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED,
- * but we may receive more than one empty codec intersection
- * in the session, so we ignore the rest */
- return;
- default:
- g_assert_not_reached();
- }
- }
-
- priv_request_response_step (session);
-}
-
-static void
-priv_stream_state_changed_cb (RakiaMediaStream *stream,
- guint state,
- RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = session->priv;
-
- tp_svc_channel_type_streamed_media_emit_stream_state_changed(
- priv->channel,
- rakia_media_stream_get_id (stream), state);
-
- /* Check if DTMF can now be played */
- if (!priv->audio_connected
- && state == TP_MEDIA_STREAM_STATE_CONNECTED
- && rakia_media_stream_get_media_type (stream)
- == TP_MEDIA_STREAM_TYPE_AUDIO)
- {
- priv->audio_connected = TRUE;
-
- if (priv->accepted)
- g_signal_emit (session, signals[SIG_DTMF_READY], 0);
- }
-}
-
-static void
-priv_stream_direction_changed_cb (RakiaMediaStream *stream,
- guint direction,
- guint pending_send_flags,
- RakiaMediaChannel *channel)
-{
- g_assert (RAKIA_IS_MEDIA_CHANNEL (channel));
- tp_svc_channel_type_streamed_media_emit_stream_direction_changed (
- channel,
- rakia_media_stream_get_id (stream), direction, pending_send_flags);
-}
-
-static void
-priv_stream_hold_state_cb (RakiaMediaStream *stream,
- GParamSpec *pspec,
- RakiaMediaSession *session)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (session);
- gboolean hold;
- guint i;
-
- /* Determine the hold state all streams shall come to */
- switch (priv->hold_state)
- {
- case TP_LOCAL_HOLD_STATE_PENDING_HOLD:
- hold = TRUE;
- break;
- case TP_LOCAL_HOLD_STATE_PENDING_UNHOLD:
- hold = FALSE;
- break;
- default:
- SESSION_MESSAGE (session, "unexpected hold state change from a stream");
-
- /* Try to follow the changes and report the resulting hold state */
- g_object_get (stream, "hold-state", &hold, NULL);
- priv->hold_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
- }
-
- /* Check if all streams have reached the desired hold state */
- for (i = 0; i < priv->streams->len; i++)
- {
- stream = g_ptr_array_index (priv->streams, i);
- if (stream != NULL)
- {
- gboolean stream_held = FALSE;
- g_object_get (stream, "hold-state", &stream_held, NULL);
- if ((!stream_held) != (!hold))
- {
- SESSION_DEBUG (session, "hold/unhold not complete yet");
- return;
- }
- }
- }
-
- priv_finalize_hold (session);
-}
-
-static void
-priv_stream_unhold_failure_cb (RakiaMediaStream *stream,
- RakiaMediaSession *session)
-{
- priv_initiate_hold (session,
- TRUE,
- TP_LOCAL_HOLD_STATE_REASON_RESOURCE_NOT_AVAILABLE);
-}
-
-RakiaMediaStream*
-rakia_media_session_add_stream (RakiaMediaSession *self,
- guint media_type,
- TpMediaStreamDirection direction,
- gboolean created_locally)
-{
- RakiaMediaSessionPrivate *priv = RAKIA_MEDIA_SESSION_GET_PRIVATE (self);
- RakiaMediaStream *stream = NULL;
-
- DEBUG ("enter");
-
- if (rakia_media_session_supports_media_type (media_type)) {
- guint stream_id;
- gchar *object_path;
- guint pending_send_flags;
-
- stream_id = priv->streams->len;
- object_path = g_strdup_printf ("%s/MediaStream%u",
- priv->object_path,
- stream_id);
- pending_send_flags = created_locally
- ? TP_MEDIA_STREAM_PENDING_REMOTE_SEND
- : TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
-
- if (!created_locally)
- direction &= ~TP_MEDIA_STREAM_DIRECTION_SEND;
-
- if (rakia_media_session_is_local_hold_ongoing (self))
- direction &= ~TP_MEDIA_STREAM_DIRECTION_RECEIVE;
-
- stream = g_object_new (RAKIA_TYPE_MEDIA_STREAM,
- "dbus-daemon", priv->dbus_daemon,
- "media-session", self,
- "media-type", media_type,
- "object-path", object_path,
- "id", stream_id,
- "direction", direction,
- "pending-send-flags", pending_send_flags,
- "created-locally", created_locally,
- NULL);
-
- g_free (object_path);
-
- g_signal_connect (stream, "close",
- G_CALLBACK (priv_stream_close_cb),
- self);
- g_signal_connect (stream, "ready",
- G_CALLBACK (priv_stream_ready_cb),
- self);
- g_signal_connect (stream, "supported-codecs",
- G_CALLBACK (priv_stream_supported_codecs_cb),
- self);
- g_signal_connect (stream, "state-changed",
- G_CALLBACK (priv_stream_state_changed_cb),
- self);
- g_signal_connect (stream, "direction-changed",
- G_CALLBACK (priv_stream_direction_changed_cb),
- priv->channel);
- g_signal_connect_swapped (stream, "local-media-updated",
- G_CALLBACK (priv_local_media_changed),
- self);
- g_signal_connect (stream, "notify::hold-state",
- G_CALLBACK (priv_stream_hold_state_cb),
- self);
- g_signal_connect (stream, "unhold-failure",
- G_CALLBACK (priv_stream_unhold_failure_cb),
- self);
-
- g_assert (priv->local_non_ready >= 0);
- ++priv->local_non_ready;
-
- if (priv->se_ready)
- priv_emit_new_stream (self, stream);
-
- tp_svc_channel_type_streamed_media_emit_stream_added (priv->channel,
- stream_id,
- priv->peer,
- media_type);
- if (direction != TP_MEDIA_STREAM_DIRECTION_RECEIVE
- || pending_send_flags != TP_MEDIA_STREAM_PENDING_LOCAL_SEND)
- {
- tp_svc_channel_type_streamed_media_emit_stream_direction_changed (
- priv->channel,
- stream_id,
- direction,
- pending_send_flags);
- }
- }
-
- /* note: we add an entry even for unsupported media types */
- g_ptr_array_add (priv->streams, stream);
-
- DEBUG ("exit");
-
- return stream;
-}
-
-static void
-session_handler_iface_init (gpointer g_iface, gpointer iface_data)
-{
- TpSvcMediaSessionHandlerClass *klass = (TpSvcMediaSessionHandlerClass *)g_iface;
-
-#define IMPLEMENT(x) tp_svc_media_session_handler_implement_##x (\
- klass, (tp_svc_media_session_handler_##x##_impl) rakia_media_session_##x)
- IMPLEMENT(error);
- IMPLEMENT(ready);
-#undef IMPLEMENT
-}
-
-/* Checks if RTCP is not disabled with bandwidth modifiers
- * as described in RFC 3556 */
-gboolean
-rakia_sdp_rtcp_bandwidth_throttled (const sdp_bandwidth_t *b)
-{
- const sdp_bandwidth_t *b_RS = NULL;
- const sdp_bandwidth_t *b_RR = NULL;
-
- while (b != NULL)
- {
- if (b->b_modifier_name != NULL)
- {
- if (strcmp (b->b_modifier_name, "RS") == 0)
- b_RS = b;
- else if (strcmp (b->b_modifier_name, "RR") == 0)
- b_RR = b;
- }
- b = b->b_next;
- }
-
- return (b_RS != NULL && b_RS->b_value == 0
- && b_RR != NULL && b_RR->b_value == 0);
-}
diff --git a/rakia/media-session.h b/rakia/media-session.h
deleted file mode 100644
index 6fc52a1..0000000
--- a/rakia/media-session.h
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * sip-media-session.h - Header for RakiaMediaSession
- * Copyright (C) 2005 Collabora Ltd.
- * Copyright (C) 2005-2010 Nokia Corporation
- *
- * This work is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This work is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this work; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef __RAKIA_MEDIA_SESSION_H__
-#define __RAKIA_MEDIA_SESSION_H__
-
-#include <rakia/media-stream.h>
-
-#include <glib-object.h>
-#include <telepathy-glib/handle.h>
-#include <sofia-sip/sdp.h>
-
-G_BEGIN_DECLS
-
-typedef enum {
- RAKIA_MEDIA_SESSION_STATE_CREATED = 0,
- RAKIA_MEDIA_SESSION_STATE_INVITE_SENT,
- RAKIA_MEDIA_SESSION_STATE_INVITE_RECEIVED,
- RAKIA_MEDIA_SESSION_STATE_RESPONSE_RECEIVED,
- RAKIA_MEDIA_SESSION_STATE_ACTIVE,
- RAKIA_MEDIA_SESSION_STATE_REINVITE_SENT,
- RAKIA_MEDIA_SESSION_STATE_REINVITE_RECEIVED,
- RAKIA_MEDIA_SESSION_STATE_REINVITE_PENDING,
- RAKIA_MEDIA_SESSION_STATE_ENDED,
-
- NUM_RAKIA_MEDIA_SESSION_STATES
-} RakiaMediaSessionState;
-
-typedef struct _RakiaMediaSession RakiaMediaSession;
-typedef struct _RakiaMediaSessionClass RakiaMediaSessionClass;
-typedef struct _RakiaMediaSessionPrivate RakiaMediaSessionPrivate;
-
-struct _RakiaMediaSessionClass {
- GObjectClass parent_class;
-};
-
-struct _RakiaMediaSession {
- GObject parent;
- RakiaMediaSessionPrivate *priv;
-};
-
-GType rakia_media_session_get_type(void);
-
-/* TYPE MACROS */
-#define RAKIA_TYPE_MEDIA_SESSION \
- (rakia_media_session_get_type())
-#define RAKIA_MEDIA_SESSION(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST((obj), RAKIA_TYPE_MEDIA_SESSION, RakiaMediaSession))
-#define RAKIA_MEDIA_SESSION_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), RAKIA_TYPE_MEDIA_SESSION, RakiaMediaSessionClass))
-#define RAKIA_IS_MEDIA_SESSION(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_MEDIA_SESSION))
-#define RAKIA_IS_MEDIA_SESSION_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_MEDIA_SESSION))
-#define RAKIA_MEDIA_SESSION_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), RAKIA_TYPE_MEDIA_SESSION, RakiaMediaSessionClass))
-
-/***********************************************************************
- * Additional declarations (not based on generated templates)
- ***********************************************************************/
-
-TpHandle rakia_media_session_get_peer (RakiaMediaSession *session);
-void rakia_media_session_terminate (RakiaMediaSession *session);
-RakiaMediaSessionState rakia_media_session_get_state (RakiaMediaSession *session);
-void rakia_media_session_change_state (RakiaMediaSession *session,
- RakiaMediaSessionState new_state);
-gboolean rakia_media_session_set_remote_media (RakiaMediaSession *chan,
- const sdp_session_t* r_sdp);
-RakiaMediaStream* rakia_media_session_add_stream (RakiaMediaSession *self,
- guint media_type,
- TpMediaStreamDirection direction,
- gboolean created_locally);
-gboolean rakia_media_session_request_streams (RakiaMediaSession *session,
- const GArray *media_types,
- GPtrArray *ret,
- GError **error);
-gboolean rakia_media_session_remove_streams (RakiaMediaSession *session,
- const GArray *stream_ids,
- GError **error);
-void rakia_media_session_list_streams (RakiaMediaSession *session,
- GPtrArray *ret);
-gboolean rakia_media_session_request_stream_direction (RakiaMediaSession *session,
- guint stream_id,
- guint direction,
- GError **error);
-void rakia_media_session_receive_invite (RakiaMediaSession *self);
-void rakia_media_session_receive_reinvite (RakiaMediaSession *self);
-void rakia_media_session_accept (RakiaMediaSession *self);
-void rakia_media_session_respond (RakiaMediaSession *self,
- gint status,
- const char *message);
-gboolean rakia_media_session_is_accepted (RakiaMediaSession *self);
-void rakia_media_session_resolve_glare (RakiaMediaSession *self);
-
-TpLocalHoldState rakia_media_session_get_hold_state (RakiaMediaSession *session);
-void rakia_media_session_request_hold (RakiaMediaSession *session,
- gboolean hold);
-
-gboolean rakia_media_session_has_media (RakiaMediaSession *self,
- TpMediaStreamType type);
-
-void rakia_media_session_start_telephony_event (RakiaMediaSession *self,
- guchar event);
-void rakia_media_session_stop_telephony_event (RakiaMediaSession *self);
-
-gint rakia_media_session_rate_native_transport (RakiaMediaSession *session,
- const GValue *transport);
-
-gboolean rakia_sdp_rtcp_bandwidth_throttled (const sdp_bandwidth_t *b);
-
-gchar * rakia_sdp_get_string_attribute (const sdp_attribute_t *attrs,
- const char *name);
-
-G_END_DECLS
-
-#endif /* #ifndef __RAKIA_MEDIA_SESSION_H__*/
diff --git a/rakia/media-stream.c b/rakia/media-stream.c
deleted file mode 100644
index f223f7d..0000000
--- a/rakia/media-stream.c
+++ /dev/null
@@ -1,2001 +0,0 @@
-/*
- * sip-media-stream.c - Source for RakiaMediaStream
- * Copyright (C) 2006 Collabora Ltd.
- * Copyright (C) 2006-2010 Nokia Corporation
- * @author Kai Vehmanen <first.surname@nokia.com>
- * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
- *
- * Based on telepathy-gabble implementation (gabble-media-stream).
- * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
- *
- * This work is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This work is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this work; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#include "config.h"
-#include "rakia/media-stream.h"
-
-#include <dbus/dbus-glib.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <telepathy-glib/dbus.h>
-#include <telepathy-glib/enums.h>
-#include <telepathy-glib/errors.h>
-#include <telepathy-glib/gtypes.h>
-#include <telepathy-glib/interfaces.h>
-#include <telepathy-glib/svc-generic.h>
-#include <telepathy-glib/svc-media-interfaces.h>
-#include <telepathy-glib/util.h>
-
-#include "config.h"
-
-#include <rakia/codec-param-formats.h>
-
-#include "rakia/media-session.h"
-
-#include <sofia-sip/msg_parser.h>
-
-#include "signals-marshal.h"
-
-#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
-#include "rakia/debug.h"
-
-
-#define same_boolean(old, new) ((!(old)) == (!(new)))
-
-
-#ifdef ENABLE_DEBUG
-
-#define STREAM_DEBUG(stream, format, ...) \
- rakia_log (DEBUG_FLAG, G_LOG_LEVEL_DEBUG, "stream %u: " format, \
- (stream)->priv->id,##__VA_ARGS__)
-
-#define STREAM_MESSAGE(stream, format, ...) \
- rakia_log (DEBUG_FLAG, G_LOG_LEVEL_MESSAGE, "stream %u: " format, \
- (stream)->priv->id,##__VA_ARGS__)
-
-#else
-
-#define STREAM_DEBUG(stream, format, ...) G_STMT_START { } G_STMT_END
-#define STREAM_MESSAGE(stream, format, ...) G_STMT_START { } G_STMT_END
-
-#endif
-
-
-static void stream_handler_iface_init (gpointer, gpointer);
-
-G_DEFINE_TYPE_WITH_CODE(RakiaMediaStream,
- rakia_media_stream,
- G_TYPE_OBJECT,
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_MEDIA_STREAM_HANDLER,
- stream_handler_iface_init);
- G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
- tp_dbus_properties_mixin_iface_init);
- )
-
-/* signal enum */
-enum
-{
- SIG_READY,
- SIG_SUPPORTED_CODECS,
- SIG_STATE_CHANGED,
- SIG_DIRECTION_CHANGED,
- SIG_LOCAL_MEDIA_UPDATED,
- SIG_UNHOLD_FAILURE,
-
- SIG_LAST_SIGNAL
-};
-
-static guint signals[SIG_LAST_SIGNAL] = {0};
-
-/* properties */
-enum
-{
- PROP_MEDIA_SESSION = 1,
- PROP_DBUS_DAEMON,
- PROP_OBJECT_PATH,
- PROP_ID,
- PROP_MEDIA_TYPE,
- PROP_STATE,
- PROP_DIRECTION,
- PROP_PENDING_SEND_FLAGS,
- PROP_HOLD_STATE,
- PROP_CREATED_LOCALLY,
- PROP_NAT_TRAVERSAL,
- PROP_STUN_SERVERS,
- PROP_RELAY_INFO,
- LAST_PROPERTY
-};
-
-static GPtrArray *rakia_media_stream_relay_info_empty = NULL;
-
-/* private structure */
-struct _RakiaMediaStreamPrivate
-{
- TpDBusDaemon *dbus_daemon;
- RakiaMediaSession *session; /* see gobj. prop. 'media-session' */
- gchar *object_path; /* see gobj. prop. 'object-path' */
- guint id; /* see gobj. prop. 'id' */
- guint media_type; /* see gobj. prop. 'media-type' */
- guint state; /* see gobj. prop. 'state' */
- guint direction; /* see gobj. prop. 'direction' */
- guint pending_send_flags; /* see gobj. prop. 'pending-send-flags' */
- gboolean hold_state; /* see gobj. prop. 'hold-state' */
- gboolean created_locally; /* see gobj. prop. 'created-locally' */
-
- GValue native_codecs; /* intersected codec list */
- GValue native_candidates;
-
- const sdp_media_t *remote_media; /* pointer to the SDP media structure
- * owned by the session object */
-
- guint remote_candidate_counter;
- gchar *remote_candidate_id;
-
- gchar *native_candidate_id;
-
- gboolean ready_received; /* our ready method has been called */
- gboolean playing; /* stream set to playing */
- gboolean sending; /* stream set to sending */
- gboolean pending_remote_receive; /* TRUE if remote is to agree to receive media */
- gboolean native_cands_prepared; /* all candidates discovered */
- gboolean native_codecs_prepared; /* all codecs discovered */
- gboolean push_remote_cands_pending; /* SetRemoteCandidates emission is pending */
- gboolean push_remote_codecs_pending; /* SetRemoteCodecs emission is pending */
- gboolean codec_intersect_pending; /* codec intersection is pending */
- gboolean requested_hold_state; /* hold state last requested from the stream handler */
- gboolean dispose_has_run;
-};
-
-#define RAKIA_MEDIA_STREAM_GET_PRIVATE(stream) ((stream)->priv)
-
-static void push_remote_codecs (RakiaMediaStream *stream);
-static void push_remote_candidates (RakiaMediaStream *stream);
-static void push_active_candidate_pair (RakiaMediaStream *stream);
-static void priv_update_sending (RakiaMediaStream *stream,
- TpMediaStreamDirection direction);
-static void priv_emit_local_ready (RakiaMediaStream *stream);
-static const char *priv_get_preferred_native_candidate (
- RakiaMediaStreamPrivate *priv,
- const GPtrArray **transports);
-
-/***********************************************************************
- * Set: Gobject interface
- ***********************************************************************/
-
-static void
-rakia_media_stream_init (RakiaMediaStream *self)
-{
- RakiaMediaStreamPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
- RAKIA_TYPE_MEDIA_STREAM, RakiaMediaStreamPrivate);
-
- self->priv = priv;
-
- g_value_init (&priv->native_codecs, TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST);
- g_value_take_boxed (&priv->native_codecs,
- dbus_g_type_specialized_construct (TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST));
-
- g_value_init (&priv->native_candidates, TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE_LIST);
- g_value_take_boxed (&priv->native_candidates,
- dbus_g_type_specialized_construct (TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE_LIST));
-}
-
-static void
-rakia_media_stream_constructed (GObject *obj)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (
- RAKIA_MEDIA_STREAM (obj));
- GObjectClass *parent_object_class =
- G_OBJECT_CLASS (rakia_media_stream_parent_class);
-
- /* call base class method */
- if (parent_object_class->constructed != NULL)
- parent_object_class->constructed (obj);
-
- /* XXX: overloading the remote pending send flag to check
- * if this is a locally offered stream. The code creating such streams
- * always sets the flag, because the remote end is supposed to decide
- * whether it wants to send.
- * This may look weird during a local hold. However, the pending flag
- * will be harmlessly cleared once the offer-answer is complete. */
- if ((priv->direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 0
- && (priv->pending_send_flags & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0)
- {
- /* Block sending until the stream is remotely accepted */
- priv->pending_remote_receive = TRUE;
- }
-
- /* go for the bus */
- g_assert (TP_IS_DBUS_DAEMON (priv->dbus_daemon));
- tp_dbus_daemon_register_object (priv->dbus_daemon, priv->object_path, obj);
-}
-
-static void
-rakia_media_stream_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- RakiaMediaStream *stream = RAKIA_MEDIA_STREAM (object);
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- switch (property_id)
- {
- case PROP_DBUS_DAEMON:
- g_value_set_object (value, priv->dbus_daemon);
- break;
- case PROP_MEDIA_SESSION:
- g_value_set_object (value, priv->session);
- break;
- case PROP_OBJECT_PATH:
- g_value_set_string (value, priv->object_path);
- break;
- case PROP_ID:
- g_value_set_uint (value, priv->id);
- break;
- case PROP_MEDIA_TYPE:
- g_value_set_uint (value, priv->media_type);
- break;
- case PROP_STATE:
- g_value_set_uint (value, priv->state);
- break;
- case PROP_DIRECTION:
- g_value_set_uint (value, priv->direction);
- break;
- case PROP_PENDING_SEND_FLAGS:
- g_value_set_uint (value, priv->pending_send_flags);
- break;
- case PROP_HOLD_STATE:
- g_value_set_boolean (value, priv->hold_state);
- break;
- case PROP_CREATED_LOCALLY:
- g_value_set_boolean (value, priv->created_locally);
- break;
- case PROP_NAT_TRAVERSAL:
- g_value_set_static_string (value, "none");
- break;
- case PROP_STUN_SERVERS:
- g_return_if_fail (priv->session != NULL);
- g_object_get_property (G_OBJECT (priv->session), "stun-servers", value);
- break;
- case PROP_RELAY_INFO:
- g_value_set_static_boxed (value, rakia_media_stream_relay_info_empty);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void
-rakia_media_stream_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- RakiaMediaStream *stream = RAKIA_MEDIA_STREAM (object);
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- switch (property_id)
- {
- case PROP_DBUS_DAEMON:
- g_assert (priv->dbus_daemon == NULL); /* construct-only */
- priv->dbus_daemon = g_value_dup_object (value);
- break;
- case PROP_MEDIA_SESSION:
- priv->session = g_value_get_object (value);
- break;
- case PROP_OBJECT_PATH:
- g_free (priv->object_path);
- priv->object_path = g_value_dup_string (value);
- break;
- case PROP_ID:
- priv->id = g_value_get_uint (value);
- break;
- case PROP_MEDIA_TYPE:
- priv->media_type = g_value_get_uint (value);
- break;
- case PROP_STATE:
- priv->state = g_value_get_uint (value);
- break;
- case PROP_DIRECTION:
- priv->direction = g_value_get_uint (value);
- break;
- case PROP_PENDING_SEND_FLAGS:
- priv->pending_send_flags = g_value_get_uint (value);
- break;
- case PROP_HOLD_STATE:
- priv->hold_state = g_value_get_boolean (value);
- break;
- case PROP_CREATED_LOCALLY:
- priv->created_locally = g_value_get_boolean (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- }
-}
-
-static void rakia_media_stream_dispose (GObject *object);
-static void rakia_media_stream_finalize (GObject *object);
-
-static void
-rakia_media_stream_class_init (RakiaMediaStreamClass *klass)
-{
- static TpDBusPropertiesMixinPropImpl stream_handler_props[] = {
- { "CreatedLocally", "created-locally", NULL },
- { "NATTraversal", "nat-traversal", NULL },
- { "STUNServers", "stun-servers", NULL },
- { "RelayInfo", "relay-info", NULL },
- { NULL }
- };
-
- static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
- { TP_IFACE_MEDIA_STREAM_HANDLER,
- tp_dbus_properties_mixin_getter_gobject_properties,
- NULL,
- stream_handler_props,
- },
- { NULL }
- };
-
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
- GType stream_type = G_OBJECT_CLASS_TYPE (klass);
- GParamSpec *param_spec;
-
- g_type_class_add_private (klass, sizeof (RakiaMediaStreamPrivate));
-
- object_class->constructed = rakia_media_stream_constructed;
-
- object_class->get_property = rakia_media_stream_get_property;
- object_class->set_property = rakia_media_stream_set_property;
-
- object_class->dispose = rakia_media_stream_dispose;
- object_class->finalize = rakia_media_stream_finalize;
-
- param_spec = g_param_spec_object ("dbus-daemon", "TpDBusDaemon",
- "Connection to D-Bus.", TP_TYPE_DBUS_DAEMON,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec);
-
- param_spec = g_param_spec_object ("media-session", "RakiaMediaSession object",
- "SIP media session object that owns this media stream object.",
- RAKIA_TYPE_MEDIA_SESSION,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_MEDIA_SESSION, param_spec);
-
- param_spec = g_param_spec_string ("object-path", "D-Bus object path",
- "The D-Bus object path used for this object on the bus.",
- NULL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec);
-
- param_spec = g_param_spec_uint ("id", "Stream ID",
- "A stream number for the stream used in the D-Bus API.",
- 0, G_MAXUINT,
- 0,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_ID, param_spec);
-
- param_spec = g_param_spec_uint ("media-type", "Stream media type",
- "A constant indicating which media type the stream carries.",
- TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_TYPE_VIDEO,
- TP_MEDIA_STREAM_TYPE_AUDIO,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_MEDIA_TYPE, param_spec);
-
- param_spec = g_param_spec_uint ("state", "Connection state",
- "Connection state of the media stream",
- TP_MEDIA_STREAM_STATE_DISCONNECTED, TP_MEDIA_STREAM_STATE_CONNECTED,
- TP_MEDIA_STREAM_STATE_DISCONNECTED,
- G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_STATE, param_spec);
-
- /* We don't change the following two as individual properties
- * after construction, use rakia_media_stream_set_direction() */
-
- param_spec = g_param_spec_uint ("direction", "Stream direction",
- "A value indicating the current direction of the stream",
- TP_MEDIA_STREAM_DIRECTION_NONE, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
- TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_DIRECTION, param_spec);
-
- param_spec = g_param_spec_uint ("pending-send-flags", "Pending send flags",
- "Flags indicating the current pending send state of the stream",
- 0,
- TP_MEDIA_STREAM_PENDING_LOCAL_SEND | TP_MEDIA_STREAM_PENDING_REMOTE_SEND,
- 0,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class,
- PROP_PENDING_SEND_FLAGS,
- param_spec);
-
- param_spec = g_param_spec_boolean ("hold-state", "Hold state",
- "Hold state of the media stream as reported by the stream engine",
- FALSE,
- G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class,
- PROP_HOLD_STATE,
- param_spec);
-
- param_spec = g_param_spec_boolean ("created-locally", "Created locally?",
- "True if this stream was created by the local user", FALSE,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_CREATED_LOCALLY,
- param_spec);
-
- param_spec = g_param_spec_string ("nat-traversal", "NAT traversal",
- "NAT traversal mechanism for this stream", NULL,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_NAT_TRAVERSAL,
- param_spec);
-
- param_spec = g_param_spec_boxed ("stun-servers", "STUN servers",
- "Array of IP address-port pairs for available STUN servers",
- TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_STUN_SERVERS, param_spec);
-
- param_spec = g_param_spec_boxed ("relay-info", "Relay info",
- "Array of mappings containing relay server information",
- TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_RELAY_INFO, param_spec);
-
- rakia_media_stream_relay_info_empty = g_ptr_array_new ();
-
- /* signals not exported by DBus interface */
- signals[SIG_READY] =
- g_signal_new ("ready",
- stream_type,
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- signals[SIG_SUPPORTED_CODECS] =
- g_signal_new ("supported-codecs",
- stream_type,
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__UINT,
- G_TYPE_NONE, 1, G_TYPE_UINT);
-
- signals[SIG_STATE_CHANGED] =
- g_signal_new ("state-changed",
- stream_type,
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__UINT,
- G_TYPE_NONE, 1, G_TYPE_UINT);
-
- signals[SIG_DIRECTION_CHANGED] =
- g_signal_new ("direction-changed",
- stream_type,
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- _rakia_marshal_VOID__UINT_UINT,
- G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
-
- signals[SIG_LOCAL_MEDIA_UPDATED] =
- g_signal_new ("local-media-updated",
- stream_type,
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- signals[SIG_UNHOLD_FAILURE] =
- g_signal_new ("unhold-failure",
- stream_type,
- G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
- 0,
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- klass->dbus_props_class.interfaces = prop_interfaces;
- tp_dbus_properties_mixin_class_init (object_class,
- G_STRUCT_OFFSET (RakiaMediaStreamClass, dbus_props_class));
-}
-
-void
-rakia_media_stream_dispose (GObject *object)
-{
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (object);
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
-
- if (priv->dispose_has_run)
- return;
-
- priv->dispose_has_run = TRUE;
-
- tp_clear_object (&priv->dbus_daemon);
-
- if (G_OBJECT_CLASS (rakia_media_stream_parent_class)->dispose)
- G_OBJECT_CLASS (rakia_media_stream_parent_class)->dispose (object);
-
- DEBUG ("exit");
-}
-
-void
-rakia_media_stream_finalize (GObject *object)
-{
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (object);
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
-
- /* free any data held directly by the object here */
- g_free (priv->object_path);
-
- g_value_unset (&priv->native_codecs);
- g_value_unset (&priv->native_candidates);
-
- g_free (priv->native_candidate_id);
- g_free (priv->remote_candidate_id);
-
- G_OBJECT_CLASS (rakia_media_stream_parent_class)->finalize (object);
-
- DEBUG ("exit");
-}
-
-/***********************************************************************
- * Set: Media.StreamHandler interface implementation (same for 0.12/0.13???)
- ***********************************************************************/
-
-/**
- * rakia_media_stream_codec_choice
- *
- * Implements DBus method CodecChoice
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_codec_choice (TpSvcMediaStreamHandler *iface,
- guint codec_id,
- DBusGMethodInvocation *context)
-{
-#ifdef ENABLE_DEBUG
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (iface);
-#endif
-
- /* Inform the connection manager of the current codec choice. */
-
- STREAM_DEBUG (self, "stream engine has chosen codec %u (incoming packets received?)", codec_id);
-
- tp_svc_media_stream_handler_return_from_codec_choice (context);
-}
-
-/**
- * rakia_media_stream_error
- *
- * Implements DBus method Error
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_error (TpSvcMediaStreamHandler *iface,
- guint errno,
- const gchar *message,
- DBusGMethodInvocation *context)
-{
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (iface);
-
- STREAM_DEBUG (self, "StreamHandler.Error called: %u %s", errno, message);
-
- rakia_media_stream_close (self);
-
- tp_svc_media_stream_handler_return_from_error (context);
-}
-
-/**
- * rakia_media_stream_native_candidates_prepared
- *
- * Implements DBus method NativeCandidatesPrepared
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_native_candidates_prepared (TpSvcMediaStreamHandler *iface,
- DBusGMethodInvocation *context)
-{
- /* purpose: "Informs the connection manager that all possible native candisates
- * have been discovered for the moment."
- */
-
- RakiaMediaStream *obj = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv = obj->priv;
-
- STREAM_DEBUG(obj, "Media.StreamHandler.NativeCandidatesPrepared called");
-
- priv->native_cands_prepared = TRUE;
-
- if (priv->native_codecs_prepared)
- priv_emit_local_ready (obj);
-
- push_active_candidate_pair (obj);
-
- tp_svc_media_stream_handler_return_from_native_candidates_prepared (context);
-}
-
-
-/**
- * rakia_media_stream_new_active_candidate_pair
- *
- * Implements DBus method NewActiveCandidatePair
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_new_active_candidate_pair (TpSvcMediaStreamHandler *iface,
- const gchar *native_candidate_id,
- const gchar *remote_candidate_id,
- DBusGMethodInvocation *context)
-{
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv = self->priv;
-
- STREAM_DEBUG (self, "stream engine reported new active candidate pair %s-%s",
- native_candidate_id, remote_candidate_id);
-
- if (priv->remote_candidate_id == NULL
- || strcmp (priv->remote_candidate_id, remote_candidate_id))
- {
- GError *err;
- err = g_error_new (TP_ERRORS,
- TP_ERROR_INVALID_ARGUMENT,
- "Remote candidate ID does not match the locally "
- "stored data");
- dbus_g_method_return_error (context, err);
- g_error_free (err);
- return;
- }
-
- tp_svc_media_stream_handler_return_from_new_active_candidate_pair (context);
-}
-
-
-/**
- * rakia_media_stream_new_native_candidate
- *
- * Implements DBus method NewNativeCandidate
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_new_native_candidate (TpSvcMediaStreamHandler *iface,
- const gchar *candidate_id,
- const GPtrArray *transports,
- DBusGMethodInvocation *context)
-{
- RakiaMediaStream *obj = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv;
- GPtrArray *candidates;
- GValue candidate = { 0, };
- GValue transport = { 0, };
- gint tr_goodness;
-
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (obj);
-
- g_return_if_fail (transports->len >= 1);
-
- /* Rate the preferability of the address */
- g_value_init (&transport, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT);
- g_value_set_static_boxed (&transport, g_ptr_array_index (transports, 0));
- tr_goodness = rakia_media_session_rate_native_transport (priv->session,
- &transport);
-
- candidates = g_value_get_boxed (&priv->native_candidates);
-
- if (tr_goodness > 0)
- {
- STREAM_DEBUG (obj, "native candidate '%s' is rated as preferable", candidate_id);
- g_free (priv->native_candidate_id);
- priv->native_candidate_id = g_strdup (candidate_id);
-
- /* Drop the candidates received previously */
- g_value_reset (&priv->native_candidates);
- candidates = dbus_g_type_specialized_construct (
- TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE_LIST);
- g_value_take_boxed (&priv->native_candidates, candidates);
- }
-
- g_value_init (&candidate, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE);
- g_value_take_boxed (&candidate,
- dbus_g_type_specialized_construct (TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE));
-
- dbus_g_type_struct_set (&candidate,
- 0, candidate_id,
- 1, transports,
- G_MAXUINT);
-
- g_ptr_array_add (candidates, g_value_get_boxed (&candidate));
-
- STREAM_DEBUG(obj, "put native candidate '%s' into cache", candidate_id);
-
- tp_svc_media_stream_handler_return_from_new_native_candidate (context);
-}
-
-static void
-priv_set_local_codecs (RakiaMediaStream *self,
- const GPtrArray *codecs)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
-
- STREAM_DEBUG(self, "putting list of %d locally supported codecs into cache",
- codecs->len);
- g_value_set_boxed (&priv->native_codecs, codecs);
-
- priv->native_codecs_prepared = TRUE;
- if (priv->native_cands_prepared)
- priv_emit_local_ready (self);
-}
-
-static void
-rakia_media_stream_codecs_updated (TpSvcMediaStreamHandler *iface,
- const GPtrArray *codecs,
- DBusGMethodInvocation *context)
-{
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
-
- if (!priv->native_codecs_prepared)
- {
- GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
- "CodecsUpdated may not be called before codecs have been provided "
- "with SetLocalCodecs or Ready" };
-
- STREAM_DEBUG (self,
- "CodecsUpdated called before SetLocalCodecs or Ready");
-
- dbus_g_method_return_error (context, &e);
- }
- else
- {
- STREAM_DEBUG (self, "putting list of %d locally supported "
- "codecs from CodecsUpdated into cache", codecs->len);
- g_value_set_boxed (&priv->native_codecs, codecs);
-
- if (priv->native_cands_prepared)
- g_signal_emit (self, signals[SIG_LOCAL_MEDIA_UPDATED], 0);
-
- tp_svc_media_stream_handler_return_from_codecs_updated (context);
- }
-}
-
-/**
- * rakia_media_stream_ready
- *
- * Implements DBus method Ready
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_ready (TpSvcMediaStreamHandler *iface,
- const GPtrArray *codecs,
- DBusGMethodInvocation *context)
-{
- /* purpose: "Inform the connection manager that a client is ready to handle
- * this StreamHandler. Also provide it with info about all supported
- * codecs."
- *
- * - note, with SIP we don't send the invite just yet (we need
- * candidates first
- */
-
- RakiaMediaStream *obj = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv = obj->priv;
-
- STREAM_DEBUG (obj, "Media.StreamHandler.Ready called");
-
- if (priv->ready_received)
- {
- STREAM_MESSAGE (obj, "Ready called more than once");
- tp_svc_media_stream_handler_return_from_ready (context);
- return;
- }
-
- priv->ready_received = TRUE;
-
- if (codecs->len != 0)
- priv_set_local_codecs (obj, codecs);
-
- /* Push the initial sending/playing state */
- tp_svc_media_stream_handler_emit_set_stream_playing (
- iface, priv->playing);
- tp_svc_media_stream_handler_emit_set_stream_sending (
- iface, priv->sending);
-
- if (priv->push_remote_cands_pending)
- {
- priv->push_remote_cands_pending = FALSE;
- push_remote_candidates (obj);
- }
- if (priv->push_remote_codecs_pending)
- {
- priv->push_remote_codecs_pending = FALSE;
- push_remote_codecs (obj);
- }
-
- /* note: for inbound sessions, emit active candidate pair once
- remote info is set */
- push_active_candidate_pair (obj);
-
- tp_svc_media_stream_handler_return_from_ready (context);
-}
-
-static void
-rakia_media_stream_set_local_codecs (TpSvcMediaStreamHandler *iface,
- const GPtrArray *codecs,
- DBusGMethodInvocation *context)
-{
- priv_set_local_codecs (RAKIA_MEDIA_STREAM (iface), codecs);
- tp_svc_media_stream_handler_return_from_set_local_codecs (context);
-}
-
-/**
- * rakia_media_stream_stream_state
- *
- * Implements DBus method StreamState
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_stream_state (TpSvcMediaStreamHandler *iface,
- guint state,
- DBusGMethodInvocation *context)
-{
- /* purpose: "Informs the connection manager of the stream's current state
- * State is as specified in *ChannelTypeStreamedMedia::GetStreams."
- *
- * - set the stream state for session
- */
-
- RakiaMediaStream *obj = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv;
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (obj);
-
- if (priv->state != state)
- {
- STREAM_DEBUG (obj, "stream state change %u -> %u", priv->state, state);
- priv->state = state;
- g_signal_emit (obj, signals[SIG_STATE_CHANGED], 0, state);
- }
-
- tp_svc_media_stream_handler_return_from_stream_state (context);
-}
-
-/**
- * rakia_media_stream_supported_codecs
- *
- * Implements DBus method SupportedCodecs
- * on interface org.freedesktop.Telepathy.Media.StreamHandler
- */
-static void
-rakia_media_stream_supported_codecs (TpSvcMediaStreamHandler *iface,
- const GPtrArray *codecs,
- DBusGMethodInvocation *context)
-{
- /* purpose: "Inform the connection manager of the supported codecs for this session.
- * This is called after the connection manager has emitted SetRemoteCodecs
- * to notify what codecs are supported by the peer, and will thus be an
- * intersection of all locally supported codecs (passed to Ready)
- * and those supported by the peer."
- *
- * - emit SupportedCodecs
- */
-
- RakiaMediaStream *self = RAKIA_MEDIA_STREAM (iface);
- RakiaMediaStreamPrivate *priv;
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
-
- STREAM_DEBUG (self,
- "got codec intersection containing %u codecs from stream-engine",
- codecs->len);
-
- /* Save the local codecs, but avoid triggering a new
- * session update at this point. If the stream engine have changed any codec
- * parameters, it is supposed to follow up with CodecsUpdated. */
- g_value_set_boxed (&priv->native_codecs, codecs);
-
- if (priv->codec_intersect_pending)
- {
- if (priv->push_remote_codecs_pending)
- {
- /* The remote codec list has been updated since the intersection
- * has started, plunge into a new intersection immediately */
- priv->push_remote_codecs_pending = FALSE;
- push_remote_codecs (self);
- }
- else
- {
- priv->codec_intersect_pending = FALSE;
- g_signal_emit (self, signals[SIG_SUPPORTED_CODECS], 0, codecs->len);
- }
- }
- else
- WARNING("SupportedCodecs called when no intersection is ongoing");
-
- tp_svc_media_stream_handler_return_from_supported_codecs (context);
-}
-
-static void
-rakia_media_stream_hold_state (TpSvcMediaStreamHandler *self,
- gboolean held,
- DBusGMethodInvocation *context)
-{
- g_object_set (self, "hold-state", held, NULL);
- tp_svc_media_stream_handler_return_from_hold_state (context);
-}
-
-static void
-rakia_media_stream_unhold_failure (TpSvcMediaStreamHandler *self,
- DBusGMethodInvocation *context)
-{
- /* Not doing anything to hold_state or requested_hold_state,
- * because the session is going to put all streams on hold after getting
- * the signal below */
-
- g_signal_emit (self, signals[SIG_UNHOLD_FAILURE], 0);
- tp_svc_media_stream_handler_return_from_unhold_failure (context);
-}
-
-/***********************************************************************
- * Helper functions follow (not based on generated templates)
- ***********************************************************************/
-
-guint
-rakia_media_stream_get_id (RakiaMediaStream *self)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
- return priv->id;
-}
-
-guint
-rakia_media_stream_get_media_type (RakiaMediaStream *self)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
- return priv->media_type;
-}
-
-void
-rakia_media_stream_close (RakiaMediaStream *self)
-{
- tp_svc_media_stream_handler_emit_close (self);
-}
-
-TpMediaStreamDirection
-rakia_media_stream_direction_from_remote_media (const sdp_media_t *media)
-{
- sdp_mode_t mode = media->m_mode;
- return ((mode & sdp_recvonly)? TP_MEDIA_STREAM_DIRECTION_SEND : 0)
- | ((mode & sdp_sendonly)? TP_MEDIA_STREAM_DIRECTION_RECEIVE : 0);
-}
-
-static gboolean
-rakia_sdp_codecs_differ (const sdp_rtpmap_t *m1, const sdp_rtpmap_t *m2)
-{
- while (m1 != NULL && m2 != NULL)
- {
- if (sdp_rtpmap_cmp (m1, m2) != 0)
- return TRUE;
- m1 = m1->rm_next;
- m2 = m2->rm_next;
- }
- return m1 != NULL || m2 != NULL;
-}
-
-/*
- * Returns stream direction as requested by the latest local or remote
- * direction change.
- */
-static TpMediaStreamDirection
-priv_get_requested_direction (RakiaMediaStreamPrivate *priv)
-{
- TpMediaStreamDirection direction;
-
- direction = priv->direction;
- if ((priv->pending_send_flags & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
- direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
- return direction;
-}
-
-/**
- * Sets the remote candidates and codecs for this stream, as
- * received via signaling.
- *
- * Parses the SDP information, updates TP remote candidates and
- * codecs if the client is ready.
- *
- * Note that the pointer to the media description structure is saved,
- * implying that the structure shall not go away for the lifetime of
- * the stream, preferably kept in the memory home attached to
- * the session object.
- *
- * @return TRUE if the remote information has been accepted,
- * FALSE if the update is not acceptable.
- */
-gboolean
-rakia_media_stream_set_remote_media (RakiaMediaStream *stream,
- const sdp_media_t *new_media,
- guint direction_up_mask,
- guint pending_send_mask)
-{
- RakiaMediaStreamPrivate *priv;
- sdp_connection_t *sdp_conn;
- const sdp_media_t *old_media;
- gboolean transport_changed = TRUE;
- gboolean codecs_changed = TRUE;
- guint old_direction;
- guint new_direction;
-
- DEBUG ("enter");
-
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- /* Do sanity checks */
-
- g_return_val_if_fail (new_media != NULL, FALSE);
-
- if (new_media->m_rejected || new_media->m_port == 0)
- {
- STREAM_DEBUG (stream, "the stream is rejected remotely");
- return FALSE;
- }
-
- if (new_media->m_proto != sdp_proto_rtp)
- {
- STREAM_MESSAGE (stream, "the remote protocol is not RTP/AVP");
- return FALSE;
- }
-
- sdp_conn = sdp_media_connections (new_media);
- if (sdp_conn == NULL)
- {
- STREAM_MESSAGE (stream, "no valid remote connections");
- return FALSE;
- }
-
- if (new_media->m_rtpmaps == NULL)
- {
- STREAM_MESSAGE (stream, "no remote codecs");
- return FALSE;
- }
-
- /* Note: always update the pointer to the current media structure
- * because of memory management done in the session object */
- old_media = priv->remote_media;
- priv->remote_media = new_media;
-
- /* Check if there was any media update at all */
-
- if (sdp_media_cmp (old_media, new_media) == 0)
- {
- STREAM_DEBUG (stream, "no media changes detected for the stream");
- return TRUE;
- }
-
- old_direction = priv_get_requested_direction (priv);
- new_direction = rakia_media_stream_direction_from_remote_media (new_media);
-
- /* Make sure the peer can only enable sending or receiving direction
- * if it's allowed to */
- new_direction &= old_direction | direction_up_mask;
-
- if (old_media != NULL)
- {
- /* Check if the transport candidate needs to be changed */
- if (!sdp_connection_cmp (sdp_media_connections (old_media), sdp_conn))
- transport_changed = FALSE;
-
- /* Check if the codec list needs to be updated */
- codecs_changed = rakia_sdp_codecs_differ (old_media->m_rtpmaps,
- new_media->m_rtpmaps);
-
- /* Disable sending at this point if it will be disabled
- * accordingly to the new direction */
- priv_update_sending (stream,
- priv->direction & new_direction);
- }
-
- /* First add the new candidate, then update the codec set.
- * The offerer isn't supposed to send us anything from the new transport
- * until we accept; if it's the answer, both orderings have problems. */
-
- if (transport_changed)
- {
- /* Make sure we stop sending before we use the new set of codecs
- * intended for the new connection */
- if (codecs_changed)
- rakia_media_stream_set_sending (stream, FALSE);
-
- push_remote_candidates (stream);
- }
-
- if (codecs_changed)
- {
- if (!priv->codec_intersect_pending)
- {
- priv->codec_intersect_pending = TRUE;
- push_remote_codecs (stream);
- }
- else
- {
- priv->push_remote_codecs_pending = TRUE;
- }
- }
-
- /* TODO: this will go to session change commit code */
-
- /* note: for outbound sessions (for which remote cands become
- * available at a later stage), emit active candidate pair
- * (and playing status?) once remote info set */
- push_active_candidate_pair (stream);
-
- /* Set the final direction and update pending send flags */
- rakia_media_stream_set_direction (stream,
- new_direction,
- pending_send_mask);
-
- return TRUE;
-}
-
-/**
- * Converts a sofia-sip media type enum to Telepathy media type.
- * See <sofia-sip/sdp.h> and <telepathy-constants.h>.
- *
- * @return G_MAXUINT if the media type cannot be mapped
- */
-guint
-rakia_tp_media_type (sdp_media_e sip_mtype)
-{
- switch (sip_mtype)
- {
- case sdp_media_audio: return TP_MEDIA_STREAM_TYPE_AUDIO;
- case sdp_media_video: return TP_MEDIA_STREAM_TYPE_VIDEO;
- default: return G_MAXUINT;
- }
-}
-
-/**
- * Sets the media state to playing or non-playing. When not playing,
- * received RTP packets may not be played locally.
- */
-void rakia_media_stream_set_playing (RakiaMediaStream *stream, gboolean playing)
-{
- RakiaMediaStreamPrivate *priv;
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- if (same_boolean (priv->playing, playing))
- return;
-
- STREAM_DEBUG (stream, "set playing to %s", playing? "TRUE" : "FALSE");
-
- priv->playing = playing;
-
- if (priv->ready_received)
- tp_svc_media_stream_handler_emit_set_stream_playing (
- (TpSvcMediaStreamHandler *)stream, playing);
-}
-
-/**
- * Sets the media state to sending or non-sending. When not sending,
- * captured media are not sent over the network.
- */
-void
-rakia_media_stream_set_sending (RakiaMediaStream *stream, gboolean sending)
-{
- RakiaMediaStreamPrivate *priv;
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- if (same_boolean(priv->sending, sending))
- return;
-
- STREAM_DEBUG (stream, "set sending to %s", sending? "TRUE" : "FALSE");
-
- priv->sending = sending;
-
- if (priv->ready_received)
- tp_svc_media_stream_handler_emit_set_stream_sending (
- (TpSvcMediaStreamHandler *)stream, sending);
-}
-
-static void
-priv_update_sending (RakiaMediaStream *stream,
- TpMediaStreamDirection direction)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
- gboolean sending = TRUE;
-
- /* XXX: the pending send flag check is probably an overkill
- * considering that effective sending direction and pending send should be
- * mutually exclusive */
- if ((direction & TP_MEDIA_STREAM_DIRECTION_SEND) == 0
- || priv->pending_remote_receive
- || (priv->pending_send_flags & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0
- || !rakia_media_session_is_accepted (priv->session))
- {
- sending = FALSE;
- }
-
- rakia_media_stream_set_sending (stream, sending);
-}
-
-void
-rakia_media_stream_set_direction (RakiaMediaStream *stream,
- TpMediaStreamDirection direction,
- guint pending_send_mask)
-{
- RakiaMediaStreamPrivate *priv;
- guint pending_send_flags;
- TpMediaStreamDirection old_sdp_direction;
-
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
- pending_send_flags = priv->pending_send_flags & pending_send_mask;
-
- if ((direction & TP_MEDIA_STREAM_DIRECTION_SEND) == 0)
- {
- /* We won't be sending, clear the pending local send flag */
- pending_send_flags &= ~TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
- }
- else if ((direction & TP_MEDIA_STREAM_DIRECTION_SEND & ~priv->direction) != 0)
- {
- /* We are requested to start sending, but... */
- if ((pending_send_mask
- & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
- {
- /* ... but we need to confirm this with the client.
- * Clear the sending bit and set the pending send flag. */
- direction &= ~(guint)TP_MEDIA_STREAM_DIRECTION_SEND;
- pending_send_flags |= TP_MEDIA_STREAM_PENDING_LOCAL_SEND;
- }
- if ((pending_send_mask
- & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0
- && (priv->pending_send_flags
- & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) == 0)
- {
- g_assert ((priv_get_requested_direction (priv) & TP_MEDIA_STREAM_DIRECTION_SEND) == 0);
-
- /* ... but the caller wants to agree with the remote
- * end first. Block the stream handler from sending for now. */
- priv->pending_remote_receive = TRUE;
- }
- }
-
- if ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) == 0)
- {
- /* We are not going to receive, clear the pending remote send flag */
- pending_send_flags &= ~TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
- }
- else if ((direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE & ~priv->direction) != 0
- && (pending_send_mask
- & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0)
- {
- /* We're requested to start receiving, but the remote end did not
- * confirm if it will send. Set the pending send flag. */
- pending_send_flags |= TP_MEDIA_STREAM_PENDING_REMOTE_SEND;
- }
-
- if (priv->direction == direction
- && priv->pending_send_flags == pending_send_flags)
- return;
-
- old_sdp_direction = priv_get_requested_direction (priv);
-
- priv->direction = direction;
- priv->pending_send_flags = pending_send_flags;
-
- STREAM_DEBUG (stream, "set direction %u, pending send flags %u", priv->direction, priv->pending_send_flags);
-
- g_signal_emit (stream, signals[SIG_DIRECTION_CHANGED], 0,
- priv->direction, priv->pending_send_flags);
-
- if (priv->remote_media != NULL)
- priv_update_sending (stream, priv->direction);
-
- if (priv->native_cands_prepared
- && priv->native_codecs_prepared
- && priv_get_requested_direction (priv)
- != old_sdp_direction)
- g_signal_emit (stream, signals[SIG_LOCAL_MEDIA_UPDATED], 0);
-}
-
-/*
- * Clears the pending send flag(s) present in @pending_send_mask.
- * If #TP_MEDIA_STREAM_PENDING_LOCAL_SEND is thus cleared,
- * enable the sending bit in the stream direction.
- * If @pending_send_mask has #TP_MEDIA_STREAM_PENDING_REMOTE_SEND flag set,
- * also start sending if agreed by the stream direction.
- */
-void
-rakia_media_stream_apply_pending_direction (RakiaMediaStream *stream,
- guint pending_send_mask)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
- guint flags;
-
-
- /* Don't apply pending send for new streams that haven't been negotiated */
- if (priv->remote_media == NULL)
- return;
-
- /* Remember the flags that got changes and then clear the set */
- flags = (priv->pending_send_flags & pending_send_mask);
- priv->pending_send_flags &= ~pending_send_mask;
-
- if (flags != 0)
- {
- if ((flags & TP_MEDIA_STREAM_PENDING_LOCAL_SEND) != 0)
- priv->direction |= TP_MEDIA_STREAM_DIRECTION_SEND;
-
- STREAM_DEBUG (stream, "set direction %u, pending send flags %u", priv->direction, priv->pending_send_flags);
-
- g_signal_emit (stream, signals[SIG_DIRECTION_CHANGED], 0,
- priv->direction, priv->pending_send_flags);
- }
-
- if ((pending_send_mask & TP_MEDIA_STREAM_PENDING_REMOTE_SEND) != 0)
- {
- priv->pending_remote_receive = FALSE;
- STREAM_DEBUG (stream, "remote end ready to receive");
- }
-
- /* Always check to enable sending because the session could become accepted */
- priv_update_sending (stream, priv->direction);
-}
-
-TpMediaStreamDirection
-rakia_media_stream_get_requested_direction (RakiaMediaStream *self)
-{
- return priv_get_requested_direction (RAKIA_MEDIA_STREAM_GET_PRIVATE (self));
-}
-
-/**
- * Returns true if the stream has a valid SDP description and
- * connection has been established with the stream engine.
- */
-gboolean rakia_media_stream_is_local_ready (RakiaMediaStream *self)
-{
- RakiaMediaStreamPrivate *priv = self->priv;
- return (priv->ready_received && priv->native_cands_prepared
- && priv->native_codecs_prepared);
-}
-
-gboolean
-rakia_media_stream_is_codec_intersect_pending (RakiaMediaStream *self)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
- return priv->codec_intersect_pending;
-}
-
-void
-rakia_media_stream_start_telephony_event (RakiaMediaStream *self, guchar event)
-{
- tp_svc_media_stream_handler_emit_start_telephony_event (
- (TpSvcMediaStreamHandler *)self, event);
-}
-
-void
-rakia_media_stream_stop_telephony_event (RakiaMediaStream *self)
-{
- tp_svc_media_stream_handler_emit_stop_telephony_event (
- (TpSvcMediaStreamHandler *)self);
-}
-
-gboolean
-rakia_media_stream_request_hold_state (RakiaMediaStream *self, gboolean hold)
-{
- RakiaMediaStreamPrivate *priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (self);
-
- if ((!priv->requested_hold_state) != (!hold))
- {
- priv->requested_hold_state = hold;
- tp_svc_media_stream_handler_emit_set_stream_held (self, hold);
- return TRUE;
- }
- return FALSE;
-}
-
-static void
-priv_emit_local_ready (RakiaMediaStream *self)
-{
- /* Trigger any session updates that are due in the current session state */
- g_signal_emit (self, signals[SIG_LOCAL_MEDIA_UPDATED], 0);
- g_signal_emit (self, signals[SIG_READY], 0);
-}
-
-/**
- * Notify StreamEngine of remote codecs.
- *
- * @pre Ready signal must be receiveid (priv->ready_received)
- */
-static void push_remote_codecs (RakiaMediaStream *stream)
-{
- RakiaMediaStreamPrivate *priv;
- GPtrArray *codecs;
- GHashTable *opt_params;
- GType codecs_type;
- GType codec_type;
- const sdp_media_t *sdpmedia;
- const sdp_rtpmap_t *rtpmap;
- gchar *ptime = NULL;
- gchar *max_ptime = NULL;
-
- DEBUG ("enter");
-
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- sdpmedia = priv->remote_media;
- if (sdpmedia == NULL)
- {
- STREAM_DEBUG (stream, "remote media description is not received yet");
- return;
- }
-
- if (!priv->ready_received)
- {
- STREAM_DEBUG (stream, "the stream engine is not ready, SetRemoteCodecs is pending");
- priv->push_remote_codecs_pending = TRUE;
- return;
- }
-
- ptime = rakia_sdp_get_string_attribute (sdpmedia->m_attributes, "ptime");
- if (ptime == NULL)
- {
- g_object_get (priv->session,
- "remote-ptime", &ptime,
- NULL);
- }
- max_ptime = rakia_sdp_get_string_attribute (sdpmedia->m_attributes, "maxptime");
- if (max_ptime == NULL)
- {
- g_object_get (priv->session,
- "remote-max-ptime", &max_ptime,
- NULL);
- }
-
- codec_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC;
- codecs_type = TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST;
-
- codecs = dbus_g_type_specialized_construct (codecs_type);
- opt_params = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- g_free);
-
- rtpmap = sdpmedia->m_rtpmaps;
- while (rtpmap)
- {
- GValue codec = { 0, };
-
- g_value_init (&codec, codec_type);
- g_value_take_boxed (&codec,
- dbus_g_type_specialized_construct (codec_type));
-
- if (ptime != NULL)
- g_hash_table_insert (opt_params,
- g_strdup("ptime"), g_strdup (ptime));
- if (max_ptime != NULL)
- g_hash_table_insert (opt_params,
- g_strdup("maxptime"), g_strdup (max_ptime));
-
- rakia_codec_param_parse (priv->media_type, rtpmap->rm_encoding,
- rtpmap->rm_fmtp, opt_params);
-
- /* RFC2327: see "m=" line definition
- * - note, 'encoding_params' is assumed to be channel
- * count (i.e. channels in farsight) */
-
- dbus_g_type_struct_set (&codec,
- /* payload type: */
- 0, rtpmap->rm_pt,
- /* encoding name: */
- 1, rtpmap->rm_encoding,
- /* media type */
- 2, (guint)priv->media_type,
- /* clock-rate */
- 3, rtpmap->rm_rate,
- /* number of supported channels: */
- 4, rtpmap->rm_params ? atoi(rtpmap->rm_params) : 0,
- /* optional params: */
- 5, opt_params,
- G_MAXUINT);
-
- g_hash_table_remove_all (opt_params);
-
- g_ptr_array_add (codecs, g_value_get_boxed (&codec));
-
- rtpmap = rtpmap->rm_next;
- }
-
- g_hash_table_unref (opt_params);
- g_free (ptime);
- g_free (max_ptime);
-
- STREAM_DEBUG(stream, "emitting %d remote codecs to the handler",
- codecs->len);
-
- tp_svc_media_stream_handler_emit_set_remote_codecs (
- (TpSvcMediaStreamHandler *)stream, codecs);
-
- g_boxed_free (codecs_type, codecs);
-}
-
-static void push_remote_candidates (RakiaMediaStream *stream)
-{
- RakiaMediaStreamPrivate *priv;
- GValue candidate = { 0 };
- GValue transport = { 0 };
- GValue transport_rtcp = { 0 };
- GPtrArray *candidates;
- GPtrArray *transports;
- GType candidate_type;
- GType candidates_type;
- GType transport_type;
- GType transports_type;
- const sdp_media_t *media;
- const sdp_connection_t *sdp_conn;
- gchar *candidate_id;
- guint port;
-
- DEBUG("enter");
-
- priv = RAKIA_MEDIA_STREAM_GET_PRIVATE (stream);
-
- media = priv->remote_media;
- if (media == NULL)
- {
- STREAM_DEBUG (stream, "remote media description is not received yet");
- return;
- }
-
- if (!priv->ready_received)
- {
- STREAM_DEBUG (stream, "the stream engine is not ready, SetRemoteCandidateList is pending");
- priv->push_remote_cands_pending = TRUE;
- return;
- }
-
- /* use the address from SDP c-line as the only remote candidate */
-
- sdp_conn = sdp_media_connections (media);
- g_return_if_fail (sdp_conn != NULL);
-
- port = (guint) media->m_port;
-
- transports_type = TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT_LIST;
- transports = dbus_g_type_specialized_construct (transports_type);
-
- transport_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT;
- g_value_init (&transport, transport_type);
- g_value_take_boxed (&transport,
- dbus_g_type_specialized_construct (transport_type));
- dbus_g_type_struct_set (&transport,
- 0, 1, /* component number */
- 1, sdp_conn->c_address,
- 2, port,
- 3, TP_MEDIA_STREAM_BASE_PROTO_UDP,
- 4, "RTP",
- 5, "AVP",
- /* 6, 0.0f, */
- 7, TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL,
- /* 8, "", */
- /* 9, "", */
- G_MAXUINT);
-
- STREAM_DEBUG (stream, "remote RTP address=<%s>, port=<%u>", sdp_conn->c_address, port);
- g_ptr_array_add (transports, g_value_get_boxed (&transport));
-
- if (!rakia_sdp_rtcp_bandwidth_throttled (media->m_bandwidths))
- {
- gboolean session_rtcp_enabled = TRUE;
- g_object_get (priv->session,
- "rtcp-enabled", &session_rtcp_enabled,
- NULL);
- if (session_rtcp_enabled)
- {
- const sdp_attribute_t *rtcp_attr;
- const char *rtcp_address;
- guint rtcp_port;
-
- /* Get the port and optional address for RTCP accordingly to RFC 3605 */
- rtcp_address = sdp_conn->c_address;
- rtcp_attr = sdp_attribute_find (media->m_attributes, "rtcp");
- if (rtcp_attr == NULL || rtcp_attr->a_value == NULL)
- {
- rtcp_port = port + 1;
- }
- else
- {
- const char *rest;
- rtcp_port = (guint) g_ascii_strtoull (rtcp_attr->a_value,
- (gchar **) &rest,
- 10);
- if (rtcp_port != 0
- && (strncmp (rest, " IN IP4 ", 8) == 0
- || strncmp (rest, " IN IP6 ", 8) == 0))
- rtcp_address = rest + 8;
- }
-
- g_value_init (&transport_rtcp, transport_type);
- g_value_take_boxed (&transport_rtcp,
- dbus_g_type_specialized_construct (transport_type));
- dbus_g_type_struct_set (&transport_rtcp,
- 0, 2, /* component number */
- 1, rtcp_address,
- 2, rtcp_port,
- 3, TP_MEDIA_STREAM_BASE_PROTO_UDP,
- 4, "RTCP",
- 5, "AVP",
- /* 6, 0.0f, */
- 7, TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL,
- /* 8, "", */
- /* 9, "", */
- G_MAXUINT);
-
- STREAM_DEBUG (stream, "remote RTCP address=<%s>, port=<%u>", rtcp_address, rtcp_port);
- g_ptr_array_add (transports, g_value_get_boxed (&transport_rtcp));
- }
- }
-
- g_free (priv->remote_candidate_id);
- candidate_id = g_strdup_printf ("L%u", ++priv->remote_candidate_counter);
- priv->remote_candidate_id = candidate_id;
-
- candidate_type = TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE;
- g_value_init (&candidate, candidate_type);
- g_value_take_boxed (&candidate,
- dbus_g_type_specialized_construct (candidate_type));
- dbus_g_type_struct_set (&candidate,
- 0, candidate_id,
- 1, transports,
- G_MAXUINT);
-
- candidates_type = TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CANDIDATE_LIST;
- candidates = dbus_g_type_specialized_construct (candidates_type);
- g_ptr_array_add (candidates, g_value_get_boxed (&candidate));
-
- STREAM_DEBUG (stream, "emitting SetRemoteCandidateList with %s", candidate_id);
-
- tp_svc_media_stream_handler_emit_set_remote_candidate_list (
- (TpSvcMediaStreamHandler *)stream, candidates);
-
- g_boxed_free (candidates_type, candidates);
- g_boxed_free (transports_type, transports);
-}
-
-static void
-push_active_candidate_pair (RakiaMediaStream *stream)
-{
- RakiaMediaStreamPrivate *priv = stream->priv;
-
- if (priv->ready_received && priv->native_cands_prepared
- && priv->remote_candidate_id != NULL)
- {
- const char *native_candidate_id;
-
- native_candidate_id = priv_get_preferred_native_candidate (priv, NULL);
- STREAM_DEBUG (stream, "emitting SetActiveCandidatePair for %s-%s",
- native_candidate_id, priv->remote_candidate_id);
- tp_svc_media_stream_handler_emit_set_active_candidate_pair (stream,
- native_candidate_id, priv->remote_candidate_id);
- }
-}
-
-static const char* priv_media_type_to_str(guint media_type)
-{
-switch (media_type)
- {
- case TP_MEDIA_STREAM_TYPE_AUDIO: return "audio";
- case TP_MEDIA_STREAM_TYPE_VIDEO: return "video";
- default: g_assert_not_reached ();
- ;
- }
-return "-";
-}
-
-static void
-priv_append_rtpmaps (const GPtrArray *codecs, GString *mline, GString *alines)
-{
- GValue codec = { 0, };
- gchar *co_name = NULL;
- guint co_id;
- guint co_type;
- guint co_clockrate;
- guint co_channels;
- GHashTable *co_params = NULL;
- guint i;
-
- g_value_init (&codec, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_CODEC);
-
- for (i = 0; i < codecs->len; i++)
- {
- g_value_set_static_boxed (&codec, g_ptr_array_index (codecs, i));
-
- dbus_g_type_struct_get (&codec,
- 0, &co_id,
- 1, &co_name,
- 2, &co_type,
- 3, &co_clockrate,
- 4, &co_channels,
- 5, &co_params,
- G_MAXUINT);
-
- /* g_return_if_fail (co_type == priv->media_type); */
-
- /* Add rtpmap entry to the a= lines */
- g_string_append_printf (alines,
- "a=rtpmap:%u %s/%u",
- co_id,
- co_name,
- co_clockrate);
- if (co_channels > 1)
- g_string_append_printf (alines, "/%u", co_channels);
- g_string_append (alines, "\r\n");
-
- /* Marshal parameters into the fmtp attribute */
- if (g_hash_table_size (co_params) != 0)
- {
- GString *fmtp_value;
- g_string_append_printf (alines, "a=fmtp:%u ", co_id);
- fmtp_value = g_string_new (NULL);
- rakia_codec_param_format (co_type, co_name,
- co_params, fmtp_value);
- g_string_append (alines, fmtp_value->str);
- g_string_free (fmtp_value, TRUE);
- g_string_append (alines, "\r\n");
- }
-
- /* Add PT id to the m= line */
- g_string_append_printf (mline, " %u", co_id);
-
- g_free (co_name);
- co_name = NULL;
- g_hash_table_unref (co_params);
- co_params = NULL;
- }
-}
-
-/**
-* Refreshes the local SDP based on Farsight stream, and current
-* object, state.
-*/
-static const char *
-priv_get_preferred_native_candidate (RakiaMediaStreamPrivate *priv,
- const GPtrArray **transports)
-{
- const GPtrArray *candidates;
- const gchar *candidate_id = NULL;
- const GPtrArray *ca_tports = NULL;
- GValue transport = { 0 };
- int i;
-
- candidates = g_value_get_boxed (&priv->native_candidates);
-
- g_value_init (&transport, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT);
-
- /* Find the preferred candidate, if defined,
- * else the last acceptable candidate */
-
- for (i = candidates->len - 1; i >= 0; --i)
- {
- GValueArray *candidate;
- guint tr_proto = (guint) -1;
- guint j;
-
- candidate = g_ptr_array_index (candidates, i);
- candidate_id =
- g_value_get_string (g_value_array_get_nth (candidate, 0));
- ca_tports = g_value_get_boxed (g_value_array_get_nth (candidate, 1));
-
- if (ca_tports->len == 0)
- {
- WARNING ("candidate '%s' lists no transports, skipping", candidate_id);
- continue;
- }
-
- for (j = 0; j < ca_tports->len; j++)
- {
- guint tr_component = 0;
-
- g_value_set_static_boxed (&transport,
- g_ptr_array_index (ca_tports, j));
-
- /* Find the RTP component */
- dbus_g_type_struct_get (&transport,
- 0, &tr_component,
- 3, &tr_proto,
- G_MAXUINT);
- if (tr_component == 1)
- break;
- }
-
- if (priv->native_candidate_id != NULL)
- {
- if (!strcmp (candidate_id, priv->native_candidate_id))
- break;
- }
- else if (tr_proto == TP_MEDIA_STREAM_BASE_PROTO_UDP)
- {
- break;
- }
- }
-
- if (i < 0)
- {
- WARNING ("preferred candidate not found");
- return NULL;
- }
-
- if (transports != NULL)
- *transports = ca_tports;
-
- return candidate_id;
-}
-
-/**
- * Produces the SDP description of the stream based on Farsight state and
- * current object state.
- *
- * @param stream The stream object
- * @param signal_update If true, emit the signal "local-media-updated".
- */
-void
-rakia_media_stream_generate_sdp (RakiaMediaStream *stream, GString *out)
-{
- RakiaMediaStreamPrivate *priv = stream->priv;
- GString *alines;
- GValue transport = { 0 };
- const GPtrArray *transports = NULL;
- gchar *tr_addr = NULL;
- /* gchar *tr_user = NULL; */
- /* gchar *tr_pass = NULL; */
- gchar *tr_subtype = NULL;
- gchar *tr_profile = NULL;
- guint tr_port;
- /* guint tr_type; */
- /* gdouble tr_pref; */
- guint rtcp_port = 0;
- gchar *rtcp_address = NULL;
- const gchar *dirline;
- guint j;
-
- priv_get_preferred_native_candidate (priv, &transports);
-
- g_return_if_fail (transports != NULL);
-
- g_value_init (&transport, TP_STRUCT_TYPE_MEDIA_STREAM_HANDLER_TRANSPORT);
-
- for (j = 0; j != transports->len; j++)
- {
- guint tr_component;
-
- g_value_set_static_boxed (&transport,
- g_ptr_array_index (transports, j));
-
- dbus_g_type_struct_get (&transport,
- 0, &tr_component,
- G_MAXUINT);
- switch (tr_component)
- {
- case 1: /* RTP */
- dbus_g_type_struct_get (&transport,
- 1, &tr_addr,
- 2, &tr_port,
- 4, &tr_subtype,
- 5, &tr_profile,
- /* 6, &tr_pref, */
- /* 7, &tr_type, */
- /* 8, &tr_user, */
- /* 9, &tr_pass, */
- G_MAXUINT);
- break;
- case 2: /* RTCP */
- dbus_g_type_struct_get (&transport,
- 1, &rtcp_address,
- 2, &rtcp_port,
- G_MAXUINT);
- break;
- }
- }
-
- g_return_if_fail (tr_addr != NULL);
- g_return_if_fail (tr_subtype != NULL);
- g_return_if_fail (tr_profile != NULL);
-
- g_string_append (out, "m=");
- g_string_append_printf (out,
- "%s %u %s/%s",
- priv_media_type_to_str (priv->media_type),
- tr_port,
- tr_subtype,
- tr_profile);
-
- switch (priv_get_requested_direction (priv))
- {
- case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL:
- dirline = "";
- break;
- case TP_MEDIA_STREAM_DIRECTION_SEND:
- dirline = "a=sendonly\r\n";
- break;
- case TP_MEDIA_STREAM_DIRECTION_RECEIVE:
- dirline = "a=recvonly\r\n";
- break;
- case TP_MEDIA_STREAM_DIRECTION_NONE:
- dirline = "a=inactive\r\n";
- break;
- default:
- g_assert_not_reached();
- }
-
- alines = g_string_new (dirline);
-
- if (rtcp_address != NULL)
- {
- /* Add RTCP attribute as per RFC 3605 */
- if (strcmp (rtcp_address, tr_addr) != 0)
- {
- g_string_append_printf (alines,
- "a=rtcp:%u IN %s %s\r\n",
- rtcp_port,
- (strchr (rtcp_address, ':') == NULL)
- ? "IP4" : "IP6",
- rtcp_address);
- }
- else if (rtcp_port != tr_port + 1)
- {
- g_string_append_printf (alines,
- "a=rtcp:%u\r\n",
- rtcp_port);
- }
- }
-
- priv_append_rtpmaps (g_value_get_boxed (&priv->native_codecs),
- out, alines);
-
- g_string_append_printf (out, "\r\nc=IN %s %s\r\n",
- (strchr (tr_addr, ':') == NULL)? "IP4" : "IP6",
- tr_addr);
-
- g_string_append (out, alines->str);
-
- g_free (tr_addr);
- g_free (tr_profile);
- g_free (tr_subtype);
- /* g_free (tr_user); */
- /* g_free (tr_pass); */
- g_free (rtcp_address);
-
- g_string_free (alines, TRUE);
-}
-
-static void
-stream_handler_iface_init (gpointer g_iface, gpointer iface_data)
-{
- TpSvcMediaStreamHandlerClass *klass = (TpSvcMediaStreamHandlerClass *)g_iface;
-
-#define IMPLEMENT(x) tp_svc_media_stream_handler_implement_##x (\
- klass, (tp_svc_media_stream_handler_##x##_impl) rakia_media_stream_##x)
- IMPLEMENT(codec_choice);
- IMPLEMENT(error);
- IMPLEMENT(native_candidates_prepared);
- IMPLEMENT(new_active_candidate_pair);
- IMPLEMENT(new_native_candidate);
- IMPLEMENT(ready);
- IMPLEMENT(set_local_codecs);
- IMPLEMENT(codecs_updated);
- IMPLEMENT(stream_state);
- IMPLEMENT(supported_codecs);
- IMPLEMENT(hold_state);
- IMPLEMENT(unhold_failure);
-#undef IMPLEMENT
-}
diff --git a/rakia/media-stream.h b/rakia/media-stream.h
deleted file mode 100644
index 7f1ab87..0000000
--- a/rakia/media-stream.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * sip-media-stream.h - Header for RakiaMediaStream
- * Copyright (C) 2005 Collabora Ltd.
- * Copyright (C) 2005-2010 Nokia Corporation
- *
- * This work is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This work is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this work; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-#ifndef __RAKIA_MEDIA_STREAM_H__
-#define __RAKIA_MEDIA_STREAM_H__
-
-#include <glib-object.h>
-#include <telepathy-glib/dbus-properties-mixin.h>
-#include <telepathy-glib/enums.h>
-#include <sofia-sip/sdp.h>
-
-G_BEGIN_DECLS
-
-typedef struct _RakiaMediaStream RakiaMediaStream;
-typedef struct _RakiaMediaStreamClass RakiaMediaStreamClass;
-typedef struct _RakiaMediaStreamPrivate RakiaMediaStreamPrivate;
-
-struct _RakiaMediaStreamClass {
- GObjectClass parent_class;
- TpDBusPropertiesMixinClass dbus_props_class;
-};
-
-struct _RakiaMediaStream {
- GObject parent;
- RakiaMediaStreamPrivate *priv;
-};
-
-GType rakia_media_stream_get_type(void);
-
-/* TYPE MACROS */
-#define RAKIA_TYPE_MEDIA_STREAM \
- (rakia_media_stream_get_type())
-#define RAKIA_MEDIA_STREAM(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST((obj), RAKIA_TYPE_MEDIA_STREAM, RakiaMediaStream))
-#define RAKIA_MEDIA_STREAM_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), RAKIA_TYPE_MEDIA_STREAM, RakiaMediaStreamClass))
-#define RAKIA_IS_MEDIA_STREAM(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_MEDIA_STREAM))
-#define RAKIA_IS_MEDIA_STREAM_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_MEDIA_STREAM))
-#define RAKIA_MEDIA_STREAM_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), RAKIA_TYPE_MEDIA_STREAM, RakiaMediaStreamClass))
-
-/***********************************************************************
- * Additional declarations (not based on generated templates)
- ***********************************************************************/
-
-void rakia_media_stream_close (RakiaMediaStream *self);
-guint rakia_media_stream_get_id (RakiaMediaStream *self);
-guint rakia_media_stream_get_media_type (RakiaMediaStream *self);
-void rakia_media_stream_generate_sdp (RakiaMediaStream *self, GString *out);
-gboolean rakia_media_stream_set_remote_media (RakiaMediaStream *self,
- const sdp_media_t *media,
- guint direction_up_mask,
- guint pending_send_mask);
-void rakia_media_stream_set_playing (RakiaMediaStream *self, gboolean playing);
-void rakia_media_stream_set_sending (RakiaMediaStream *self, gboolean sending);
-void rakia_media_stream_set_direction (RakiaMediaStream *stream,
- TpMediaStreamDirection direction,
- guint pending_send_mask);
-void rakia_media_stream_apply_pending_direction (RakiaMediaStream *stream,
- guint pending_send_mask);
-TpMediaStreamDirection rakia_media_stream_get_requested_direction (RakiaMediaStream *self);
-gboolean rakia_media_stream_is_local_ready (RakiaMediaStream *self);
-gboolean rakia_media_stream_is_codec_intersect_pending (RakiaMediaStream *self);
-void rakia_media_stream_start_telephony_event (RakiaMediaStream *self, guchar event);
-void rakia_media_stream_stop_telephony_event (RakiaMediaStream *self);
-gboolean rakia_media_stream_request_hold_state (RakiaMediaStream *self,
- gboolean hold);
-
-guint rakia_tp_media_type (sdp_media_e sip_mtype);
-TpMediaStreamDirection rakia_media_stream_direction_from_remote_media (
- const sdp_media_t *media);
-
-G_END_DECLS
-
-#endif /* #ifndef __RAKIA_MEDIA_STREAM_H__*/
diff --git a/rakia/sip-media.c b/rakia/sip-media.c
new file mode 100644
index 0000000..3e5e7b3
--- /dev/null
+++ b/rakia/sip-media.c
@@ -0,0 +1,1236 @@
+/*
+ * rakia-sip-media.c - Source for RakiaSipMedia
+ * Copyright (C) 2005-2012 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ * Copyright (C) 2005-2010 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * Based on telepathy-gabble implementation
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ *
+ * This work is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this work; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "rakia/sip-media.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <sofia-sip/sip_status.h>
+
+
+#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
+#include "rakia/debug.h"
+#include "rakia/codec-param-formats.h"
+#include "rakia/sip-session.h"
+
+
+#ifdef ENABLE_DEBUG
+
+#define MEDIA_DEBUG(media, format, ...) \
+ rakia_log (DEBUG_FLAG, G_LOG_LEVEL_DEBUG, "media %s %p: " format, \
+ priv_media_type_to_str ((media)->priv->media_type), (media), \
+ ##__VA_ARGS__)
+
+#define MEDIA_MESSAGE(media, format, ...) \
+ rakia_log (DEBUG_FLAG, G_LOG_LEVEL_MESSAGE, "media %s %p: " format, \
+ priv_media_type_to_str ((media)->priv->media_type), (media), \
+ ##__VA_ARGS__)
+
+#else
+
+#define MEDIA_DEBUG(media, format, ...) G_STMT_START { } G_STMT_END
+#define MEDIA_MESSAGE(media, format, ...) G_STMT_START { } G_STMT_END
+
+#endif
+
+
+/* The timeout for outstanding re-INVITE transactions in seconds.
+ * Chosen to match the allowed cancellation timeout for proxies
+ * described in RFC 3261 Section 13.3.1.1 */
+#define RAKIA_REINVITE_TIMEOUT 180
+
+G_DEFINE_TYPE(RakiaSipMedia,
+ rakia_sip_media,
+ G_TYPE_OBJECT)
+
+/* signals */
+enum
+{
+ SIG_LOCAL_NEGOTIATION_COMPLETE,
+ SIG_REMOTE_CODEC_OFFER_UPDATED,
+ SIG_REMOTE_CANDIDATES_UPDATED,
+ SIG_LOCAL_UPDATED,
+ SIG_DIRECTION_CHANGED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+
+
+/* private structure */
+struct _RakiaSipMediaPrivate
+{
+ TpMediaStreamType media_type;
+
+ RakiaSipSession *session;
+
+ gchar *name;
+
+ GPtrArray *local_codecs;
+ GPtrArray *local_candidates;
+ gboolean local_candidates_prepared;
+
+ RakiaDirection direction;
+ RakiaDirection requested_direction;
+
+ gboolean hold_requested;
+ gboolean created_locally;
+
+ const sdp_media_t *remote_media; /* pointer to the SDP media structure
+ * owned by the session object */
+
+
+ gboolean codec_intersect_pending; /* codec intersection is pending */
+ gboolean push_remote_codecs_pending; /* SetRemoteCodecs emission is pending */
+ gboolean push_candidates_on_new_codecs;
+
+ GPtrArray *remote_codec_offer;
+ GPtrArray *remote_candidates;
+
+ gboolean can_receive;
+};
+
+
+#define RAKIA_SIP_MEDIA_GET_PRIVATE(media) ((media)->priv)
+
+
+
+static void push_remote_candidates (RakiaSipMedia *media);
+
+static void rakia_sip_media_dispose (GObject *object);
+static void rakia_sip_media_finalize (GObject *object);
+
+
+static void
+rakia_sip_media_init (RakiaSipMedia *self)
+{
+ RakiaSipMediaPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ RAKIA_TYPE_SIP_MEDIA, RakiaSipMediaPrivate);
+
+ self->priv = priv;
+}
+
+static void
+rakia_sip_media_class_init (RakiaSipMediaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (RakiaSipMediaPrivate));
+
+ object_class->dispose = rakia_sip_media_dispose;
+ object_class->finalize = rakia_sip_media_finalize;
+
+ signals[SIG_LOCAL_NEGOTIATION_COMPLETE] =
+ g_signal_new ("local-negotiation-complete",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+
+ signals[SIG_REMOTE_CODEC_OFFER_UPDATED] =
+ g_signal_new ("remote-codec-offer-updated",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+
+
+ signals[SIG_REMOTE_CANDIDATES_UPDATED] =
+ g_signal_new ("remote-candidates-updated",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+
+ signals[SIG_LOCAL_UPDATED] =
+ g_signal_new ("local-updated",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+
+ signals[SIG_DIRECTION_CHANGED] =
+ g_signal_new ("direction-changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+rakia_sip_media_dispose (GObject *object)
+{
+ // RakiaSipMedia *self = RAKIA_SIP_MEDIA (object);
+
+ DEBUG("enter");
+
+ if (G_OBJECT_CLASS (rakia_sip_media_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_sip_media_parent_class)->dispose (object);
+
+ DEBUG("exit");
+}
+
+static void
+rakia_sip_media_finalize (GObject *object)
+{
+ RakiaSipMedia *self = RAKIA_SIP_MEDIA (object);
+ RakiaSipMediaPrivate *priv = self->priv;
+
+ if (priv->local_candidates)
+ g_ptr_array_unref (priv->local_candidates);
+ if (priv->local_codecs)
+ g_ptr_array_unref (priv->local_codecs);
+ if (priv->remote_candidates)
+ g_ptr_array_unref (priv->remote_candidates);
+ if (priv->remote_codec_offer)
+ g_ptr_array_unref (priv->remote_codec_offer);
+
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (rakia_sip_media_parent_class)->finalize (object);
+
+ DEBUG("exit");
+}
+
+
+TpMediaStreamType
+rakia_sip_media_get_media_type (RakiaSipMedia *self)
+{
+ return self->priv->media_type;
+}
+
+static const char *
+priv_media_type_to_str(TpMediaStreamType media_type)
+{
+ switch (media_type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ return "audio";
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ return "video";
+ default:
+ g_assert_not_reached ();
+ return "-";
+ }
+}
+
+const gchar *
+sip_media_get_media_type_str (RakiaSipMedia *self)
+{
+ g_return_val_if_fail (RAKIA_IS_SIP_MEDIA (self), "");
+
+ return priv_media_type_to_str (self->priv->media_type);
+}
+
+
+static void
+priv_append_rtpmaps (TpMediaStreamType media_type,
+ const GPtrArray *codecs, GString *mline, GString *alines)
+{
+ guint i;
+
+ for (i = 0; i < codecs->len; i++)
+ {
+ RakiaSipCodec *codec = g_ptr_array_index (codecs, i);
+
+ /* Add rtpmap entry to the a= lines */
+ g_string_append_printf (alines,
+ "a=rtpmap:%u %s/%u",
+ codec->id,
+ codec->encoding_name,
+ codec->clock_rate);
+ if (codec->channels > 1)
+ g_string_append_printf (alines, "/%u", codec->channels);
+ g_string_append (alines, "\r\n");
+
+ /* Marshal parameters into the fmtp attribute */
+ if (codec->params != NULL)
+ {
+ GString *fmtp_value;
+ g_string_append_printf (alines, "a=fmtp:%u ", codec->id);
+ fmtp_value = g_string_new (NULL);
+ rakia_codec_param_format (media_type, codec, fmtp_value);
+ g_string_append (alines, fmtp_value->str);
+ g_string_free (fmtp_value, TRUE);
+ g_string_append (alines, "\r\n");
+ }
+
+ /* Add PT id to the m= line */
+ g_string_append_printf (mline, " %u", codec->id);
+ }
+}
+
+static void
+priv_get_preferred_local_candidates (RakiaSipMedia *media,
+ RakiaSipCandidate **rtp_cand, RakiaSipCandidate **rtcp_cand)
+{
+ RakiaSipMediaPrivate *priv = media->priv;
+ guint i;
+
+ g_assert (priv->local_candidates);
+ g_assert (priv->local_candidates->len > 0);
+
+ *rtp_cand = NULL;
+
+ for (i = 0; i < priv->local_candidates->len; i++)
+ {
+ RakiaSipCandidate *tmpcand =
+ g_ptr_array_index (priv->local_candidates, i);
+
+ if (tmpcand->component != 1)
+ continue;
+
+ if (*rtp_cand == NULL)
+ *rtp_cand = tmpcand;
+ else if ((*rtp_cand)->priority > tmpcand->priority)
+ *rtp_cand = tmpcand;
+ }
+
+ g_assert (*rtp_cand != NULL);
+
+ if (rtcp_cand == NULL)
+ return;
+
+ *rtcp_cand = NULL;
+
+ for (i = 0; i < priv->local_candidates->len; i++)
+ {
+ RakiaSipCandidate *tmpcand =
+ g_ptr_array_index (priv->local_candidates, i);
+
+ if (tmpcand->component != 2)
+ continue;
+
+ if (((*rtp_cand)->foundation == NULL && tmpcand->foundation == NULL) ||
+ ((*rtp_cand)->foundation != NULL && tmpcand->foundation != NULL &&
+ !strcmp ((*rtp_cand)->foundation, tmpcand->foundation)))
+ {
+ if (*rtcp_cand == NULL)
+ *rtcp_cand = g_ptr_array_index (priv->local_candidates, i);
+ else if ((*rtcp_cand)->priority > tmpcand->priority)
+ *rtcp_cand = tmpcand;
+ }
+ }
+}
+
+
+static void
+rakia_sip_media_set_direction (RakiaSipMedia *media,
+ RakiaDirection direction)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ priv->direction = direction;
+
+ g_signal_emit (media, signals[SIG_DIRECTION_CHANGED], 0);
+}
+
+
+static RakiaDirection
+priv_get_sdp_direction (RakiaSipMedia *media, gboolean authoritative)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+ RakiaDirection direction = priv->requested_direction;
+
+ DEBUG ("req: %s auth: %d remote: %p %s hold: %d",
+ rakia_direction_to_string (direction),
+ authoritative,
+ priv->remote_media,
+ rakia_direction_to_string (rakia_sip_media_get_remote_direction (media)),
+ priv->hold_requested);
+
+ if (!authoritative && priv->remote_media)
+ direction &= rakia_sip_media_get_remote_direction (media);
+
+ /* Don't allow send, only receive if a hold is requested */
+ if (priv->hold_requested)
+ direction &= RAKIA_DIRECTION_SEND;
+
+ if (!authoritative)
+ rakia_sip_media_set_direction (media, direction);
+
+ return direction;
+}
+
+/**
+ * Produces the SDP description of the media based on Farsight state and
+ * current object state.
+ *
+ * @param media The media object
+ * @param signal_update If true, emit the signal "local-media-updated".
+ */
+void
+rakia_sip_media_generate_sdp (RakiaSipMedia *media, GString *out,
+ gboolean authoritative)
+{
+ RakiaSipMediaPrivate *priv = media->priv;
+ GString *alines;
+ const gchar *dirline;
+ RakiaSipCandidate *rtp_cand, *rtcp_cand;
+
+ priv_get_preferred_local_candidates (media, &rtp_cand, &rtcp_cand);
+
+ g_return_if_fail (rtp_cand != NULL);
+
+ g_string_append (out, "m=");
+ g_string_append_printf (out,
+ "%s %u RTP/AVP",
+ priv_media_type_to_str (priv->media_type),
+ rtp_cand->port);
+
+ switch (priv_get_sdp_direction (media, authoritative))
+ {
+ case RAKIA_DIRECTION_BIDIRECTIONAL:
+ dirline = "";
+ break;
+ case RAKIA_DIRECTION_SEND:
+ dirline = "a=sendonly\r\n";
+ break;
+ case RAKIA_DIRECTION_RECEIVE:
+ dirline = "a=recvonly\r\n";
+ break;
+ case RAKIA_DIRECTION_NONE:
+ dirline = "a=inactive\r\n";
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ alines = g_string_new (dirline);
+
+ if (rtcp_cand != NULL)
+ {
+ /* Add RTCP attribute as per RFC 3605 */
+ if (strcmp (rtcp_cand->ip, rtp_cand->ip) != 0)
+ {
+ g_string_append_printf (alines,
+ "a=rtcp:%u IN %s %s\r\n",
+ rtcp_cand->port,
+ (strchr (rtcp_cand->ip, ':') == NULL)
+ ? "IP4" : "IP6",
+ rtcp_cand->ip);
+ }
+ else if (rtcp_cand->port != rtp_cand->port + 1)
+ {
+ g_string_append_printf (alines,
+ "a=rtcp:%u\r\n",
+ rtcp_cand->port);
+ }
+ }
+
+ priv_append_rtpmaps (priv->media_type,
+ priv->local_codecs,
+ out, alines);
+
+ g_string_append_printf (out, "\r\nc=IN %s %s\r\n",
+ (strchr (rtp_cand->ip, ':') == NULL)? "IP4" : "IP6",
+ rtp_cand->ip);
+
+ g_string_append (out, alines->str);
+
+ g_string_free (alines, TRUE);
+}
+
+
+RakiaSipCodec*
+rakia_sip_codec_new (guint id, const gchar *encoding_name,
+ guint clock_rate, guint channels)
+{
+ RakiaSipCodec *codec = g_slice_new (RakiaSipCodec);
+
+ codec->id = id;
+ codec->encoding_name = g_strdup (encoding_name);
+ codec->clock_rate = clock_rate;
+ codec->channels = channels;
+ codec->params = NULL;
+
+ return codec;
+}
+
+static void
+rakia_sip_codec_param_free (RakiaSipCodecParam *param)
+{
+ g_free (param->name);
+ g_free (param->value);
+ g_slice_free (RakiaSipCodecParam, param);
+}
+
+void
+rakia_sip_codec_add_param (RakiaSipCodec *codec, const gchar *name,
+ const gchar *value)
+{
+ RakiaSipCodecParam *param;
+
+ if (codec->params == NULL)
+ codec->params = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) rakia_sip_codec_param_free);
+
+ param = g_slice_new (RakiaSipCodecParam);
+ param->name = g_strdup (name);
+ param->value = g_strdup (value);
+ g_ptr_array_add (codec->params, param);
+}
+
+void
+rakia_sip_codec_free (RakiaSipCodec *codec)
+{
+ g_free (codec->encoding_name);
+ if (codec->params)
+ g_ptr_array_unref (codec->params);
+ g_slice_free (RakiaSipCodec, codec);
+}
+
+
+RakiaSipCandidate*
+rakia_sip_candidate_new (guint component, const gchar *ip, guint port,
+ const gchar *foundation, guint priority)
+{
+ RakiaSipCandidate *candidate = g_slice_new (RakiaSipCandidate);
+
+ candidate->component = component;
+ candidate->ip = g_strdup (ip);
+ candidate->port = port;
+ candidate->foundation = g_strdup (foundation);
+ candidate->priority = priority;
+
+ return candidate;
+}
+
+
+void
+rakia_sip_candidate_free (RakiaSipCandidate *candidate)
+{
+ g_free (candidate->ip);
+ g_free (candidate->foundation);
+ g_slice_free (RakiaSipCandidate, candidate);
+}
+
+
+
+RakiaDirection
+rakia_direction_from_remote_media (const sdp_media_t *media)
+{
+ sdp_mode_t mode = media->m_mode;
+ return ((mode & sdp_recvonly)? RAKIA_DIRECTION_SEND : 0)
+ | ((mode & sdp_sendonly)? RAKIA_DIRECTION_RECEIVE : 0);
+}
+
+
+static gboolean
+rakia_sdp_codecs_differ (const sdp_rtpmap_t *m1, const sdp_rtpmap_t *m2)
+{
+ while (m1 != NULL && m2 != NULL)
+ {
+ if (sdp_rtpmap_cmp (m1, m2) != 0)
+ return TRUE;
+ m1 = m1->rm_next;
+ m2 = m2->rm_next;
+ }
+ return m1 != NULL || m2 != NULL;
+}
+
+
+gchar *
+rakia_sdp_get_string_attribute (const sdp_attribute_t *attrs, const char *name)
+{
+ sdp_attribute_t *attr;
+
+ attr = sdp_attribute_find (attrs, name);
+ if (attr == NULL)
+ return NULL;
+
+ return g_strdup (attr->a_value);
+}
+
+
+RakiaDirection
+rakia_sip_media_get_remote_direction (RakiaSipMedia *media)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ if (priv->remote_media == NULL)
+ return RAKIA_DIRECTION_NONE;
+
+ return rakia_direction_from_remote_media (priv->remote_media);
+}
+
+
+static void
+priv_update_sending (RakiaSipMedia *media, RakiaDirection send_direction)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+ RakiaDirection recv_direction;
+
+ /* Only keep the receiving bit from the current direction */
+ recv_direction = priv->direction & RAKIA_DIRECTION_RECEIVE;
+
+ /* And only the sending bit from the new direction */
+ send_direction &= RAKIA_DIRECTION_SEND;
+
+ rakia_sip_media_set_direction (media, send_direction | recv_direction);
+}
+
+
+void
+rakia_sip_media_set_requested_direction (RakiaSipMedia *media,
+ RakiaDirection direction)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ if (priv->requested_direction == direction)
+ return;
+
+ priv->requested_direction = direction;
+
+ if (priv->requested_direction == priv->direction)
+ return;
+
+ rakia_sip_media_local_updated (media);
+}
+
+static void push_remote_codecs (RakiaSipMedia *media)
+{
+ RakiaSipMediaPrivate *priv;
+ GPtrArray *codecs;
+ const sdp_media_t *sdpmedia;
+ const sdp_rtpmap_t *rtpmap;
+ gchar *ptime = NULL;
+ gchar *max_ptime = NULL;
+
+ DEBUG ("enter");
+
+ priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ sdpmedia = priv->remote_media;
+ if (sdpmedia == NULL)
+ {
+ MEDIA_DEBUG (media, "remote media description is not received yet");
+ return;
+ }
+
+
+ ptime = rakia_sdp_get_string_attribute (sdpmedia->m_attributes, "ptime");
+ if (ptime == NULL)
+ {
+ g_object_get (priv->session,
+ "remote-ptime", &ptime,
+ NULL);
+ }
+ max_ptime = rakia_sdp_get_string_attribute (sdpmedia->m_attributes, "maxptime");
+ if (max_ptime == NULL)
+ {
+ g_object_get (priv->session,
+ "remote-max-ptime", &max_ptime,
+ NULL);
+ }
+
+
+ codecs = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) rakia_sip_codec_free);
+
+ rtpmap = sdpmedia->m_rtpmaps;
+ while (rtpmap)
+ {
+ RakiaSipCodec *codec;
+
+
+ codec = rakia_sip_codec_new (rtpmap->rm_pt, rtpmap->rm_encoding,
+ rtpmap->rm_rate,
+ rtpmap->rm_params ? atoi(rtpmap->rm_params) : 0);
+
+
+ rakia_codec_param_parse (priv->media_type, codec,
+ rtpmap->rm_fmtp);
+
+ if (ptime != NULL)
+ rakia_sip_codec_add_param (codec, "ptime", ptime);
+ if (max_ptime != NULL)
+ rakia_sip_codec_add_param (codec, "maxptime", max_ptime);
+
+ g_ptr_array_add (codecs, codec);
+
+ rtpmap = rtpmap->rm_next;
+ }
+
+ g_free (ptime);
+ g_free (max_ptime);
+
+ if (priv->remote_codec_offer)
+ g_ptr_array_unref (priv->remote_codec_offer);
+
+ priv->remote_codec_offer = codecs;
+
+ g_signal_emit (media, signals[SIG_REMOTE_CODEC_OFFER_UPDATED], 0,
+ priv->codec_intersect_pending);
+
+ MEDIA_DEBUG(media, "emitting %d remote codecs to the handler",
+ codecs->len);
+}
+
+
+static void push_remote_candidates (RakiaSipMedia *media)
+{
+ RakiaSipMediaPrivate *priv;
+ RakiaSipCandidate *rtp_cand;
+ const sdp_media_t *sdp_media;
+ const sdp_connection_t *sdp_conn;
+ GPtrArray *candidates;
+ guint port;
+
+ DEBUG("enter");
+
+ priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ sdp_media = priv->remote_media;
+ if (sdp_media == NULL)
+ {
+ MEDIA_DEBUG (media, "remote media description is not received yet");
+ return;
+ }
+
+
+ /* use the address from SDP c-line as the only remote candidate */
+
+ sdp_conn = sdp_media_connections (sdp_media);
+ g_return_if_fail (sdp_conn != NULL);
+
+ port = (guint) sdp_media->m_port;
+
+
+ rtp_cand = rakia_sip_candidate_new (1, sdp_conn->c_address, port, NULL, 0);
+ candidates = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) rakia_sip_candidate_free);
+
+ g_ptr_array_add (candidates, rtp_cand);
+
+
+ MEDIA_DEBUG (media, "remote RTP address=<%s>, port=<%u>", sdp_conn->c_address, port);
+
+ if (!rakia_sdp_rtcp_bandwidth_throttled (sdp_media->m_bandwidths))
+ {
+ gboolean session_rtcp_enabled = TRUE;
+
+ g_object_get (priv->session,
+ "rtcp-enabled", &session_rtcp_enabled,
+ NULL);
+
+ if (session_rtcp_enabled)
+ {
+ const sdp_attribute_t *rtcp_attr;
+ const char *rtcp_address;
+ guint rtcp_port;
+ RakiaSipCandidate *rtcp_cand;
+
+ /* Get the port and optional address for RTCP accordingly to RFC 3605 */
+ rtcp_address = sdp_conn->c_address;
+ rtcp_attr = sdp_attribute_find (sdp_media->m_attributes, "rtcp");
+ if (rtcp_attr == NULL || rtcp_attr->a_value == NULL)
+ {
+ rtcp_port = port + 1;
+ }
+ else
+ {
+ const char *rest;
+ rtcp_port = (guint) g_ascii_strtoull (rtcp_attr->a_value,
+ (gchar **) &rest,
+ 10);
+ if (rtcp_port != 0
+ && (strncmp (rest, " IN IP4 ", 8) == 0
+ || strncmp (rest, " IN IP6 ", 8) == 0))
+ rtcp_address = rest + 8;
+ }
+
+
+ rtcp_cand = rakia_sip_candidate_new (2, rtcp_address, rtcp_port,
+ NULL, 0);
+ g_ptr_array_add (candidates, rtcp_cand);
+ }
+ }
+
+ if (priv->remote_candidates != NULL)
+ g_ptr_array_unref (priv->remote_candidates);
+ priv->remote_candidates = candidates;
+
+ g_signal_emit (media, signals[SIG_REMOTE_CANDIDATES_UPDATED], 0);
+}
+
+
+/*
+ * Sets the remote candidates and codecs for this stream, as
+ * received via signaling.
+ *
+ * Parses the SDP information, updates TP remote candidates and
+ * codecs if the client is ready.
+ *
+ * Note that the pointer to the media description structure is saved,
+ * implying that the structure shall not go away for the lifetime of
+ * the stream, preferably kept in the memory home attached to
+ * the session object.
+ *
+ * @return TRUE if the remote information has been accepted,
+ * FALSE if the update is not acceptable.
+ */
+gboolean
+rakia_sip_media_set_remote_media (RakiaSipMedia *media,
+ const sdp_media_t *new_media,
+ gboolean authoritative)
+{
+ RakiaSipMediaPrivate *priv;
+ sdp_connection_t *sdp_conn;
+ const sdp_media_t *old_media;
+ gboolean transport_changed = TRUE;
+ gboolean codecs_changed = TRUE;
+ guint new_direction;
+ RakiaDirection direction_up_mask;
+
+ DEBUG ("enter");
+
+ priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ /* Do sanity checks */
+
+ g_return_val_if_fail (new_media != NULL, FALSE);
+
+ if (new_media->m_rejected || new_media->m_port == 0)
+ {
+ MEDIA_DEBUG (media, "the media is rejected remotely");
+ return FALSE;
+ }
+
+ if (new_media->m_proto != sdp_proto_rtp)
+ {
+ MEDIA_MESSAGE (media, "the remote protocol is not RTP/AVP");
+ return FALSE;
+ }
+
+ sdp_conn = sdp_media_connections (new_media);
+ if (sdp_conn == NULL)
+ {
+ MEDIA_MESSAGE (media, "no valid remote connections");
+ return FALSE;
+ }
+
+ if (new_media->m_rtpmaps == NULL)
+ {
+ MEDIA_MESSAGE (media, "no remote codecs");
+ return FALSE;
+ }
+
+ /* Note: always update the pointer to the current media structure
+ * because of memory management done in the session object */
+ old_media = priv->remote_media;
+ priv->remote_media = new_media;
+
+ /* Check if there was any media update at all */
+
+ new_direction = rakia_direction_from_remote_media (new_media);
+
+
+ /*
+ * Do not allow:
+ * 1) an answer to bump up directions beyond what's been offered;
+ * 2) an offer to remove the local hold.
+ */
+ if (authoritative)
+ direction_up_mask = priv->hold_requested ?
+ RAKIA_DIRECTION_SEND : RAKIA_DIRECTION_BIDIRECTIONAL;
+ else
+ direction_up_mask = 0;
+
+ /* Make sure the peer can only enable sending or receiving direction
+ * if it's allowed to */
+ new_direction &= priv->requested_direction | direction_up_mask;
+
+
+ if (sdp_media_cmp (old_media, new_media) == 0)
+ {
+ MEDIA_DEBUG (media, "no media changes detected for the media");
+ goto done;
+ }
+
+ if (old_media != NULL)
+ {
+ /* Check if the transport candidate needs to be changed */
+ if (!sdp_connection_cmp (sdp_media_connections (old_media), sdp_conn))
+ transport_changed = FALSE;
+
+ /* Check if the codec list needs to be updated */
+ codecs_changed = rakia_sdp_codecs_differ (old_media->m_rtpmaps,
+ new_media->m_rtpmaps);
+
+ /* Disable sending at this point if it will be disabled
+ * accordingly to the new direction */
+ priv_update_sending (media, new_direction & RAKIA_DIRECTION_SEND);
+ }
+
+ /* First add the new candidate, then update the codec set.
+ * The offerer isn't supposed to send us anything from the new transport
+ * until we accept; if it's the answer, both orderings have problems. */
+
+ if (transport_changed)
+ {
+ /* Make sure we stop sending before we use the new set of codecs
+ * intended for the new connection
+ * This only applies if we were already sending to somewhere else before.
+ */
+ if (codecs_changed && old_media)
+ {
+ priv->push_candidates_on_new_codecs = TRUE;
+ if (priv->remote_candidates != NULL)
+ {
+ g_ptr_array_unref (priv->remote_candidates);
+ priv->remote_candidates = NULL;
+ g_signal_emit (media, signals[SIG_REMOTE_CANDIDATES_UPDATED], 0);
+ }
+ }
+ else
+ {
+ push_remote_candidates (media);
+ }
+ }
+
+ if (codecs_changed)
+ {
+ if (authoritative)
+ priv->codec_intersect_pending = TRUE;
+
+ if (priv->remote_codec_offer == NULL)
+ push_remote_codecs (media);
+ else
+ priv->push_remote_codecs_pending = TRUE;
+ }
+
+ /* TODO: this will go to session change commit code */
+
+ done:
+
+ /* Set the final direction */
+ rakia_sip_media_set_direction (media, new_direction);
+
+ return TRUE;
+}
+
+RakiaDirection
+rakia_sip_media_get_requested_direction (RakiaSipMedia *self)
+{
+ return self->priv->requested_direction;
+}
+
+
+gboolean
+rakia_sip_media_is_codec_intersect_pending (RakiaSipMedia *self)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (self);
+
+ return priv->codec_intersect_pending;
+}
+
+gboolean
+rakia_sip_media_is_ready (RakiaSipMedia *self)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (self);
+
+ MEDIA_DEBUG (self, "is_ready, requested_recv: %d can_recv: %d "
+ "local_cand_prep: %d local_codecs: %p local_inter_pending: %d",
+ priv->requested_direction & RAKIA_DIRECTION_RECEIVE,
+ priv->can_receive,
+ self->priv->local_candidates_prepared,
+ self->priv->local_codecs,
+ priv->codec_intersect_pending);
+
+ if (priv->requested_direction & RAKIA_DIRECTION_RECEIVE &&
+ !priv->can_receive &&
+ !priv->hold_requested)
+ return FALSE;
+
+ return (self->priv->local_candidates_prepared &&
+ self->priv->local_codecs &&
+ !priv->codec_intersect_pending);
+}
+
+void
+rakia_sip_media_take_local_codecs (RakiaSipMedia *self, GPtrArray *local_codecs)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (self);
+
+ if (priv->local_codecs)
+ g_ptr_array_unref (priv->local_codecs);
+ priv->local_codecs = local_codecs;
+
+ if (priv->push_remote_codecs_pending)
+ {
+ priv->push_remote_codecs_pending = FALSE;
+ push_remote_codecs (self);
+ }
+ else
+ {
+
+ if (priv->push_candidates_on_new_codecs)
+ {
+ /* Push the new candidates now that we have new codecs */
+ priv->push_candidates_on_new_codecs = FALSE;
+ push_remote_candidates (self);
+ }
+
+ if (priv->codec_intersect_pending)
+ {
+
+ priv->codec_intersect_pending = FALSE;
+ if (rakia_sip_media_is_ready (self))
+ {
+ g_signal_emit (self, signals[SIG_LOCAL_NEGOTIATION_COMPLETE], 0,
+ TRUE);
+ g_ptr_array_unref (priv->remote_codec_offer);
+ priv->remote_codec_offer = NULL;
+ }
+ }
+ else
+ {
+ rakia_sip_media_local_updated (self);
+ }
+ }
+}
+
+
+void
+rakia_sip_media_take_local_candidate (RakiaSipMedia *self,
+ RakiaSipCandidate *candidate)
+{
+ g_return_if_fail (!self->priv->local_candidates_prepared);
+
+ if (self->priv->local_candidates == NULL)
+ self->priv->local_candidates = g_ptr_array_new_with_free_func (
+ (GDestroyNotify) rakia_sip_candidate_free);
+
+ g_ptr_array_add (self->priv->local_candidates, candidate);
+}
+
+gboolean
+rakia_sip_media_local_candidates_prepared (RakiaSipMedia *self)
+{
+ RakiaSipCandidate *rtp_cand = NULL;
+
+ if (self->priv->local_candidates == NULL)
+ return FALSE;
+
+ if (self->priv->local_candidates_prepared)
+ return FALSE;
+
+ priv_get_preferred_local_candidates (self, &rtp_cand, NULL);
+
+ if (!rtp_cand)
+ return FALSE;
+
+ self->priv->local_candidates_prepared = TRUE;
+
+ if (rakia_sip_media_is_ready (self))
+ {
+ g_signal_emit (self, signals[SIG_LOCAL_NEGOTIATION_COMPLETE], 0,
+ TRUE);
+ }
+
+ return TRUE;
+}
+
+GPtrArray *
+rakia_sip_media_get_remote_codec_offer (RakiaSipMedia *self)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (self);
+
+ return priv->remote_codec_offer;
+}
+
+GPtrArray *
+rakia_sip_media_get_remote_candidates (RakiaSipMedia *self)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (self);
+
+ return priv->remote_candidates;
+}
+
+gboolean
+rakia_sip_media_is_created_locally (RakiaSipMedia *self)
+{
+ return self->priv->created_locally;
+}
+
+void
+rakia_sip_media_local_updated (RakiaSipMedia *self)
+{
+ g_signal_emit (self, signals[SIG_LOCAL_UPDATED], 0);
+}
+
+RakiaSipMedia *
+rakia_sip_media_new (RakiaSipSession *session,
+ TpMediaStreamType media_type,
+ const gchar *name,
+ RakiaDirection requested_direction,
+ gboolean created_locally,
+ gboolean hold_requested)
+{
+ RakiaSipMedia *self;
+
+ g_return_val_if_fail (media_type == TP_MEDIA_STREAM_TYPE_VIDEO ||
+ media_type == TP_MEDIA_STREAM_TYPE_AUDIO, NULL);
+ g_return_val_if_fail (requested_direction <= RAKIA_DIRECTION_BIDIRECTIONAL,
+ NULL);
+
+ self = g_object_new (RAKIA_TYPE_SIP_MEDIA, NULL);
+
+ self->priv->session = session;
+ self->priv->media_type = media_type;
+ self->priv->name = g_strdup (name);
+ self->priv->requested_direction = requested_direction;
+ self->priv->created_locally = created_locally;
+ self->priv->hold_requested = hold_requested;
+
+ return self;
+}
+
+const gchar *
+rakia_sip_media_get_name (RakiaSipMedia *media)
+{
+ return media->priv->name;
+}
+
+RakiaSipSession *
+rakia_sip_media_get_session (RakiaSipMedia *media)
+{
+ return media->priv->session;
+}
+
+void
+rakia_sip_media_codecs_rejected (RakiaSipMedia *media)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ if (priv->push_remote_codecs_pending)
+ {
+ priv->push_remote_codecs_pending = FALSE;
+ push_remote_codecs (media);
+ }
+ else
+ {
+ priv->codec_intersect_pending = FALSE;
+ g_signal_emit (media, signals[SIG_LOCAL_NEGOTIATION_COMPLETE], 0, FALSE);
+ g_ptr_array_unref (priv->remote_codec_offer);
+ priv->remote_codec_offer = NULL;
+ }
+}
+
+RakiaDirection
+rakia_sip_media_get_direction (RakiaSipMedia *media)
+{
+ return media->priv->direction;
+}
+
+
+void
+rakia_sip_media_set_hold_requested (RakiaSipMedia *media,
+ gboolean hold_requested)
+{
+ if (media->priv->hold_requested == hold_requested)
+ return;
+
+ media->priv->hold_requested = hold_requested;
+}
+
+gboolean
+rakia_sip_media_get_hold_requested (RakiaSipMedia *media)
+{
+ return media->priv->hold_requested;
+}
+
+
+gboolean
+rakia_sip_media_is_held (RakiaSipMedia *media)
+{
+ return !(media->priv->direction & RAKIA_DIRECTION_SEND);
+}
+
+void
+rakia_sip_media_set_can_receive (RakiaSipMedia *media, gboolean can_receive)
+{
+ RakiaSipMediaPrivate *priv = RAKIA_SIP_MEDIA_GET_PRIVATE (media);
+
+ if (priv->can_receive == can_receive)
+ return;
+
+ priv->can_receive = can_receive;
+
+ if (rakia_sip_media_is_ready (media))
+ {
+ g_signal_emit (media, signals[SIG_LOCAL_NEGOTIATION_COMPLETE], 0, TRUE);
+ if (priv->remote_codec_offer)
+ {
+ g_ptr_array_unref (priv->remote_codec_offer);
+ priv->remote_codec_offer = NULL;
+ }
+ }
+}
+
+gboolean
+rakia_sip_media_has_remote_media (RakiaSipMedia *media)
+{
+ return (media->priv->remote_media != NULL);
+}
+
+const gchar *
+rakia_direction_to_string (RakiaDirection direction)
+{
+ switch (direction)
+ {
+ case RAKIA_DIRECTION_NONE:
+ return "none";
+ case RAKIA_DIRECTION_SEND:
+ return "send";
+ case RAKIA_DIRECTION_RECEIVE:
+ return "recv";
+ case RAKIA_DIRECTION_BIDIRECTIONAL:
+ return "bidi";
+ default:
+ g_warning ("Invalid direction %d", direction);
+ return "broken";
+ }
+}
diff --git a/rakia/sip-media.h b/rakia/sip-media.h
new file mode 100644
index 0000000..73598aa
--- /dev/null
+++ b/rakia/sip-media.h
@@ -0,0 +1,182 @@
+/*
+ * rakia-sip-media.h - Header for RakiaSipMedia
+ * Copyright (C) 2005-2012 Collabora Ltd.
+ * Copyright (C) 2005-2010 Nokia Corporation
+ *
+ * This work is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this work; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __RAKIA_SIP_MEDIA_H__
+#define __RAKIA_SIP_MEDIA_H__
+
+#include <glib-object.h>
+#include <sofia-sip/sdp.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+G_BEGIN_DECLS
+
+
+
+typedef struct _RakiaSipMedia RakiaSipMedia;
+typedef struct _RakiaSipMediaClass RakiaSipMediaClass;
+typedef struct _RakiaSipMediaPrivate RakiaSipMediaPrivate;
+
+typedef struct _RakiaSipSession RakiaSipSession;
+
+struct _RakiaSipMediaClass {
+ GObjectClass parent_class;
+};
+
+struct _RakiaSipMedia {
+ GObject parent;
+ RakiaSipMediaPrivate *priv;
+};
+
+typedef enum {
+ RAKIA_DIRECTION_NONE = 0,
+ RAKIA_DIRECTION_SEND = 1,
+ RAKIA_DIRECTION_RECEIVE = 2,
+ RAKIA_DIRECTION_BIDIRECTIONAL = 3,
+} RakiaDirection;
+
+typedef struct _RakiaSipCodecParam {
+ gchar *name;
+ gchar *value;
+} RakiaSipCodecParam;
+
+typedef struct _RakiaSipCodec {
+ guint id;
+ gchar *encoding_name;
+ guint clock_rate;
+ guint channels;
+ GPtrArray *params;
+} RakiaSipCodec;
+
+
+typedef struct _RakiaSipCandidate {
+ guint component;
+ gchar *ip;
+ guint port;
+ gchar *foundation;
+ guint priority;
+} RakiaSipCandidate;
+
+GType rakia_sip_media_get_type(void);
+
+/* TYPE MACROS */
+#define RAKIA_TYPE_SIP_MEDIA \
+ (rakia_sip_media_get_type())
+#define RAKIA_SIP_MEDIA(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), RAKIA_TYPE_SIP_MEDIA, RakiaSipMedia))
+#define RAKIA_SIP_MEDIA_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), RAKIA_TYPE_SIP_MEDIA, RakiaSipMediaClass))
+#define RAKIA_IS_SIP_MEDIA(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_SIP_MEDIA))
+#define RAKIA_IS_SIP_MEDIA_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_SIP_MEDIA))
+#define RAKIA_SIP_MEDIA_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), RAKIA_TYPE_SIP_MEDIA, RakiaSipMediaClass))
+
+/* For RakiaSipSession */
+
+gchar * rakia_sdp_get_string_attribute (const sdp_attribute_t *attrs,
+ const char *name);
+
+gboolean rakia_sip_media_set_remote_media (RakiaSipMedia *media,
+ const sdp_media_t *new_media, gboolean authoritative);
+
+void rakia_sip_media_generate_sdp (RakiaSipMedia *media, GString *out,
+ gboolean authoritative);
+
+gboolean rakia_sip_media_is_ready (RakiaSipMedia *self);
+
+gboolean rakia_sip_media_is_codec_intersect_pending (RakiaSipMedia *self);
+
+RakiaDirection rakia_direction_from_remote_media (const sdp_media_t *media);
+
+void rakia_sip_media_local_updated (RakiaSipMedia *self); /* ?? */
+
+void rakia_sip_media_set_hold_requested (RakiaSipMedia *media,
+ gboolean hold_requested);
+gboolean rakia_sip_media_is_held (RakiaSipMedia *media);
+
+RakiaSipMedia *rakia_sip_media_new (RakiaSipSession *session,
+ TpMediaStreamType media_type,
+ const gchar *name,
+ RakiaDirection requested_direction,
+ gboolean created_locally,
+ gboolean hold_requested);
+
+
+const gchar * sip_media_get_media_type_str (RakiaSipMedia *self);
+
+/* Functions for both */
+
+TpMediaStreamType rakia_sip_media_get_media_type (RakiaSipMedia *self);
+
+/* Functions for the upper layers */
+
+
+RakiaSipCodec* rakia_sip_codec_new (guint id, const gchar *encoding_name,
+ guint clock_rate, guint channels);
+void rakia_sip_codec_add_param (RakiaSipCodec *codec, const gchar *name,
+ const gchar *value);
+void rakia_sip_codec_free (RakiaSipCodec *codec);
+
+RakiaSipCandidate* rakia_sip_candidate_new (guint component,
+ const gchar *ip, guint port,
+ const gchar *foundation, guint priority);
+void rakia_sip_candidate_free (RakiaSipCandidate *candidate);
+
+void rakia_sip_media_take_local_codecs (RakiaSipMedia *self,
+ GPtrArray *local_codecs);
+void rakia_sip_media_take_local_candidate (RakiaSipMedia *self,
+ RakiaSipCandidate *candidate);
+gboolean rakia_sip_media_local_candidates_prepared (RakiaSipMedia *self);
+
+GPtrArray *rakia_sip_media_get_remote_codec_offer (RakiaSipMedia *self);
+GPtrArray *rakia_sip_media_get_remote_candidates (RakiaSipMedia *self);
+
+const gchar *rakia_sip_media_get_name (RakiaSipMedia *media);
+
+RakiaSipSession *rakia_sip_media_get_session (RakiaSipMedia *media);
+
+void rakia_sip_media_codecs_rejected (RakiaSipMedia *media);
+
+gboolean rakia_sip_media_is_created_locally (RakiaSipMedia *self);
+
+void rakia_sip_media_set_requested_direction (RakiaSipMedia *media,
+ RakiaDirection direction);
+
+RakiaDirection rakia_sip_media_get_direction (RakiaSipMedia *media);
+
+RakiaDirection rakia_sip_media_get_remote_direction (RakiaSipMedia *media);
+
+RakiaDirection rakia_sip_media_get_requested_direction (
+ RakiaSipMedia *self);
+
+gboolean rakia_sip_media_get_hold_requested (RakiaSipMedia *media);
+
+void rakia_sip_media_set_can_receive (RakiaSipMedia *media,
+ gboolean can_receive);
+
+gboolean rakia_sip_media_has_remote_media (RakiaSipMedia *media);
+
+const gchar *rakia_direction_to_string (RakiaDirection direction);
+
+G_END_DECLS
+
+#endif /* #ifndef __RAKIA_SIP_MEDIA_H__*/
diff --git a/rakia/sip-session.c b/rakia/sip-session.c
new file mode 100644
index 0000000..05e147f
--- /dev/null
+++ b/rakia/sip-session.c
@@ -0,0 +1,1826 @@
+/*
+ * rakia-sip-session.c - Source for RakiaSipSession
+ * Copyright (C) 2005-2012 Collabora Ltd.
+ * @author Olivier Crete <olivier.crete@collabora.com>
+ * Copyright (C) 2005-2010 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * Based on telepathy-gabble implementation
+ * @author Ole Andre Vadla Ravnaas <ole.andre.ravnaas@collabora.co.uk>
+ *
+ * This work is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this work; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "rakia/sip-session.h"
+
+#include <string.h>
+
+#include <sofia-sip/sip_status.h>
+
+
+
+#define DEBUG_FLAG RAKIA_DEBUG_MEDIA
+#include "rakia/debug.h"
+#include "rakia/base-connection.h"
+#include "rakia/event-target.h"
+#include "rakia/sip-media.h"
+
+
+/* The timeout for outstanding re-INVITE transactions in seconds.
+ * Chosen to match the allowed cancellation timeout for proxies
+ * described in RFC 3261 Section 13.3.1.1 */
+#define RAKIA_REINVITE_TIMEOUT 180
+
+static void event_target_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE(RakiaSipSession,
+ rakia_sip_session,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (RAKIA_TYPE_EVENT_TARGET, event_target_init)
+)
+
+
+
+#ifdef ENABLE_DEBUG
+
+/**
+ * Sip session states:
+ * - created, objects created, local cand/codec query ongoing
+ * - invite-sent, an INVITE with local SDP sent, awaiting response
+ * - invite-received, a remote INVITE received, response is pending
+ * - response-received, a 200 OK received, codec intersection is in progress
+ * - active, codecs and candidate pairs have been negotiated (note,
+ * SteamEngine might still fail to verify connectivity and report
+ * an error)
+ * - reinvite-sent, a local re-INVITE sent, response is pending
+ * - reinvite-received, a remote re-INVITE received, response is pending
+ * - ended, session has ended
+ */
+static const char *const session_states[NUM_RAKIA_SIP_SESSION_STATES] =
+{
+ "created",
+ "invite-sent",
+ "invite-received",
+ "response-received",
+ "active",
+ "reinvite-sent",
+ "reinvite-received",
+ "reinvite-pending",
+ "ended"
+};
+
+#define SESSION_DEBUG(session, format, ...) \
+ rakia_log (DEBUG_FLAG, G_LOG_LEVEL_DEBUG, "%s [%-17s]: " format, \
+ G_STRFUNC, session_states[(session)->priv->state],##__VA_ARGS__)
+
+#define SESSION_MESSAGE(session, format, ...) \
+ rakia_log (DEBUG_FLAG, G_LOG_LEVEL_MESSAGE, "%s [%-17s]: " format, \
+ G_STRFUNC, session_states[(session)->priv->state],##__VA_ARGS__)
+
+#else /* !ENABLE_DEBUG */
+
+#define SESSION_DEBUG(session, format, ...) G_STMT_START { } G_STMT_END
+#define SESSION_MESSAGE(session, format, ...) G_STMT_START { } G_STMT_END
+
+#endif /* ENABLE_DEBUG */
+
+
+/* properties */
+enum
+{
+ PROP_REMOTE_PTIME = 1,
+ PROP_REMOTE_MAX_PTIME,
+ PROP_RTCP_ENABLED,
+ PROP_HOLD_STATE,
+ PROP_REMOTE_HELD,
+ LAST_PROPERTY
+};
+
+
+/* signals */
+enum
+{
+ SIG_ENDED,
+ SIG_RINGING,
+ SIG_QUEUED,
+ SIG_IN_PROGRESS,
+ SIG_INCOMING_CALL,
+ SIG_MEDIA_ADDED,
+ SIG_MEDIA_REMOVED,
+ SIG_STATE_CHANGED,
+ SIG_START_RECEIVING,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+
+/* private structure */
+struct _RakiaSipSessionPrivate
+{
+ nua_handle_t *nua_op; /* see gobj. prop. 'nua-handle' */
+ RakiaSipSessionState state; /* session state */
+
+ gboolean immutable_streams; /* immutable streams */
+
+ GPtrArray *medias;
+
+ gboolean incoming; /* Is this an incoming call ? (or outgoing */
+ RakiaBaseConnection *conn;
+
+ nua_saved_event_t saved_event[1]; /* Saved incoming request event */
+ TpLocalHoldState hold_state; /* local hold state aggregated from stream directions */
+ gboolean hold_requested; /* if the local hold has been requested by the user */
+ gchar *remote_ptime; /* see gobj. prop. 'remote-ptime' */
+ gchar *remote_max_ptime; /* see gobj. prop. 'remote-max-ptime' */
+ guint remote_media_count; /* number of m= last seen in a remote offer */
+ gboolean rtcp_enabled; /* see gobj. prop. 'rtcp-enabled' */
+ gchar *local_sdp; /* local session as SDP string */
+ su_home_t *home; /* Sofia memory home for remote SDP session structure */
+ su_home_t *backup_home; /* Sofia memory home for previous generation remote SDP session*/
+ sdp_session_t *remote_sdp; /* last received remote session */
+ sdp_session_t *backup_remote_sdp; /* previous remote session */
+
+ gboolean accepted; /*< session has been locally accepted for use */
+
+ gboolean pending_offer; /*< local media have been changed, but a re-INVITE is pending */
+ guint glare_timer_id;
+ gboolean remote_held;
+};
+
+
+#define RAKIA_SIP_SESSION_GET_PRIVATE(session) ((session)->priv)
+
+
+
+static void rakia_sip_session_dispose (GObject *object);
+static void rakia_sip_session_finalize (GObject *object);
+
+static TpMediaStreamType rakia_media_type (sdp_media_e sip_mtype);
+
+
+static void priv_session_invite (RakiaSipSession *session, gboolean reinvite);
+static gboolean priv_update_remote_media (RakiaSipSession *self,
+ gboolean authoritative);
+static void priv_request_response_step (RakiaSipSession *session);
+
+static void
+event_target_init(gpointer g_iface, gpointer iface_data)
+{
+}
+
+static void
+null_safe_unref (gpointer data)
+{
+ if (data)
+ g_object_unref (data);
+}
+
+static void
+rakia_sip_session_init (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ RAKIA_TYPE_SIP_SESSION, RakiaSipSessionPrivate);
+
+ self->priv = priv;
+
+ priv->state = RAKIA_SIP_SESSION_STATE_CREATED;
+ priv->rtcp_enabled = TRUE;
+
+ /* allocate any data required by the object here */
+ priv->medias = g_ptr_array_new_with_free_func (null_safe_unref);
+}
+
+static void rakia_sip_session_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaSipSession *session = RAKIA_SIP_SESSION (object);
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+
+ switch (property_id)
+ {
+ case PROP_REMOTE_PTIME:
+ g_value_set_string (value, priv->remote_ptime);
+ break;
+ case PROP_REMOTE_MAX_PTIME:
+ g_value_set_string (value, priv->remote_max_ptime);
+ break;
+ case PROP_RTCP_ENABLED:
+ g_value_set_boolean (value, priv->rtcp_enabled);
+ break;
+ case PROP_HOLD_STATE:
+ g_value_set_uint (value, priv->hold_state);
+ break;
+ case PROP_REMOTE_HELD:
+ g_value_set_boolean (value, priv->remote_held);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_sip_session_class_init (RakiaSipSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (RakiaSipSessionPrivate));
+
+ object_class->get_property = rakia_sip_session_get_property;
+ object_class->dispose = rakia_sip_session_dispose;
+ object_class->finalize = rakia_sip_session_finalize;
+
+ param_spec = g_param_spec_string ("remote-ptime",
+ "a=ptime value of remote media session",
+ "Value of the a=ptime attribute of the remote media session, or NULL",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_PTIME, param_spec);
+
+ param_spec = g_param_spec_string ("remote-max-ptime",
+ "a=maxptime value of remote media session",
+ "Value of the a=maxptime attribute of the remote media session, or NULL",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_MAX_PTIME, param_spec);
+
+ param_spec = g_param_spec_boolean ("rtcp-enabled", "RTCP enabled",
+ "Is RTCP enabled session-wide",
+ TRUE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_RTCP_ENABLED, param_spec);
+
+
+ param_spec = g_param_spec_uint ("hold-state", "Local Hold State",
+ "Is the call held or not",
+ 0, NUM_TP_LOCAL_HOLD_STATES - 1,
+ TP_LOCAL_HOLD_STATE_UNHELD,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_HOLD_STATE, param_spec);
+
+
+ param_spec = g_param_spec_boolean ("remote-held", "Remote Held",
+ "Are we remotely held",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_REMOTE_HELD, param_spec);
+
+
+
+ signals[SIG_ENDED] =
+ g_signal_new ("ended",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 3, G_TYPE_BOOLEAN, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[SIG_RINGING] =
+ g_signal_new ("ringing",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ signals[SIG_QUEUED] =
+ g_signal_new ("queued",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ signals[SIG_IN_PROGRESS] =
+ g_signal_new ("in-progress",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ signals[SIG_INCOMING_CALL] =
+ g_signal_new ("incoming-call",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ signals[SIG_MEDIA_ADDED] =
+ g_signal_new ("media-added",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, RAKIA_TYPE_SIP_MEDIA);
+
+ signals[SIG_MEDIA_REMOVED] =
+ g_signal_new ("media-removed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 1, RAKIA_TYPE_SIP_MEDIA);
+
+ signals[SIG_STATE_CHANGED] =
+ g_signal_new ("state-changed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
+
+ signals[SIG_START_RECEIVING] =
+ g_signal_new ("start-receiving",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+}
+
+
+static void
+rakia_sip_session_dispose (GObject *object)
+{
+ RakiaSipSession *self = RAKIA_SIP_SESSION (object);
+
+ SESSION_DEBUG (self, "enter");
+
+ if (self->priv->medias)
+ {
+ g_ptr_array_unref (self->priv->medias);
+ self->priv->medias = NULL;
+ }
+
+ if (self->priv->glare_timer_id)
+ {
+ g_source_remove (self->priv->glare_timer_id);
+ self->priv->glare_timer_id = 0;
+ }
+
+ tp_clear_object (&self->priv->conn);
+
+ if (self->priv->remote_sdp != NULL)
+ {
+ self->priv->remote_sdp = NULL;
+ g_assert (self->priv->home != NULL);
+ su_home_unref (self->priv->home);
+ self->priv->home = NULL;
+ }
+
+ if (self->priv->backup_remote_sdp != NULL)
+ {
+ self->priv->backup_remote_sdp = NULL;
+ g_assert (self->priv->backup_home != NULL);
+ su_home_unref (self->priv->backup_home);
+ self->priv->backup_home = NULL;
+ }
+
+ if (G_OBJECT_CLASS (rakia_sip_session_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_sip_session_parent_class)->dispose (object);
+
+ SESSION_DEBUG (self, "exit");
+}
+
+static void
+rakia_sip_session_finalize (GObject *object)
+{
+ RakiaSipSession *self = RAKIA_SIP_SESSION (object);
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ g_assert (priv->nua_op == NULL);
+
+ g_free (priv->local_sdp);
+
+ G_OBJECT_CLASS (rakia_sip_session_parent_class)->finalize (object);
+
+ DEBUG("exit");
+}
+
+void
+rakia_sip_session_change_state (RakiaSipSession *self,
+ RakiaSipSessionState new_state)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ guint old_state;
+
+ if (priv->state == new_state)
+ return;
+
+ SESSION_DEBUG (self, "changing state to %s", session_states[new_state]);
+
+ old_state = priv->state;
+ priv->state = new_state;
+
+ switch (new_state)
+ {
+ case RAKIA_SIP_SESSION_STATE_CREATED:
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED:
+ case RAKIA_SIP_SESSION_STATE_INVITE_SENT:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_SENT:
+ case RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_PENDING:
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+ break;
+ case RAKIA_SIP_SESSION_STATE_ENDED:
+ SESSION_DEBUG (self, "destroying the NUA handle %p", priv->nua_op);
+ if (priv->nua_op != NULL)
+ {
+ nua_handle_destroy (priv->nua_op);
+ priv->nua_op = NULL;
+ }
+ break;
+ case NUM_RAKIA_SIP_SESSION_STATES:
+ g_assert_not_reached();
+
+ /* Don't add default because we want to be warned by the compiler
+ * about unhandled states */
+ }
+
+ g_signal_emit (self, signals[SIG_STATE_CHANGED], 0, old_state, new_state);
+
+ if (new_state == RAKIA_SIP_SESSION_STATE_ACTIVE && priv->pending_offer)
+ priv_session_invite (self, TRUE);
+}
+
+
+static void
+priv_zap_event (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ if (priv->saved_event[0])
+ {
+ nua_event_data_t const *ev_data = nua_event_data (priv->saved_event);
+ g_assert (ev_data != NULL);
+ WARNING ("zapping unhandled saved event '%s'", nua_event_name (ev_data->e_event));
+ nua_destroy_event (priv->saved_event);
+ }
+}
+
+
+static void
+priv_save_event (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ priv_zap_event (self);
+
+ rakia_base_connection_save_event (priv->conn, priv->saved_event);
+
+#ifdef ENABLE_DEBUG
+ {
+ nua_event_data_t const *ev_data = nua_event_data (priv->saved_event);
+ g_assert (ev_data != NULL);
+ DEBUG("saved the last event: %s %hd %s", nua_event_name (ev_data->e_event), ev_data->e_status, ev_data->e_phrase);
+ }
+#endif
+}
+
+static void
+rakia_sip_session_receive_reinvite (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ /* Check for permitted state transitions */
+ switch (priv->state)
+ {
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+ case RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED:
+ break;
+ case RAKIA_SIP_SESSION_STATE_REINVITE_PENDING:
+ g_source_remove (priv->glare_timer_id);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ priv_save_event (self);
+
+ rakia_sip_session_change_state (self,
+ RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED);
+}
+
+
+static gboolean
+priv_nua_i_invite_cb (RakiaSipSession *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ /* nua_i_invite delivered for a bound handle means a re-INVITE */
+
+ rakia_sip_session_receive_reinvite (self);
+
+ return TRUE;
+}
+
+
+
+static gboolean
+priv_nua_i_bye_cb (RakiaSipSession *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ g_signal_emit (self, signals[SIG_ENDED], 0, FALSE, 0, "");
+
+ return TRUE;
+}
+
+
+static gboolean
+priv_nua_i_cancel_cb (RakiaSipSession *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ const sip_reason_t *reason;
+ guint cause = 0;
+ const gchar *message = NULL;
+ gboolean self_actor = FALSE;
+
+ /* FIXME: implement cancellation of an incoming re-INVITE, if ever
+ * found in real usage and not caused by a request timeout */
+
+ if (ev->sip != NULL)
+ for (reason = ev->sip->sip_reason;
+ reason != NULL;
+ reason = reason->re_next)
+ {
+ const char *protocol = reason->re_protocol;
+ if (protocol == NULL || strcmp (protocol, "SIP") != 0)
+ continue;
+ if (reason->re_cause != NULL)
+ {
+ cause = (guint) g_ascii_strtoull (reason->re_cause, NULL, 10);
+ message = reason->re_text;
+ break;
+ }
+ }
+
+ switch (cause)
+ {
+ case 200:
+ case 603:
+ /* The user must have acted on another branch of the forked call */
+ self_actor = TRUE;
+ break;
+ default:
+ self_actor = FALSE;
+ }
+
+
+ if (message == NULL || !g_utf8_validate (message, -1, NULL))
+ message = "";
+
+ g_signal_emit (self, signals[SIG_ENDED], 0, self_actor, cause, message);
+
+
+ return TRUE;
+}
+
+void
+rakia_sip_session_ringing (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->state == RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED);
+
+ nua_respond (priv->nua_op, SIP_180_RINGING, TAG_END());
+}
+
+
+void
+rakia_sip_session_queued (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->state == RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED);
+
+ nua_respond (priv->nua_op, SIP_182_QUEUED, TAG_END());
+}
+
+static void
+rakia_sip_session_receive_invite (RakiaSipSession *self)
+{
+ g_return_if_fail (self->priv->nua_op != NULL);
+
+ /* We'll do Ringing later instead */
+ /* nua_respond (priv->nua_op, SIP_183_SESSION_PROGRESS, TAG_END()); */
+
+ rakia_sip_session_change_state (self,
+ RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED);
+}
+
+
+/*
+ * Handles an incoming call, called shortly after the channel
+ * has been created with initiator handle of the sender, when remote SDP
+ * session data are reported by the NUA stack.
+ */
+static void
+rakia_sip_session_handle_incoming_call (RakiaSipSession *self,
+ nua_handle_t *nh,
+ const sdp_session_t *sdp)
+{
+ g_assert (self->priv->incoming);
+
+ rakia_sip_session_receive_invite (self);
+
+ /* Tell the factory to emit NewChannel(s) */
+ g_signal_emit (self, signals[SIG_INCOMING_CALL], 0);
+}
+
+
+static void
+rakia_sip_session_peer_error (RakiaSipSession *self,
+ guint status,
+ const char* message)
+{
+ if (message == NULL || !g_utf8_validate (message, -1, NULL))
+ message = "";
+
+ g_signal_emit (self, signals[SIG_ENDED], 0, FALSE, status, message);
+}
+
+
+
+/* Checks if RTCP is not disabled with bandwidth modifiers
+ * as described in RFC 3556 */
+gboolean
+rakia_sdp_rtcp_bandwidth_throttled (const sdp_bandwidth_t *b)
+{
+ const sdp_bandwidth_t *b_RS = NULL;
+ const sdp_bandwidth_t *b_RR = NULL;
+
+ while (b != NULL)
+ {
+ if (b->b_modifier_name != NULL)
+ {
+ if (strcmp (b->b_modifier_name, "RS") == 0)
+ b_RS = b;
+ else if (strcmp (b->b_modifier_name, "RR") == 0)
+ b_RR = b;
+ }
+ b = b->b_next;
+ }
+
+ return (b_RS != NULL && b_RS->b_value == 0
+ && b_RR != NULL && b_RR->b_value == 0);
+}
+
+
+static gboolean
+rakia_sip_session_supports_media_type (TpMediaStreamType media_type)
+{
+ switch (media_type)
+ {
+ case TP_MEDIA_STREAM_TYPE_AUDIO:
+ case TP_MEDIA_STREAM_TYPE_VIDEO:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+
+static void
+priv_session_rollback (RakiaSipSession *session)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+
+ DEBUG("enter");
+
+ if (priv->remote_sdp != NULL)
+ {
+ priv->remote_sdp = NULL;
+ g_assert (priv->home != NULL);
+ su_home_unref (priv->home);
+ priv->home = NULL;
+ }
+ if (priv->backup_remote_sdp == NULL)
+ {
+ rakia_sip_session_terminate (session, 0, NULL);
+ return;
+ }
+
+ /* restore remote SDP from the backup */
+ priv->remote_sdp = priv->backup_remote_sdp;
+ g_assert (priv->backup_home != NULL);
+ priv->home = priv->backup_home;
+ priv->backup_remote_sdp = NULL;
+ priv->backup_home = NULL;
+
+ priv_update_remote_media (session, FALSE);
+
+ if (priv->saved_event[0])
+ {
+ nua_respond (priv->nua_op, SIP_488_NOT_ACCEPTABLE,
+ NUTAG_WITH(nua_saved_event_request (priv->saved_event)),
+ TAG_END());
+ nua_destroy_event (priv->saved_event);
+ }
+ else
+ {
+ nua_respond (priv->nua_op, SIP_488_NOT_ACCEPTABLE,
+ TAG_END());
+ }
+
+ rakia_sip_session_change_state (session, RAKIA_SIP_SESSION_STATE_ACTIVE);
+}
+
+
+static void
+priv_media_local_negotiation_complete_cb (RakiaSipMedia *media,
+ gboolean success, RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ SESSION_DEBUG (self, "negotiation complete %d", success);
+
+ if (!success)
+ {
+ /* This remote media description got no codec intersection. */
+ switch (priv->state)
+ {
+ case RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED:
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ SESSION_DEBUG (self, "no codec intersection, closing the stream");
+ rakia_sip_session_remove_media (self, media, 488,
+ "No codec intersection");
+ break;
+ case RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED:
+ /* In this case, we have the stream negotiated already,
+ * and we don't want to close it just because the remote party
+ * offers a different set of codecs.
+ * Roll back the whole session to the previously negotiated state. */
+ priv_session_rollback (self);
+ return;
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+ /* We've most likely rolled back from
+ * RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED,
+ * but we may receive more than one empty codec intersection
+ * in the session, so we ignore the rest */
+ return;
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ priv_request_response_step (self);
+}
+
+static gboolean
+priv_has_all_media_ready (RakiaSipSession *self)
+{
+ guint i;
+
+ for (i = 0; i < self->priv->medias->len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (self->priv->medias, i);;;;
+
+ if (media == NULL)
+ continue;
+ if (!rakia_sip_media_is_ready (media))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+rakia_sip_session_media_changed (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ SESSION_DEBUG (self, "media changed");
+
+ switch (priv->state)
+ {
+ case RAKIA_SIP_SESSION_STATE_CREATED:
+ /* If all medias are ready, send an offer now */
+ priv_request_response_step (self);
+ break;
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED:
+ /* The changes to existing medias will be included in the
+ * eventual answer (FIXME: implement postponed direction changes,
+ * which are applied after the remote offer has been processed).
+ * Check, however, if there are new medias not present in the
+ * remote offer, that will need another offer-answer round */
+ if (priv->remote_media_count < priv->medias->len)
+ priv->pending_offer = TRUE;
+ break;
+ case RAKIA_SIP_SESSION_STATE_INVITE_SENT:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_SENT:
+ case RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED:
+ /* Cannot send another offer right now */
+ priv->pending_offer = TRUE;
+ break;
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+ /* Check if we are allowed to send re-INVITES */
+ if (priv->immutable_streams) {
+ SESSION_MESSAGE (self, "sending of a local media update disabled"
+ " by parameter 'immutable-streams'");
+ break;
+ }
+ /* Fall through to the next case */
+ case RAKIA_SIP_SESSION_STATE_REINVITE_PENDING:
+ if (priv_has_all_media_ready (self))
+ priv_session_invite (self, TRUE);
+ else
+ priv->pending_offer = TRUE;
+ break;
+ case RAKIA_SIP_SESSION_STATE_ENDED:
+ /* We've already ended the call, ignore any change request */
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+
+static RakiaSipMedia *
+rakia_sip_session_add_media_internal (RakiaSipSession *self,
+ TpMediaStreamType media_type,
+ const gchar *name,
+ RakiaDirection direction,
+ const sdp_media_t *sdp_media,
+ gboolean authoritative,
+ gint slot)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ RakiaSipMedia *media = NULL;
+ gboolean created_locally = (sdp_media == NULL);
+
+ SESSION_DEBUG (self, "enter");
+
+ if ((!sdp_media || !sdp_media->m_rejected) &&
+ rakia_sip_session_supports_media_type (media_type)) {
+ media = rakia_sip_media_new (self, media_type, name, direction,
+ created_locally, priv->hold_requested);
+
+ g_signal_connect_object (media, "local-negotiation-complete",
+ G_CALLBACK (priv_media_local_negotiation_complete_cb), self, 0);
+ g_signal_connect_object (media, "local-updated",
+ G_CALLBACK (rakia_sip_session_media_changed), self,
+ G_CONNECT_SWAPPED);
+
+ if (sdp_media == NULL ||
+ rakia_sip_media_set_remote_media (media, sdp_media, authoritative))
+ {
+ g_signal_emit (self, signals[SIG_MEDIA_ADDED], 0, media);
+ }
+ else
+ {
+ rakia_sip_session_remove_media (self, media, 488,
+ "Can not process this SDP");
+ media = NULL;
+ }
+ }
+
+ /* note: we add an entry even for unsupported media types */
+ if (slot >= 0)
+ {
+ if (slot < priv->medias->len)
+ {
+ g_assert (g_ptr_array_index (priv->medias, slot) == NULL);
+ g_ptr_array_index (priv->medias, slot) = media;
+ }
+ else
+ {
+ g_assert (slot == priv->medias->len);
+ g_ptr_array_add (priv->medias, media);
+ }
+ }
+ else
+ {
+ g_ptr_array_add (priv->medias, media);
+ }
+
+ SESSION_DEBUG (self, "exit");
+
+ return media;
+}
+
+RakiaSipMedia *
+rakia_sip_session_add_media (RakiaSipSession *self,
+ TpMediaStreamType media_type,
+ const gchar *name,
+ RakiaDirection direction)
+{
+ return rakia_sip_session_add_media_internal (self, media_type, name,
+ direction, NULL, TRUE, -1);
+}
+
+
+static void
+priv_update_remote_hold (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ RakiaSipMedia *media;
+ gboolean has_medias = FALSE;
+ gboolean remote_held = TRUE;
+ guint i;
+
+ /* The call is remotely unheld if there's at least one sending media */
+ for (i = 0; i < priv->medias->len; i++)
+ {
+ media = g_ptr_array_index(priv->medias, i);
+ if (media != NULL)
+ {
+ if (rakia_sip_media_get_direction (media) & RAKIA_DIRECTION_SEND ||
+ !(rakia_sip_media_get_requested_direction (media) &
+ RAKIA_DIRECTION_SEND))
+ remote_held = FALSE;
+
+ has_medias = TRUE;
+ }
+ }
+
+ if (!has_medias)
+ return;
+
+ SESSION_DEBUG (self, "is remotely %s", remote_held? "held" : "unheld");
+
+ priv->remote_held = remote_held;
+ g_object_notify (G_OBJECT (self), "remote-held");
+}
+
+
+static gboolean
+priv_update_remote_media (RakiaSipSession *self, gboolean authoritative)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ const sdp_session_t *sdp = priv->remote_sdp;
+ const sdp_media_t *sdp_media;
+ gboolean has_supported_media = FALSE;
+ guint i;
+
+ g_return_val_if_fail (sdp != NULL, FALSE);
+
+ /* Update the session-wide parameters
+ * before updating medias */
+
+ priv->remote_ptime = rakia_sdp_get_string_attribute (
+ sdp->sdp_attributes, "ptime");
+ priv->remote_max_ptime = rakia_sdp_get_string_attribute (
+ sdp->sdp_attributes, "maxptime");
+
+ priv->rtcp_enabled = !rakia_sdp_rtcp_bandwidth_throttled (
+ sdp->sdp_bandwidths);
+
+
+ sdp_media = sdp->sdp_media;
+
+ /* note: for each session, we maintain an ordered list of
+ * medias (SDP m-lines) which are matched 1:1 to
+ * the medias of the remote SDP */
+
+ for (i = 0; sdp_media != NULL; sdp_media = sdp_media->m_next, i++)
+ {
+ RakiaSipMedia *media = NULL;
+ TpMediaStreamType media_type;
+
+ media_type = rakia_media_type (sdp_media->m_type);
+
+ if (i < priv->medias->len)
+ media = g_ptr_array_index(priv->medias, i);
+
+ /* Check if the media type has changed, if it has
+ * just replace the media with the new one.
+ */
+ if (media != NULL && rakia_sip_media_get_media_type (media) != media_type)
+ {
+ g_object_ref (media);
+ g_ptr_array_index (self->priv->medias, i) = NULL;
+ g_signal_emit (self, signals[SIG_MEDIA_REMOVED], 0, media);
+ g_object_unref (media);
+ media = NULL;
+ }
+
+ /* Add the new media if it hasn't been rejected */
+ if (media == NULL && !sdp_media->m_rejected)
+ {
+ media = rakia_sip_session_add_media_internal (
+ self,
+ media_type,
+ NULL,
+ /* Don't start sending unless requested by the user */
+ rakia_direction_from_remote_media (sdp_media) &
+ RAKIA_DIRECTION_RECEIVE,
+ sdp_media, authoritative, i);
+
+ if (media != NULL)
+ has_supported_media = TRUE;
+ continue;
+ }
+
+ /* note: it is ok for the media to be NULL (unsupported media type) */
+ if (media == NULL)
+ continue;
+
+ SESSION_DEBUG (self, "setting remote SDP for media %u", i);
+
+ /* Remove rejected medias */
+ if (sdp_media->m_rejected)
+ {
+ SESSION_DEBUG (self, "the media has been rejected, closing");
+ rakia_sip_session_remove_media (self, media, 488,
+ "Can not process this media type");
+ continue;
+ }
+
+ if (!rakia_sip_media_set_remote_media (media, sdp_media,
+ authoritative))
+ {
+ rakia_sip_session_remove_media (self, media, 488,
+ "Can not process this media type");
+ continue;
+ }
+ has_supported_media = TRUE;
+ }
+ g_assert(sdp_media == NULL);
+ g_assert(i <= priv->medias->len);
+ g_assert(!authoritative || i == priv->remote_media_count);
+
+ if (i < priv->medias->len && !priv->pending_offer)
+ {
+ /*
+ * It's not defined what we should do if there are previously offered
+ * medias not accounted in the remote SDP, in violation of RFC 3264.
+ * Closing them off serves resource preservation and gives better
+ * clue to the client as to the real state of the session.
+ * Note that this situation is masked if any local media updates
+ * have been requested and are pending until the present remote session
+ * answer is received and applied. In such a case, we'll issue a new offer
+ * at the closest available time, with the "overhanging" media entries
+ * intact.
+ */
+ do
+ {
+ RakiaSipMedia *media;
+ media = g_ptr_array_index(priv->medias, i);
+ if (media != NULL)
+ {
+ SESSION_MESSAGE (self, "removing a mismatched media %u", i);
+ rakia_sip_session_remove_media (self, media,
+ 488, "Media type mismatch");
+ }
+ }
+ while (++i < priv->medias->len);
+ }
+
+ if (has_supported_media)
+ priv_update_remote_hold (self);
+
+ DEBUG("exit");
+
+ return has_supported_media;
+}
+
+
+
+static GString *
+priv_session_generate_sdp (RakiaSipSession *session,
+ gboolean authoritative)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+ GString *user_sdp;
+ guint len;
+ guint i;
+
+ g_return_val_if_fail (priv_has_all_media_ready (session), NULL);
+
+ user_sdp = g_string_new ("v=0\r\n");
+
+ len = priv->medias->len;
+ if (!authoritative && len > priv->remote_media_count)
+ {
+ len = priv->remote_media_count;
+ SESSION_DEBUG (session, "clamped response to %u medias seen in the offer", len);
+ }
+
+ for (i = 0; i < len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (priv->medias, i);
+ if (media)
+ rakia_sip_media_generate_sdp (media, user_sdp, authoritative);
+ else
+ g_string_append (user_sdp, "m=audio 0 RTP/AVP 0\r\n");
+ }
+
+ return user_sdp;
+}
+
+static void
+priv_session_invite (RakiaSipSession *session, gboolean reinvite)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+ GString *user_sdp;
+
+ DEBUG("enter");
+
+ g_return_if_fail (priv->nua_op != NULL);
+
+ user_sdp = priv_session_generate_sdp (session, TRUE);
+
+ g_return_if_fail (user_sdp != NULL);
+
+ if (!reinvite
+ || priv->state == RAKIA_SIP_SESSION_STATE_REINVITE_PENDING
+ || tp_strdiff (priv->local_sdp, user_sdp->str))
+ {
+ g_free (priv->local_sdp);
+ priv->local_sdp = g_string_free (user_sdp, FALSE);
+
+ /* We need to be prepared to receive media right after the
+ * offer is sent, so we must set the streams to playing */
+ g_signal_emit (session, signals[SIG_START_RECEIVING], 0);
+
+ nua_invite (priv->nua_op,
+ SOATAG_USER_SDP_STR(priv->local_sdp),
+ SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
+ SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
+ SOATAG_ORDERED_USER(1),
+ NUTAG_AUTOANSWER(0),
+ TAG_IF(reinvite,
+ NUTAG_INVITE_TIMER (RAKIA_REINVITE_TIMEOUT)),
+ TAG_END());
+ priv->pending_offer = FALSE;
+
+ rakia_sip_session_change_state (
+ session,
+ reinvite? RAKIA_SIP_SESSION_STATE_REINVITE_SENT
+ : RAKIA_SIP_SESSION_STATE_INVITE_SENT);
+ }
+ else
+ {
+ SESSION_DEBUG (session, "SDP unchanged, not sending a re-INVITE");
+ g_string_free (user_sdp, TRUE);
+ }
+}
+
+static void
+priv_session_respond (RakiaSipSession *session)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+ msg_t *msg;
+
+ g_return_if_fail (priv->nua_op != NULL);
+
+ {
+ GString *user_sdp = priv_session_generate_sdp (session, FALSE);
+
+ g_free (priv->local_sdp);
+ priv->local_sdp = g_string_free (user_sdp, FALSE);
+ }
+
+ /* We need to be prepared to receive media right after the
+ * answer is sent, so we must set the streams to playing */
+ g_signal_emit (session, signals[SIG_START_RECEIVING], 0);
+
+ msg = (priv->saved_event[0])
+ ? nua_saved_event_request (priv->saved_event) : NULL;
+
+ nua_respond (priv->nua_op, SIP_200_OK,
+ TAG_IF(msg, NUTAG_WITH(msg)),
+ SOATAG_USER_SDP_STR(priv->local_sdp),
+ SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
+ SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
+ NUTAG_AUTOANSWER(0),
+ TAG_END());
+
+ if (priv->saved_event[0])
+ nua_destroy_event (priv->saved_event);
+
+ rakia_sip_session_change_state (session, RAKIA_SIP_SESSION_STATE_ACTIVE);
+}
+
+
+
+static gboolean
+priv_is_codec_intersect_pending (RakiaSipSession *session)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+ guint i;
+
+ for (i = 0; i < priv->medias->len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (priv->medias, i);
+ if (media != NULL
+ && rakia_sip_media_is_codec_intersect_pending (media))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/**
+ * Sends requests and responses with an outbound offer/answer
+ * if all streams of the session are prepared.
+ *
+ * Following inputs are considered in decision making:
+ * - state of the session (is remote INVITE being handled)
+ * - status of local streams (set up with stream-engine)
+ * - whether session is locally accepted
+ */
+static void
+priv_request_response_step (RakiaSipSession *session)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+
+ if (!priv_has_all_media_ready (session))
+ {
+ SESSION_DEBUG (session, "there are local streams not ready, postponed");
+ return;
+ }
+
+ switch (priv->state)
+ {
+ case RAKIA_SIP_SESSION_STATE_CREATED:
+ priv_session_invite (session, FALSE);
+ break;
+ case RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED:
+ if (priv->accepted
+ && !priv_is_codec_intersect_pending (session))
+ rakia_sip_session_change_state (session,
+ RAKIA_SIP_SESSION_STATE_ACTIVE);
+ break;
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ /* TODO: if the call has not yet been accepted locally
+ * and the remote endpoint supports 100rel, send them
+ * an early session answer in a reliable 183 response */
+ if (priv->accepted
+ && !priv_is_codec_intersect_pending (session))
+ priv_session_respond (session);
+ break;
+ case RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED:
+ if (!priv_is_codec_intersect_pending (session))
+ priv_session_respond (session);
+ break;
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_PENDING:
+ if (priv->pending_offer)
+ priv_session_invite (session, TRUE);
+ break;
+ default:
+ SESSION_DEBUG (session, "no action taken in the current state");
+ }
+}
+
+
+static gboolean
+rakia_sip_session_set_remote_media (RakiaSipSession *self,
+ const sdp_session_t* sdp)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ gboolean authoritative;
+
+ SESSION_DEBUG (self, "enter");
+
+ if (priv->state == RAKIA_SIP_SESSION_STATE_INVITE_SENT
+ || priv->state == RAKIA_SIP_SESSION_STATE_REINVITE_SENT)
+ {
+ rakia_sip_session_change_state (
+ self,
+ RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED);
+ }
+ else
+ {
+ /* Remember the m= line count in the remote offer,
+ * to match it with exactly this number of answer lines */
+ sdp_media_t *media;
+ guint count = 0;
+
+ for (media = sdp->sdp_media; media != NULL; media = media->m_next)
+ ++count;
+
+ priv->remote_media_count = count;
+ }
+
+ /* Shortcut session non-updates */
+ if (!sdp_session_cmp (priv->remote_sdp, sdp))
+ goto finally;
+
+ /* Delete a backup session structure, if any */
+ if (priv->backup_remote_sdp != NULL)
+ {
+ priv->backup_remote_sdp = NULL;
+ g_assert (priv->backup_home != NULL);
+ su_home_unref (priv->backup_home);
+ priv->backup_home = NULL;
+ }
+ /* Back up the old session.
+ * The medias still need the old media descriptions */
+ if (priv->remote_sdp != NULL)
+ {
+ g_assert (priv->home != NULL);
+ g_assert (priv->backup_home == NULL);
+ priv->backup_home = priv->home;
+ priv->backup_remote_sdp = priv->remote_sdp;
+ }
+
+ /* Store the session description structure */
+ priv->home = su_home_create ();
+ priv->remote_sdp = sdp_session_dup (priv->home, sdp);
+ g_return_val_if_fail (priv->remote_sdp != NULL, FALSE);
+
+ authoritative = (priv->state == RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED
+ || priv->state == RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED);
+ if (!priv_update_remote_media (self, authoritative))
+ return FALSE;
+
+finally:
+ /* Make sure to always transition states and send out the response,
+ * even if no stream-engine roundtrips were initiated */
+ priv_request_response_step (self);
+ return TRUE;
+}
+
+
+
+
+static gboolean
+priv_glare_retry (gpointer session)
+{
+ RakiaSipSession *self = session;
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ SESSION_DEBUG (self, "glare resolution interval is over");
+
+ if (priv->state == RAKIA_SIP_SESSION_STATE_REINVITE_PENDING)
+ priv_session_invite (self, TRUE);
+
+ /* Reap the timer */
+ priv->glare_timer_id = 0;
+ return FALSE;
+}
+
+static void
+rakia_sip_session_resolve_glare (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ guint interval;
+
+ if (priv->state != RAKIA_SIP_SESSION_STATE_REINVITE_SENT)
+ {
+ SESSION_DEBUG (self, "glare resolution triggered in unexpected state");
+ return;
+ }
+
+ /*
+ * Set the grace interval accordinlgly to RFC 3261 section 14.1:
+ *
+ * 1. If the UAC is the owner of the Call-ID of the dialog ID
+ * (meaning it generated the value), T has a randomly chosen value
+ * between 2.1 and 4 seconds in units of 10 ms.
+ * 2. If the UAC is not the owner of the Call-ID of the dialog ID, T
+ * has a randomly chosen value of between 0 and 2 seconds in units
+ * of 10 ms.
+ */
+ if (priv->pending_offer)
+ interval = 0; /* cut short, we have new things to negotiate */
+ else if (priv->incoming)
+ interval = g_random_int_range (0, 200) * 10;
+ else
+ interval = g_random_int_range (210, 400) * 10;
+
+ if (priv->glare_timer_id != 0)
+ g_source_remove (priv->glare_timer_id);
+
+ priv->glare_timer_id = g_timeout_add (interval, priv_glare_retry, self);
+
+ SESSION_DEBUG (self, "glare resolution interval %u msec", interval);
+
+ rakia_sip_session_change_state (
+ self, RAKIA_SIP_SESSION_STATE_REINVITE_PENDING);
+}
+
+
+
+
+static gboolean
+priv_nua_i_state_cb (RakiaSipSession *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ const sdp_session_t *r_sdp = NULL;
+ int offer_recv = 0;
+ int answer_recv = 0;
+ int ss_state = nua_callstate_init;
+ gint status = ev->status;
+
+ tl_gets(tags,
+ NUTAG_CALLSTATE_REF(ss_state),
+ NUTAG_OFFER_RECV_REF(offer_recv),
+ NUTAG_ANSWER_RECV_REF(answer_recv),
+ SOATAG_REMOTE_SDP_REF(r_sdp),
+ TAG_END());
+
+ DEBUG("call with handle %p is %s", ev->nua_handle,
+ nua_callstate_name (ss_state));
+
+ if (r_sdp)
+ {
+ g_return_val_if_fail (answer_recv || offer_recv, FALSE);
+ if (!rakia_sip_session_set_remote_media (self, r_sdp))
+ {
+ rakia_sip_session_terminate (self, 0, NULL);
+ return TRUE;
+ }
+ }
+
+
+ if (ss_state == nua_callstate_received &&
+ priv->state == RAKIA_SIP_SESSION_STATE_CREATED)
+ {
+ /* Let's announce the new call now that the initial streams have
+ * been created
+ */
+ rakia_sip_session_handle_incoming_call (self, ev->nua_handle, r_sdp);
+ }
+
+ switch ((enum nua_callstate)ss_state)
+ {
+ case nua_callstate_proceeding:
+ switch (status)
+ {
+ case 180:
+ g_signal_emit (self, signals[SIG_RINGING], 0);
+ break;
+ case 182:
+ g_signal_emit (self, signals[SIG_QUEUED], 0);
+ break;
+ case 183:
+ g_signal_emit (self, signals[SIG_IN_PROGRESS], 0);
+ break;
+ }
+ break;
+
+ case nua_callstate_completing:
+ /* In auto-ack mode, we don't need to call nua_ack(), see NUTAG_AUTOACK() */
+ break;
+
+ case nua_callstate_ready:
+
+ /* FIXME: Clear any pre-establishment call states */
+ /* This are queued/ringing/in-progress */
+
+ if (status < 300)
+ {
+ rakia_sip_session_accept (self);
+ }
+ else if (status == 491)
+ rakia_sip_session_resolve_glare (self);
+ else
+ {
+ /* Was something wrong with our re-INVITE? We can't cope anyway. */
+ MESSAGE ("can't handle non-fatal response %d %s", status, ev->text);
+ rakia_sip_session_terminate (self, 480, "Re-invite rejected");
+ }
+ break;
+
+ case nua_callstate_terminated:
+ /* In cases of self-inflicted termination,
+ * we should have already gone through the moves */
+ if (priv->state == RAKIA_SIP_SESSION_STATE_ENDED)
+ break;
+
+ if (status >= 300)
+ {
+ rakia_sip_session_peer_error (self, status, ev->text);
+ }
+
+ rakia_sip_session_change_state (self,
+ RAKIA_SIP_SESSION_STATE_ENDED);
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+
+static void
+rakia_sip_session_attach_to_nua_handle (RakiaSipSession *self,
+ nua_handle_t *nh, RakiaBaseConnection *conn)
+{
+ rakia_event_target_attach (nh, (GObject *) self);
+
+ /* have the connection handle authentication, before all other
+ * response callbacks */
+ rakia_base_connection_add_auth_handler (conn, RAKIA_EVENT_TARGET (self));
+
+ g_signal_connect (self,
+ "nua-event::nua_i_invite",
+ G_CALLBACK (priv_nua_i_invite_cb),
+ NULL);
+ g_signal_connect (self,
+ "nua-event::nua_i_bye",
+ G_CALLBACK (priv_nua_i_bye_cb),
+ NULL);
+ g_signal_connect (self,
+ "nua-event::nua_i_cancel",
+ G_CALLBACK (priv_nua_i_cancel_cb),
+ NULL);
+ g_signal_connect (self,
+ "nua-event::nua_i_state",
+ G_CALLBACK (priv_nua_i_state_cb),
+ NULL);
+
+}
+
+
+RakiaSipSession *
+rakia_sip_session_new (nua_handle_t *nh, RakiaBaseConnection *conn,
+ gboolean incoming, gboolean immutable_streams)
+{
+ RakiaSipSession *self = g_object_new (RAKIA_TYPE_SIP_SESSION, NULL);
+
+ self->priv->incoming = incoming;
+ self->priv->nua_op = nh;
+ self->priv->conn = g_object_ref (conn);
+ self->priv->immutable_streams = immutable_streams;
+ nua_handle_ref (self->priv->nua_op);
+ rakia_sip_session_attach_to_nua_handle (self, nh, conn);
+
+ return self;
+}
+
+
+/**
+ * Converts a sofia-sip media type enum to Telepathy media type.
+ * See <sofia-sip/sdp.h> and <telepathy-constants.h>.
+ *
+ * @return G_MAXUINT if the media type cannot be mapped
+ */
+static TpMediaStreamType
+rakia_media_type (sdp_media_e sip_mtype)
+{
+ switch (sip_mtype)
+ {
+ case sdp_media_audio:
+ return TP_MEDIA_STREAM_TYPE_AUDIO;
+ case sdp_media_video:
+ return TP_MEDIA_STREAM_TYPE_VIDEO;
+ default:
+ /* some invalid value */
+ return G_MAXINT;
+ }
+}
+
+gboolean
+rakia_sip_session_remove_media (RakiaSipSession *self, RakiaSipMedia *media,
+ guint status, const gchar *reason)
+{
+ guint i;
+ gboolean has_removed_media = FALSE;
+ gboolean has_media = TRUE;
+
+ g_return_val_if_fail (RAKIA_IS_SIP_MEDIA (media), FALSE);
+
+ for (i = 0; i < self->priv->medias->len; i++)
+ {
+ if (media == g_ptr_array_index (self->priv->medias, i))
+ {
+ g_object_ref (media);
+ g_ptr_array_index (self->priv->medias, i) = NULL;
+ g_signal_emit (self, signals[SIG_MEDIA_REMOVED], 0, media);
+ g_object_unref (media);
+ has_removed_media = TRUE;
+ }
+ else if (g_ptr_array_index (self->priv->medias, i) != NULL)
+ has_media = TRUE;
+ }
+
+ if (!has_media)
+ rakia_sip_session_terminate (self, status, reason);
+
+ return has_removed_media;
+}
+
+
+gboolean
+rakia_sip_session_has_media (RakiaSipSession *self,
+ TpMediaStreamType media_type)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ guint i;
+
+ for (i = 0; i < priv->medias->len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (priv->medias, i);
+ if (media == NULL)
+ continue;
+ if (rakia_sip_media_get_media_type (media) == media_type)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+void
+rakia_sip_session_respond (RakiaSipSession *self,
+ gint status,
+ const char *message)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ SESSION_DEBUG (self, "responding: %03d %s", status, message ? message : "");
+
+ if (message != NULL && !message[0])
+ message = NULL;
+
+ if (priv->nua_op)
+ nua_respond (priv->nua_op, status, message, TAG_END());
+}
+
+
+void
+rakia_sip_session_accept (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ if (priv->accepted)
+ return;
+
+ SESSION_DEBUG (self, "accepting the session");
+
+ priv->accepted = TRUE;
+
+ /* Will change session state to active when streams are ready */
+ priv_request_response_step (self);
+}
+
+gboolean
+rakia_sip_session_is_accepted (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+ return priv->accepted;
+}
+
+
+void
+rakia_sip_session_terminate (RakiaSipSession *session, guint status,
+ const gchar *reason)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+
+ DEBUG ("enter");
+
+ if (priv->state == RAKIA_SIP_SESSION_STATE_ENDED)
+ return;
+
+ if (status == 0)
+ {
+ status = SIP_480_TEMPORARILY_UNAVAILABLE;
+ reason = "Terminated";
+ }
+
+ if (priv->nua_op != NULL)
+ {
+ /* XXX: should the stack do pretty much the same
+ * (except freeing the saved event) upon nua_handle_destroy()? */
+ switch (priv->state)
+ {
+ case RAKIA_SIP_SESSION_STATE_ACTIVE:
+ case RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_SENT:
+ case RAKIA_SIP_SESSION_STATE_REINVITE_PENDING:
+ SESSION_DEBUG (session, "sending BYE");
+ nua_bye (priv->nua_op, TAG_END());
+ break;
+ case RAKIA_SIP_SESSION_STATE_INVITE_SENT:
+ SESSION_DEBUG (session, "sending CANCEL");
+ nua_cancel (priv->nua_op, TAG_END());
+ break;
+ case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED:
+ SESSION_DEBUG (session, "sending the %d response to an incoming INVITE", status);
+ nua_respond (priv->nua_op, status, reason, TAG_END());
+ break;
+ case RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED:
+ if (priv->saved_event[0])
+ {
+ SESSION_DEBUG (session, "sending the %d response to an incoming re-INVITE", status);
+ nua_respond (priv->nua_op, status, reason,
+ NUTAG_WITH(nua_saved_event_request (priv->saved_event)),
+ TAG_END());
+ nua_destroy_event (priv->saved_event);
+ }
+ SESSION_DEBUG (session, "sending BYE to terminate the call itself");
+ nua_bye (priv->nua_op, TAG_END());
+ break;
+ default:
+ /* let the Sofia stack decide what do to */;
+ }
+ }
+
+ rakia_sip_session_change_state (session, RAKIA_SIP_SESSION_STATE_ENDED);
+}
+
+gboolean
+rakia_sip_session_pending_offer (RakiaSipSession *self)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (self);
+
+ return priv->pending_offer;
+}
+
+RakiaSipSessionState
+rakia_sip_session_get_state (RakiaSipSession *session)
+{
+ return session->priv->state;
+}
+
+gboolean
+rakia_sip_session_is_held (RakiaSipSession *session)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+ guint i;
+
+ for (i = 0; i < priv->medias->len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (priv->medias, i);
+
+ if (!media)
+ continue;
+
+ if (!rakia_sip_media_is_held (media))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+rakia_sip_session_set_hold_requested (RakiaSipSession *session,
+ gboolean hold_requested)
+{
+ RakiaSipSessionPrivate *priv = RAKIA_SIP_SESSION_GET_PRIVATE (session);
+ guint i;
+
+ if (session->priv->hold_requested == hold_requested)
+ return;
+
+ SESSION_DEBUG (session, "set hold: %d", hold_requested);
+
+ session->priv->hold_requested = hold_requested;
+
+ for (i = 0; i < priv->medias->len; i++)
+ {
+ RakiaSipMedia *media = g_ptr_array_index (priv->medias, i);
+
+ if (media == NULL)
+ continue;
+
+ rakia_sip_media_set_hold_requested (media, hold_requested);
+ }
+
+ rakia_sip_session_media_changed (session);
+
+}
+
+GPtrArray *
+rakia_sip_session_get_medias (RakiaSipSession *self)
+{
+ return self->priv->medias;
+}
diff --git a/rakia/sip-session.h b/rakia/sip-session.h
new file mode 100644
index 0000000..d3f330b
--- /dev/null
+++ b/rakia/sip-session.h
@@ -0,0 +1,142 @@
+/*
+ * rakia-sip-session.h - Header for RakiaSipSession
+ * Copyright (C) 2005-2012 Collabora Ltd.
+ * Copyright (C) 2005-2010 Nokia Corporation
+ *
+ * This work is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this work; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __RAKIA_SIP_SESSION_H__
+#define __RAKIA_SIP_SESSION_H__
+
+#include <glib-object.h>
+#include <sofia-sip/sdp.h>
+
+#include <rakia/base-connection.h>
+#include <rakia/sip-media.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ RAKIA_SIP_SESSION_STATE_CREATED = 0,
+ RAKIA_SIP_SESSION_STATE_INVITE_SENT,
+ RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED,
+ RAKIA_SIP_SESSION_STATE_RESPONSE_RECEIVED,
+ RAKIA_SIP_SESSION_STATE_ACTIVE,
+ RAKIA_SIP_SESSION_STATE_REINVITE_SENT,
+ RAKIA_SIP_SESSION_STATE_REINVITE_RECEIVED,
+ RAKIA_SIP_SESSION_STATE_REINVITE_PENDING,
+ RAKIA_SIP_SESSION_STATE_ENDED,
+
+ NUM_RAKIA_SIP_SESSION_STATES
+} RakiaSipSessionState;
+
+
+/* RakiaSipSession is defined in sip-media.h */
+/* typedef struct _RakiaSipSession RakiaSipSession; */
+typedef struct _RakiaSipSessionClass RakiaSipSessionClass;
+typedef struct _RakiaSipSessionPrivate RakiaSipSessionPrivate;
+
+struct _RakiaSipSessionClass {
+ GObjectClass parent_class;
+};
+
+struct _RakiaSipSession {
+ GObject parent;
+ RakiaSipSessionPrivate *priv;
+};
+
+GType rakia_sip_session_get_type(void);
+
+/* TYPE MACROS */
+#define RAKIA_TYPE_SIP_SESSION \
+ (rakia_sip_session_get_type())
+#define RAKIA_SIP_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), RAKIA_TYPE_SIP_SESSION, RakiaSipSession))
+#define RAKIA_SIP_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), RAKIA_TYPE_SIP_SESSION, RakiaSipSessionClass))
+#define RAKIA_IS_SIP_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), RAKIA_TYPE_SIP_SESSION))
+#define RAKIA_IS_SIP_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), RAKIA_TYPE_SIP_SESSION))
+#define RAKIA_SIP_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), RAKIA_TYPE_SIP_SESSION, RakiaSipSessionClass))
+
+
+/* For use by RakiaSipMedia */
+
+gboolean rakia_sdp_rtcp_bandwidth_throttled (const sdp_bandwidth_t *b);
+
+gchar * rakia_sdp_get_string_attribute (const sdp_attribute_t *attrs,
+ const char *name);
+
+
+/* For use by the upper layers */
+
+RakiaSipSession *
+rakia_sip_session_new (nua_handle_t *nh, RakiaBaseConnection *conn,
+ gboolean incoming, gboolean immutable_streams);
+
+void rakia_sip_session_terminate (RakiaSipSession *session, guint status,
+ const gchar *reason);
+RakiaSipSessionState rakia_sip_session_get_state (RakiaSipSession *session);
+
+
+RakiaSipMedia* rakia_sip_session_add_media (RakiaSipSession *self,
+ TpMediaStreamType media_type,
+ const gchar *name,
+ RakiaDirection direction);
+
+gboolean rakia_sip_session_remove_media (RakiaSipSession *self,
+ RakiaSipMedia *media,
+ guint status,
+ const gchar *reason);
+
+void rakia_sip_session_ringing (RakiaSipSession *self);
+void rakia_sip_session_queued (RakiaSipSession *self);
+void rakia_sip_session_accept (RakiaSipSession *self);
+
+void rakia_sip_session_media_changed (RakiaSipSession *self);
+
+GPtrArray *rakia_sip_session_get_medias (RakiaSipSession *self);
+
+
+/* What is for ? */
+gboolean rakia_sip_session_pending_offer (RakiaSipSession *self);
+
+/* Should be private */
+
+void rakia_sip_session_change_state (RakiaSipSession *session,
+ RakiaSipSessionState new_state);
+
+/* Obsolete */
+
+void rakia_sip_session_respond (RakiaSipSession *self, gint status,
+ const char *message);
+
+gboolean rakia_sip_session_is_accepted (RakiaSipSession *self);
+
+gboolean rakia_sip_session_has_media (RakiaSipSession *self,
+ TpMediaStreamType media_type);
+
+gint rakia_sip_session_rate_native_transport (RakiaSipSession *session,
+ const GValue *transport);
+
+void rakia_sip_session_set_hold_requested (RakiaSipSession *session,
+ gboolean hold_requested);
+
+G_END_DECLS
+
+#endif /* #ifndef __RAKIA_SIP_SESSION_H__*/
diff --git a/tests/suppressions/rakia.supp b/tests/suppressions/rakia.supp
new file mode 100644
index 0000000..2052a1c
--- /dev/null
+++ b/tests/suppressions/rakia.supp
@@ -0,0 +1,47 @@
+# GObject
+
+{
+ GType keeps its memory forever
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:g_type_class_ref
+}
+{
+ GType keeps its memory forever
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:g_type_add_interface_static
+}
+{
+ GType keeps its memory forever
+ Memcheck:Leak
+ fun:*alloc
+ ...
+ fun:g_type_init_with_debug_flags
+}
+
+# Sofia SIP
+
+{
+ Ignore all errors inside sofia sip
+ Memcheck:Addr8
+ fun:__strspn_sse42
+ ...
+ fun:nua_stack_respond
+}
+{
+ Ignore all errors inside sofia sip
+ Memcheck:Addr8
+ fun:__strspn_sse42
+ ...
+ fun:sdp_parse
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Addr8
+ fun:__strspn_sse42
+ ...
+ fun:nua_client_init_request
+}
diff --git a/tests/suppressions/tp-glib.supp b/tests/suppressions/tp-glib.supp
new file mode 100644
index 0000000..18d0dac
--- /dev/null
+++ b/tests/suppressions/tp-glib.supp
@@ -0,0 +1,250 @@
+# Valgrind error suppression file
+
+# ============================= libc ==================================
+
+{
+ ld.so initialization + selinux
+ Memcheck:Leak
+ ...
+ fun:_dl_init
+ obj:/lib/ld-*.so
+}
+
+{
+ dlopen initialization, triggered by handle-leak-debug code
+ Memcheck:Leak
+ ...
+ fun:__libc_dlopen_mode
+ fun:init
+ fun:backtrace
+ fun:handle_leak_debug_bt
+ fun:dynamic_ensure_handle
+ fun:tp_handle_ensure
+}
+
+# ============================= GLib ==================================
+
+{
+ g_set_prgname copies its argument
+ Memcheck:Leak
+ ...
+ fun:g_set_prgname
+}
+
+{
+ one g_get_charset per child^Wprocess
+ Memcheck:Leak
+ ...
+ fun:g_get_charset
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_static_string
+}
+
+{
+ GQuarks can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_quark_from_string
+}
+
+{
+ interned strings can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_intern_string
+}
+
+{
+ interned strings can't be freed
+ Memcheck:Leak
+ ...
+ fun:g_intern_static_string
+}
+
+{
+ shared global default g_main_context
+ Memcheck:Leak
+ ...
+ fun:g_main_context_new
+ fun:g_main_context_default
+}
+
+{
+ GTest initialization
+ Memcheck:Leak
+ ...
+ fun:g_test_init
+ fun:main
+}
+
+{
+ GTest admin
+ Memcheck:Leak
+ ...
+ fun:g_test_add_vtable
+}
+
+{
+ GTest pseudorandomness
+ Memcheck:Leak
+ ...
+ fun:g_rand_new_with_seed_array
+ fun:test_run_seed
+ ...
+ fun:g_test_run
+}
+
+{
+ GSLice initialization
+ Memcheck:Leak
+ ...
+ fun:g_malloc0
+ fun:g_slice_init_nomessage
+ fun:g_slice_alloc
+}
+
+# ============================= GObject ===============================
+
+{
+ g_type_init
+ Memcheck:Leak
+ ...
+ fun:g_type_init
+}
+
+{
+ g_type_register_static
+ Memcheck:Leak
+ ...
+ fun:g_type_register_static
+}
+
+# ============================= dbus-glib =============================
+
+{
+ dbus-glib, https://bugs.freedesktop.org/show_bug.cgi?id=14125
+ Memcheck:Addr4
+ fun:g_hash_table_foreach
+ obj:/usr/lib/libdbus-glib-1.so.2.1.0
+ fun:g_object_run_dispose
+}
+
+{
+ registering marshallers is permanent
+ Memcheck:Leak
+ ...
+ fun:dbus_g_object_register_marshaller_array
+ fun:dbus_g_object_register_marshaller
+}
+
+{
+ dbus-glib specialized GTypes are permanent
+ Memcheck:Leak
+ ...
+ fun:dbus_g_type_specialized_init
+}
+
+{
+ libdbus shared connection
+ Memcheck:Leak
+ ...
+ fun:dbus_g_bus_get
+}
+
+{
+ dbus-gobject registrations aren't freed unless we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:g_slist_append
+ fun:dbus_g_connection_register_g_object
+}
+
+{
+ DBusGProxy slots aren't freed unless we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_allocate_data_slot
+ ...
+ fun:dbus_g_proxy_constructor
+}
+
+{
+ error registrations are for life, not just for Christmas
+ Memcheck:Leak
+ ...
+ fun:dbus_g_error_domain_register
+}
+
+# ============================= telepathy-glib ========================
+
+{
+ tp_dbus_daemon_constructor @daemons once per DBusConnection
+ Memcheck:Leak
+ ...
+ fun:g_slice_alloc
+ fun:tp_dbus_daemon_constructor
+}
+
+{
+ tp_proxy_subclass_add_error_mapping refs the enum
+ Memcheck:Leak
+ ...
+ fun:g_type_class_ref
+ fun:tp_proxy_subclass_add_error_mapping
+}
+
+{
+ tp_proxy_or_subclass_hook_on_interface_add never frees its list
+ Memcheck:Leak
+ ...
+ fun:tp_proxy_or_subclass_hook_on_interface_add
+}
+
+{
+ tp_dbus_daemon_constructor filter not freed til we fall off the bus
+ Memcheck:Leak
+ ...
+ fun:dbus_connection_add_filter
+ fun:tp_dbus_daemon_constructor
+}
+
+# ============================= unclassified ==========================
+
+{
+ creating param specs in tp_proxy_class_intern_init
+ Memcheck:Leak
+ fun:memalign
+ fun:posix_memalign
+ fun:slab_allocator_alloc_chunk
+ fun:g_slice_alloc
+ fun:g_slice_alloc0
+ fun:g_type_create_instance
+ fun:g_param_spec_internal
+ fun:g_param_spec_string
+}
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:_dl_relocate_object
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}
+
+{
+ ld.so initialization on glibc 2.9
+ Memcheck:Cond
+ fun:strlen
+ fun:_dl_init_paths
+ fun:dl_main
+ fun:_dl_sysdep_start
+ fun:_dl_start
+ obj:/lib/ld-2.9.so
+}
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index 74645c2..a0b30d6 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -10,9 +10,11 @@ TWISTED_TESTS = \
test-message.py \
test-self-alias.py \
text/initiate-requestotron.py \
- voip/incoming-basics.py \
- voip/outgoing-basics.py \
- voip/dtmf.py \
+ voip/calltest.py \
+ voip/ringing-queued.py \
+ voip/requestable-classes.py \
+ voip/direction-change.py \
+ voip/add-remove-content.py \
$(NULL)
TESTS =
diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py
index c590b3d..1edb092 100644
--- a/tests/twisted/constants.py
+++ b/tests/twisted/constants.py
@@ -27,14 +27,13 @@ CHANNEL_IFACE_TUBE = CHANNEL + ".Interface.Tube"
CHANNEL_IFACE_SASL_AUTH = CHANNEL + ".Interface.SaslAuthentication.DRAFT"
CHANNEL_IFACE_CONFERENCE = CHANNEL + '.Interface.Conference'
-CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call.DRAFT"
+CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call1"
CHANNEL_TYPE_CONTACT_LIST = CHANNEL + ".Type.ContactList"
CHANNEL_TYPE_CONTACT_SEARCH = CHANNEL + ".Type.ContactSearch"
CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
CHANNEL_TYPE_TUBES = CHANNEL + ".Type.Tubes"
CHANNEL_TYPE_STREAM_TUBE = CHANNEL + ".Type.StreamTube"
CHANNEL_TYPE_DBUS_TUBE = CHANNEL + ".Type.DBusTube"
-CHANNEL_TYPE_STREAMED_MEDIA = CHANNEL + ".Type.StreamedMedia"
CHANNEL_TYPE_TEXT = CHANNEL + ".Type.Text"
CHANNEL_TYPE_FILE_TRANSFER = CHANNEL + ".Type.FileTransfer"
CHANNEL_TYPE_SERVER_AUTHENTICATION = \
@@ -56,35 +55,46 @@ INITIATOR_HANDLE = CHANNEL + '.InitiatorHandle'
INITIATOR_ID = CHANNEL + '.InitiatorID'
INTERFACES = CHANNEL + '.Interfaces'
-INITIAL_AUDIO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialAudio'
-INITIAL_VIDEO = CHANNEL_TYPE_STREAMED_MEDIA + '.InitialVideo'
-IMMUTABLE_STREAMS = CHANNEL_TYPE_STREAMED_MEDIA + '.ImmutableStreams'
-
-CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio'
-CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo'
-CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents'
+INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio'
+INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo'
+INITIAL_AUDIO_NAME = CHANNEL_TYPE_CALL + '.InitialAudioName'
+INITIAL_VIDEO_NAME = CHANNEL_TYPE_CALL + '.InitialVideoName'
+INITIAL_TRANSPORT = CHANNEL_TYPE_CALL + '.InitialTransport'
+MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents'
DTMF_INITIAL_TONES = CHANNEL_IFACE_DTMF + '.InitialTones'
-CALL_CONTENT = 'org.freedesktop.Telepathy.Call.Content.DRAFT'
+CALL_CONTENT = 'org.freedesktop.Telepathy.Call1.Content'
CALL_CONTENT_IFACE_MEDIA = \
- 'org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT'
+ 'org.freedesktop.Telepathy.Call1.Content.Interface.Media'
-CALL_CONTENT_CODECOFFER = \
- 'org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT'
+CALL_CONTENT_MEDIA_DESCRIPTION = \
+ 'org.freedesktop.Telepathy.Call1.Content.MediaDescription'
-CALL_STREAM = 'org.freedesktop.Telepathy.Call.Stream.DRAFT'
+CALL_STREAM = 'org.freedesktop.Telepathy.Call1.Stream'
CALL_STREAM_IFACE_MEDIA = \
- 'org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT'
+ 'org.freedesktop.Telepathy.Call1.Stream.Interface.Media'
+
+CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call1.Stream.Endpoint'
-CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT'
+
+CALL_STREAM_ENDPOINT_STATE_CONNECTING = 0
+CALL_STREAM_ENDPOINT_STATE_PROVISIONALLY_CONNECTED = 1
+CALL_STREAM_ENDPOINT_STATE_FULLY_CONNECTED = 2
+CALL_STREAM_ENDPOINT_STATE_EXHAUSTED_CANDIDATES = 3
+CALL_STREAM_ENDPOINT_STATE_FAILED = 4
CALL_MEDIA_TYPE_AUDIO = 0
CALL_MEDIA_TYPE_VIDEO = 1
-CALL_STREAM_TRANSPORT_RAW_UDP = 0
-CALL_STREAM_TRANSPORT_ICE = 1
-CALL_STREAM_TRANSPORT_GOOGLE = 2
+
+MEDIA_STREAM_BASE_PROTO_UDP = 0
+MEDIA_STREAM_BASE_PROTO_TCP = 1
+
+CALL_STREAM_TRANSPORT_UNKNOWN = 0
+CALL_STREAM_TRANSPORT_RAW_UDP = 1
+CALL_STREAM_TRANSPORT_ICE = 2
+
CALL_STATE_UNKNOWN = 0
CALL_STATE_PENDING_INITIATOR = 1
@@ -96,12 +106,17 @@ CALL_MEMBER_FLAG_RINGING = 1
CALL_MEMBER_FLAG_HELD = 2
CALL_DISPOSITION_NONE = 0
-CALL_DISPOSITION_EARLY_MEDIA = 1
-CALL_DISPOSITION_INITIAL = 2
+CALL_DISPOSITION_INITIAL = 1
CALL_SENDING_STATE_NONE = 0
CALL_SENDING_STATE_PENDING_SEND = 1
CALL_SENDING_STATE_SENDING = 2
+CALL_SENDING_STATE_PENDING_STOP_SENDING = 3
+
+CALL_STREAM_FLOW_STATE_STOPPED = 0
+CALL_STREAM_FLOW_STATE_PENDING_START = 1
+CALL_STREAM_FLOW_STATE_PENDING_STOP = 2
+CALL_STREAM_FLOW_STATE_STARTED = 3
SUBSCRIPTION_STATE_UNKNOWN = 0
SUBSCRIPTION_STATE_NO = 1
@@ -293,10 +308,23 @@ HSR_NONE = 0
HSR_REQUESTED = 1
HSR_RESOURCE_NOT_AVAILABLE = 2
-CALL_STATE_RINGING = 1
-CALL_STATE_QUEUED = 2
-CALL_STATE_HELD = 4
-CALL_STATE_FORWARDED = 8
+CALL_STATE_UNKNOWN = 0,
+CALL_STATE_PENDING_INITIATOR = 1
+CALL_STATE_INITIALISING = 2
+CALL_STATE_INITIALISED = 3
+CALL_STATE_ACCEPTED = 4
+CALL_STATE_ACTIVE = 5
+CALL_STATE_ENDED = 6
+
+
+CALL_MEMBER_FLAG_RINGING = 1
+CALL_MEMBER_FLAG_HELD = 2
+
+CALL_FLAG_LOCALLY_HELD = 1
+CALL_FLAG_LOCALLY_RINGING = 2
+CALL_FLAG_LOCALLY_QUEUED = 4
+CALL_FLAG_FORWARDED = 8
+CALL_FLAG_CLEARING = 16
CONN_STATUS_CONNECTED = 0
CONN_STATUS_CONNECTING = 1
@@ -401,3 +429,13 @@ TLS_CERT_STATE_REJECTED = 2
TLS_REJECT_REASON_UNKNOWN = 0
TLS_REJECT_REASON_UNTRUSTED = 1
+
+
+CALL_CONTENT_PACKETIZATION_RTP = 0
+CALL_CONTENT_PACKETIZATION_RAW = 1
+CALL_CONTENT_PACKETIZATION_MSN_WEBCAM = 2
+
+CALL_SCR_UNKNOWN = 0
+CALL_SCR_PROGRESS_MADE = 1
+CALL_SCR_USER_REQUESTED = 2
+CALL_SCR_MEDIA_ERROR = 12
diff --git a/tests/twisted/sofiatest.py b/tests/twisted/sofiatest.py
index 2dec912..ad85947 100644
--- a/tests/twisted/sofiatest.py
+++ b/tests/twisted/sofiatest.py
@@ -32,7 +32,7 @@ class SipProxy(sip.RegisterProxy):
def handle_request(self, message, addr):
if message.method == 'REGISTER':
- return sip.RegisterProxy.handle_request(self, message, addr)
+ return sip.RegisterProxy.handle_REGISTER_request(self, message, addr)
elif message.method == 'OPTIONS' and \
'REGISTRATION PROBE' == message.headers.get('subject','')[0]:
self.deliverResponse(self.responseFromRequest(200, message))
@@ -58,7 +58,8 @@ def prepare_test(event_func, register_cb, params=None):
'password': 'testpwd',
'proxy-host': '127.0.0.1',
'port': dbus.UInt16(random.randint(9090, 9999)),
- 'local-ip-address': '127.0.0.1'
+ 'local-ip-address': '127.0.0.1',
+ 'transport': 'udp'
}
if params is not None:
diff --git a/tests/twisted/tools/Makefile.am b/tests/twisted/tools/Makefile.am
index 9dab883..dd2d2b9 100644
--- a/tests/twisted/tools/Makefile.am
+++ b/tests/twisted/tools/Makefile.am
@@ -1,5 +1,6 @@
exec-with-log.sh: exec-with-log.sh.in
- sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" $< > $@
+ sed -e "s|[@]abs_top_builddir[@]|@abs_top_builddir@|g" \
+ -e "s|[@]abs_top_srcdir[@]|@abs_top_srcdir@|g" $< > $@
chmod +x $@
%.conf: %.conf.in
diff --git a/tests/twisted/tools/exec-with-log.sh.in b/tests/twisted/tools/exec-with-log.sh.in
index 907bbf8..6f37ef8 100644
--- a/tests/twisted/tools/exec-with-log.sh.in
+++ b/tests/twisted/tools/exec-with-log.sh.in
@@ -13,6 +13,9 @@ if test -n "$RAKIA_TEST_VALGRIND"; then
export G_DEBUG=gc-friendly
export G_SLICE=always-malloc
RAKIA_WRAPPER="valgrind --leak-check=full"
+# RAKIA_WRAPPER="$RAKIA_WRAPPER --gen-suppressions=all"
+ RAKIA_WRAPPER="$RAKIA_WRAPPER --suppressions=@abs_top_srcdir@/tests/suppressions/tp-glib.supp"
+ RAKIA_WRAPPER="$RAKIA_WRAPPER --suppressions=@abs_top_srcdir@/tests/suppressions/rakia.supp"
fi
exec $RAKIA_WRAPPER @abs_top_builddir@/src/telepathy-rakia
diff --git a/tests/twisted/voip/add-remove-content.py b/tests/twisted/voip/add-remove-content.py
new file mode 100644
index 0000000..21f9088
--- /dev/null
+++ b/tests/twisted/voip/add-remove-content.py
@@ -0,0 +1,212 @@
+import calltest
+import constants as cs
+import re
+from servicetest import (
+ EventPattern, call_async,
+ assertEquals, assertNotEquals, assertContains, assertLength,
+ assertDoesNotContain
+ )
+
+class AddRemoveContent(calltest.CallTest):
+
+ def __init__(self, *params):
+ calltest.CallTest.__init__(self, *params)
+
+ def add_content_succesful(self):
+ o = self.chan.Call1.AddContent("new audio", cs.MEDIA_STREAM_TYPE_AUDIO,
+ cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
+
+
+ self.q.expect('dbus-signal', signal='ContentAdded',
+ args=[o], path=self.chan.__dbus_object_path__)
+
+ content = self.add_content(o, initial=False, incoming=False)
+
+ self.add_candidates(content.stream)
+
+ md_path, _ = content.Get(cs.CALL_CONTENT_IFACE_MEDIA,
+ 'MediaDescriptionOffer')
+ md = self.bus.get_object (self.conn.bus_name, md_path)
+ md.Accept(self.context.get_audio_md_dbus(self.remote_handle))
+ self.q.expect_many(
+ EventPattern('dbus-signal', signal='MediaDescriptionOfferDone'),
+ EventPattern('dbus-signal', signal='LocalMediaDescriptionChanged'),
+ EventPattern('dbus-signal', signal='RemoteMediaDescriptionsChanged'))
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ reinvite_event, _ = self.q.expect_many(
+ EventPattern('sip-invite'),
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__))
+
+ self.add_to_medias('audio')
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ self.medias)
+
+ self.context.accept(reinvite_event.sip_message)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+
+ self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ EventPattern('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START],
+ path=content.stream.__dbus_object_path__))
+
+
+ content.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ self.q.expect('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__)
+
+ return content
+
+
+ def add_content_rejected(self):
+ o = self.chan.Call1.AddContent("new audio", cs.MEDIA_STREAM_TYPE_AUDIO,
+ cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
+
+
+ self.q.expect('dbus-signal', signal='ContentAdded',
+ args=[o], path=self.chan.__dbus_object_path__)
+
+ content = self.add_content(o, initial=False, incoming=False)
+
+ self.add_candidates(content.stream)
+
+ md_path, _ = content.Get(cs.CALL_CONTENT_IFACE_MEDIA,
+ 'MediaDescriptionOffer')
+ md = self.bus.get_object (self.conn.bus_name, md_path)
+ md.Accept(self.context.get_audio_md_dbus(self.remote_handle))
+ self.q.expect_many(
+ EventPattern('dbus-signal', signal='MediaDescriptionOfferDone'),
+ EventPattern('dbus-signal', signal='LocalMediaDescriptionChanged'),
+ EventPattern('dbus-signal', signal='RemoteMediaDescriptionsChanged'))
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ reinvite_event, _ = self.q.expect_many(
+ EventPattern('sip-invite'),
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__))
+
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ self.medias + [('audio', None, None)])
+ res = re.match('(.*)(m=.*)', reinvite_event.sip_message.body,
+ re.MULTILINE | re.DOTALL)
+
+ body = res.group(1) + 'm=audio 0 RTP/AVP 0'
+
+ self.add_to_medias(None)
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+
+ o = self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ EventPattern('dbus-signal', signal='ContentRemoved',
+ path=self.chan_path))
+
+ assertEquals(content.__dbus_object_path__, o[1].args[0])
+ assertEquals(self.remote_handle, o[1].args[1][0])
+
+ self.contents.remove(content)
+
+
+ def remove_content_successful(self, x):
+ content = self.contents[x]
+ self.contents.remove(content)
+ content.Remove()
+
+ reinvite_event, content_removed = self.q.expect_many(
+ EventPattern('sip-invite'),
+ EventPattern('dbus-signal', signal='ContentRemoved',
+ path=self.chan.__dbus_object_path__))
+ assertEquals(content.__dbus_object_path__, content_removed.args[0])
+ assertEquals(self.self_handle, content_removed.args[1][0])
+ assertEquals(cs.CALL_SCR_USER_REQUESTED, content_removed.args[1][1])
+
+ self.medias[x] = (None, None)
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ self.medias)
+
+ self.context.accept(reinvite_event.sip_message)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ def remote_add(self):
+ self.add_to_medias('audio')
+
+ self.context.reinvite(self.medias)
+
+ ca = self.q.expect('dbus-signal', signal='ContentAdded')
+
+ content = self.add_content(ca.args[0], incoming=True)
+
+ self.add_candidates(content.stream)
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ o = self.q.expect_many(
+ EventPattern('sip-response', code=200),
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__))
+
+ acc = o[0]
+ self.context.check_call_sdp(acc.sip_message.body, self.medias)
+ self.context.ack(acc.sip_message)
+
+ return content
+
+
+ def remote_remove(self, index):
+ self.medias[index] = (None, None)
+
+ self.context.reinvite(self.medias)
+
+ ca = self.q.expect('dbus-signal', signal='ContentRemoved')
+
+ o = self.q.expect_many(
+ EventPattern('sip-response', code=200))
+
+
+ acc = o[0]
+ self.context.check_call_sdp(acc.sip_message.body, self.medias)
+ self.context.ack(acc.sip_message)
+
+
+ def during_call(self):
+ self.add_content_succesful()
+ self.add_content_succesful()
+ self.add_content_rejected()
+ self.add_content_succesful()
+ self.remove_content_successful(1)
+ self.add_content_succesful()
+ self.remote_add()
+ self.remote_remove(-1)
+ self.add_content_succesful()
+ self.remote_remove(-3)
+
+
+ return calltest.CallTest.during_call(self)
+
+
+
+
+if __name__ == '__main__':
+ calltest.run(klass=AddRemoveContent)
diff --git a/tests/twisted/voip/calltest.py b/tests/twisted/voip/calltest.py
new file mode 100644
index 0000000..f038d72
--- /dev/null
+++ b/tests/twisted/voip/calltest.py
@@ -0,0 +1,507 @@
+import dbus
+
+from sofiatest import exec_test
+
+from servicetest import (
+ make_channel_proxy, wrap_channel, sync_dbus,
+ EventPattern, call_async, ProxyWrapper,
+ assertEquals, assertNotEquals, assertContains, assertLength,
+ )
+import constants as cs
+from voip_test import VoipTestContext
+
+class CallTest:
+ def __init__(self, q, bus, conn, sip_proxy, incoming, audio=True,
+ video=False, peer='foo@bar.com'):
+ self.q = q
+ self.bus = bus
+ self.conn = conn
+ self.sip_proxy = sip_proxy
+ self.incoming = incoming
+ self.peer = peer
+ self.content_count = 0;
+ if audio:
+ self.content_count += 1
+ if incoming:
+ self.initial_audio_content_name = 'initial_audio_' + str(self.content_count)
+ else:
+ self.initial_audio_content_name = 'initialaudio'
+ else:
+ self.initial_audio_content_name = None
+ if video:
+ self.content_count += 1
+ if incoming:
+ self.initial_video_content_name = 'initial_video_' + str(self.content_count)
+ else:
+ self.initial_video_content_name = 'initialvideo'
+ else:
+ self.initial_video_content_name = None
+ self.contents = []
+
+ self.medias = []
+ if self.initial_audio_content_name:
+ self.add_to_medias('audio')
+ if self.initial_video_content_name:
+ self.add_to_medias('video')
+
+
+ def add_to_medias(self, mediatype, direction=None):
+ for i in range(0, len(self.medias)):
+ if self.medias[i][1] == 0:
+ self.medias[i] = (mediatype, direction)
+ return
+ self.medias += [(mediatype, direction)]
+
+ def connect(self):
+ self.conn.Connect()
+ self.q.expect('dbus-signal', signal='StatusChanged', args=[0, 1])
+
+ self.context = VoipTestContext(self.q, self.conn, self.bus,
+ self.sip_proxy,
+ 'sip:testacc@127.0.0.1', self.peer)
+ self.self_handle = self.conn.GetSelfHandle()
+ self.remote_handle = self.conn.RequestHandles(1,
+ [self.context.peer])[0]
+
+ def check_channel_props(self, props, initial):
+ assertEquals(cs.CHANNEL_TYPE_CALL, props[cs.CHANNEL_TYPE])
+ assertEquals(cs.HT_CONTACT, props[cs.CHANNEL + '.TargetHandleType'])
+ assertEquals(self.remote_handle, props[cs.CHANNEL + '.TargetHandle'])
+ if self.incoming:
+ assertEquals(self.remote_handle,
+ props[cs.CHANNEL + '.InitiatorHandle'])
+ else:
+ assertEquals(self.self_handle,
+ props[cs.CHANNEL + '.InitiatorHandle'])
+ if initial and self.initial_audio_content_name is not None:
+ assertEquals(True, props[cs.CHANNEL_TYPE_CALL + '.InitialAudio'])
+ assertEquals(self.initial_audio_content_name,
+ props[cs.CHANNEL_TYPE_CALL + '.InitialAudioName'])
+ else:
+ assertEquals(False, props[cs.CHANNEL_TYPE_CALL + '.InitialAudio'])
+ if initial and self.initial_video_content_name is not None:
+ assertEquals(True, props[cs.CHANNEL_TYPE_CALL + '.InitialVideo'])
+ assertEquals(self.initial_video_content_name,
+ props[cs.CHANNEL_TYPE_CALL + '.InitialVideoName'])
+ else:
+ assertEquals(False, props[cs.CHANNEL_TYPE_CALL + '.InitialVideo'])
+ assertEquals(True, props[cs.CHANNEL_TYPE_CALL + '.MutableContents'])
+ assertEquals(False, props[cs.CHANNEL_TYPE_CALL + '.HardwareStreaming'])
+
+ def check_endpoint(self, content, endpoint_path):
+ endpoint = self.bus.get_object(self.conn.bus_name, endpoint_path)
+ endpoint_props = endpoint.GetAll(cs.CALL_STREAM_ENDPOINT)
+ assertEquals(('',''), endpoint_props['RemoteCredentials'])
+ assertEquals(self.context.get_remote_candidates_dbus(),
+ endpoint_props['RemoteCandidates'])
+ assertLength(0, endpoint_props['EndpointState'])
+ assertEquals(cs.CALL_STREAM_TRANSPORT_RAW_UDP,
+ endpoint_props['Transport'])
+ assertEquals(False, endpoint_props['IsICELite'])
+
+
+ def __add_stream (self, content, stream_path, initial, incoming):
+ tmpstream = self.bus.get_object (self.conn.bus_name, stream_path)
+
+ content.stream = ProxyWrapper (tmpstream, cs.CALL_STREAM,
+ {'Media': cs.CALL_STREAM_IFACE_MEDIA})
+
+ stream_props = content.stream.Properties.GetAll(cs.CALL_STREAM)
+ assertEquals(True, stream_props['CanRequestReceiving'])
+ if incoming:
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
+ stream_props['LocalSendingState'])
+ else:
+ assertEquals(cs.CALL_SENDING_STATE_SENDING,
+ stream_props['LocalSendingState'])
+
+ if incoming:
+ assertEquals(
+ {self.remote_handle: cs.CALL_SENDING_STATE_SENDING},
+ stream_props['RemoteMembers'])
+ else:
+ assertEquals(
+ {self.remote_handle: cs.CALL_SENDING_STATE_PENDING_SEND},
+ stream_props['RemoteMembers'])
+
+ smedia_props = content.stream.Properties.GetAll(
+ cs.CALL_STREAM_IFACE_MEDIA)
+ assertEquals(cs.CALL_SENDING_STATE_NONE, smedia_props['SendingState'])
+ if initial:
+ assertEquals(cs.CALL_SENDING_STATE_NONE,
+ smedia_props['ReceivingState'])
+ else:
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
+ smedia_props['ReceivingState'])
+ assertEquals(cs.CALL_STREAM_TRANSPORT_RAW_UDP,
+ smedia_props['Transport'])
+ assertEquals([], smedia_props['LocalCandidates'])
+ assertEquals(("",""), smedia_props['LocalCredentials'])
+ assertEquals([], smedia_props['STUNServers'])
+ assertEquals([], smedia_props['RelayInfo'])
+ assertEquals(True, smedia_props['HasServerInfo'])
+ if incoming:
+ assertLength(1, smedia_props['Endpoints'])
+ self.check_endpoint(content, smedia_props['Endpoints'][0])
+
+ else:
+ assertEquals([], smedia_props['Endpoints'])
+ assertEquals(False, smedia_props['ICERestartPending'])
+
+ def get_md(self, content):
+ if content.media_type == cs.MEDIA_STREAM_TYPE_AUDIO:
+ return context.get_audio_md_dbus(self.remote_handle)
+ elif content.media_type == cs.MEDIA_STREAM_TYPE_VIDEO:
+ return context.get_video_md_dbus(self.remote_handle)
+ else:
+ assert False
+
+ def add_content(self, content_path, initial = False, incoming = None):
+
+ if initial:
+ incoming = self.incoming
+ else:
+ assert incoming is not None
+
+ content = self.bus.get_object (self.conn.bus_name, content_path)
+
+ content_props = content.GetAll(cs.CALL_CONTENT)
+ if initial:
+ assertEquals(cs.CALL_DISPOSITION_INITIAL,
+ content_props['Disposition'])
+ if content_props['Type'] == cs.MEDIA_STREAM_TYPE_AUDIO:
+ assertEquals(self.initial_audio_content_name,
+ content_props['Name'])
+ elif content_props['Type'] == cs.MEDIA_STREAM_TYPE_VIDEO:
+ assertEquals(self.initial_video_content_name,
+ content_props['Name'])
+ else:
+ assert Fale
+
+ else:
+ assertEquals(cs.CALL_DISPOSITION_NONE,
+ content_props['Disposition'])
+
+ content.media_type = content_props['Type']
+
+ cmedia_props = content.GetAll(cs.CALL_CONTENT_IFACE_MEDIA)
+ assertLength(0, cmedia_props['RemoteMediaDescriptions'])
+ assertLength(0, cmedia_props['LocalMediaDescriptions'])
+ if incoming:
+ assertNotEquals('/', cmedia_props['MediaDescriptionOffer'][0])
+ else:
+ assertNotEquals('/', cmedia_props['MediaDescriptionOffer'][0])
+ assertEquals(cs.CALL_CONTENT_PACKETIZATION_RTP,
+ cmedia_props['Packetization'])
+ assertEquals(cs.CALL_SENDING_STATE_NONE, cmedia_props['CurrentDTMFState'])
+
+ self.contents.append(content)
+
+ self.__add_stream(content, content_props['Streams'][0], initial,
+ incoming)
+
+ if incoming:
+ md = self.bus.get_object (self.conn.bus_name,
+ cmedia_props['MediaDescriptionOffer'][0])
+ md.Accept(self.context.get_audio_md_dbus(self.remote_handle))
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='MediaDescriptionOfferDone'),
+ EventPattern('dbus-signal', signal='LocalMediaDescriptionChanged'),
+ EventPattern('dbus-signal', signal='RemoteMediaDescriptionsChanged'))
+
+ return content
+
+ def check_call_properties(self, call_props):
+ if self.incoming:
+ assertEquals(cs.CALL_STATE_INITIALISED, call_props['CallState'])
+ else:
+ assertEquals(cs.CALL_STATE_PENDING_INITIATOR,
+ call_props['CallState'])
+ assertEquals(0, call_props['CallFlags'])
+ assertEquals(False, call_props['HardwareStreaming'])
+ assertEquals(True, call_props['MutableContents'])
+ assertEquals(self.initial_audio_content_name is not None,
+ call_props['InitialAudio'])
+ assertEquals(self.initial_audio_content_name or "",
+ call_props['InitialAudioName'])
+ assertEquals(self.initial_video_content_name is not None,
+ call_props['InitialVideo'])
+ assertEquals(self.initial_video_content_name or "",
+ call_props['InitialVideoName'])
+ assertEquals(cs.CALL_STREAM_TRANSPORT_RAW_UDP,
+ call_props['InitialTransport'])
+ assertEquals({self.remote_handle: 0}, call_props['CallMembers'])
+
+
+ assertLength(self.content_count, call_props['Contents'])
+
+
+ def initiate(self):
+ if self.incoming:
+ self.context.incoming_call(self.medias)
+ else:
+ self.chan_path = self.conn.Requests.CreateChannel({
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_HANDLE: self.remote_handle,
+ cs.INITIAL_AUDIO: self.initial_audio_content_name is not None,
+ cs.INITIAL_AUDIO_NAME: self.initial_audio_content_name or "",
+ cs.INITIAL_VIDEO: self.initial_video_content_name is not None,
+ cs.INITIAL_VIDEO_NAME: self.initial_video_content_name or "",
+ })[0]
+
+ nc = self.q.expect('dbus-signal', signal='NewChannels')
+
+ assertLength(1, nc.args)
+ assertLength(1, nc.args[0]) # one channel
+ assertLength(2, nc.args[0][0]) # two struct members
+ self.chan_path, props = nc.args[0][0]
+ self.check_channel_props(props, True)
+
+ self.chan = wrap_channel(
+ self.bus.get_object(self.conn.bus_name, self.chan_path), 'Call1',
+ ['Hold'])
+
+ call_props = self.chan.Properties.GetAll(cs.CHANNEL_TYPE_CALL)
+ self.check_call_properties(call_props)
+ for c in call_props['Contents']:
+ self.add_content(c, True)
+
+ if not self.incoming:
+ self.chan.Call1.Accept()
+
+ self.q.expect_many(
+ *self.stream_dbus_signal_event('ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]))
+
+ for c in self.contents:
+ c.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ mdo = c.Get(cs.CALL_CONTENT_IFACE_MEDIA,
+ 'MediaDescriptionOffer')
+ md = self.bus.get_object (self.conn.bus_name, mdo[0])
+ md.Accept(self.context.get_audio_md_dbus(
+ self.remote_handle))
+
+ self.q.expect_many(
+ EventPattern('dbus-signal', signal='MediaDescriptionOfferDone',
+ path=c.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='LocalMediaDescriptionChanged',
+ path=c.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMediaDescriptionsChanged',
+ path=c.__dbus_object_path__))
+
+ mdo = c.Get(cs.CALL_CONTENT_IFACE_MEDIA,
+ 'MediaDescriptionOffer')
+ assertEquals(('/', {}), mdo)
+
+ self.add_candidates(c.stream)
+
+ self.invite_event = self.q.expect('sip-invite')
+
+ def content_dbus_signal_event(self, s, **kwparams):
+ return map(
+ lambda c:
+ EventPattern('dbus-signal', signal=s,
+ path=c.__dbus_object_path__,
+ **kwparams),
+ self.contents)
+
+ def stream_dbus_signal_event(self, s, **kwparams):
+ return map(
+ lambda c:
+ EventPattern('dbus-signal', signal=s,
+ path=c.stream.__dbus_object_path__,
+ **kwparams),
+ self.contents)
+
+ def add_candidates(self, stream):
+ stream.Media.AddCandidates(self.context.get_remote_candidates_dbus())
+ stream.Media.FinishInitialCandidates()
+
+ self.q.expect('dbus-signal', signal='LocalCandidatesAdded',
+ path=stream.__dbus_object_path__)
+
+ def accept_incoming(self):
+ if not self.incoming:
+ return
+
+ self.chan.Call1.Accept()
+
+ events = self.stream_dbus_signal_event(
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]) + \
+ self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='CallStateChanged'),
+ EventPattern('dbus-signal', signal='CallStateChanged'),
+ *events)
+ assertEquals(cs.CALL_STATE_ACCEPTED, o[0].args[0])
+ assertEquals(cs.CALL_STATE_ACTIVE, o[1].args[0])
+
+
+ for c in self.contents:
+ c.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ #self.q.expect('dbus-signal', signal='ReceivingStateChanged',
+ # args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ # path=c.stream.__dbus_object_path__)
+
+ self.q.expect_many(
+ *self.stream_dbus_signal_event(
+ 'LocalSendingStateChanged',
+ predicate=lambda e: cs.CALL_SENDING_STATE_SENDING == e.args[0]))
+
+ for c in self.contents:
+ c.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ self.q.expect('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=c.stream.__dbus_object_path__)
+
+ self.add_candidates(c.stream)
+
+
+ acc = self.q.expect('sip-response', call_id=self.context.call_id,
+ code=200)
+
+ self.context.check_call_sdp(acc.sip_message.body, self.medias)
+ self.context.ack(acc.sip_message)
+
+ def accept_outgoing(self):
+ if self.incoming:
+ return
+
+ self.context.check_call_sdp(self.invite_event.sip_message.body,
+ self.medias)
+ self.context.accept(self.invite_event.sip_message)
+
+ ack_cseq = "%s ACK" % self.invite_event.cseq.split()[0]
+ del self.invite_event
+
+ events = self.content_dbus_signal_event('NewMediaDescriptionOffer') + \
+ self.stream_dbus_signal_event('EndpointsChanged')
+ o = self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ # Call accepted
+ *events)
+
+ for i in o:
+ if i.type != 'dbus-signal' or \
+ i.signal != 'NewMediaDescriptionOffer':
+ continue
+ md = self.bus.get_object (self.conn.bus_name, i.args[0])
+ md.Accept(self.context.get_audio_md_dbus(self.remote_handle))
+
+ o = self.q.expect_many(
+ # Call accepted
+ EventPattern('dbus-signal', signal='CallStateChanged'))
+
+ assertEquals(cs.CALL_STATE_ACCEPTED, o[0].args[0])
+
+ for c in self.contents:
+ mdo = c.Get(cs.CALL_CONTENT_IFACE_MEDIA, 'MediaDescriptionOffer')
+ assertEquals(('/', {}), mdo)
+
+
+ for c in self.contents:
+ c.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+ self.q.expect('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=c.stream.__dbus_object_path__)
+ for i in o:
+ if i.type == 'dbus-signal' and i.signal == 'EndpointsChanged':
+ assertLength(1, i.args[0])
+ assertLength(0, i.args[1])
+ self.check_endpoint(c, i.args[0][0])
+
+ def accept(self):
+ if self.incoming:
+ self.accept_incoming()
+ else:
+ self.accept_outgoing()
+
+ def running_check(self):
+ self.context.options_ping(self.q)
+ sync_dbus(self.bus, self.q, self.conn)
+
+ for c in self.contents:
+ props = c.stream.Properties.GetAll(cs.CALL_STREAM)
+ assertEquals(cs.CALL_SENDING_STATE_SENDING,
+ props['LocalSendingState'])
+ assertEquals({self.remote_handle: cs.CALL_SENDING_STATE_SENDING},
+ props['RemoteMembers'])
+
+ props = c.stream.Properties.GetAll(cs.CALL_STREAM_IFACE_MEDIA)
+ assertEquals(cs.CALL_STREAM_FLOW_STATE_STARTED,
+ props['SendingState'])
+ assertEquals(cs.CALL_STREAM_FLOW_STATE_STARTED,
+ props['ReceivingState'])
+
+
+ def hangup(self):
+ if self.incoming:
+ bye_msg = self.context.terminate()
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='CallStateChanged',
+ path=self.chan_path),
+ EventPattern('sip-response', cseq=bye_msg.headers['cseq'][0]))
+ assertEquals(cs.CALL_STATE_ENDED, o[0].args[0])
+ assertEquals(0, o[0].args[1])
+ assertEquals(self.remote_handle, o[0].args[2][0])
+ else:
+ self.chan.Call1.Hangup(cs.CALL_SCR_USER_REQUESTED, "",
+ "User hangs up")
+ ended_event, bye_event = self.q.expect_many(
+ EventPattern('dbus-signal', signal='CallStateChanged'),
+ EventPattern('sip-bye', call_id=self.context.call_id))
+ # Check that we're the actor
+ assertEquals(cs.CALL_STATE_ENDED, ended_event.args[0])
+ assertEquals(0, ended_event.args[1])
+ assertEquals((self.self_handle, cs.CALL_SCR_USER_REQUESTED, "",
+ "User hangs up"), ended_event.args[2])
+
+ # For completeness, reply to the BYE.
+ bye_response = self.sip_proxy.responseFromRequest(
+ 200, bye_event.sip_message)
+ self.sip_proxy.deliverResponse(bye_response)
+
+
+ def during_call(self):
+ pass
+
+ def run(self):
+ self.connect()
+ self.initiate()
+ self.accept()
+ self.running_check()
+ self.during_call()
+ self.hangup()
+ self.chan.Close()
+
+
+
+
+def run_call_test(q, bus, conn, sip_proxy, incoming=False, klass=CallTest,
+ **params):
+ test = klass(q, bus, conn, sip_proxy, incoming, **params)
+ test.run()
+
+def run(**params):
+ exec_test(lambda q, b, c, s:
+ run_call_test(q, b, c, s, incoming=True, **params))
+ exec_test(lambda q, b, c, s:
+ run_call_test(q, b, c, s, incoming=False, **params))
+
+if __name__ == '__main__':
+ run()
+ run(peer='foo@sip.bar.com')
+ run(video=True)
+ run(video=True,audio=False)
diff --git a/tests/twisted/voip/direction-change.py b/tests/twisted/voip/direction-change.py
new file mode 100644
index 0000000..cd69940
--- /dev/null
+++ b/tests/twisted/voip/direction-change.py
@@ -0,0 +1,771 @@
+import calltest
+import constants as cs
+from servicetest import (
+ EventPattern, call_async, ProxyWrapper,
+ assertEquals, assertNotEquals, assertContains, assertLength,
+ assertDoesNotContain
+ )
+
+class DirectionChange(calltest.CallTest):
+
+ def __init__(self, *params):
+ self.sending = True
+ self.receiving = True
+ calltest.CallTest.__init__(self, *params)
+
+ def stop_sending(self, content):
+
+ self.sending = False
+
+ content.stream.SetSending(False)
+ self.q.expect('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP],
+ path=content.stream.__dbus_object_path__)
+
+ content.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STOPPED)
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STOPPED],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='LocalSendingStateChanged',
+ path=content.stream.__dbus_object_path__),
+ EventPattern('sip-invite'))
+
+ assertEquals(cs.CALL_SENDING_STATE_NONE, o[1].args[0])
+ assertEquals(self.self_handle, o[1].args[1][0])
+ reinvite_event = o[2]
+
+ assertContains('a=recvonly', reinvite_event.sip_message.body)
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+ body = reinvite_event.sip_message.body.replace(
+ 'recvonly', self.receiving and 'sendonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ def start_sending(self, content):
+ content.stream.SetSending(True)
+
+ self.sending = True
+
+ reinvite_event, lss = self.q.expect_many(
+ EventPattern('sip-invite'),
+ EventPattern('dbus-signal', signal='LocalSendingStateChanged',
+ path=content.stream.__dbus_object_path__))
+
+ assertEquals(cs.CALL_SENDING_STATE_SENDING, lss.args[0])
+ assertEquals(self.self_handle, lss.args[1][0])
+
+ assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body)
+
+ if self.receiving:
+ assertDoesNotContain('a=inactive',
+ reinvite_event.sip_message.body)
+ assertDoesNotContain('a=recvonly',
+ reinvite_event.sip_message.body)
+ else:
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ [('audio','recvonly')])
+
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+ self.context.accept(reinvite_event.sip_message)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ EventPattern('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START]))
+
+ content.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ self.q.expect('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__)
+
+ def stop_start_sending_user_requested(self, content):
+ self.stop_sending(content)
+ self.start_sending(content)
+
+
+ def stop_start_sending_remote_requested(self, content):
+ lss_event = [
+ EventPattern('dbus-signal', signal='LocalSendingStateChanged')]
+ direction_events = [
+ EventPattern('dbus-signal', signal='SendingStateChanged'),
+ EventPattern('dbus-signal', signal='ReceivingStateChanged'),
+ ]
+
+ self.stop_sending(content)
+ self.q.forbid_events(lss_event)
+ self.q.forbid_events(direction_events)
+
+ self.context.reinvite([('audio', None, 'sendonly')])
+
+ acc = self.q.expect('sip-response', call_id=self.context.call_id,
+ code=200)
+
+ self.context.check_call_sdp(acc.sip_message.body,
+ [('audio', None, 'recvonly')])
+ self.context.ack(acc.sip_message)
+
+ self.q.unforbid_events(lss_event)
+
+ self.context.reinvite([('audio',None, None)])
+
+ acc, lss = self.q.expect_many(
+ EventPattern('sip-response', call_id=self.context.call_id,
+ code=200),
+ EventPattern('dbus-signal', signal='LocalSendingStateChanged'))
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND, lss.args[0])
+ assertEquals(self.remote_handle, lss.args[1][0])
+ self.context.check_call_sdp(acc.sip_message.body,
+ [('audio', None, 'recvonly')])
+
+ assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED,
+ content.stream.Properties.Get(cs.CALL_STREAM_IFACE_MEDIA,
+ 'SendingState'))
+
+ self.context.check_call_sdp(acc.sip_message.body)
+ self.context.ack(acc.sip_message)
+
+ self.q.unforbid_events(direction_events)
+ self.start_sending(content)
+
+ def reject_stop_receiving(self, content):
+ content.stream.RequestReceiving(self.remote_handle, False)
+
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__),
+ EventPattern('sip-invite'))
+
+
+ assertLength(0, o[1].args[2])
+ assertLength(1, o[1].args[0])
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_STOP_SENDING,
+ o[1].args[0][self.remote_handle])
+ assertEquals(self.self_handle, o[1].args[3][0])
+ assertEquals(cs.CALL_SCR_USER_REQUESTED, o[1].args[3][1])
+ reinvite_event = o[2]
+
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ [('audio', None, 'sendonly')])
+ if self.sending:
+ body = reinvite_event.sip_message.body.replace('sendonly',
+ 'sendrecv')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ # Return to regular state
+
+ invite_event = [EventPattern('sip-invite')]
+
+ self.q.forbid_events(invite_event)
+
+ content.stream.RequestReceiving(self.remote_handle, True)
+
+ _ , o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__,
+ predicate=lambda e: self.remote_handle in e.args[0] and e.args[0][self.remote_handle] == cs.CALL_SENDING_STATE_PENDING_SEND))
+
+ assertLength(1, o.args[0])
+ assertLength(0, o.args[2])
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
+ o.args[0][self.remote_handle])
+ assertEquals(self.self_handle, o.args[3][0])
+ assertEquals(cs.CALL_SCR_USER_REQUESTED, o.args[3][1])
+
+ self.context.options_ping(self.q)
+ self.q.unforbid_events(invite_event)
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ _, reinvite_event = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('sip-invite'))
+
+ assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body)
+ assertDoesNotContain('a=recvonly', reinvite_event.sip_message.body)
+ assertDoesNotContain('a=inactive', reinvite_event.sip_message.body)
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+ self.context.accept(reinvite_event.sip_message)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ o = self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__))
+
+ assertLength(0, o[1].args[2])
+ assertLength(1, o[1].args[0])
+ assertEquals(cs.CALL_SENDING_STATE_SENDING,
+ o[1].args[0][self.remote_handle])
+
+
+
+
+ def stop_receiving(self, content):
+ self.receiving = False
+
+ content.stream.RequestReceiving(self.remote_handle, False)
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__),
+ EventPattern('sip-invite'))
+
+
+ assertLength(0, o[1].args[2])
+ assertLength(1, o[1].args[0])
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_STOP_SENDING,
+ o[1].args[0][self.remote_handle])
+ assertEquals(self.self_handle, o[1].args[3][0])
+ assertEquals(cs.CALL_SCR_USER_REQUESTED, o[1].args[3][1])
+ reinvite_event = o[2]
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ [('audio', None, 'sendonly')])
+ body = reinvite_event.sip_message.body.replace(
+ 'sendonly', self.sending and 'recvonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ o = self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__))
+
+ assertLength(1, o[1].args[0])
+ assertLength(0, o[1].args[2])
+ assertEquals(cs.CALL_SENDING_STATE_NONE,
+ o[1].args[0][self.remote_handle])
+ #assertEquals(self.remote_handle, o[1].args[3][0])
+ #assertEquals(cs.CALL_SCR_USER_REQUESTED, o[1].args[3][1])
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STOPPED)
+
+ self.q.expect('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STOPPED],
+ path=content.stream.__dbus_object_path__),
+
+ def start_receiving(self, content, already_receiving=False):
+ self.receiving = True
+
+ content.stream.RequestReceiving(self.remote_handle, True)
+
+ self.q.expect('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START],
+ path=content.stream.__dbus_object_path__),
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__),
+ EventPattern('sip-invite'))
+
+
+ assertLength(0, o[1].args[2])
+ assertLength(1, o[1].args[0])
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
+ o[1].args[0][self.remote_handle])
+ assertEquals(self.self_handle, o[1].args[3][0])
+ assertEquals(cs.CALL_SCR_USER_REQUESTED, o[1].args[3][1])
+ reinvite_event = o[2]
+
+ assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body)
+ assertDoesNotContain('a=inactive', reinvite_event.sip_message.body)
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+
+ self.context.accept(reinvite_event.sip_message)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ o = self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__))
+
+ assertLength(1, o[1].args[0])
+ assertLength(0, o[1].args[2])
+ assertEquals(cs.CALL_SENDING_STATE_SENDING,
+ o[1].args[0][self.remote_handle])
+ #assertEquals(self.remote_handle, o[1].args[3][0])
+ #assertEquals(cs.CALL_SCR_USER_REQUESTED, o[1].args[3][1])
+
+
+ def stop_start_receiving_user_requested(self, content):
+ self.stop_receiving(content)
+ self.start_receiving(content)
+
+ def reject_start_receiving(self, content):
+ self.stop_receiving(content)
+
+ content.stream.RequestReceiving(self.remote_handle, True)
+
+ self.q.expect('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START],
+ path=content.stream.__dbus_object_path__),
+
+ content.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__),
+ EventPattern('sip-invite'))
+
+
+ assertLength(0, o[1].args[2])
+ assertLength(1, o[1].args[0])
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
+ o[1].args[0][self.remote_handle])
+ assertEquals(self.self_handle, o[1].args[3][0])
+ assertEquals(cs.CALL_SCR_USER_REQUESTED, o[1].args[3][1])
+ reinvite_event = o[2]
+
+ assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body)
+ assertDoesNotContain('a=inactive', reinvite_event.sip_message.body)
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+ body = reinvite_event.sip_message.body + 'a=recvonly\r\r'
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect_many(
+ EventPattern('sip-ack', cseq=ack_cseq))
+
+ # Now let's restart receiving for real
+ self.receiving = True
+ self.context.reinvite()
+
+ acc , rmb = self.q.expect_many(
+ EventPattern('sip-response', code=200),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__,
+ predicate=lambda e: e.args[0] == {self.remote_handle: cs.CALL_SENDING_STATE_SENDING}))
+
+ self.context.check_call_sdp(acc.sip_message.body, self.medias)
+ self.context.ack(acc.sip_message)
+
+
+ def hold(self):
+ self.chan.Hold.RequestHold(True)
+
+ events = self.stream_dbus_signal_event (
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP])
+ events += self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP])
+ o = self.q.expect_many(
+ EventPattern('sip-invite'),
+ EventPattern('dbus-signal', signal='HoldStateChanged',
+ args=[cs.HS_PENDING_HOLD, cs.HSR_REQUESTED]),
+ *events)
+ reinvite_event = o[0]
+ for c in self.contents:
+ c.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STOPPED)
+ c.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STOPPED)
+
+ events = self.stream_dbus_signal_event (
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STOPPED])
+ events += self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STOPPED])
+ self.q.expect_many(
+ EventPattern('dbus-signal', signal='HoldStateChanged',
+ args=[cs.HS_HELD, cs.HSR_REQUESTED]),
+ *events)
+ medias = map(lambda x: (x[0], x[1] == 'recvonly' and 'inactive' or 'sendonly'), self.medias)
+ self.context.check_call_sdp(reinvite_event.sip_message.body, medias)
+
+ body = reinvite_event.sip_message.body.replace('sendonly', 'recvonly')
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+
+ def unhold_fail(self, receiving=True):
+ self.chan.Hold.RequestHold(False)
+
+
+ events = self.stream_dbus_signal_event (
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])
+ events += self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='HoldStateChanged',
+ args=[cs.HS_PENDING_UNHOLD, cs.HSR_REQUESTED]),
+ *events)
+
+ if receiving:
+ self.contents[0].stream.Media.ReportReceivingFailure(
+ cs.CALL_SCR_MEDIA_ERROR, "", "")
+ events = self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP])
+ else:
+ self.contents[0].stream.Media.ReportSendingFailure(
+ cs.CALL_SCR_MEDIA_ERROR, "", "")
+ events = self.stream_dbus_signal_event(
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_STOP])
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='HoldStateChanged',
+ args=[cs.HS_PENDING_HOLD,
+ cs.HSR_RESOURCE_NOT_AVAILABLE]),
+ *events)
+
+
+
+ def unhold_succeed(self):
+ self.chan.Hold.RequestHold(False)
+
+ events = self.stream_dbus_signal_event (
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])
+ events += self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_PENDING_START])
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='HoldStateChanged',
+ args=[cs.HS_PENDING_UNHOLD, cs.HSR_REQUESTED]),
+ *events)
+ for c in self.contents:
+ c.stream.Media.CompleteReceivingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+ c.stream.Media.CompleteSendingStateChange(
+ cs.CALL_STREAM_FLOW_STATE_STARTED)
+
+ events = self.stream_dbus_signal_event (
+ 'ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED])
+ events += self.stream_dbus_signal_event(
+ 'SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STARTED])
+ o = self.q.expect_many(
+ EventPattern('sip-invite'),
+ EventPattern('dbus-signal', signal='HoldStateChanged',
+ args=[cs.HS_UNHELD, cs.HSR_REQUESTED]),
+ *events)
+ reinvite_event = o[0]
+ medias = map(lambda x: (x[0], None), self.medias)
+ assertDoesNotContain('a=sendonly', reinvite_event.sip_message.body)
+ assertDoesNotContain('a=inactive', reinvite_event.sip_message.body)
+ self.context.check_call_sdp(reinvite_event.sip_message.body, medias)
+
+ self.context.accept(reinvite_event.sip_message)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ def sending_failed(self, content):
+
+ self.sending = False
+
+ content.stream.Media.ReportSendingFailure(
+ cs.CALL_SCR_MEDIA_ERROR, "", "sending error")
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='SendingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STOPPED],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='LocalSendingStateChanged',
+ path=content.stream.__dbus_object_path__,
+ predicate=lambda e: e.args[0] == 0),
+ EventPattern('sip-invite'))
+
+ assertEquals(cs.CALL_SENDING_STATE_NONE, o[1].args[0])
+ assertEquals(self.self_handle, o[1].args[1][0])
+ reinvite_event = o[2]
+
+ assertContains('a=recvonly', reinvite_event.sip_message.body)
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+ body = reinvite_event.sip_message.body.replace(
+ 'recvonly', self.receiving and 'sendonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ self.start_sending(content)
+
+
+ def receiving_failed(self, content):
+
+ self.receiving = False
+
+ assert self.contents[0].stream.Properties.Get(cs.CALL_STREAM_IFACE_MEDIA,
+ 'SendingState') == cs.CALL_STREAM_FLOW_STATE_STARTED
+
+ content.stream.Media.ReportReceivingFailure(
+ cs.CALL_SCR_MEDIA_ERROR, "", "receiving error")
+
+ o = self.q.expect_many(
+ EventPattern('dbus-signal', signal='ReceivingStateChanged',
+ args=[cs.CALL_STREAM_FLOW_STATE_STOPPED],
+ path=content.stream.__dbus_object_path__),
+ EventPattern('dbus-signal', signal='RemoteMembersChanged',
+ path=content.stream.__dbus_object_path__,
+ predicate=lambda e: e.args[0] == {self.remote_handle: cs.CALL_SENDING_STATE_PENDING_STOP_SENDING}),
+ EventPattern('sip-invite'))
+
+ reinvite_event = o[2]
+
+ assertContains('a=sendonly', reinvite_event.sip_message.body)
+ self.context.check_call_sdp(reinvite_event.sip_message.body)
+ body = reinvite_event.sip_message.body.replace(
+ 'sendonly', self.sending and 'recvonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ self.start_receiving(content)
+
+
+ def add_local_content_during_hold(self):
+
+ media_unhold_events = [
+ EventPattern('dbus-signal', signal='SendingStateChanged'),
+ EventPattern('dbus-signal', signal='ReceivingStateChanged')]
+ self.q.forbid_events(media_unhold_events)
+
+ content_path = self.chan.Call1.AddContent(
+ "NewContent", cs.MEDIA_STREAM_TYPE_AUDIO,
+ cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL)
+
+
+ self.q.expect('dbus-signal', signal='ContentAdded',
+ args=[content_path])
+
+ content = self.bus.get_object (self.conn.bus_name, content_path)
+
+ content_props = content.GetAll(cs.CALL_CONTENT)
+ assertEquals(cs.CALL_DISPOSITION_NONE, content_props['Disposition'])
+ assertEquals('NewContent', content_props['Name'])
+ assertEquals(cs.MEDIA_STREAM_TYPE_AUDIO, content_props['Type'])
+
+ assertLength(1, content_props['Streams'])
+
+ tmpstream = self.bus.get_object (self.conn.bus_name,
+ content_props['Streams'][0])
+
+ stream = ProxyWrapper (tmpstream, cs.CALL_STREAM,
+ {'Media': cs.CALL_STREAM_IFACE_MEDIA})
+
+ stream_props = stream.Properties.GetAll(cs.CALL_STREAM)
+ assertEquals(True, stream_props['CanRequestReceiving'])
+ assertEquals(
+ {self.remote_handle: cs.CALL_SENDING_STATE_PENDING_SEND},
+ stream_props['RemoteMembers'])
+ assertEquals(cs.CALL_SENDING_STATE_SENDING,
+ stream_props['LocalSendingState'])
+
+ smedia_props = stream.Properties.GetAll(
+ cs.CALL_STREAM_IFACE_MEDIA)
+ assertEquals(cs.CALL_SENDING_STATE_NONE, smedia_props['SendingState'])
+ assertEquals(cs.CALL_SENDING_STATE_NONE,
+ smedia_props['ReceivingState'])
+
+ mdo = content.Get(cs.CALL_CONTENT_IFACE_MEDIA,
+ 'MediaDescriptionOffer')
+ md = self.bus.get_object (self.conn.bus_name, mdo[0])
+ md.Accept(self.context.get_audio_md_dbus(
+ self.remote_handle))
+
+ self.add_candidates(stream)
+
+ reinvite_event = self.q.expect('sip-invite')
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ self.medias + [('audio', 'sendonly')])
+ body = reinvite_event.sip_message.body.replace(
+ 'sendonly', self.receiving and 'recvonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+ content.Remove()
+
+
+ reinvite_event = self.q.expect('sip-invite')
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ self.medias)
+ body = reinvite_event.sip_message.body.replace(
+ 'sendonly', self.receiving and 'recvonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+
+ self.q.unforbid_events(media_unhold_events)
+
+
+ def add_remote_content_during_hold(self):
+
+ media_unhold_events = [
+ EventPattern('dbus-signal', signal='SendingStateChanged'),
+ EventPattern('dbus-signal', signal='ReceivingStateChanged')]
+ self.q.forbid_events(media_unhold_events)
+
+ self.context.reinvite([('audio', 'recvonly'), ('audio', None)])
+
+ ca = self.q.expect('dbus-signal', signal='ContentAdded')
+
+ content = self.bus.get_object (self.conn.bus_name, ca.args[0])
+
+ content_props = content.GetAll(cs.CALL_CONTENT)
+ assertEquals(cs.CALL_DISPOSITION_NONE, content_props['Disposition'])
+ assertEquals(cs.MEDIA_STREAM_TYPE_AUDIO, content_props['Type'])
+
+ assertLength(1, content_props['Streams'])
+
+ tmpstream = self.bus.get_object (self.conn.bus_name,
+ content_props['Streams'][0])
+
+ stream = ProxyWrapper (tmpstream, cs.CALL_STREAM,
+ {'Media': cs.CALL_STREAM_IFACE_MEDIA})
+
+ stream_props = stream.Properties.GetAll(cs.CALL_STREAM)
+ assertEquals(True, stream_props['CanRequestReceiving'])
+ assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
+ stream_props['LocalSendingState'])
+ assertEquals(
+ {self.remote_handle: cs.CALL_SENDING_STATE_SENDING},
+ stream_props['RemoteMembers'])
+
+ smedia_props = stream.Properties.GetAll(
+ cs.CALL_STREAM_IFACE_MEDIA)
+ assertEquals(cs.CALL_SENDING_STATE_NONE, smedia_props['SendingState'])
+ assertEquals(cs.CALL_SENDING_STATE_NONE,
+ smedia_props['ReceivingState'])
+
+ mdo = content.Get(cs.CALL_CONTENT_IFACE_MEDIA,
+ 'MediaDescriptionOffer')
+ md = self.bus.get_object (self.conn.bus_name, mdo[0])
+ md.Accept(self.context.get_audio_md_dbus(
+ self.remote_handle))
+
+ self.add_candidates(stream)
+
+ acc = self.q.expect('sip-response', code=200)
+ self.context.check_call_sdp(
+ acc.sip_message.body,
+ [('audio', 'sendonly'), ('audio', 'inactive')])
+ self.context.ack(acc.sip_message)
+
+ content.Remove()
+
+
+ reinvite_event = self.q.expect('sip-invite')
+
+ self.context.check_call_sdp(reinvite_event.sip_message.body,
+ self.medias)
+ body = reinvite_event.sip_message.body.replace(
+ 'sendonly', self.receiving and 'recvonly' or 'inactive')
+
+ self.context.accept(reinvite_event.sip_message, body)
+
+ ack_cseq = "%s ACK" % reinvite_event.cseq.split()[0]
+ self.q.expect('sip-ack', cseq=ack_cseq)
+
+
+ self.q.unforbid_events(media_unhold_events)
+
+
+ def during_call(self):
+ content = self.contents[0]
+
+ remote_hold_event = [
+ EventPattern('dbus-signal', signal='CallStateChanged')]
+ self.q.forbid_events(remote_hold_event)
+
+ self.stop_start_sending_user_requested(content)
+ self.stop_start_sending_remote_requested(content)
+
+ self.stop_start_receiving_user_requested(content)
+
+ self.running_check()
+
+ self.reject_stop_receiving(content)
+ self.stop_start_receiving_user_requested(content)
+ self.reject_start_receiving(content)
+
+ self.running_check()
+
+ self.sending_failed(content)
+ self.receiving_failed(content)
+
+ self.running_check()
+
+ direction_change_events = \
+ self.stream_dbus_signal_event('LocalSendingStateChanged') + \
+ self.stream_dbus_signal_event('RemoteMembersChanged')
+
+ self.q.forbid_events(direction_change_events)
+ self.hold()
+ self.add_local_content_during_hold()
+ self.add_remote_content_during_hold()
+ self.unhold_fail(receiving=True)
+ self.unhold_fail(receiving=False)
+ self.unhold_succeed()
+ self.q.unforbid_events(direction_change_events)
+
+ self.q.unforbid_events(remote_hold_event)
+
+ return calltest.CallTest.during_call(self)
+
+
+
+
+if __name__ == '__main__':
+ calltest.run(klass=DirectionChange)
+
diff --git a/tests/twisted/voip/dtmf.py b/tests/twisted/voip/dtmf.py
deleted file mode 100644
index 4369bc3..0000000
--- a/tests/twisted/voip/dtmf.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-Test DTMF dialstring playback and signalling.
-"""
-
-from sofiatest import exec_test
-from servicetest import (
- call_async, wrap_channel, EventPattern,
- assertEquals, assertContains, assertLength, assertSameSets
- )
-from voip_test import VoipTestContext
-import constants as cs
-
-def setup_dtmf_channel(context, initial_tones=None):
- q = context.q
- bus = context.bus
- conn = context.conn
-
- conn.Connect()
- q.expect('dbus-signal', signal='StatusChanged', args=[0, 1])
-
- request_params = {
- cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA,
- cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
- cs.TARGET_ID: context.peer,
- cs.INITIAL_AUDIO: True,
- }
- if initial_tones:
- request_params[cs.DTMF_INITIAL_TONES] = initial_tones
-
- path = conn.Requests.CreateChannel(request_params)[0]
-
- chan = wrap_channel(bus.get_object(conn.bus_name, path), 'StreamedMedia',
- ['MediaSignalling', 'DTMF'])
-
- channel_props = chan.Properties.GetAll(cs.CHANNEL)
-
- assertContains(cs.CHANNEL_IFACE_DTMF, channel_props['Interfaces'])
-
- dtmf_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_DTMF)
-
- if initial_tones:
- assertEquals(initial_tones, dtmf_props['InitialTones'])
- else:
- assertEquals('', dtmf_props['InitialTones'])
- assertEquals(False, dtmf_props['CurrentlySendingTones'])
-
- stream_handler = context.handle_audio_session(chan)
-
- invite_event = q.expect('sip-invite')
-
- context.accept(invite_event.sip_message)
-
- q.expect('dbus-signal', signal='SetRemoteCodecs')
-
- stream_handler.SupportedCodecs(context.get_audio_codecs_dbus())
- stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
-
- return chan
-
-def request_initial_tones(q, bus, conn, sip_proxy, peer='foo@bar.com'):
- context = VoipTestContext(q, conn, bus, sip_proxy, 'sip:testacc@127.0.0.1', peer)
-
- tones = '123'
-
- chan = setup_dtmf_channel(context, tones)
-
- q.expect_many(
- EventPattern('dbus-signal', signal='SendingTones', args=[tones]),
- EventPattern('dbus-signal', signal='StartTelephonyEvent', args=[int(tones[0])]))
-
- assertEquals(True, chan.Properties.Get(cs.CHANNEL_IFACE_DTMF, 'CurrentlySendingTones'))
-
- q.expect('dbus-signal', signal='StopTelephonyEvent')
-
- for i in range(1, len(tones) - 1):
- q.expect('dbus-signal', signal='StartTelephonyEvent', args=[int(tones[i])])
- q.expect('dbus-signal', signal='StopTelephonyEvent')
-
- q.expect('dbus-signal', signal='StoppedTones', args=[False])
-
- assertEquals(False, chan.Properties.Get(cs.CHANNEL_IFACE_DTMF, 'CurrentlySendingTones'))
-
-def multiple_tones(q, bus, conn, sip_proxy, peer='foo@bar.com'):
-
- context = VoipTestContext(q, conn, bus, sip_proxy, 'sip:testacc@127.0.0.1', peer)
-
- chan = setup_dtmf_channel(context)
-
- tones_deferred = '78'
- tones = '56w' + tones_deferred
-
- chan.DTMF.MultipleTones(tones)
-
- q.expect_many(
- EventPattern('dbus-signal', signal='SendingTones', args=[tones]),
- EventPattern('dbus-signal', signal='StartTelephonyEvent', args=[int(tones[0])]))
-
- dtmf_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_DTMF)
- assertEquals(True, dtmf_props['CurrentlySendingTones'])
- assertEquals('', dtmf_props['DeferredTones'])
-
- q.expect('dbus-signal', signal='StopTelephonyEvent')
-
- q.expect('dbus-signal', signal='StartTelephonyEvent', args=[int(tones[1])])
- q.expect('dbus-signal', signal='StopTelephonyEvent')
-
- q.expect('dbus-signal', signal='TonesDeferred', args=[tones_deferred])
-
- dtmf_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_DTMF)
-
- assertEquals(False, dtmf_props['CurrentlySendingTones'])
- assertEquals(tones_deferred, dtmf_props['DeferredTones'])
-
- chan.DTMF.MultipleTones(tones_deferred)
-
- q.expect_many(
- EventPattern('dbus-signal', signal='SendingTones', args=[tones_deferred]),
- EventPattern('dbus-signal', signal='StartTelephonyEvent', args=[int(tones_deferred[0])]))
-
- dtmf_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_DTMF)
-
- assertEquals(True, dtmf_props['CurrentlySendingTones'])
- assertEquals('', dtmf_props['DeferredTones'])
-
- q.expect('dbus-signal', signal='StopTelephonyEvent')
-
- for i in range(1, len(tones_deferred) - 1):
- q.expect('dbus-signal', signal='StartTelephonyEvent', args=[int(tones_deferred[i])])
- q.expect('dbus-signal', signal='StopTelephonyEvent')
-
- q.expect('dbus-signal', signal='StoppedTones', args=[False])
-
-def bleep_bloop(q, bus, conn, sip_proxy, peer='foo@bar.com'):
-
- context = VoipTestContext(q, conn, bus, sip_proxy, 'sip:testacc@127.0.0.1', peer)
-
- chan = setup_dtmf_channel(context)
-
- call_async(q, chan.DTMF, 'StartTone', 666, 3)
- q.expect_many(
- EventPattern('dbus-signal', signal='StartTelephonyEvent'),
- EventPattern('dbus-signal', signal='SendingTones', args=['3']),
- EventPattern('dbus-return', method='StartTone'),
- )
-
- assertEquals(True, chan.Properties.Get(cs.CHANNEL_IFACE_DTMF, 'CurrentlySendingTones'))
-
- call_async(q, chan.DTMF, 'StopTone', 666)
- q.expect_many(
- EventPattern('dbus-signal', signal='StopTelephonyEvent'),
- EventPattern('dbus-signal', signal='StoppedTones', args=[True]),
- EventPattern('dbus-return', method='StopTone'),
- )
-
- assertEquals(False, chan.Properties.Get(cs.CHANNEL_IFACE_DTMF, 'CurrentlySendingTones'))
-
-if __name__ == '__main__':
- exec_test(request_initial_tones)
- exec_test(multiple_tones)
- exec_test(bleep_bloop)
diff --git a/tests/twisted/voip/incoming-basics.py b/tests/twisted/voip/incoming-basics.py
deleted file mode 100644
index 9ab3949..0000000
--- a/tests/twisted/voip/incoming-basics.py
+++ /dev/null
@@ -1,163 +0,0 @@
-"""
-Test incoming call handling.
-"""
-
-import dbus
-
-from sofiatest import exec_test
-from servicetest import (
- make_channel_proxy, wrap_channel,
- EventPattern, call_async,
- assertEquals, assertContains, assertLength,
- )
-import constants as cs
-from voip_test import VoipTestContext
-
-def test(q, bus, conn, sip_proxy, peer='foo@bar.com'):
- conn.Connect()
- q.expect('dbus-signal', signal='StatusChanged', args=[0, 1])
-
- context = VoipTestContext(q, conn, bus, sip_proxy, 'sip:testacc@127.0.0.1', peer)
-
- self_handle = conn.GetSelfHandle()
- remote_handle = conn.RequestHandles(cs.HT_CONTACT, [context.peer])[0]
-
- # Try making a call to ourself. StreamedMedia should refuse this because
- # the API doesn't support it.
- context.incoming_call_from_self()
- q.expect('sip-response', code=501)
-
- # Remote end calls us
- context.incoming_call()
-
- nc, e = q.expect_many(
- EventPattern('dbus-signal', signal='NewChannels'),
- EventPattern('dbus-signal', signal='NewSessionHandler'),
- )[0:2]
-
- path, props = nc.args[0][0]
- ct = props[cs.CHANNEL_TYPE]
- ht = props[cs.CHANNEL + '.TargetHandleType']
- h = props[cs.CHANNEL + '.TargetHandle']
-
- assert ct == cs.CHANNEL_TYPE_STREAMED_MEDIA, ct
- assert ht == cs.HT_CONTACT, ht
- assert h == remote_handle, h
-
- 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
- assert e.args[1] == 'rtp'
- 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')
-
- streams = media_iface.ListStreams()
- assertLength(1, streams)
-
- stream_id, stream_handle, stream_type, _, stream_direction, pending_flags =\
- streams[0]
- assertEquals(remote_handle, stream_handle)
- assertEquals(cs.MEDIA_STREAM_TYPE_AUDIO, stream_type)
- assertEquals(cs.MEDIA_STREAM_DIRECTION_RECEIVE, stream_direction)
- assertEquals(cs.MEDIA_STREAM_PENDING_LOCAL_SEND, pending_flags)
-
- # Exercise channel properties
- channel_props = media_chan.GetAll(
- cs.CHANNEL, dbus_interface=dbus.PROPERTIES_IFACE)
- assertEquals(remote_handle, channel_props['TargetHandle'])
- assertEquals(cs.HT_CONTACT, channel_props['TargetHandleType'])
- assertEquals((cs.HT_CONTACT, remote_handle),
- media_chan.GetHandle(dbus_interface=cs.CHANNEL))
- assertEquals(context.peer_id, channel_props['TargetID'])
- assertEquals(context.peer_id, channel_props['InitiatorID'])
- assertEquals(remote_handle, channel_props['InitiatorHandle'])
- assertEquals(False, channel_props['Requested'])
-
- group_props = media_chan.GetAll(
- cs.CHANNEL_IFACE_GROUP, dbus_interface=dbus.PROPERTIES_IFACE)
-
- assert group_props['SelfHandle'] == self_handle, \
- (group_props['SelfHandle'], self_handle)
-
- flags = group_props['GroupFlags']
- assert flags & cs.GF_PROPERTIES, flags
- # Changing members in any way other than adding or removing yourself is
- # meaningless for incoming calls, and the flags need not be sent to change
- # your own membership.
- assert not flags & cs.GF_CAN_ADD, flags
- assert not flags & cs.GF_CAN_REMOVE, flags
- assert not flags & cs.GF_CAN_RESCIND, flags
-
- assert group_props['Members'] == [remote_handle], group_props['Members']
- assert group_props['RemotePendingMembers'] == [], \
- group_props['RemotePendingMembers']
- # We're local pending because remote_handle invited us.
- assert group_props['LocalPendingMembers'] == \
- [(self_handle, remote_handle, cs.GC_REASON_INVITED, '')], \
- unwrap(group_props['LocalPendingMembers'])
-
- streams = media_chan.ListStreams(
- dbus_interface=cs.CHANNEL_TYPE_STREAMED_MEDIA)
- assert len(streams) == 1, streams
- assert len(streams[0]) == 6, streams[0]
- # streams[0][0] is the stream identifier, which in principle we can't
- # make any assertion about (although in practice it's probably 1)
- assert streams[0][1] == remote_handle, (streams[0], remote_handle)
- assert streams[0][2] == cs.MEDIA_STREAM_TYPE_AUDIO, streams[0]
- # We haven't connected yet
- assert streams[0][3] == cs.MEDIA_STREAM_STATE_DISCONNECTED, streams[0]
- # In Gabble, incoming streams start off with remote send enabled, and
- # local send requested
- assert streams[0][4] == cs.MEDIA_STREAM_DIRECTION_RECEIVE, streams[0]
- assert streams[0][5] == cs.MEDIA_STREAM_PENDING_LOCAL_SEND, streams[0]
-
- # Connectivity checks happen before we have accepted the call
- stream_handler.NewNativeCandidate("fake", context.get_remote_transports_dbus())
- stream_handler.NativeCandidatesPrepared()
- stream_handler.Ready(context.get_audio_codecs_dbus())
- stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
- stream_handler.SupportedCodecs(context.get_audio_codecs_dbus())
-
- # At last, accept the call
- media_chan.AddMembers([self_handle], 'accepted')
-
- # Call is accepted, we become a member, and the stream that was pending
- # local send is now sending.
- memb, acc, _, _, _ = q.expect_many(
- EventPattern('dbus-signal', signal='MembersChanged',
- args=[u'', [self_handle], [], [], [], self_handle,
- cs.GC_REASON_NONE]),
- EventPattern('sip-response', call_id=context.call_id, code=200),
- 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]),
- )
-
- context.check_call_sdp(acc.sip_message.body)
-
- context.ack(acc.sip_message)
-
- # we are now both in members
- members = media_chan.GetMembers()
- assert set(members) == set([self_handle, remote_handle]), members
-
- # Connected! Blah, blah, ...
-
- # 'Nuff said
- bye_msg = context.terminate()
-
- q.expect_many(EventPattern('dbus-signal', signal='Closed', path=path),
- EventPattern('sip-response', cseq=bye_msg.headers['cseq'][0]))
-
-if __name__ == '__main__':
- exec_test(test)
- exec_test(lambda q, bus, conn, stream:
- test(q, bus, conn, stream, 'foo@sip.bar.com'))
diff --git a/tests/twisted/voip/outgoing-basics.py b/tests/twisted/voip/outgoing-basics.py
deleted file mode 100644
index d840c18..0000000
--- a/tests/twisted/voip/outgoing-basics.py
+++ /dev/null
@@ -1,284 +0,0 @@
-"""
-Test basic outgoing call handling, using CreateChannel and all three variations
-of RequestChannel.
-"""
-
-import dbus
-
-from sofiatest import exec_test
-from servicetest import (
- wrap_channel, EventPattern, call_async,
- assertEquals, assertContains, assertLength, assertSameSets
- )
-import constants as cs
-from voip_test import VoipTestContext
-
-# There are various deprecated APIs for requesting calls, documented at
-# <http://telepathy.freedesktop.org/wiki/Requesting StreamedMedia channels>.
-# These are ordered from most recent to most deprecated.
-CREATE = 0 # CreateChannel({TargetHandleType: Contact, TargetHandle: h});
- # RequestStreams()
-REQUEST_ANONYMOUS = 1 # RequestChannel(HandleTypeNone, 0); RequestStreams()
-REQUEST_ANONYMOUS_AND_ADD = 2 # RequestChannel(HandleTypeNone, 0);
- # AddMembers([h], ...); RequestStreams(h,...)
-REQUEST_NONYMOUS = 3 # RequestChannel(HandleTypeContact, h);
- # RequestStreams(h, ...)
-
-def create(q, bus, conn, sip_proxy, peer='foo@bar.com'):
- worker(q, bus, conn, sip_proxy, CREATE, peer)
-
-def request_anonymous(q, bus, conn, sip_proxy, peer='publish@foo.com'):
- worker(q, bus, conn, sip_proxy, REQUEST_ANONYMOUS, peer)
-
-def request_anonymous_and_add(q, bus, conn, sip_proxy,
- peer='publish-subscribe@foo.com/Res'):
- worker(q, bus, conn, sip_proxy, REQUEST_ANONYMOUS_AND_ADD, peer)
-
-def request_nonymous(q, bus, conn, sip_proxy, peer='subscribe@foo.com'):
- worker(q, bus, conn, sip_proxy, REQUEST_NONYMOUS, peer)
-
-def worker(q, bus, conn, sip_proxy, variant, peer):
- conn.Connect()
- q.expect('dbus-signal', signal='StatusChanged', args=[0, 1])
-
- self_handle = conn.GetSelfHandle()
- context = VoipTestContext(q, conn, bus, sip_proxy, 'sip:testacc@127.0.0.1', peer)
-
- self_handle = conn.GetSelfHandle()
- remote_handle = conn.RequestHandles(1, [context.peer])[0]
-
- if variant == REQUEST_NONYMOUS:
- path = conn.RequestChannel(cs.CHANNEL_TYPE_STREAMED_MEDIA,
- cs.HT_CONTACT, remote_handle, True)
- elif variant == CREATE:
- path = conn.Requests.CreateChannel({
- cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA,
- cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
- cs.TARGET_HANDLE: remote_handle,
- })[0]
- else:
- path = conn.RequestChannel(cs.CHANNEL_TYPE_STREAMED_MEDIA,
- cs.HT_NONE, 0, True)
-
- old_sig, new_sig = q.expect_many(
- EventPattern('dbus-signal', signal='NewChannel',
- predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args),
- EventPattern('dbus-signal', signal='NewChannels',
- predicate=lambda e:
- cs.CHANNEL_TYPE_CONTACT_LIST not in e.args[0][0][1].values()),
- )
-
- if variant == REQUEST_NONYMOUS or variant == CREATE:
- assertEquals( [path, cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.HT_CONTACT,
- remote_handle, True], old_sig.args)
- else:
- assertEquals( [path, cs.CHANNEL_TYPE_STREAMED_MEDIA, cs.HT_NONE, 0,
- True], old_sig.args)
-
- assertLength(1, new_sig.args)
- assertLength(1, new_sig.args[0]) # one channel
- assertLength(2, new_sig.args[0][0]) # two struct members
- emitted_props = new_sig.args[0][0][1]
-
- assertEquals(
- cs.CHANNEL_TYPE_STREAMED_MEDIA, emitted_props[cs.CHANNEL_TYPE])
-
- if variant == REQUEST_NONYMOUS or variant == CREATE:
- assertEquals(remote_handle, emitted_props[cs.TARGET_HANDLE])
- assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE])
- assertEquals(context.peer_id, emitted_props[cs.TARGET_ID])
- else:
- assertEquals(0, emitted_props[cs.TARGET_HANDLE])
- assertEquals(cs.HT_NONE, emitted_props[cs.TARGET_HANDLE_TYPE])
- assertEquals('', emitted_props[cs.TARGET_ID])
-
- assertEquals(True, emitted_props[cs.REQUESTED])
- assertEquals(self_handle, emitted_props[cs.INITIATOR_HANDLE])
- assertEquals('sip:testacc@127.0.0.1', emitted_props[cs.INITIATOR_ID])
-
- chan = wrap_channel(bus.get_object(conn.bus_name, path), 'StreamedMedia',
- ['MediaSignalling'])
-
- # Exercise basic Channel Properties
- channel_props = chan.Properties.GetAll(cs.CHANNEL)
-
- assertEquals(cs.CHANNEL_TYPE_STREAMED_MEDIA,
- channel_props.get('ChannelType'))
-
- if variant == REQUEST_NONYMOUS or variant == CREATE:
- assertEquals(remote_handle, channel_props['TargetHandle'])
- assertEquals(cs.HT_CONTACT, channel_props['TargetHandleType'])
- assertEquals(context.peer_id, channel_props['TargetID'])
- assertEquals((cs.HT_CONTACT, remote_handle), chan.GetHandle())
- else:
- assertEquals(0, channel_props['TargetHandle'])
- assertEquals(cs.HT_NONE, channel_props['TargetHandleType'])
- assertEquals('', channel_props['TargetID'])
- assertEquals((cs.HT_NONE, 0), chan.GetHandle())
-
- for interface in [
- cs.CHANNEL_IFACE_GROUP, cs.CHANNEL_IFACE_MEDIA_SIGNALLING,
- cs.TP_AWKWARD_PROPERTIES, cs.CHANNEL_IFACE_HOLD]:
- assertContains(interface, channel_props['Interfaces'])
-
- assertEquals(True, channel_props['Requested'])
- assertEquals('sip:testacc@127.0.0.1', channel_props['InitiatorID'])
- assertEquals(conn.GetSelfHandle(), channel_props['InitiatorHandle'])
-
- # Exercise Group Properties
- group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP)
-
- assertEquals([self_handle], group_props['Members'])
- assertEquals([], group_props['LocalPendingMembers'])
-
- if variant == REQUEST_NONYMOUS:
- # In this variant, they're meant to be in RP even though we've sent
- # nothing
- assertEquals([remote_handle], group_props['RemotePendingMembers'])
- else:
- # For an anonymous channel, the peer isn't yet known; for a Create-d
- # channel, the peer only appears in RP when we actually send them the
- # session-initiate
- assertEquals([], group_props['RemotePendingMembers'])
-
- if variant == REQUEST_ANONYMOUS_AND_ADD:
- # but we should be allowed to add the peer.
- chan.Group.AddMembers([remote_handle], 'I love backwards compat')
-
- base_flags = cs.GF_PROPERTIES | cs.GF_CAN_REMOVE | cs.GF_CAN_RESCIND
-
- if variant in [REQUEST_ANONYMOUS_AND_ADD, REQUEST_ANONYMOUS, CREATE]:
- expected_flags = base_flags | cs.GF_CAN_ADD
- else:
- expected_flags = base_flags
-
- assertEquals(bin(expected_flags), bin(group_props['GroupFlags']))
- assertEquals({}, group_props['HandleOwners'])
-
- assertEquals([], chan.StreamedMedia.ListStreams())
- streams = chan.StreamedMedia.RequestStreams(remote_handle,
- [cs.MEDIA_STREAM_TYPE_AUDIO])
- assertEquals(streams, chan.StreamedMedia.ListStreams())
- assertLength(1, streams)
-
- # streams[0][0] is the stream identifier, which in principle we can't
- # make any assertion about (although in practice it's probably 1)
-
- assertEquals((
- remote_handle,
- cs.MEDIA_STREAM_TYPE_AUDIO,
- # We haven't connected yet
- cs.MEDIA_STREAM_STATE_DISCONNECTED,
- # In Gabble, requested streams start off bidirectional
- cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL,
- cs.MEDIA_STREAM_PENDING_REMOTE_SEND),
- streams[0][1:])
-
- stream_handler = context.handle_audio_session(chan)
-
- sh_props = stream_handler.GetAll(
- cs.STREAM_HANDLER, dbus_interface=dbus.PROPERTIES_IFACE)
- assertEquals('none', sh_props['NATTraversal'])
- assertEquals(True, sh_props['CreatedLocally'])
-
- if variant == CREATE:
- # When we actually send INVITE to the peer, they should pop up in remote
- # pending.
- invite_event, _ = q.expect_many(
- EventPattern('sip-invite'),
- EventPattern('dbus-signal', signal='MembersChanged',
- args=["", [], [], [], [remote_handle], self_handle,
- cs.GC_REASON_INVITED]),
- )
- else:
- invite_event = q.expect('sip-invite')
-
- # Check the Group interface's properties again. Regardless of the call
- # requesting API in use, the state should be the same here:
- group_props = chan.Properties.GetAll(cs.CHANNEL_IFACE_GROUP)
- assertContains('HandleOwners', group_props)
- assertEquals([self_handle], group_props['Members'])
- assertEquals([], group_props['LocalPendingMembers'])
- assertEquals([remote_handle], group_props['RemotePendingMembers'])
-
- context.check_call_sdp(invite_event.sip_message.body)
- context.accept(invite_event.sip_message)
-
- ack_cseq = "%s ACK" % invite_event.cseq.split()[0]
- q.expect_many(
- EventPattern('sip-ack', cseq=ack_cseq),
- # Call accepted
- EventPattern('dbus-signal', signal='MembersChanged',
- args=['', [remote_handle], [], [], [], remote_handle,
- cs.GC_REASON_NONE]),
- EventPattern('dbus-signal', signal='SetRemoteCodecs'),
- ),
-
- stream_handler.SupportedCodecs(context.get_audio_codecs_dbus())
- stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
-
- # Time passes ... afterwards we close the chan
-
- chan.Group.RemoveMembers([self_handle], 'closed')
-
-
- mc_event, _, bye_event = q.expect_many(
- EventPattern('dbus-signal', signal='MembersChanged'),
- EventPattern('dbus-signal', signal='Close'),
- EventPattern('sip-bye', call_id=context.call_id),
- )
- # Check that we're the actor
- assertEquals(self_handle, mc_event.args[5])
-
- # For completeness, reply to the BYE.
- bye_response = sip_proxy.responseFromRequest(200, bye_event.sip_message)
- sip_proxy.deliverResponse(bye_response)
-
-def rccs(q, bus, conn, stream):
- """
- Tests that the connection's RequestableChannelClasses for StreamedMedia are
- sane.
- """
- conn.Connect()
-
- q.expect('dbus-signal', signal='StatusChanged',
- args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
-
- rccs = conn.Properties.Get(cs.CONN_IFACE_REQUESTS,
- 'RequestableChannelClasses')
-
- # Test Channel.Type.StreamedMedia
- media_classes = [ rcc for rcc in rccs
- if rcc[0][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_STREAMED_MEDIA ]
-
- assertLength(1, media_classes)
-
- fixed, allowed = media_classes[0]
-
- assertEquals(cs.HT_CONTACT, fixed[cs.TARGET_HANDLE_TYPE])
-
- expected_allowed = [
- cs.TARGET_ID, cs.TARGET_HANDLE,
- cs.INITIAL_VIDEO, cs.INITIAL_AUDIO,
- cs.DTMF_INITIAL_TONES
- ]
-
- allowed.sort()
- expected_allowed.sort()
- assertSameSets(expected_allowed, allowed)
-
-if __name__ == '__main__':
-
- exec_test(rccs)
- exec_test(create)
- exec_test(request_anonymous)
- exec_test(request_anonymous_and_add)
- exec_test(request_nonymous)
- exec_test(lambda q, b, c, s:
- create(q, b, c, s, peer='foo@gw.bar.com'))
- exec_test(lambda q, b, c, s:
- request_anonymous(q, b, c, s, peer='foo@gw.bar.com'))
- exec_test(lambda q, b, c, s:
- request_anonymous_and_add(q, b, c, s, peer='foo@gw.bar.com'))
- exec_test(lambda q, b, c, s:
- request_nonymous(q, b, c, s, peer='foo@gw.bar.com'))
diff --git a/tests/twisted/voip/requestable-classes.py b/tests/twisted/voip/requestable-classes.py
new file mode 100644
index 0000000..fde4ee6
--- /dev/null
+++ b/tests/twisted/voip/requestable-classes.py
@@ -0,0 +1,59 @@
+"""
+Test Requestable channels classes
+"""
+
+import dbus
+
+from sofiatest import exec_test
+from servicetest import (
+ wrap_channel, EventPattern, call_async, ProxyWrapper,
+ assertEquals, assertContains, assertLength, assertSameSets,
+ assertNotEquals
+ )
+import constants as cs
+from voip_test import VoipTestContext
+
+def rccs(q, bus, conn, stream):
+ """
+ Tests that the connection's RequestableChannelClasses for StreamedMedia are
+ sane.
+ """
+ conn.Connect()
+
+
+ a = q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_CONNECTING, cs.CSR_REQUESTED])
+
+ a = q.expect('dbus-signal', signal='StatusChanged',
+ args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED])
+
+ rccs = conn.Properties.Get(cs.CONN_IFACE_REQUESTS,
+ 'RequestableChannelClasses')
+
+ # Test Channel.Type.StreamedMedia
+ media_classes = [ rcc for rcc in rccs
+ if rcc[0][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_CALL ]
+
+ assertLength(2, media_classes)
+
+ for media_class in media_classes:
+ fixed, allowed = media_class
+
+ assertEquals(cs.HT_CONTACT, fixed[cs.TARGET_HANDLE_TYPE])
+ assert fixed.has_key(cs.INITIAL_AUDIO) or fixed.has_key(cs.INITIAL_VIDEO)
+
+ expected_allowed = [
+ cs.TARGET_ID, cs.TARGET_HANDLE,
+ cs.INITIAL_VIDEO, cs.INITIAL_AUDIO,
+ cs.INITIAL_VIDEO_NAME, cs.INITIAL_AUDIO_NAME,
+ cs.INITIAL_TRANSPORT,
+ cs.DTMF_INITIAL_TONES,
+ ]
+
+ allowed.sort()
+ expected_allowed.sort()
+ assertSameSets(expected_allowed, allowed)
+
+if __name__ == '__main__':
+
+ exec_test(rccs)
diff --git a/tests/twisted/voip/ringing-queued.py b/tests/twisted/voip/ringing-queued.py
new file mode 100644
index 0000000..a066a55
--- /dev/null
+++ b/tests/twisted/voip/ringing-queued.py
@@ -0,0 +1,44 @@
+import calltest
+import constants as cs
+from servicetest import (
+ EventPattern, call_async,
+ assertEquals, assertNotEquals, assertContains, assertLength,
+ )
+
+class RingingQueued(calltest.CallTest):
+
+ def accept(self):
+ if self.incoming:
+
+ self.chan.Call1.SetQueued()
+
+ o = self.q.expect_many(
+ EventPattern('sip-response', call_id=self.context.call_id,
+ code=182),
+ EventPattern('dbus-signal', signal='CallStateChanged'))
+ assertEquals(cs.CALL_STATE_INITIALISED, o[1].args[0])
+ assertEquals(cs.CALL_FLAG_LOCALLY_QUEUED, o[1].args[1])
+
+ self.chan.Call1.SetRinging()
+
+ o = self.q.expect_many(
+ EventPattern('sip-response', call_id=self.context.call_id,
+ code=180),
+ EventPattern('dbus-signal', signal='CallStateChanged'))
+ assertEquals(cs.CALL_STATE_INITIALISED, o[1].args[0])
+ assertEquals(cs.CALL_FLAG_LOCALLY_RINGING, o[1].args[1])
+ else:
+ # Send Ringing
+ self.context.pr_respond(self.invite_event, 180)
+ o = self.q.expect('dbus-signal', signal='CallMembersChanged')
+ assertEquals(cs.CALL_MEMBER_FLAG_RINGING,
+ o.args[0][self.remote_handle])
+
+ return calltest.CallTest.accept(self)
+
+
+
+
+if __name__ == '__main__':
+ calltest.run(klass=RingingQueued)
+
diff --git a/tests/twisted/voip/voip_test.py b/tests/twisted/voip/voip_test.py
index c8724af..99d95ad 100644
--- a/tests/twisted/voip/voip_test.py
+++ b/tests/twisted/voip/voip_test.py
@@ -1,6 +1,7 @@
import dbus
import uuid
+import re
import twisted.protocols.sip
@@ -8,6 +9,7 @@ from servicetest import (
make_channel_proxy,
assertContains,
)
+import constants as cs
class VoipTestContext(object):
# Default audio codecs for the remote end
@@ -17,21 +19,23 @@ class VoipTestContext(object):
# Default video codecs for the remote end. I have no idea what's
# a suitable value here...
- video_codecs = [ ('WTF', 42, 80000, {}) ]
+ video_codecs = [ ('H264', 96, 90000, {}) ]
# Default candidates for the remote end
- remote_transports = [
- ( "192.168.0.1", # host
- 666, # port
- 0, # protocol = TP_MEDIA_STREAM_BASE_PROTO_UDP
- "RTP", # protocol subtype
- "AVP", # profile
- 1.0, # preference
- 0, # transport type = TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL,
- "username",
- "password" ) ]
-
- _mline_template = 'm=audio %(port)s %(subtype)s/%(profile)s %(codec_ids)s'
+ remote_candidates = [
+ (1, # Component
+ "192.168.0.1", # ip
+ 2222, # port
+ {'protocol': cs.MEDIA_STREAM_BASE_PROTO_UDP,
+ 'priority': 0}),
+ (2, # Component
+ "192.168.0.1", # ip
+ 2223, # port
+ {'protocol': cs.MEDIA_STREAM_BASE_PROTO_UDP,
+ 'priority': 0})
+ ]
+
+ _mline_template = 'm=%(mediatype)s %(port)s RTP/AVP %(codec_ids)s'
_aline_template = 'a=rtpmap:%(codec_id)s %(name)s/%(rate)s'
def __init__(self, q, conn, bus, sip_proxy, our_uri, peer):
@@ -43,63 +47,34 @@ class VoipTestContext(object):
self.peer_id = "sip:" + peer
self.sip_proxy = sip_proxy
self._cseq_id = 1
+ self.to = None
def dbusify_codecs(self, codecs):
- dbussed_codecs = [ (id, name, 0, rate, 0, params )
+ dbussed_codecs = [ (id, name, rate, 1, False, params )
for (name, id, rate, params) in codecs ]
- return dbus.Array(dbussed_codecs, signature='(usuuua{ss})')
+ return dbus.Array(dbussed_codecs, signature='(usuuba{ss})')
def dbusify_codecs_with_params (self, codecs):
return self.dbusify_codecs(codecs)
- def get_audio_codecs_dbus(self):
- return self.dbusify_codecs(self.audio_codecs)
+ def get_md_dbus(self, codecs, remote_contact):
+ return dbus.Dictionary(
+ {cs.CALL_CONTENT_MEDIA_DESCRIPTION + ".Codecs": self.dbusify_codecs(codecs),
+ cs.CALL_CONTENT_MEDIA_DESCRIPTION + ".RemoteContact":
+ dbus.UInt32(remote_contact)},
+ signature='sv')
- def get_video_codecs_dbus(self):
- return self.dbusify_codecs(self.video_codecs)
+ def get_audio_md_dbus(self, remote_contact):
+ return self.get_md_dbus(self.audio_codecs, remote_contact)
- def dbusify_call_codecs(self, codecs):
- dbussed_codecs = [ (id, name, rate, 0, params)
- for (name, id, rate, params) in codecs ]
- return dbus.Array(dbussed_codecs, signature='(usuua{ss})')
-
- def dbusify_call_codecs_with_params(self, codecs):
- return dbusify_call_codecs (self, codecs)
-
- def get_call_audio_codecs_dbus(self):
- return self.dbusify_call_codecs(self.audio_codecs)
-
- def get_call_video_codecs_dbus(self):
- return self.dbusify_call_codecs(self.video_codecs)
-
-
- def get_remote_transports_dbus(self):
- return dbus.Array([
- (dbus.UInt32(1 + i), host, port, proto, subtype,
- profile, pref, transtype, user, pwd)
- for i, (host, port, proto, subtype, profile,
- pref, transtype, user, pwd)
- in enumerate(self.remote_transports) ],
- signature='(usuussduss)')
-
- def get_call_remote_transports_dbus(self):
- return dbus.Array([
- (1 , host, port,
- { "Type": transtype,
- "Foundation": "",
- "Protocol": proto,
- "Priority": int((1+i) * 65536),
- "Username": user,
- "Password": pwd }
- ) for i, (host, port, proto, subtype, profile,
- pref, transtype, user, pwd)
- in enumerate(self.remote_transports) ],
- signature='(usqa{sv})')
-
- def get_call_sdp(self):
- (ip, port, protocol, subtype, profile, preference,
- transport, username, password) = self.remote_transports[0]
+ def get_video_md_dbus(self, remote_contact):
+ return self.get_md_dbus(self.video_codecs, remote_contact)
+
+ def get_remote_candidates_dbus(self):
+ return dbus.Array(self.remote_candidates, signature='(usua{sv})')
+ def get_call_sdp(self, medias):
+ (component, ip, port, info) = self.remote_candidates[0]
codec_id_list = []
codec_list = []
for name, codec_id, rate, _misc in self.audio_codecs:
@@ -108,25 +83,42 @@ class VoipTestContext(object):
codec_ids = ' '.join(codec_id_list)
codecs = '\r\n'.join(codec_list)
- sdp_string = ('v=0\r\n'
- 'o=- 7047265765596858314 2813734028456100815 IN IP4 %(ip)s\r\n'
- 's=-\r\n'
+ sdp_string = 'v=0\r\n' + \
+ 'o=- 7047265765596858314 2813734028456100815 IN IP4 %(ip)s\r\n' + \
+ 's=-\r\n' + \
't=0 0\r\n'
- 'm=audio %(port)s RTP/AVP 3 8 0\r\n'
- 'c=IN IP4 %(ip)s\r\n'
- '%(codecs)s\r\n') % locals()
- return sdp_string
+ for m in medias:
+ if m[0]:
+ sdp_string += 'm=' + m[0] + ' %(port)s RTP/AVP 3 8 0\r\n' \
+ 'c=IN IP4 %(ip)s\r\n' \
+ '%(codecs)s\r\n'
+ if m[1]:
+ sdp_string += 'a=' + m[1] + '\r\n'
+ else:
+ sdp_string += 'm=audio 0 RTP/AVP\r\n'
+
+
+ return sdp_string % locals()
- def check_call_sdp(self, sdp_string):
+ def check_call_sdp(self, sdp_string, medias=[('audio', None)]):
codec_id_list = []
for name, codec_id, rate, _misc in self.audio_codecs:
assertContains (self._aline_template % locals(), sdp_string)
codec_id_list.append(str(codec_id))
codec_ids = ' '.join(codec_id_list)
- (ip, port, protocol, subtype, profile, preference,
- transport, username, password) = self.remote_transports[0]
- assert self._mline_template % locals() in sdp_string
+ (component, ip, port, info) = self.remote_candidates[0]
+ pattern = '.*'
+ for m in medias:
+ if m[0]:
+ mediatype = m[0]
+ pattern += self._mline_template % locals()
+ pattern += '.*'
+ if m[1]:
+ pattern += 'a=' + m[1] + '.*'
+ else:
+ pattern += 'm=audio 0 RTP/AVP .*'
+ assert re.search(pattern, sdp_string, re.MULTILINE | re.DOTALL)
def send_message(self, message_type, body='', to_=None, from_=None,
**additional_headers):
@@ -136,7 +128,7 @@ class VoipTestContext(object):
msg.body = body
msg.addHeader('content-length', '%d' % len(msg.body))
msg.addHeader('from', from_ or '<%s>;tag=XYZ' % self.peer_id)
- msg.addHeader('to', to_ or '<sip:testacc@127.0.0.1>')
+ msg.addHeader('to', to_ or self.to or '<sip:testacc@127.0.0.1>')
self._cseq_id += 1
additional_headers.setdefault('cseq', '%d %s' % (self._cseq_id, message_type))
for key, vals in additional_headers.items():
@@ -152,29 +144,42 @@ class VoipTestContext(object):
self.sip_proxy.sendMessage(destination, msg)
return msg
- def accept(self, invite_message):
+ def accept(self, invite_message, body=None):
self.call_id = invite_message.headers['call-id'][0]
+ if invite_message.headers['from'][0].find('tag='):
+ self.to = invite_message.headers['from'][0]
response = self.sip_proxy.responseFromRequest(200, invite_message)
# Echo rakia's SDP back to it. It doesn't care.
response.addHeader('content-type', 'application/sdp')
- response.body = invite_message.body
+ response.body = body or invite_message.body
response.addHeader('content-length', '%d' % len(response.body))
self.sip_proxy.deliverResponse(response)
return response
+
+ def pr_respond(self, invite_message, number):
+ self.call_id = invite_message.headers['call-id'][0]
+ response = self.sip_proxy.responseFromRequest(number, invite_message)
+ self.sip_proxy.deliverResponse(response)
+ return response
def ack(self, ok_message):
cseq = '%s ACK' % ok_message.headers['cseq'][0].split()[0]
self.send_message('ACK', call_id=self.call_id, cseq=cseq)
+
+ def reinvite(self, medias=[('audio', None)]):
+ body = self.get_call_sdp(medias)
+ return self.send_message('INVITE', body, content_type='application/sdp',
+ supported='timer, 100rel', call_id=self.call_id)
- def incoming_call(self):
+ def incoming_call(self, medias=[('audio', None)]):
self.call_id = uuid.uuid4().hex
- body = self.get_call_sdp()
+ body = self.get_call_sdp(medias)
return self.send_message('INVITE', body, content_type='application/sdp',
supported='timer, 100rel', call_id=self.call_id)
def incoming_call_from_self(self):
self.call_id = uuid.uuid4().hex
- body = self.get_call_sdp()
+ body = self.get_call_sdp([('audio', None)])
return self.send_message('INVITE', body, content_type='application/sdp',
supported='timer, 100rel', call_id=self.call_id,
from_='<sip:testacc@127.0.0.1>')
@@ -182,27 +187,9 @@ class VoipTestContext(object):
def terminate(self):
return self.send_message('BYE', call_id=self.call_id)
- def handle_audio_session(self, chan):
- """
- Serves a SessionHandler and a StreamHandler for the MediaSignalling
- channel. Returns the interface proxy for StreamHandler.
- """
- session_handlers = chan.MediaSignalling.GetSessionHandlers()
- sh_path, sh_type = session_handlers[0]
-
- assert sh_type == 'rtp'
-
- session_handler = make_channel_proxy(self.conn, sh_path,
- 'Media.SessionHandler')
- session_handler.Ready()
-
- e = self.q.expect('dbus-signal', signal='NewStreamHandler')
-
- stream_handler = make_channel_proxy(self.conn, e.args[0],
- 'Media.StreamHandler')
-
- stream_handler.NewNativeCandidate("fake", self.get_remote_transports_dbus())
- stream_handler.NativeCandidatesPrepared()
- stream_handler.Ready(self.get_audio_codecs_dbus())
-
- return stream_handler
+ def options_ping(self, q):
+ self.send_message('OPTIONS',
+ supported='timer, 100rel', call_id=self.call_id)
+ acc = q.expect('sip-response', call_id=self.call_id, code=200,
+ cseq='%s OPTIONS' % (self._cseq_id))
+ self.ack(acc.sip_message)