diff options
author | Olivier Crête <olivier.crete@collabora.com> | 2012-05-08 12:51:51 -0400 |
---|---|---|
committer | Olivier Crête <olivier.crete@collabora.com> | 2012-05-08 12:51:51 -0400 |
commit | 0d032e114d76461f33dfd1138f3be5f5e9a4403c (patch) | |
tree | 96d01c71ef6227d85009cc2f28ee7cbad9b69cf0 | |
parent | 8cb2b73e711e86dd2efeb73b20fc3973ef8683c0 (diff) | |
parent | 86f1d77943a377053f06d898767535f36a3f0a24 (diff) |
Merge branch 'call1'
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) |