summaryrefslogtreecommitdiff
path: root/rakia
diff options
context:
space:
mode:
authorDavid Laban <david.laban@collabora.co.uk>2011-02-12 10:03:25 +0000
committerDavid Laban <david.laban@collabora.co.uk>2011-02-12 11:02:57 +0000
commit8dcddb61752cfa140df30c94b54bd02684419323 (patch)
tree5ba95bbfdafdfd8152d783c180bfd8c49499fd7e /rakia
parentada52bcb3064a5a7fa0e75038f4365b45376518c (diff)
Rename project to telepathy-rakia
For the curious, these are the commands you need: git clean -d git mv tpsip-extensions extensions git mv tpsip rakia sed -i \ -e 's/libtpsip-extensions.la/librakia-extensions.la/g' \ -e 's/libtpsip_extensions_la/librakia_extensions_la/g' \ -e 's/tpsip-extensions/extensions/g' \ -e 's/sofiasip/rakia/g' \ -e 's/tpsip/rakia/g' \ -e 's/Tpsip/Rakia/g' \ -e 's/SOFIASIP/RAKIA/g' \ */*.[ch] .gitignore configure.ac $(find -name 'Makefile.am' ; find -name '*.py' ; find -name '*.in') git mv ./docs/telepathy-sofiasip.8.in ./docs/telepathy-rakia.8.in git mv ./src/telepathy-sofiasip.c ./src/telepathy-rakia.c git mv ./data/org.freedesktop.Telepathy.ConnectionManager.sofiasip.service.in \ ./data/org.freedesktop.Telepathy.ConnectionManager.rakia.service.in git mv ./tests/twisted/tools/sofiasip.service.in \ ./tests/twisted/tools/rakia.service.in ./autogen.sh && make distcheck
Diffstat (limited to 'rakia')
-rw-r--r--rakia/Makefile.am68
-rw-r--r--rakia/base-connection-sofia.c169
-rw-r--r--rakia/base-connection.c205
-rw-r--r--rakia/base-connection.h92
-rw-r--r--rakia/codec-param-formats.c335
-rw-r--r--rakia/codec-param-formats.h80
-rw-r--r--rakia/connection-aliasing.c399
-rw-r--r--rakia/connection-aliasing.h58
-rw-r--r--rakia/debug.c197
-rw-r--r--rakia/debug.h70
-rw-r--r--rakia/event-target.c271
-rw-r--r--rakia/event-target.h92
-rw-r--r--rakia/handles.c345
-rw-r--r--rakia/handles.h47
-rw-r--r--rakia/media-channel.c2096
-rw-r--r--rakia/media-channel.h92
-rw-r--r--rakia/media-manager.c701
-rw-r--r--rakia/media-manager.h57
-rw-r--r--rakia/media-session.c2243
-rw-r--r--rakia/media-session.h143
-rw-r--r--rakia/media-stream.c2002
-rw-r--r--rakia/media-stream.h92
-rw-r--r--rakia/signals-marshal.list2
-rw-r--r--rakia/sofia-decls.h50
-rw-r--r--rakia/text-channel.c955
-rw-r--r--rakia/text-channel.h72
-rw-r--r--rakia/text-manager.c642
-rw-r--r--rakia/text-manager.h57
-rw-r--r--rakia/util.c161
-rw-r--r--rakia/util.h38
30 files changed, 11831 insertions, 0 deletions
diff --git a/rakia/Makefile.am b/rakia/Makefile.am
new file mode 100644
index 0000000..3e8f03d
--- /dev/null
+++ b/rakia/Makefile.am
@@ -0,0 +1,68 @@
+#
+# Makefile.am for telepathy-rakia/rakia
+#
+# Copyright (C) 2008 Nokia Corporation
+# Contact: Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+# Licensed under LGPL. See file COPYING.
+#
+
+noinst_LTLIBRARIES = librakia.la
+
+AM_CPPFLAGS = @GLIB_CFLAGS@ @TELEPATHY_GLIB_CFLAGS@ @SOFIA_SIP_UA_CFLAGS@ \
+ -I$(top_builddir) -I$(top_srcdir)
+
+AM_CFLAGS = $(ERROR_CFLAGS) $(COVERAGE_CFLAGS)
+
+rakia_includedir = $(includedir)/telepathy-rakia-0.7/rakia
+
+rakia_include_HEADERS = \
+ base-connection.h \
+ connection-aliasing.h \
+ sofia-decls.h \
+ codec-param-formats.h \
+ event-target.h \
+ handles.h \
+ debug.h \
+ media-manager.h \
+ text-manager.h \
+ util.h
+
+BUILT_SOURCES = \
+ signals-marshal.h \
+ signals-marshal.c
+
+librakia_la_SOURCES = \
+ base-connection.c \
+ base-connection-sofia.c \
+ connection-aliasing.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 \
+ text-channel.h \
+ text-channel.c \
+ text-manager.c \
+ util.c
+
+nodist_librakia_la_SOURCES = \
+ signals-marshal.h \
+ signals-marshal.c
+
+EXTRA_DIST = signals-marshal.list
+
+CLEANFILES = $(BUILT_SOURCES)
+
+CLEANFILES += *.gcno
+
+signals-marshal.c: ${srcdir}/signals-marshal.list
+ glib-genmarshal --body --prefix=_rakia_marshal $< >$@ || rm -f $@
+
+signals-marshal.h: ${srcdir}/signals-marshal.list
+ glib-genmarshal --header --prefix=_rakia_marshal $< >$@ || rm -f $@
diff --git a/rakia/base-connection-sofia.c b/rakia/base-connection-sofia.c
new file mode 100644
index 0000000..de68ea5
--- /dev/null
+++ b/rakia/base-connection-sofia.c
@@ -0,0 +1,169 @@
+/*
+ * sip-connection-sofia.c - Source for RakiaConnection Sofia event handling
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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/base-connection.h>
+#include <sofia-sip/su_tag_io.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_EVENTS
+#include "rakia/debug.h"
+
+static void
+priv_r_shutdown(int status,
+ nua_t *nua)
+{
+ GSource *source;
+ gboolean source_recursive;
+
+ if (status < 200)
+ return;
+
+ /* Should be the source of the Sofia root */
+ source = g_main_current_source ();
+
+ /* XXX: temporarily allow recursion in the Sofia source to work around
+ * nua_destroy() requiring nested mainloop iterations to complete
+ * (Sofia-SIP bug #1624446). Actual recursion safety of the source is to be
+ * examined. */
+ source_recursive = g_source_get_can_recurse (source);
+ if (!source_recursive)
+ {
+ DEBUG("forcing Sofia root GSource to be recursive");
+ g_source_set_can_recurse (source, TRUE);
+ }
+
+ DEBUG("destroying Sofia-SIP NUA at address %p", nua);
+ nua_destroy (nua);
+
+ if (!source_recursive)
+ g_source_set_can_recurse (source, FALSE);
+}
+
+#if 0
+static void
+priv_r_unregister (int status,
+ char const *phrase,
+ nua_handle_t *nh)
+{
+ DEBUG("un-REGISTER got response: %03d %s", status, phrase);
+
+ if (status < 200)
+ return;
+
+ if (status == 401 || status == 407)
+ {
+ /* In SIP, de-registration can fail! However, there's not a lot we can
+ * do about this in the Telepathy model - once you've gone DISCONNECTED
+ * you're really not meant to go "oops, I'm still CONNECTED after all".
+ * So we ignore it and hope it goes away. */
+ WARNING ("Registrar won't let me unregister: %d %s", status, phrase);
+ }
+}
+#endif
+
+#ifdef ENABLE_DEBUG
+static void
+priv_r_get_params (int status,
+ nua_t *nua,
+ nua_handle_t *nh,
+ tagi_t tags[])
+{
+ if (status < 200)
+ return;
+
+ if (nh != NULL)
+ return;
+
+ /* note: print contents of all tags to stdout */
+ tl_print(stdout, "Sofia-SIP NUA stack parameters:\n", tags);
+}
+#endif
+
+/**
+ * Callback for events delivered by the SIP stack.
+ *
+ * See libsofia-sip-ua/nua/nua.h documentation.
+ */
+void
+rakia_base_connection_sofia_callback (nua_event_t event,
+ int status,
+ char const *phrase,
+ nua_t *nua,
+ RakiaBaseConnection *conn,
+ nua_handle_t *nh,
+ RakiaEventTarget *target,
+ sip_t const *sip,
+ tagi_t tags[])
+{
+ DEBUG("event %s: %03d %s",
+ nua_event_name (event), status, phrase);
+
+ switch (event)
+ {
+#ifdef ENABLE_DEBUG
+ case nua_r_get_params:
+ priv_r_get_params (status, nua, nh, tags);
+ return;
+#endif
+ case nua_r_shutdown:
+ priv_r_shutdown (status, nua);
+ return;
+ default:
+ break;
+ }
+
+ g_assert (conn != NULL);
+
+ DEBUG("connection %p, refcount %d", conn, ((GObject *)conn)->ref_count);
+
+ {
+ RakiaNuaEvent ev = {
+ event,
+ status,
+ phrase,
+ nua,
+ nh,
+ sip
+ };
+
+ if (target == NULL)
+ {
+ target = (RakiaEventTarget *) conn;
+ DEBUG("dispatching to connection %p (unbound handle %p)", conn, nh);
+ }
+ else
+ {
+ g_assert (nh != NULL);
+ DEBUG("dispatching to target %p (handle %p)", target, nh);
+ }
+
+ if (!rakia_event_target_emit_nua_event (target,
+ &ev,
+ tags))
+ {
+ DEBUG("event %s for target %p was not consumed", nua_event_name (event), target);
+ }
+ }
+
+ DEBUG ("exit");
+}
diff --git a/rakia/base-connection.c b/rakia/base-connection.c
new file mode 100644
index 0000000..597b5f5
--- /dev/null
+++ b/rakia/base-connection.c
@@ -0,0 +1,205 @@
+/*
+ * sip-base-connection.c - source for SipBaseConnection
+ * Copyright (C) 2011 Nokia Corporation.
+ * Copyright (C) 2005-2007 Collabora Ltd.
+ * Copyright (C) 2005-2011 Nokia Corporation
+ * @author Kai Vehmanen <first.surname@nokia.com>
+ * @author Martti Mela <first.surname@nokia.com>
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ * @author Pekka Pessi <pekka.pessi@nokia.com>
+ *
+ * Based on rakia-connection and gabble implementation (gabble-connection).
+ * @author See gabble-connection.c
+ *
+ * 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/base-connection.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <rakia/sofia-decls.h>
+
+struct _RakiaBaseConnectionPrivate
+{
+ su_root_t *sofia_root;
+
+ unsigned dispose_has_run:1; unsigned :0;
+};
+
+enum {
+ PROP_NONE,
+ PROP_SOFIA_ROOT,
+ PROP_SOFIA_NUA,
+};
+
+/* ---------------------------------------------------------------------- */
+/* GObject implementation */
+
+static void event_target_iface_init (gpointer iface, gpointer data) {}
+
+G_DEFINE_TYPE_WITH_CODE (RakiaBaseConnection,
+ rakia_base_connection, TP_TYPE_BASE_CONNECTION,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+ tp_contacts_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TPSIP_TYPE_EVENT_TARGET, event_target_iface_init);
+);
+
+static void
+rakia_base_connection_init (RakiaBaseConnection *self)
+{
+ GObject *object = G_OBJECT (self);
+ TpBaseConnection *base = TP_BASE_CONNECTION (self);
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TPSIP_TYPE_BASE_CONNECTION,
+ RakiaBaseConnectionPrivate);
+
+ tp_contacts_mixin_init (object,
+ G_STRUCT_OFFSET (RakiaBaseConnection, contacts_mixin));
+
+ /* org.freedesktop.Telepathy.Connection attributes */
+ tp_base_connection_register_with_contacts_mixin (base);
+}
+
+static void
+rakia_base_connection_constructed(GObject *object)
+{
+ if (G_OBJECT_CLASS(rakia_base_connection_parent_class)->constructed)
+ G_OBJECT_CLASS(rakia_base_connection_parent_class)->constructed(object);
+}
+
+static void
+rakia_base_connection_dispose(GObject *object)
+{
+ RakiaBaseConnection *self = TPSIP_BASE_CONNECTION(object);
+
+ if (self->priv->dispose_has_run)
+ return;
+ self->priv->dispose_has_run = 1;
+
+ G_OBJECT_CLASS(rakia_base_connection_parent_class)->dispose(object);
+}
+
+void
+rakia_base_connection_finalize(GObject *object)
+{
+ G_OBJECT_CLASS(rakia_base_connection_parent_class)->finalize(object);
+}
+
+static void
+rakia_base_connection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaBaseConnection *self = TPSIP_BASE_CONNECTION (object);
+ RakiaBaseConnectionPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_SOFIA_ROOT:
+ priv->sofia_root = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_base_connection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaBaseConnection *self = TPSIP_BASE_CONNECTION (object);
+ RakiaBaseConnectionPrivate *priv = self->priv;
+
+ switch (property_id)
+ {
+ case PROP_SOFIA_ROOT:
+ g_value_set_pointer (value, priv->sofia_root);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+
+static void
+rakia_base_connection_class_init (RakiaBaseConnectionClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ g_type_class_add_private (klass, sizeof (RakiaBaseConnectionPrivate));
+
+ object_class->constructed = rakia_base_connection_constructed;
+ object_class->dispose = rakia_base_connection_dispose;
+ object_class->finalize = rakia_base_connection_finalize;
+ object_class->get_property = rakia_base_connection_get_property;
+ object_class->set_property = rakia_base_connection_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_SOFIA_ROOT,
+ g_param_spec_pointer ("sofia-root",
+ "Sofia-SIP root",
+ "The root object for Sofia-SIP",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_SOFIA_NUA,
+ g_param_spec_pointer ("sofia-nua",
+ "Sofia-SIP UA",
+ "The UA object for Sofia-SIP",
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ tp_contacts_mixin_class_init (object_class,
+ G_STRUCT_OFFSET(RakiaBaseConnectionClass, contacts_mixin_class));
+}
+
+nua_handle_t *
+rakia_base_connection_create_handle (RakiaBaseConnection *self,
+ TpHandle tphandle)
+{
+ RakiaBaseConnectionClass *cls = TPSIP_BASE_CONNECTION_GET_CLASS (self);
+
+ return cls->create_handle (self, tphandle);
+}
+
+void
+rakia_base_connection_add_auth_handler (RakiaBaseConnection *self,
+ RakiaEventTarget *target)
+{
+ RakiaBaseConnectionClass *cls = TPSIP_BASE_CONNECTION_GET_CLASS (self);
+
+ if (cls->add_auth_handler)
+ cls->add_auth_handler (self, target);
+}
+
+void
+rakia_base_connection_save_event (RakiaBaseConnection *self,
+ nua_saved_event_t ret_saved [1])
+{
+ nua_t *nua;
+
+ g_object_get (self, "sofia-nua", &nua, NULL);
+
+ nua_save_event (nua, ret_saved);
+}
diff --git a/rakia/base-connection.h b/rakia/base-connection.h
new file mode 100644
index 0000000..dafb70f
--- /dev/null
+++ b/rakia/base-connection.h
@@ -0,0 +1,92 @@
+/*
+ * sip-base-connection.h - Header for SipBaseConnection
+ * 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 __TPSIP_BASE_CONNECTION_H__
+#define __TPSIP_BASE_CONNECTION_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+
+#include <rakia/sofia-decls.h>
+#include <rakia/event-target.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaBaseConnection RakiaBaseConnection;
+typedef struct _RakiaBaseConnectionClass RakiaBaseConnectionClass;
+typedef struct _RakiaBaseConnectionPrivate RakiaBaseConnectionPrivate;
+
+struct _RakiaBaseConnectionClass {
+ TpBaseConnectionClass parent_class;
+ TpContactsMixinClass contacts_mixin_class;
+
+ nua_handle_t *(*create_handle) (RakiaBaseConnection *, TpHandle contact);
+ void (*add_auth_handler) (RakiaBaseConnection *, RakiaEventTarget *);
+};
+
+struct _RakiaBaseConnection {
+ TpBaseConnection parent;
+ TpContactsMixin contacts_mixin;
+ RakiaBaseConnectionPrivate *priv;
+};
+
+GType rakia_base_connection_get_type (void) G_GNUC_CONST;
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_BASE_CONNECTION \
+ (rakia_base_connection_get_type())
+#define TPSIP_BASE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPSIP_TYPE_BASE_CONNECTION, RakiaBaseConnection))
+#define TPSIP_BASE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ TPSIP_TYPE_BASE_CONNECTION, RakiaBaseConnectionClass))
+#define TPSIP_IS_BASE_CONNECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_BASE_CONNECTION))
+#define TPSIP_IS_BASE_CONNECTION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_BASE_CONNECTION))
+#define TPSIP_BASE_CONNECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ TPSIP_TYPE_BASE_CONNECTION, RakiaBaseConnectionClass))
+
+/***********************************************************************
+ * Functions for accessing Sofia-SIP interface handles
+ ***********************************************************************/
+
+nua_handle_t *rakia_base_connection_create_handle (RakiaBaseConnection *,
+ TpHandle contact);
+void rakia_base_connection_add_auth_handler (RakiaBaseConnection *self,
+ RakiaEventTarget *target);
+void rakia_base_connection_save_event (RakiaBaseConnection *self,
+ nua_saved_event_t ret_saved [1]);
+
+/** Callback for events delivered by the SIP stack. */
+void rakia_base_connection_sofia_callback (nua_event_t event,
+ int status, char const *phrase,
+ nua_t *nua, RakiaBaseConnection *conn,
+ nua_handle_t *nh, RakiaEventTarget *target,
+ sip_t const *sip,
+ tagi_t tags[]);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPSIP_BASE_CONNECTION_H__*/
diff --git a/rakia/codec-param-formats.c b/rakia/codec-param-formats.c
new file mode 100644
index 0000000..5751a35
--- /dev/null
+++ b/rakia/codec-param-formats.c
@@ -0,0 +1,335 @@
+/*
+ * codec-param-formats.c - Implementation of codec parameter formatter infra
+ * Copyright (C) 2009, 2010 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 "codec-param-formats.h"
+
+#include <string.h>
+
+#include <rakia/util.h>
+
+/* 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]+|\"([^\"\\\\]|\\\\.)*\""
+/* Indexes of the respective match groups in the whole regexp below */
+#define FMTP_MATCH_NAME_PARAM "p"
+#define FMTP_MATCH_NAME_VALUE "v"
+
+typedef struct _RakiaCodecParamFormatting {
+ RakiaCodecParamFormatFunc format;
+ RakiaCodecParamParseFunc parse;
+} RakiaCodecParamFormatting;
+
+static GRegex *fmtp_attr_regex = NULL;
+static GRegex *dtmf_events_regex = NULL;
+
+static GHashTable *codec_param_formats[NUM_TP_MEDIA_STREAM_TYPES];
+
+static void rakia_codec_param_formats_init ();
+
+/**
+ * rakia_codec_param_format:
+ * @media: 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
+ *
+ * Formats the parameters passed in the @params into a string suitable for
+ * <literal>a=fmtp</literal> attribute for an RTP payload description,
+ * 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)
+{
+ 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);
+
+ if (fmt != NULL && fmt->format != NULL)
+ fmt->format (params, out);
+ else
+ rakia_codec_param_format_generic (params, out);
+}
+
+/**
+ * rakia_codec_param_parse:
+ * @media: 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.
+ */
+void
+rakia_codec_param_parse (TpMediaStreamType media, const char *name,
+ const gchar *fmtp, GHashTable *out)
+{
+ RakiaCodecParamFormatting *fmt;
+
+ if (fmtp == NULL)
+ return;
+
+ rakia_codec_param_formats_init ();
+
+ /* XXX: thread unsafe, we don't care for now */
+ fmt = g_hash_table_lookup (codec_param_formats[media], name);
+
+ if (fmt != NULL && fmt->parse != NULL)
+ fmt->parse (fmtp, out);
+ else
+ rakia_codec_param_parse_generic (fmtp, out);
+}
+
+/**
+ * rakia_codec_param_register_format:
+ * @media: 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
+ *
+ * Registers custom SDP payload parameter formatting routines for a media
+ * type.
+ */
+void
+rakia_codec_param_register_format (TpMediaStreamType media, const char *name,
+ RakiaCodecParamFormatFunc format,
+ RakiaCodecParamParseFunc parse)
+{
+ RakiaCodecParamFormatting *fmt;
+
+ rakia_codec_param_formats_init ();
+
+ fmt = g_slice_new (RakiaCodecParamFormatting);
+ fmt->format = format;
+ 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);
+ }
+}
+
+/**
+ * rakia_codec_param_format_generic:
+ * @params: the map of codec parameters
+ * @out: a #GString for the output
+ *
+ * Formats the parameters as a semicolon separated list of
+ * <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)
+{
+ g_hash_table_foreach (params, format_param_generic, out);
+}
+
+/**
+ * rakia_codec_param_parse_generic:
+ * @fmtp: a string value with the parameter description
+ * @out: the parameter map to populate
+ *
+ * Parses parameters formatted as a semicolon separated list of
+ * <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)
+{
+ GMatchInfo *match = NULL;
+ gint pos;
+ gint value_start;
+ gint value_end;
+
+ if (fmtp == NULL)
+ return;
+
+ pos = 0;
+
+ /* Fast path for trivial cases, not involving the regex engine */
+ while (g_ascii_isspace (fmtp[pos]))
+ ++pos;
+ if (!fmtp[pos])
+ return;
+
+ g_assert (fmtp_attr_regex != NULL);
+
+ g_regex_match_full (fmtp_attr_regex,
+ fmtp, -1, pos, G_REGEX_MATCH_ANCHORED, &match, NULL);
+
+ while (g_match_info_matches (match))
+ {
+ gchar *name;
+ gchar *value;
+
+ name = g_match_info_fetch_named (match, FMTP_MATCH_NAME_PARAM);
+
+ g_match_info_fetch_named_pos (match, FMTP_MATCH_NAME_VALUE,
+ &value_start, &value_end);
+
+ if (value_end - 1 > value_start
+ && fmtp[value_start] == '\"' && fmtp[value_end - 1] == '\"')
+ {
+ value = rakia_unquote_string (fmtp + value_start,
+ value_end - value_start);
+ }
+ else
+ {
+ value = g_strndup (fmtp + value_start,
+ value_end - value_start);
+ }
+
+ g_hash_table_insert (out, name, value);
+
+ g_match_info_fetch_pos (match, 0, NULL, &pos);
+ if (!fmtp[pos])
+ break;
+
+ g_match_info_next (match, NULL);
+ }
+
+ g_match_info_free (match);
+
+ if (fmtp[pos])
+ g_message ("failed to parse part of format parameters"
+ " as an attribute-value list: %s", &fmtp[pos]);
+}
+
+/* Custom format for audio/telephone-event */
+
+static void
+rakia_codec_param_format_telephone_event (GHashTable *params, GString *out)
+{
+ const gchar *events;
+
+ /* events parameter value comes first without the parameter name */
+ events = g_hash_table_lookup (params, "events");
+ if (events != NULL)
+ {
+ g_string_append (out, events);
+ g_hash_table_remove (params, "events");
+ }
+
+ /* format the rest of the parameters, if any */
+ rakia_codec_param_format_generic (params, out);
+}
+
+static void
+rakia_codec_param_parse_telephone_event (const gchar *fmtp, GHashTable *out)
+{
+ GMatchInfo *match = NULL;
+ gint end_pos = 0;
+
+ g_assert (dtmf_events_regex != NULL);
+
+ /* Parse the events list */
+
+ g_regex_match (dtmf_events_regex, fmtp, 0, &match);
+
+ if (g_match_info_matches (match))
+ {
+ gchar *events;
+
+ events = g_match_info_fetch (match, 1);
+ g_hash_table_insert (out, g_strdup ("events"), 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_formats_init:
+ *
+ * Initializes the codec parameter formatting infrastructure.
+ * This function must be called before using any other functions in this module.
+ * Calling the function more than once has no effect.
+ */
+static void
+rakia_codec_param_formats_init ()
+{
+ static volatile gsize been_here = 0;
+
+ int i;
+
+ if (g_once_init_enter (&been_here))
+ g_once_init_leave (&been_here, 1);
+ else
+ return;
+
+ for (i = 0; i < NUM_TP_MEDIA_STREAM_TYPES; ++i)
+ {
+ /* XXX: we ignore deallocation of values for now */
+ codec_param_formats[i] = g_hash_table_new (g_str_hash, g_str_equal);
+ }
+
+ rakia_codec_param_register_format (
+ TP_MEDIA_STREAM_TYPE_AUDIO, "telephone-event",
+ rakia_codec_param_format_telephone_event,
+ rakia_codec_param_parse_telephone_event);
+
+ fmtp_attr_regex = g_regex_new (
+ "(?<" FMTP_MATCH_NAME_PARAM ">" FMTP_TOKEN_PARAM ")"
+ "\\s*=\\s*"
+ "(?<" FMTP_MATCH_NAME_VALUE ">" FMTP_TOKEN_VALUE ")"
+ "\\s*(;\\s*|$)",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE,
+ 0, NULL);
+ g_assert (fmtp_attr_regex != NULL);
+
+#define DTMF_RANGE "[0-9]+(-[0-9]+)?"
+
+ dtmf_events_regex = g_regex_new (
+ "^(" DTMF_RANGE "(," DTMF_RANGE ")*)\\s*(;|$)",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE,
+ 0, NULL);
+ g_assert (dtmf_events_regex != NULL);
+}
diff --git a/rakia/codec-param-formats.h b/rakia/codec-param-formats.h
new file mode 100644
index 0000000..f5a717f
--- /dev/null
+++ b/rakia/codec-param-formats.h
@@ -0,0 +1,80 @@
+/*
+ * codec-param-formats.h - Declarations for codec parameter formatters
+ * Copyright (C) 2009 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 __TPSIP_CODEC_PARAM_FORMATS_H__
+#define __TPSIP_CODEC_PARAM_FORMATS_H__
+
+#include <glib.h>
+
+#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);
+
+/**
+ * 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);
+
+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_generic (GHashTable *params, GString *out);
+
+void rakia_codec_param_parse_generic (const gchar *str, GHashTable *out);
+
+G_END_DECLS
+
+#endif /* !__TPSIP_CODEC_PARAM_FORMATS_H__ */
diff --git a/rakia/connection-aliasing.c b/rakia/connection-aliasing.c
new file mode 100644
index 0000000..403f55e
--- /dev/null
+++ b/rakia/connection-aliasing.c
@@ -0,0 +1,399 @@
+/*
+ * connection-aliasing.c - Implementation for RakiaConnectionAliasing interface
+ * Copyright (C) 2008-2011 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ * @author Pekka Pessi <pekka.pessi@nokia.com>
+ *
+ * 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/connection-aliasing.h>
+#include <rakia/base-connection.h>
+#include <rakia/handles.h>
+
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+
+#include "rakia/handles.h"
+
+#include <string.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_CONNECTION
+#include "rakia/debug.h"
+
+enum {
+ PROP_NONE,
+ PROP_ALIAS,
+};
+
+static void
+rakia_connection_aliasing_base_init (gpointer klass)
+{
+ static gboolean initialized = FALSE;
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+
+ g_object_interface_install_property (klass,
+ g_param_spec_string ("alias", "Alias",
+ "User's display name",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ }
+}
+
+GType
+rakia_connection_aliasing_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ static const GTypeInfo info = {
+ sizeof (RakiaConnectionAliasingInterface),
+ rakia_connection_aliasing_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "RakiaConnectionAliasingInterface", &info, 0);
+
+ g_type_interface_add_prerequisite (type, TPSIP_TYPE_BASE_CONNECTION);
+ g_type_interface_add_prerequisite (type,
+ TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING);
+ }
+
+ return type;
+}
+
+static void
+rakia_connection_get_alias_flags (TpSvcConnectionInterfaceAliasing *iface,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (iface);
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ /* No server-side aliasing yet */
+ tp_svc_connection_interface_aliasing_return_from_get_alias_flags (
+ context, 0);
+}
+
+static gchar *
+conn_get_default_alias (TpBaseConnection *base,
+ TpHandleRepoIface *contact_handles,
+ TpHandle handle)
+{
+ const url_t *url;
+ gchar *alias = NULL;
+
+ /* TODO: create our custom handle repo to be able to get the URL off it.
+ * Then we can reuse the contact_handles parameter */
+ url = rakia_handle_inspect_uri (base, handle);
+
+ switch (url->url_type)
+ {
+ case url_sip:
+ /* Return the SIP URI stripped down to [user@]host */
+ if (url->url_user != NULL)
+ alias = g_strdup_printf ("%s@%s",
+ url->url_user, url->url_host);
+ else
+ alias = g_strdup (url->url_host);
+ break;
+ case url_tel:
+ /* Retrieve the telephone number */
+ alias = g_strdup (url->url_host);
+ break;
+ default:
+ /* Return the handle string as is */
+ alias = g_strdup (tp_handle_inspect (contact_handles, handle));
+ }
+ return alias;
+}
+
+static gchar *
+conn_get_alias (TpBaseConnection *base,
+ TpHandleRepoIface *contact_handles,
+ TpHandle handle)
+{
+ gchar *alias = NULL;
+
+ if (handle == base->self_handle)
+ {
+ /* Get our user-settable alias from the connection property */
+ g_object_get (base, "alias", &alias, NULL);
+ }
+
+ if (alias == NULL)
+ alias = conn_get_default_alias (base, contact_handles, handle);
+
+ g_assert (alias != NULL);
+ DEBUG("handle %u got alias %s", handle, alias);
+
+ return alias;
+}
+
+static void
+rakia_connection_request_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (iface);
+ TpHandleRepoIface *contact_handles;
+ GArray *aliases;
+ gchar **res;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (contact_handles, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ aliases = g_array_sized_new (TRUE, FALSE, sizeof (gchar *), contacts->len);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle;
+ gchar *alias;
+
+ handle = g_array_index (contacts, TpHandle, i);
+
+ alias = conn_get_alias (base, contact_handles, handle);
+
+ g_array_append_val (aliases, alias);
+ }
+
+ res = (gchar **) g_array_free (aliases, FALSE);
+
+ tp_svc_connection_interface_aliasing_return_from_request_aliases (
+ context, (const gchar **) res);
+
+ g_strfreev (res);
+}
+
+static void
+rakia_connection_get_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (iface);
+ TpHandleRepoIface *contact_handles;
+ GHashTable *result;
+ GError *error = NULL;
+ guint i;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handles_are_valid (contact_handles, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_free);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle;
+ gchar *alias;
+
+ handle = g_array_index (contacts, TpHandle, i);
+
+ alias = conn_get_alias (base, contact_handles, handle);
+
+ g_hash_table_insert (result, GUINT_TO_POINTER (handle), alias);
+ }
+
+ tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+ result);
+
+ g_hash_table_destroy (result);
+}
+
+static void
+emit_self_alias_change (TpBaseConnection *base, const gchar *alias)
+{
+ GPtrArray *change_data;
+ GValue change_pair = { 0, };
+
+ g_value_init (&change_pair, TP_STRUCT_TYPE_ALIAS_PAIR);
+ g_value_take_boxed (&change_pair,
+ dbus_g_type_specialized_construct (TP_STRUCT_TYPE_ALIAS_PAIR));
+ dbus_g_type_struct_set (&change_pair,
+ 0, base->self_handle,
+ 1, alias,
+ G_MAXUINT);
+ change_data = g_ptr_array_sized_new (1);
+ g_ptr_array_add (change_data, g_value_get_boxed (&change_pair));
+
+ tp_svc_connection_interface_aliasing_emit_aliases_changed (base, change_data);
+
+ g_ptr_array_free (change_data, TRUE);
+ g_value_unset (&change_pair);
+}
+
+static const gchar *
+collapse_whitespace (const gchar *str, gchar **to_free)
+{
+ static GRegex *whitespace_regex = NULL;
+
+ const gchar *p;
+ gchar *subst_res;
+
+ p = (const gchar *) strpbrk (str, " \t\r\n");
+ if (p == NULL)
+ return str;
+
+ if (whitespace_regex == NULL)
+ {
+ whitespace_regex = g_regex_new ("[[:space:]]+",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ }
+
+ subst_res = g_regex_replace_literal (whitespace_regex, str, -1, p - str, " ",
+ 0, NULL);
+
+ *to_free = subst_res;
+ return subst_res;
+}
+
+static void
+rakia_connection_set_aliases (TpSvcConnectionInterfaceAliasing *iface,
+ GHashTable *aliases,
+ DBusGMethodInvocation *context)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (iface);
+ TpHandleRepoIface *contact_handles;
+ const gchar *alias;
+ gchar *default_alias;
+ gchar *to_free = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+ /* We only care about the self alias */
+ alias = g_hash_table_lookup (aliases, GINT_TO_POINTER (base->self_handle));
+
+ if (alias == NULL || g_hash_table_size (aliases) > 1)
+ {
+ /* One of the handles (if there are any) cannot be the self handle */
+ GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
+ "Cannot set aliases for any contact except self" };
+ dbus_g_method_return_error (context, &err);
+ return;
+ }
+
+ alias = collapse_whitespace (alias, &to_free);
+
+ contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+ default_alias = conn_get_default_alias (base,
+ contact_handles, base->self_handle);
+
+ if (strcmp (alias, default_alias) == 0)
+ {
+ DEBUG("using default alias for self");
+ g_object_set (base, "alias", NULL, NULL);
+ }
+ else
+ {
+ DEBUG("setting alias for self: %s", alias);
+ g_object_set (base, "alias", alias, NULL);
+ }
+
+ emit_self_alias_change (base, alias);
+
+ g_free (default_alias);
+ g_free (to_free);
+
+ tp_svc_connection_interface_aliasing_return_from_set_aliases (context);
+}
+
+static void
+rakia_conn_aliasing_fill_contact_attributes (GObject *obj,
+ const GArray *contacts, GHashTable *attributes_hash)
+{
+ TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+ TpHandleRepoIface *contact_handles;
+ guint i;
+
+ contact_handles = tp_base_connection_get_handles (base,
+ TP_HANDLE_TYPE_CONTACT);
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle handle;
+ GValue *val;
+
+ handle = g_array_index (contacts, TpHandle, i);
+
+ val = tp_g_value_slice_new (G_TYPE_STRING);
+
+ g_value_take_string (val,
+ conn_get_alias (base, contact_handles, handle));
+
+ tp_contacts_mixin_set_contact_attribute (attributes_hash, handle,
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING "/alias", val);
+ }
+}
+
+void
+rakia_connection_aliasing_init (gpointer instance)
+{
+ tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (instance),
+ TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+ rakia_conn_aliasing_fill_contact_attributes);
+}
+
+
+void
+rakia_connection_aliasing_svc_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAliasingClass *klass =
+ (TpSvcConnectionInterfaceAliasingClass *) g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
+ klass, rakia_connection_##x)
+ IMPLEMENT(get_alias_flags);
+ IMPLEMENT(request_aliases);
+ IMPLEMENT(get_aliases);
+ IMPLEMENT(set_aliases);
+#undef IMPLEMENT
+}
diff --git a/rakia/connection-aliasing.h b/rakia/connection-aliasing.h
new file mode 100644
index 0000000..bc30565
--- /dev/null
+++ b/rakia/connection-aliasing.h
@@ -0,0 +1,58 @@
+/*
+ * rakia/connection-aliasing.h - Aliasing interface implementation for SIP
+ * Copyright (C) 2008, 2011 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 __TPSIP_CONNECTION_ALIASING_H__
+#define __TPSIP_CONNECTION_ALIASING_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaConnectionAliasing RakiaConnectionAliasing;
+
+typedef struct _RakiaConnectionAliasingInterface
+RakiaConnectionAliasingInterface;
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_CONNECTION_ALIASING \
+ (rakia_connection_aliasing_get_type ())
+#define TPSIP_CONNECTION_ALIASING(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ TPSIP_TYPE_CONNECTION_ALIASING, RakiaConnectionAliasing))
+#define TPSIP_IS_CONNECTION_ALIASING(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_CONNECTION_ALIASING))
+#define TPSIP_CONNECTION_ALIASING_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE((obj), \
+ TPSIP_TYPE_CONNECTION_ALIASING, RakiaConnectionAliasingInterface))
+
+struct _RakiaConnectionAliasingInterface {
+ GTypeInterface base_iface;
+};
+
+GType rakia_connection_aliasing_get_type (void) G_GNUC_CONST;
+
+void rakia_connection_aliasing_init (gpointer instance);
+
+void rakia_connection_aliasing_svc_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+G_END_DECLS
+
+#endif /*__TPSIP_CONN_ALIASING_H__*/
diff --git a/rakia/debug.c b/rakia/debug.c
new file mode 100644
index 0000000..11f7b6a
--- /dev/null
+++ b/rakia/debug.c
@@ -0,0 +1,197 @@
+/*
+ * debug.h - Debug helpers for Telepathy-SofiaSIP, implementation
+ * Copyright (C) 2007-2008 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
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <telepathy-glib/debug.h>
+#include <telepathy-glib/debug-sender.h>
+
+#include "rakia/debug.h"
+
+#include <sofia-sip/su_log.h>
+
+static RakiaDebugFlags rakia_debug_flags = 0;
+
+static const GDebugKey rakia_debug_keys[] = {
+ { "media-channel", TPSIP_DEBUG_MEDIA },
+ { "connection", TPSIP_DEBUG_CONNECTION },
+ { "im", TPSIP_DEBUG_IM },
+ { "events", TPSIP_DEBUG_EVENTS },
+ { "sofia", TPSIP_DEBUG_SOFIA },
+};
+
+void rakia_debug_set_flags_from_env ()
+{
+ const gchar *flags_string;
+
+ flags_string = g_getenv ("TPSIP_DEBUG");
+ if (flags_string == NULL)
+ flags_string = g_getenv ("RAKIA_DEBUG");
+
+ if (flags_string != NULL)
+ {
+ tp_debug_set_flags (flags_string);
+
+ rakia_debug_set_flags (g_parse_debug_string (flags_string,
+ rakia_debug_keys,
+ G_N_ELEMENTS(rakia_debug_keys)));
+ }
+}
+
+void rakia_debug_set_flags (RakiaDebugFlags new_flags)
+{
+ rakia_debug_flags |= new_flags;
+}
+
+gboolean rakia_debug_flag_is_set (RakiaDebugFlags flag)
+{
+ return (flag & rakia_debug_flags) ? TRUE : FALSE;
+}
+
+static GHashTable *flag_to_domains = NULL;
+
+static const gchar *
+debug_flag_to_domain (RakiaDebugFlags flag)
+{
+ if (G_UNLIKELY (flag_to_domains == NULL))
+ {
+ guint i;
+
+ flag_to_domains = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_free);
+
+ for (i = 0; i < G_N_ELEMENTS(rakia_debug_keys); i++)
+ {
+ GDebugKey key = (GDebugKey) rakia_debug_keys[i];
+ gchar *val;
+
+ val = g_strdup_printf ("%s/%s", "rakia", key.key);
+ g_hash_table_insert (flag_to_domains,
+ GUINT_TO_POINTER (key.value), val);
+ }
+ }
+
+ return g_hash_table_lookup (flag_to_domains, GUINT_TO_POINTER (flag));
+}
+
+void
+rakia_debug_free (void)
+{
+ if (flag_to_domains == NULL)
+ return;
+
+ g_hash_table_destroy (flag_to_domains);
+ flag_to_domains = NULL;
+}
+
+static void
+log_to_debug_sender (RakiaDebugFlags flag,
+ GLogLevelFlags level,
+ const gchar *message)
+{
+ TpDebugSender *dbg;
+ GTimeVal now;
+
+ dbg = tp_debug_sender_dup ();
+
+ g_get_current_time (&now);
+
+ tp_debug_sender_add_message (dbg, &now, debug_flag_to_domain (flag),
+ level, message);
+
+ g_object_unref (dbg);
+}
+
+void rakia_log (RakiaDebugFlags flag,
+ GLogLevelFlags level,
+ const gchar *format,
+ ...)
+{
+ gchar *message;
+ va_list args;
+
+ va_start (args, format);
+ message = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ log_to_debug_sender (flag, level, message);
+
+ if (flag & rakia_debug_flags)
+ g_log (G_LOG_DOMAIN, level, "%s", message);
+
+ g_free (message);
+}
+
+static void
+rakia_sofia_log_handler (void *logdata, const char *format, va_list args)
+{
+#ifdef ENABLE_DEBUG
+ GString *buf = (GString *)logdata;
+ g_assert (buf != NULL);
+
+ /* Append the formatted message at the end of the buffer */
+ g_string_append_vprintf (buf, format, args);
+
+ /* If we have a terminated line, log it, stripping the newline */
+ if (buf->str[buf->len - 1] == '\n')
+ {
+ g_string_truncate (buf, buf->len - 1);
+ rakia_log (TPSIP_DEBUG_SOFIA, G_LOG_LEVEL_DEBUG, "%s", buf->str);
+ g_string_truncate (buf, 0);
+ }
+#endif
+}
+
+gpointer
+rakia_sofia_log_init ()
+{
+ GString *buf;
+
+#ifdef ENABLE_DEBUG
+ buf = g_string_sized_new (80);
+#else
+ buf = NULL;
+#endif
+
+ su_log_redirect (NULL, rakia_sofia_log_handler, buf);
+
+ return buf;
+}
+
+void
+rakia_sofia_log_finalize (gpointer logdata)
+{
+#ifdef ENABLE_DEBUG
+ GString *buf = (GString *)logdata;
+
+ if (buf->len != 0)
+ {
+ /* Don't use rakia_log here because the CM has already been finalized, so
+ * out TpDebugSender will have too. It isn't crucial, anyway. */
+ g_debug ("%s", buf->str);
+ g_message ("last Sofia log message was not newline-terminated");
+ }
+
+ g_string_free (buf, TRUE);
+#endif
+}
diff --git a/rakia/debug.h b/rakia/debug.h
new file mode 100644
index 0000000..58d7628
--- /dev/null
+++ b/rakia/debug.h
@@ -0,0 +1,70 @@
+/*
+ * debug.h - Debug helpers for Telepathy-SofiaSIP, headers
+ * Copyright (C) 2007-2008 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 __DEBUG_H__
+#define __DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ TPSIP_DEBUG_CONNECTION = 1 << 0,
+ TPSIP_DEBUG_MEDIA = 1 << 1,
+ TPSIP_DEBUG_IM = 1 << 2,
+ TPSIP_DEBUG_EVENTS = 1 << 3,
+ TPSIP_DEBUG_SOFIA = 1 << 4,
+} RakiaDebugFlags;
+
+void rakia_debug_set_flags_from_env ();
+void rakia_debug_set_flags (RakiaDebugFlags flags);
+gboolean rakia_debug_flag_is_set (RakiaDebugFlags flag);
+void rakia_log (RakiaDebugFlags flag, GLogLevelFlags level,
+ const gchar *format, ...) G_GNUC_PRINTF (3, 4);
+void rakia_debug_free (void);
+
+gpointer rakia_sofia_log_init ();
+void rakia_sofia_log_finalize (gpointer logdata);
+
+G_END_DECLS
+
+#ifdef DEBUG_FLAG
+
+#define DEBUG(format, ...) \
+ rakia_log(DEBUG_FLAG, G_LOG_LEVEL_DEBUG, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define WARNING(format, ...) \
+ rakia_log(DEBUG_FLAG, G_LOG_LEVEL_WARNING, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+#define MESSAGE(format, ...) \
+ rakia_log(DEBUG_FLAG, G_LOG_LEVEL_MESSAGE, "%s: " format, \
+ G_STRFUNC, ##__VA_ARGS__)
+
+/* #define DEBUGGING rakia_debug_flag_is_set(DEBUG_FLAG) */
+
+#else /* DEBUG_FLAG */
+
+#define DEBUG(format, ...)
+#define WARNING(format, ...)
+#define MESSAGE(format, ...)
+
+#endif /* DEBUG_FLAG */
+
+#endif /* __DEBUG_H__ */
diff --git a/rakia/event-target.c b/rakia/event-target.c
new file mode 100644
index 0000000..74bec43
--- /dev/null
+++ b/rakia/event-target.c
@@ -0,0 +1,271 @@
+/*
+ * event-target.c - Implementation for RakiaEventTarget interface
+ * Copyright (C) 2008 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 "event-target.h"
+#include "signals-marshal.h"
+
+/* Define to the highest known nua_event_e enumeration member */
+#define TPSIP_NUA_EVENT_LAST nua_i_register
+
+/* Mapping of the event enumeration to signal detail quarks */
+static GQuark event_quarks[TPSIP_NUA_EVENT_LAST + 1] = {0};
+
+/* Signals */
+enum {
+ SIG_NUA_EVENT,
+ NUM_SIGNALS
+};
+static guint signals[NUM_SIGNALS] = {0};
+
+static RakiaEventTarget * rakia_event_target_gone_instance ();
+
+static void
+rakia_event_target_base_init (gpointer klass)
+{
+ static gboolean initialized = FALSE;
+ gint i;
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+
+ /**
+ * RakiaEventTarget::nua-event:
+ * @instance: an object implementing #RakiaEventTarget that emitted the signal
+ * @event: Pointer to the event data structure
+ * @tags: Tag list containing dynamically typed information about the event
+ *
+ * Emitted by the NUA event handler for an object bound
+ * to a NUA operation handle.
+ * Returns: a handler returns TRUE to indicate that further handling
+ * of the signal should cease.
+ */
+ signals[SIG_NUA_EVENT] =
+ g_signal_new ("nua-event",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST|G_SIGNAL_DETAILED,
+ 0 /* G_STRUCT_OFFSET (RakiaEventTargetInterface, nua_event) */,
+ g_signal_accumulator_true_handled,
+ NULL,
+ _rakia_marshal_BOOLEAN__POINTER_POINTER,
+ G_TYPE_BOOLEAN,
+ 2,
+ G_TYPE_POINTER,
+ G_TYPE_POINTER);
+
+ for (i = 0; i <= TPSIP_NUA_EVENT_LAST; i++)
+ event_quarks[i] =
+ g_quark_from_static_string (nua_event_name ((nua_event_t) i));
+ }
+}
+
+GType
+rakia_event_target_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ static const GTypeInfo info = {
+ sizeof (RakiaEventTargetInterface),
+ rakia_event_target_base_init, /* base_init */
+ NULL, /* base_finalize */
+ NULL, /* class_init */
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ 0,
+ 0, /* n_preallocs */
+ NULL /* instance_init */
+ };
+
+ type = g_type_register_static (G_TYPE_INTERFACE,
+ "RakiaEventTargetInterface", &info, 0);
+ }
+
+ return type;
+}
+
+static void
+rakia_event_target_retire_nua_handle (nua_handle_t *nh)
+{
+ static RakiaEventTarget *target_gone = NULL;
+
+ if (G_UNLIKELY (target_gone == NULL))
+ target_gone = rakia_event_target_gone_instance ();
+
+ nua_handle_bind (nh, target_gone);
+ nua_handle_unref (nh);
+}
+
+static void
+_rakia_event_target_finalized (gpointer data, GObject *former_obj)
+{
+ rakia_event_target_retire_nua_handle ((nua_handle_t *) data);
+}
+
+/**
+ * rakia_event_target_attach:
+ * @nh: The NUA handle
+ * @obj: an object implementing #RakiaEventTarget
+ *
+ * Attach an event target object to the NUA handle using nua_handle_bind().
+ * The reference count of the NUA handle is incremented.
+ * When the attached object is finalized, the reference count of the NUA handle
+ * is decremented, and the handle is bound to a special end-of-life event
+ * handler for debugging purposes.
+ */
+void
+rakia_event_target_attach (nua_handle_t *nh, GObject *obj)
+{
+ g_assert (nh != NULL);
+
+ nua_handle_bind (nh, TPSIP_EVENT_TARGET (obj));
+ nua_handle_ref (nh);
+
+ g_object_weak_ref (obj, _rakia_event_target_finalized, nh);
+}
+
+/**
+ * rakia_event_target_detach:
+ * @nh: The NUA handle
+ *
+ * Detach the event target object previously attached to the NUA handle
+ * using rakia_event_target_attach().
+ * The reference count of the NUA handle is decremented, and the handle
+ * is bound to a special end-of-life event handler for debugging purposes.
+ */
+void
+rakia_event_target_detach (nua_handle_t *nh)
+{
+ GObject *obj;
+
+ g_assert (nh != NULL);
+
+ obj = G_OBJECT (nua_handle_magic (nh));
+ g_object_weak_unref (obj, _rakia_event_target_finalized, nh);
+
+ rakia_event_target_retire_nua_handle (nh);
+}
+
+/**
+ * rakia_event_target_emit_nua_event:
+ * @instance: The object implementing this interface
+ * @event: Pointer to the event data structure
+ * @tags: Tag list containing dynamically typed information about the event
+ *
+ * Emit the signal #RakiaEventTarget::nua-event, detailed with the event name,
+ * on an instance of a class implementing this interface.
+ * This function is normally called by the NUA callback.
+ * Returns: TRUE if a signal handler handled the event and returned TRUE.
+ */
+gboolean
+rakia_event_target_emit_nua_event (gpointer instance,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[])
+{
+ gboolean retval = FALSE;
+ gint nua_event;
+ GQuark detail;
+
+ g_assert (TPSIP_IS_EVENT_TARGET (instance));
+
+ nua_event = ev->nua_event;
+
+ detail = G_LIKELY (nua_event >= 0 && nua_event <= TPSIP_NUA_EVENT_LAST)
+ ? event_quarks[nua_event]
+ : g_quark_from_static_string (nua_event_name (nua_event));
+
+ g_signal_emit (instance,
+ signals[SIG_NUA_EVENT],
+ detail,
+ ev,
+ tags,
+ &retval);
+
+ return retval;
+}
+
+/* RakiaEventTargetGone:
+ * a special private implementation of RakiaEventTarget for a singleton
+ * catch-all object to associate with handles that have been detached from
+ * other event targets.
+ */
+typedef struct _RakiaEventTargetGone RakiaEventTargetGone;
+typedef struct _RakiaEventTargetGoneClass RakiaEventTargetGoneClass;
+
+struct _RakiaEventTargetGone {
+ GObject parent;
+};
+
+struct _RakiaEventTargetGoneClass {
+ GObjectClass parent_class;
+};
+
+static gboolean
+rakia_late_nua_event_cb (RakiaEventTargetGone *self,
+ const RakiaNuaEvent *event,
+ tagi_t tags[],
+ gpointer foo)
+{
+ g_message ("%s received for the retired handle %p: %03d %s",
+ nua_event_name (event->nua_event),
+ event->nua_handle,
+ event->status,
+ event->text);
+ return TRUE;
+}
+
+static void
+rakia_event_target_gone_iface_init (gpointer g_iface, gpointer iface_data)
+{
+}
+
+G_DEFINE_TYPE_WITH_CODE (RakiaEventTargetGone, rakia_event_target_gone,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPSIP_TYPE_EVENT_TARGET, rakia_event_target_gone_iface_init))
+
+static void
+rakia_event_target_gone_class_init (RakiaEventTargetGoneClass *klass)
+{
+}
+
+static void
+rakia_event_target_gone_init (RakiaEventTargetGone *self)
+{
+ g_signal_connect (self, "nua-event",
+ G_CALLBACK (rakia_late_nua_event_cb),
+ NULL);
+}
+
+static gpointer
+_rakia_event_target_gone_new_instance (gpointer foo)
+{
+ return g_object_new (rakia_event_target_gone_get_type (), NULL);
+}
+
+static RakiaEventTarget *
+rakia_event_target_gone_instance ()
+{
+ static GOnce init_gone_once = G_ONCE_INIT;
+
+ g_once (&init_gone_once, _rakia_event_target_gone_new_instance, NULL);
+
+ return TPSIP_EVENT_TARGET (init_gone_once.retval);
+}
diff --git a/rakia/event-target.h b/rakia/event-target.h
new file mode 100644
index 0000000..c6aae9c
--- /dev/null
+++ b/rakia/event-target.h
@@ -0,0 +1,92 @@
+/*
+ * event-target.h - Header for RakiaEventTarget interface and related utilities
+ * Copyright (C) 2008 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 __TPSIP_EVENT_TARGET_H__
+#define __TPSIP_EVENT_TARGET_H__
+
+#include <glib-object.h>
+#include <rakia/sofia-decls.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaNuaEvent RakiaNuaEvent;
+
+/**
+ * RakiaNuaEvent:
+ * @nua_event: The NUA event identifier
+ * @status: a SIP status code, or a status value used by Sofia-SIP
+ * @text: The text corresponding to the status code
+ * @nua: Pointer to the NUA stack
+ * @nua_handle: The NUA operation handle for the event
+ * @sip: Headers in the parsed incoming message, or NULL
+ *
+ * This structure contains data passed to the NUA event callback.
+ * The event tag list is not included and passed as a separate parameter.
+ */
+struct _RakiaNuaEvent {
+ nua_event_t nua_event;
+ gint status;
+ const gchar *text;
+ nua_t *nua;
+ nua_handle_t *nua_handle;
+ const sip_t *sip;
+};
+
+/**
+ * RakiaEventTarget:
+ *
+ * A typedef representing any implementation of this interface.
+ */
+typedef struct _RakiaEventTarget RakiaEventTarget;
+
+typedef struct _RakiaEventTargetInterface RakiaEventTargetInterface;
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_EVENT_TARGET \
+ (rakia_event_target_get_type ())
+#define TPSIP_EVENT_TARGET(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_EVENT_TARGET, RakiaEventTarget))
+#define TPSIP_IS_EVENT_TARGET(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_EVENT_TARGET))
+#define TPSIP_EVENT_TARGET_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE((obj), TPSIP_TYPE_EVENT_TARGET, RakiaEventTargetInterface))
+
+struct _RakiaEventTargetInterface {
+ GTypeInterface base_iface;
+
+/*
+ gboolean (* nua_event) (RakiaEventTarget *self,
+ const RakiaEvent *event,
+ tagi_t tags[]);
+*/
+};
+
+GType rakia_event_target_get_type (void) G_GNUC_CONST;
+
+void rakia_event_target_attach (nua_handle_t *nh, GObject *target);
+void rakia_event_target_detach (nua_handle_t *nh);
+
+gboolean rakia_event_target_emit_nua_event (gpointer instance,
+ const RakiaNuaEvent *event,
+ tagi_t tags[]);
+
+G_END_DECLS
+
+#endif /*__TPSIP_EVENT_TARGET_H__*/
diff --git a/rakia/handles.c b/rakia/handles.c
new file mode 100644
index 0000000..5031dd0
--- /dev/null
+++ b/rakia/handles.c
@@ -0,0 +1,345 @@
+/*
+ * rakia/handles.c - Handler helpers
+ * Copyright (C) 2005 Collabora Ltd.
+ * Copyright (C) 2006-2011 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
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <rakia/handles.h>
+#include <sofia-sip/sip_header.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_CONNECTION
+#include "rakia/debug.h"
+
+static GQuark
+rakia_handle_url_quark ()
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0))
+ quark = g_quark_from_static_string ("rakia-handle-url");
+
+ return quark;
+}
+
+const url_t*
+rakia_handle_inspect_uri (TpBaseConnection *base,
+ TpHandle handle)
+{
+ TpHandleRepoIface *repo;
+ GQuark url_quark;
+ url_t *url;
+ GError *error;
+
+ repo = tp_base_connection_get_handles (base, TP_HANDLE_TYPE_CONTACT);
+
+ if (!tp_handle_is_valid (repo, handle, &error))
+ {
+ DEBUG("invalid handle %u: %s", handle, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ url_quark = rakia_handle_url_quark ();
+
+ url = tp_handle_get_qdata (repo, handle, url_quark);
+
+ if (url == NULL)
+ {
+ url = url_make (NULL, tp_handle_inspect (repo, handle));
+
+ tp_handle_set_qdata (repo, handle, url_quark, url, free);
+ }
+
+ return url;
+}
+
+TpHandle
+rakia_handle_ensure (TpBaseConnection *conn,
+ url_t const *uri,
+ char const *alias)
+{
+ TpHandleRepoIface *repo;
+ gchar *str;
+ TpHandle handle;
+
+ g_return_val_if_fail (TP_IS_BASE_CONNECTION (conn), 0);
+ g_return_val_if_fail (uri != NULL, 0);
+
+ repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT);
+
+ str = url_as_string (NULL, uri);
+
+ handle = tp_handle_ensure (repo, str, NULL, NULL);
+
+ su_free (NULL, str);
+
+ /* TODO: set qdata for the alias */
+
+ return handle;
+}
+
+TpHandle
+rakia_handle_by_requestor (TpBaseConnection *conn,
+ sip_t const *sip)
+{
+ url_t const *uri;
+ char const *display;
+
+ g_return_val_if_fail (sip != NULL, 0);
+ g_return_val_if_fail (sip->sip_from != NULL, 0);
+
+ uri = sip->sip_from->a_url;
+ display = sip->sip_from->a_display;
+
+ return rakia_handle_ensure (conn, uri, display);
+}
+
+void
+rakia_handle_unref (TpBaseConnection *conn,
+ TpHandle handle)
+{
+ TpHandleRepoIface *repo;
+
+ g_return_if_fail (TP_IS_BASE_CONNECTION (conn));
+ g_return_if_fail (handle != 0);
+
+ repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (repo, handle);
+}
+
+char const *
+rakia_handle_inspect (TpBaseConnection *conn,
+ TpHandle handle)
+{
+ TpHandleRepoIface *repo;
+
+ g_return_val_if_fail (TP_IS_BASE_CONNECTION (conn), NULL);
+ g_return_val_if_fail (handle != 0, NULL);
+
+ repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT);
+
+ return tp_handle_inspect (repo, handle);
+}
+
+static gboolean
+priv_is_host (const gchar* str)
+{
+ static GRegex *host_regex = NULL;
+
+#define DOMAIN "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
+#define TLD "[a-z]([-a-z0-9]*[a-z0-9])?"
+
+ if (host_regex == NULL)
+ {
+ GError *error = NULL;
+
+ host_regex = g_regex_new ("^("
+ "("DOMAIN"\\.)*"TLD"\\.?|" /* host name */
+ "[0-9]{1,3}(\\.[0-9]{1,3}){3}|" /* IPv4 address */
+ "\\[[0-9a-f:.]\\]" /* IPv6 address, sloppily */
+ ")$",
+ G_REGEX_CASELESS | G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &error);
+
+ if (error != NULL)
+ g_error ("failed to compile the host regex: %s", error->message);
+ }
+
+#undef DOMAIN
+#undef TLD
+
+ return g_regex_match (host_regex, str, 0, NULL);
+}
+
+static gboolean
+priv_is_tel_num (const gchar *str)
+{
+ static GRegex *tel_num_regex = NULL;
+
+ if (tel_num_regex == NULL)
+ {
+ GError *error = NULL;
+
+ tel_num_regex = g_regex_new (
+ "^\\s*[\\+(]?\\s*[0-9][-.0-9()\\s]*$",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &error);
+
+ if (error != NULL)
+ g_error ("failed to compile the telephone number regex: %s",
+ error->message);
+ }
+
+ return g_regex_match (tel_num_regex, str, 0, NULL);
+}
+
+/* Strip the non-essential characters from a string regarded as
+ * a telephone number */
+static gchar *
+priv_strip_tel_num (const gchar *fuzzy)
+{
+ static GRegex *cruft_regex = NULL;
+
+ if (cruft_regex == NULL)
+ {
+ GError *error = NULL;
+
+ cruft_regex = g_regex_new ("[^+0-9]+",
+ G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &error);
+
+ if (error != NULL)
+ g_error ("failed to compile the non-essential "
+ "telephone number cruft regex: %s", error->message);
+ }
+
+ return g_regex_replace_literal (cruft_regex, fuzzy, -1, 0, "", 0, NULL);
+}
+
+static const char *
+priv_lowercase_url_part (su_home_t *home, const char *src)
+{
+ size_t n = 0;
+ size_t i;
+ char *res;
+
+ for (i = 0; src[i]; i++)
+ {
+ if (g_ascii_isupper (src[i]))
+ {
+ n = i + strlen (src + i);
+ break;
+ }
+ }
+
+ if (!src[i])
+ return src;
+
+ res = su_alloc (home, n + 1);
+ memcpy (res, src, i);
+ for (; i < n; i++)
+ res[i] = g_ascii_tolower (src[i]);
+ res[i] = '\0';
+
+ return (const char *) res;
+}
+
+#define TPSIP_RESERVED_CHARS_ALLOWED_IN_USERNAME "!*'()&=+$,;?/"
+
+gchar *
+rakia_normalize_contact (const gchar *sipuri,
+ const url_t *base_url,
+ const gchar *transport,
+ GError **error)
+{
+ su_home_t home[1] = { SU_HOME_INIT(home) };
+ url_t *url;
+ gchar *retval = NULL;
+ char *c;
+
+ url = url_make (home, sipuri);
+
+ if (url == NULL ||
+ (url->url_scheme == NULL && url->url_user == NULL))
+ {
+ /* we got username or phone number, local to our domain */
+ gchar *user;
+
+ if (base_url == NULL || base_url->url_host == NULL)
+ {
+ WARNING ("bare name given, but no account URL is set");
+ goto error;
+ }
+
+ if (priv_is_tel_num (sipuri))
+ {
+ user = priv_strip_tel_num (sipuri);
+ }
+ else
+ {
+ user = g_uri_escape_string (sipuri,
+ TPSIP_RESERVED_CHARS_ALLOWED_IN_USERNAME, FALSE);
+ }
+
+ if (base_url->url_type == url_sips)
+ url = url_format (home, "sips:%s@%s",
+ user, base_url->url_host);
+ else
+ url = url_format (home, "sip:%s@%s",
+ user, base_url->url_host);
+
+ g_free (user);
+
+ if (!url) goto error;
+ }
+ else if (url->url_scheme == NULL)
+ {
+ /* Set the scheme to SIP or SIPS accordingly to the connection's
+ * transport preference */
+ if (transport != NULL
+ && g_ascii_strcasecmp (transport, "tls") == 0)
+ {
+ url->url_type = url_sips;
+ url->url_scheme = "sips";
+ }
+ else
+ {
+ url->url_type = url_sip;
+ url->url_scheme = "sip";
+ }
+ }
+
+ if (url_sanitize (url) != 0) goto error;
+
+ /* scheme should've been set by now */
+ if (url->url_scheme == NULL || (url->url_scheme[0] == 0))
+ goto error;
+
+ /* convert the scheme to lowercase */
+ /* Note: we can't do it in place because url->url_scheme may point to
+ * a static string */
+ url->url_scheme = priv_lowercase_url_part (home, url->url_scheme);
+
+ /* Check that if we have '@', the username isn't empty.
+ * Note that we rely on Sofia-SIP to canonize the user name */
+ if (url->url_user)
+ {
+ if (url->url_user[0] == 0) goto error;
+ }
+
+ /* host should be set and valid */
+ if (url->url_host == NULL || !priv_is_host (url->url_host))
+ goto error;
+
+ /* convert host to lowercase */
+ for (c = (char *) url->url_host; *c; c++)
+ {
+ *c = g_ascii_tolower (*c);
+ }
+
+ retval = g_strdup (url_as_string (home, url));
+
+error:
+ if (retval == NULL)
+ g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+ "invalid SIP URI");
+
+ su_home_deinit (home);
+ return retval;
+}
diff --git a/rakia/handles.h b/rakia/handles.h
new file mode 100644
index 0000000..914f167
--- /dev/null
+++ b/rakia/handles.h
@@ -0,0 +1,47 @@
+/*
+ * rakia/handle.h - Telepathy SIP handle management
+ * Copyright (C) 2011 Nokia Corporation
+ * @author Pekka Pessi <pekka.pessi@nokia.com>
+ *
+ * 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 TPSIP_HANDLE_H
+#define TPSIP_HANDLE_H
+
+#include <telepathy-glib/base-connection.h>
+#include <sofia-sip/sip.h>
+
+G_BEGIN_DECLS
+
+TpHandle rakia_handle_ensure (TpBaseConnection *, url_t const *, char const *);
+TpHandle rakia_handle_by_requestor (TpBaseConnection *, sip_t const *sip);
+void rakia_handle_unref (TpBaseConnection *, TpHandle handle);
+char const *rakia_handle_inspect (TpBaseConnection *, TpHandle handle);
+const url_t *rakia_handle_inspect_uri (TpBaseConnection *, TpHandle handle);
+
+gchar * rakia_handle_normalize (TpHandleRepoIface *repo,
+ const gchar *sipuri,
+ gpointer context,
+ GError **error);
+
+gchar *rakia_normalize_contact (const gchar *sipuri,
+ const url_t *base_url,
+ const gchar *transport,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* !TPSIP_HANDLE_H */
diff --git a/rakia/media-channel.c b/rakia/media-channel.c
new file mode 100644
index 0000000..089b2b9
--- /dev/null
+++ b/rakia/media-channel.c
@@ -0,0 +1,2096 @@
+/*
+ * 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/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 TPSIP_DEBUG_MEDIA
+#include "rakia/debug.h"
+
+#include <rakia/media-session.h>
+#include <rakia/base-connection.h>
+
+#define TPSIP_CHANNEL_CALL_STATE_PROCEEDING_MASK \
+ (TP_CHANNEL_CALL_STATE_RINGING | \
+ TP_CHANNEL_CALL_STATE_QUEUED | \
+ TP_CHANNEL_CALL_STATE_IN_PROGRESS)
+
+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);
+
+G_DEFINE_TYPE_WITH_CODE (RakiaMediaChannel, rakia_media_channel,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPSIP_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,
+ /* 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 */
+typedef struct _RakiaMediaChannelPrivate RakiaMediaChannelPrivate;
+
+struct _RakiaMediaChannelPrivate
+{
+ RakiaBaseConnection *conn;
+ RakiaMediaSession *session;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+ GHashTable *call_states;
+ gchar *stun_server;
+ guint stun_port;
+
+ gboolean initial_audio;
+ gboolean initial_video;
+ gboolean immutable_streams;
+ gboolean closed;
+ gboolean dispose_has_run;
+};
+
+#define TPSIP_MEDIA_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_MEDIA_CHANNEL, RakiaMediaChannelPrivate))
+
+/***********************************************************************
+ * Set: Gobject interface
+ ***********************************************************************/
+
+static void
+rakia_media_channel_init (RakiaMediaChannel *self)
+{
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ /* 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 = TPSIP_MEDIA_CHANNEL (obj);
+ RakiaMediaChannelPrivate *priv = TPSIP_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 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,
+ },
+ { 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.",
+ TPSIP_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);
+
+ 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 = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_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;
+ 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 = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_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;
+ 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 = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_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);
+
+ 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 = TPSIP_MEDIA_CHANNEL (object);
+ RakiaMediaChannelPrivate *priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ g_hash_table_destroy (priv->call_states);
+
+ 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 = TPSIP_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 (TPSIP_IS_MEDIA_CHANNEL (obj));
+ priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GPtrArray *ret;
+ GValue handler = { 0 };
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (self));
+
+ priv = TPSIP_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_free (ret, TRUE);
+}
+
+
+/***********************************************************************
+ * 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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GPtrArray *ret = NULL;
+
+ priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ GError *error = NULL;
+ GPtrArray *ret = NULL;
+ RakiaMediaChannelPrivate *priv;
+ TpHandleRepoIface *contact_repo;
+
+ DEBUG("enter");
+
+ priv = TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_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)
+ & TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_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,
+ TPSIP_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)
+ == TPSIP_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,
+ TPSIP_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 = TPSIP_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 TPSIP_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 TPSIP_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 TPSIP_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 TPSIP_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 = TPSIP_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, TPSIP_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 = TPSIP_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 (TPSIP_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);
+
+ 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 = TPSIP_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 = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (obj);
+ RakiaMediaChannelPrivate *priv = TPSIP_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,
+ TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv = TPSIP_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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+
+ priv = TPSIP_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
+rakia_media_channel_start_tone (TpSvcChannelInterfaceDTMF *iface,
+ guint stream_id,
+ guchar event,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaChannel *self = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (self));
+
+ 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;
+ }
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (!rakia_media_session_start_telephony_event (priv->session,
+ stream_id,
+ event,
+ &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 = TPSIP_MEDIA_CHANNEL (iface);
+ RakiaMediaChannelPrivate *priv;
+ GError *error = NULL;
+
+ DEBUG("enter");
+
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (self));
+
+ priv = TPSIP_MEDIA_CHANNEL_GET_PRIVATE (self);
+
+ if (!rakia_media_session_stop_telephony_event (priv->session,
+ stream_id,
+ &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_error_free (error);
+ return;
+ }
+
+ tp_svc_channel_interface_dtmf_return_from_stop_tone (context);
+}
+
+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);
+#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
new file mode 100644
index 0000000..6b8b07d
--- /dev/null
+++ b/rakia/media-channel.h
@@ -0,0 +1,92 @@
+/*
+ * 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 __TPSIP_MEDIA_CHANNEL_H__
+#define __TPSIP_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;
+
+struct _RakiaMediaChannelClass {
+ GObjectClass parent_class;
+ TpGroupMixinClass group_class;
+ TpPropertiesMixinClass properties_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _RakiaMediaChannel {
+ GObject parent;
+ TpGroupMixin group;
+ TpPropertiesMixin properties;
+};
+
+GType rakia_media_channel_get_type(void);
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_MEDIA_CHANNEL \
+ (rakia_media_channel_get_type())
+#define TPSIP_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_MEDIA_CHANNEL, RakiaMediaChannel))
+#define TPSIP_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_MEDIA_CHANNEL, RakiaMediaChannelClass))
+#define TPSIP_IS_MEDIA_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_MEDIA_CHANNEL))
+#define TPSIP_IS_MEDIA_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_MEDIA_CHANNEL))
+#define TPSIP_MEDIA_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_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 __TPSIP_MEDIA_CHANNEL_H__*/
diff --git a/rakia/media-manager.c b/rakia/media-manager.c
new file mode 100644
index 0000000..57dd807
--- /dev/null
+++ b/rakia/media-manager.c
@@ -0,0 +1,701 @@
+/*
+ * media-manager.c - Media channel manager for SIP connection manager
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-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
+ */
+
+#include "config.h"
+
+#include "rakia/media-manager.h"
+
+#include <string.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "rakia/media-channel.h"
+#include "rakia/base-connection.h"
+#include "rakia/handles.h"
+
+#include <sofia-sip/sip_status.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_CONNECTION
+#include "rakia/debug.h"
+
+typedef enum {
+ TPSIP_MEDIA_CHANNEL_CREATE_WITH_AUDIO = 1 << 0,
+ TPSIP_MEDIA_CHANNEL_CREATE_WITH_VIDEO = 1 << 1,
+} RakiaMediaChannelCreationFlags;
+
+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);
+
+G_DEFINE_TYPE_WITH_CODE (RakiaMediaManager, rakia_media_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_STUN_SERVER,
+ PROP_STUN_PORT,
+ LAST_PROPERTY
+};
+
+typedef struct _RakiaMediaManagerPrivate RakiaMediaManagerPrivate;
+struct _RakiaMediaManagerPrivate
+{
+ /* unreferenced (since it owns this manager) */
+ TpBaseConnection *conn;
+ /* array of referenced (RakiaMediaChannel *) */
+ GPtrArray *channels;
+ /* for unique channel object paths, currently always increments */
+ guint channel_index;
+
+ gulong status_changed_id;
+ gulong invite_received_id;
+
+ gchar *stun_server;
+ guint16 stun_port;
+
+ gboolean dispose_has_run;
+};
+
+#define TPSIP_MEDIA_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_MEDIA_MANAGER, RakiaMediaManagerPrivate))
+
+static void
+rakia_media_manager_init (RakiaMediaManager *fac)
+{
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ priv->conn = NULL;
+ priv->channels = g_ptr_array_sized_new (1);
+ priv->channel_index = 0;
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+rakia_media_manager_dispose (GObject *object)
+{
+ RakiaMediaManager *fac = TPSIP_MEDIA_MANAGER (object);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ rakia_media_manager_close_all (fac);
+ g_assert (priv->channels == NULL);
+
+ if (G_OBJECT_CLASS (rakia_media_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_media_manager_parent_class)->dispose (object);
+}
+
+static void
+rakia_media_manager_finalize (GObject *object)
+{
+ RakiaMediaManager *fac = TPSIP_MEDIA_MANAGER (object);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ g_free (priv->stun_server);
+}
+
+static void
+rakia_media_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaMediaManager *fac = TPSIP_MEDIA_MANAGER (object);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ 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_media_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaMediaManager *fac = TPSIP_MEDIA_MANAGER (object);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ priv->conn = g_value_get_object (value);
+ break;
+ case PROP_STUN_SERVER:
+ g_free (priv->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_media_manager_class_init (RakiaMediaManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (RakiaMediaManagerPrivate));
+
+ object_class->constructed = rakia_media_manager_constructed;
+ object_class->get_property = rakia_media_manager_get_property;
+ object_class->set_property = rakia_media_manager_set_property;
+ object_class->dispose = rakia_media_manager_dispose;
+ object_class->finalize = rakia_media_manager_finalize;
+
+ param_spec = g_param_spec_object ("connection",
+ "RakiaBaseConnection object",
+ "SIP connection that owns this media channel manager",
+ TPSIP_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 ("stun-server", "STUN server address",
+ "STUN server address",
+ 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",
+ "STUN port.",
+ 0, G_MAXUINT16,
+ TPSIP_DEFAULT_STUN_PORT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_STUN_PORT, param_spec);
+}
+
+static void
+rakia_media_manager_close_all (RakiaMediaManager *fac)
+{
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (priv->conn,
+ priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+
+ 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_free (channels, TRUE);
+ }
+}
+
+/**
+ * media_channel_closed_cb:
+ * Signal callback for when a media channel is closed. Removes the references
+ * that #RakiaMediaManager holds to them.
+ */
+static void
+media_channel_closed_cb (RakiaMediaChannel *chan, gpointer user_data)
+{
+ RakiaMediaManager *fac = TPSIP_MEDIA_MANAGER (user_data);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ tp_channel_manager_emit_channel_closed_for_object (fac,
+ TP_EXPORTABLE_CHANNEL (chan));
+
+ if (priv->channels)
+ {
+ g_ptr_array_remove_fast (priv->channels, chan);
+ g_object_unref (chan);
+ }
+}
+
+/**
+ * new_media_channel
+ *
+ * Creates a new empty RakiaMediaChannel.
+ */
+static RakiaMediaChannel *
+new_media_channel (RakiaMediaManager *fac,
+ TpHandle initiator,
+ TpHandle maybe_peer,
+ RakiaMediaChannelCreationFlags flags)
+{
+ RakiaMediaManagerPrivate *priv;
+ RakiaMediaChannel *chan = NULL;
+ gchar *object_path;
+ const gchar *nat_traversal = "none";
+ gboolean initial_audio;
+ gboolean initial_video;
+ gboolean immutable_streams = FALSE;
+
+ g_assert (initiator != 0);
+
+ priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+
+ object_path = g_strdup_printf ("%s/MediaChannel%u", priv->conn->object_path,
+ priv->channel_index++);
+
+ DEBUG("channel object path %s", object_path);
+
+ initial_audio = ((flags & TPSIP_MEDIA_CHANNEL_CREATE_WITH_AUDIO) != 0);
+ initial_video = ((flags & TPSIP_MEDIA_CHANNEL_CREATE_WITH_VIDEO) != 0);
+
+ g_object_get (priv->conn,
+ "immutable-streams", &immutable_streams,
+ NULL);
+
+ if (priv->stun_server != NULL)
+ {
+ nat_traversal = "stun";
+ }
+
+ chan = g_object_new (TPSIP_TYPE_MEDIA_CHANNEL,
+ "connection", priv->conn,
+ "object-path", object_path,
+ "handle", maybe_peer,
+ "initiator", initiator,
+ "initial-audio", initial_audio,
+ "initial-video", initial_video,
+ "immutable-streams", immutable_streams,
+ "nat-traversal", nat_traversal,
+ 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_ptr_array_add (priv->channels, chan);
+
+ return chan;
+}
+
+static void
+incoming_call_cb (RakiaMediaChannel *channel,
+ RakiaMediaManager *fac)
+{
+ g_signal_handlers_disconnect_by_func (channel,
+ G_CALLBACK (incoming_call_cb), fac);
+ tp_channel_manager_emit_new_channel (fac,
+ TP_EXPORTABLE_CHANNEL (channel), NULL);
+}
+
+static gboolean
+rakia_nua_i_invite_cb (TpBaseConnection *conn,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ RakiaMediaManager *fac)
+{
+ RakiaMediaChannel *channel;
+ TpHandle handle;
+ guint channel_flags = 0;
+
+ /* figure out a handle for the identity */
+
+ handle = rakia_handle_by_requestor (conn, ev->sip);
+ if (!handle)
+ {
+ MESSAGE ("incoming INVITE with invalid sender information");
+ nua_respond (ev->nua_handle, 400, "Invalid From address", TAG_END());
+ return TRUE;
+ }
+
+ DEBUG("Got incoming invite from <%s>",
+ rakia_handle_inspect (conn, handle));
+
+ if (handle == conn->self_handle)
+ {
+ DEBUG("cannot handle calls from self");
+ nua_respond (ev->nua_handle, 501, "Calls from self are not supported", TAG_END());
+ return TRUE;
+ }
+
+ channel = new_media_channel (fac, handle, handle, channel_flags);
+
+ rakia_handle_unref (conn, handle);
+
+ /* 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);
+
+ rakia_media_channel_attach_to_nua_handle (channel, ev->nua_handle);
+
+ return TRUE;
+}
+
+static void
+connection_status_changed_cb (RakiaBaseConnection *conn,
+ guint status,
+ guint reason,
+ RakiaMediaManager *self)
+{
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (self);
+
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTED:
+
+ priv->invite_received_id = g_signal_connect (conn,
+ "nua-event::nua_i_invite",
+ G_CALLBACK (rakia_nua_i_invite_cb), self);
+
+ break;
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+
+ rakia_media_manager_close_all (self);
+
+ if (priv->invite_received_id != 0)
+ {
+ g_signal_handler_disconnect (conn, priv->invite_received_id);
+ priv->invite_received_id = 0;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+rakia_media_manager_constructed (GObject *object)
+{
+ RakiaMediaManager *self = TPSIP_MEDIA_MANAGER (object);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (self);
+ GObjectClass *parent_object_class =
+ G_OBJECT_CLASS (rakia_media_manager_parent_class);
+
+ if (parent_object_class->constructed != NULL)
+ parent_object_class->constructed (object);
+
+ priv->status_changed_id = g_signal_connect (priv->conn,
+ "status-changed", (GCallback) connection_status_changed_cb, object);
+}
+
+static void
+rakia_media_manager_foreach_channel (TpChannelManager *manager,
+ TpExportableChannelFunc foreach,
+ gpointer user_data)
+{
+ RakiaMediaManager *fac = TPSIP_MEDIA_MANAGER (manager);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (fac);
+ guint i;
+
+ for (i = 0; i < priv->channels->len; i++)
+ {
+ TpExportableChannel *channel = TP_EXPORTABLE_CHANNEL (
+ g_ptr_array_index (priv->channels, i));
+
+ foreach (channel, user_data);
+ }
+}
+
+static const gchar * const media_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ 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",
+ NULL
+};
+
+/* not advertised in type_foreach_channel_class - can only be requested with
+ * RequestChannel, not with CreateChannel/EnsureChannel */
+static const gchar * const anon_channel_allowed_properties[] = {
+ NULL
+};
+
+static void
+rakia_media_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ 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);
+
+ 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",
+ handle_type_value);
+
+ g_value_set_uint (handle_type_value, TP_HANDLE_TYPE_CONTACT);
+ func (type, table, named_channel_allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+typedef enum
+{
+ METHOD_REQUEST,
+ METHOD_CREATE,
+ METHOD_ENSURE,
+} RequestMethod;
+
+static gboolean
+rakia_media_manager_requestotron (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties,
+ RequestMethod method)
+{
+ RakiaMediaManager *self = TPSIP_MEDIA_MANAGER (manager);
+ RakiaMediaManagerPrivate *priv = TPSIP_MEDIA_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *conn = (TpBaseConnection *) priv->conn;
+ TpHandleType handle_type;
+ TpHandle handle;
+ RakiaMediaChannel *channel = NULL;
+ GError *error = NULL;
+ GSList *request_tokens;
+ guint chan_flags = 0;
+ gboolean require_target_handle;
+ gboolean add_peer_to_remote_pending;
+
+ /* 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;
+ * call is started when caller calls RequestStreams.
+ * - CreateChannel({THT: Contact, TH: n}):
+ * channel has TargetHandle=n
+ * n is not in the group interface at all
+ * call is started when caller calls RequestStreams.
+ * - EnsureChannel({THT: Contact, TH: n}):
+ * look for a channel whose peer is n, and return that if found with
+ * 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))
+ return FALSE;
+
+ handle_type = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL);
+
+ 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;
+
+ channel = new_media_channel (self, conn->self_handle, 0, 0);
+ break;
+
+ 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;
+
+ /* 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;
+
+ for (i = 0; i < priv->channels->len; i++)
+ {
+ 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;
+ }
+ }
+ }
+
+ if (tp_asv_get_boolean (request_properties,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio", NULL))
+ chan_flags |= TPSIP_MEDIA_CHANNEL_CREATE_WITH_AUDIO;
+
+ if (tp_asv_get_boolean (request_properties,
+ TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo", NULL))
+ chan_flags |= TPSIP_MEDIA_CHANNEL_CREATE_WITH_VIDEO;
+
+ channel = new_media_channel (self, conn->self_handle, handle, chan_flags);
+
+ 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);
+
+ 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:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+static gboolean
+rakia_media_manager_request_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return rakia_media_manager_requestotron (manager, request_token,
+ request_properties, METHOD_REQUEST);
+}
+
+static gboolean
+rakia_media_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return rakia_media_manager_requestotron (manager, request_token,
+ request_properties, METHOD_CREATE);
+}
+
+static gboolean
+rakia_media_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ return rakia_media_manager_requestotron (manager, request_token,
+ request_properties, METHOD_ENSURE);
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = rakia_media_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ rakia_media_manager_type_foreach_channel_class;
+ iface->request_channel = rakia_media_manager_request_channel;
+ iface->create_channel = rakia_media_manager_create_channel;
+ iface->ensure_channel = rakia_media_manager_ensure_channel;
+}
diff --git a/rakia/media-manager.h b/rakia/media-manager.h
new file mode 100644
index 0000000..7b962be
--- /dev/null
+++ b/rakia/media-manager.h
@@ -0,0 +1,57 @@
+/*
+ * rakia/media-manager.h - Media channel manager for SIP
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007-2008 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 __TPSIP_MEDIA_MANAGER_H__
+#define __TPSIP_MEDIA_MANAGER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaMediaManager RakiaMediaManager;
+typedef struct _RakiaMediaManagerClass RakiaMediaManagerClass;
+
+struct _RakiaMediaManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _RakiaMediaManager {
+ GObject parent;
+};
+
+GType rakia_media_manager_get_type(void);
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_MEDIA_MANAGER \
+ (rakia_media_manager_get_type())
+#define TPSIP_MEDIA_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_MEDIA_MANAGER, RakiaMediaManager))
+#define TPSIP_MEDIA_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_MEDIA_MANAGER, RakiaMediaManagerClass))
+#define TPSIP_IS_MEDIA_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_MEDIA_MANAGER))
+#define TPSIP_IS_MEDIA_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_MEDIA_MANAGER))
+#define TPSIP_MEDIA_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_TYPE_MEDIA_MANAGER, RakiaMediaManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/rakia/media-session.c b/rakia/media-session.c
new file mode 100644
index 0000000..2766321
--- /dev/null
+++ b/rakia/media-session.c
@@ -0,0 +1,2243 @@
+/*
+ * 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 TPSIP_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 TPSIP_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_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* session_states[] =
+{
+ "created",
+ "invite-sent",
+ "invite-received",
+ "response-received",
+ "active",
+ "reinvite-sent",
+ "reinvite-received",
+ "reinvite-pending",
+ "ended"
+};
+
+#endif /* ENABLE_DEBUG */
+
+/* private structure */
+typedef struct _RakiaMediaSessionPrivate RakiaMediaSessionPrivate;
+
+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 */
+ 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 pending_offer; /*< local media have been changed, but a re-INVITE is pending */
+ gboolean dispose_has_run;
+};
+
+#define TPSIP_MEDIA_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_MEDIA_SESSION, RakiaMediaSessionPrivate))
+
+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 *obj)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (obj);
+
+ priv->state = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (TPSIP_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 = TPSIP_MEDIA_SESSION (object);
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_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 = TPSIP_MEDIA_SESSION (object);
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_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).",
+ TPSIP_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);
+}
+
+static void
+rakia_media_session_dispose (GObject *object)
+{
+ RakiaMediaSession *self = TPSIP_MEDIA_SESSION (object);
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_MEDIA_SESSION (object);
+ RakiaMediaSessionPrivate *priv = TPSIP_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->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 = TPSIP_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 *obj = TPSIP_MEDIA_SESSION (iface);
+ RakiaMediaSessionPrivate *priv;
+ guint i;
+
+ DEBUG ("enter");
+
+ priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (obj);
+
+ if (!priv->se_ready)
+ {
+ priv->se_ready = TRUE;
+
+ /* note: streams are generated in priv_create_media_stream() */
+
+ for (i = 0; i < priv->streams->len; i++)
+ {
+ RakiaMediaStream *stream = g_ptr_array_index (priv->streams, i);
+ if (stream)
+ priv_emit_new_stream (obj, 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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+ return priv->peer;
+}
+
+RakiaMediaSessionState
+rakia_media_session_get_state (RakiaMediaSession *session)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+ guint old_state;
+
+ if (priv->state == new_state)
+ return;
+
+ old_state = priv->state;
+ priv->state = new_state;
+
+ SESSION_DEBUG (session, "state change: %s -> %s",
+ session_states[old_state],
+ session_states[new_state]);
+
+ switch (new_state)
+ {
+ case TPSIP_MEDIA_SESSION_STATE_CREATED:
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED:
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_SENT:
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_SENT:
+ case TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_PENDING:
+ break;
+ case TPSIP_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 TPSIP_MEDIA_SESSION_STATE_ENDED:
+ priv_close_all_streams (session);
+ DEBUG("destroying the NUA handle %p", priv->nua_op);
+ if (priv->nua_op != NULL)
+ {
+ nua_handle_destroy (priv->nua_op);
+ priv->nua_op = NULL;
+ }
+ break;
+
+ /* 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 == TPSIP_MEDIA_SESSION_STATE_ACTIVE && priv->pending_offer)
+ priv_session_invite (session, TRUE);
+}
+
+#ifdef ENABLE_DEBUG
+void
+rakia_media_session_debug (RakiaMediaSession *session,
+ const gchar *format, ...)
+{
+ RakiaMediaSessionPrivate *priv;
+ va_list list;
+ gchar buf[240];
+
+ if (!rakia_debug_flag_is_set (DEBUG_FLAG))
+ return;
+
+ priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ va_start (list, format);
+
+ g_vsnprintf (buf, sizeof (buf), format, list);
+
+ va_end (list);
+
+ DEBUG ("SIP media session [%-17s]: %s",
+ session_states[priv->state], buf);
+}
+#endif /* ENABLE_DEBUG */
+
+void rakia_media_session_terminate (RakiaMediaSession *session)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ DEBUG ("enter");
+
+ if (priv->state == TPSIP_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 TPSIP_MEDIA_SESSION_STATE_ACTIVE:
+ case TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_SENT:
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_PENDING:
+ DEBUG("sending BYE");
+ nua_bye (priv->nua_op, TAG_END());
+ break;
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_SENT:
+ DEBUG("sending CANCEL");
+ nua_cancel (priv->nua_op, TAG_END());
+ break;
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED:
+ DEBUG("sending the 480 response to an incoming INVITE");
+ nua_respond (priv->nua_op, 480, "Terminated", TAG_END());
+ break;
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
+ if (priv->saved_event[0])
+ {
+ DEBUG("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);
+ }
+ DEBUG("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, TPSIP_MEDIA_SESSION_STATE_ENDED);
+}
+
+gboolean
+rakia_media_session_set_remote_media (RakiaMediaSession *session,
+ const sdp_session_t* sdp)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+ gboolean authoritative;
+
+ DEBUG ("enter");
+
+ if (priv->state == TPSIP_MEDIA_SESSION_STATE_INVITE_SENT
+ || priv->state == TPSIP_MEDIA_SESSION_STATE_REINVITE_SENT)
+ {
+ rakia_media_session_change_state (
+ session,
+ TPSIP_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 == TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED
+ || priv->state == TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_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 == TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED
+ || priv->state == TPSIP_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 = TPSIP_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 = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (self);
+
+ g_return_if_fail (priv->state == TPSIP_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, TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED);
+}
+
+void
+rakia_media_session_receive_reinvite (RakiaMediaSession *self)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (self);
+
+ /* Check for permitted state transitions */
+ switch (priv->state)
+ {
+ case TPSIP_MEDIA_SESSION_STATE_ACTIVE:
+ case TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
+ break;
+ case TPSIP_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, TPSIP_MEDIA_SESSION_STATE_REINVITE_RECEIVED);
+}
+
+void
+rakia_media_session_accept (RakiaMediaSession *self)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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);
+}
+
+void
+rakia_media_session_respond (RakiaMediaSession *self,
+ gint status,
+ const char *message)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (self);
+ return priv->accepted;
+}
+
+static gboolean
+priv_glare_retry (gpointer session)
+{
+ RakiaMediaSession *self = session;
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (self);
+
+ SESSION_DEBUG (self, "glare resolution interval is over");
+
+ if (priv->state == TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (self);
+ guint interval;
+
+ if (priv->state != TPSIP_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, TPSIP_MEDIA_SESSION_STATE_REINVITE_PENDING);
+}
+
+static RakiaMediaStream *
+rakia_media_session_get_stream (RakiaMediaSession *self,
+ guint stream_id,
+ GError **error)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (self);
+ return priv->hold_state;
+}
+
+static gboolean
+rakia_media_session_is_local_hold_ongoing (RakiaMediaSession *self)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_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 = TPSIP_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_start_telephony_event (RakiaMediaSession *self,
+ guint stream_id,
+ guchar event,
+ GError **error)
+{
+ RakiaMediaStream *stream;
+
+ stream = rakia_media_session_get_stream (self, stream_id, error);
+ if (stream == NULL)
+ return FALSE;
+
+ if (rakia_media_stream_get_media_type (stream) != TP_MEDIA_STREAM_TYPE_AUDIO)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "non-audio stream %u does not support telephony events", stream_id);
+ return FALSE;
+ }
+
+ DEBUG("starting telephony event %u on stream %u", (guint) event, stream_id);
+
+ rakia_media_stream_start_telephony_event (stream, event);
+
+ return TRUE;
+}
+
+gboolean
+rakia_media_session_stop_telephony_event (RakiaMediaSession *self,
+ guint stream_id,
+ GError **error)
+{
+ RakiaMediaStream *stream;
+
+ stream = rakia_media_session_get_stream (self, stream_id, error);
+ if (stream == NULL)
+ return FALSE;
+
+ if (rakia_media_stream_get_media_type (stream) != TP_MEDIA_STREAM_TYPE_AUDIO)
+ {
+ g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "non-audio stream %u does not support telephony events; spurious use of the stop event?", stream_id);
+ return FALSE;
+ }
+
+ DEBUG("stopping the telephony event on stream %u", stream_id);
+
+ rakia_media_stream_stop_telephony_event (stream);
+
+ return TRUE;
+}
+
+gint
+rakia_media_session_rate_native_transport (RakiaMediaSession *session,
+ const GValue *transport)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ switch (priv->state)
+ {
+ case TPSIP_MEDIA_SESSION_STATE_CREATED:
+ /* If all streams are ready, send an offer now */
+ priv_request_response_step (session);
+ break;
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED:
+ case TPSIP_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 TPSIP_MEDIA_SESSION_STATE_INVITE_SENT:
+ case TPSIP_MEDIA_SESSION_STATE_REINVITE_SENT:
+ case TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
+ /* Cannot send another offer right now */
+ priv->pending_offer = TRUE;
+ break;
+ case TPSIP_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) {
+ g_message ("sending of a local media update disabled by parameter 'immutable-streams'");
+ break;
+ }
+ }
+ /* Fall through to the next case */
+ case TPSIP_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 = TPSIP_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;
+
+ DEBUG("the 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 = TPSIP_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;
+
+ DEBUG("setting remote SDP for stream %u", i);
+
+ if (media->m_rejected)
+ {
+ DEBUG("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)
+ {
+ MESSAGE ("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 = TPSIP_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, TPSIP_MEDIA_SESSION_STATE_ACTIVE);
+}
+
+static gboolean
+priv_session_local_sdp (RakiaMediaSession *session,
+ GString *user_sdp,
+ gboolean authoritative)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+ gboolean has_supported_media = FALSE;
+ guint len;
+ guint i;
+
+ g_return_val_if_fail (priv->local_non_ready == 0, FALSE);
+
+ len = priv->streams->len;
+ if (!authoritative && len > priv->remote_stream_count)
+ {
+ len = priv->remote_stream_count;
+ DEBUG("clamped response to %u streams seen in the offer", len);
+ }
+
+ g_string_append (user_sdp, "v=0\r\n");
+
+ for (i = 0; i < len; i++)
+ {
+ RakiaMediaStream *stream = g_ptr_array_index (priv->streams, i);
+ if (stream)
+ {
+ user_sdp = g_string_append (user_sdp,
+ rakia_media_stream_local_sdp (stream));
+ has_supported_media = TRUE;
+ }
+ else
+ {
+ user_sdp = g_string_append (user_sdp, "m=audio 0 RTP/AVP 0\r\n");
+ }
+ }
+
+ return has_supported_media;
+}
+
+static void
+priv_session_invite (RakiaMediaSession *session, gboolean reinvite)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+ GString *user_sdp;
+
+ DEBUG("enter");
+
+ g_return_if_fail (priv->nua_op != NULL);
+
+ user_sdp = g_string_new (NULL);
+
+ if (priv_session_local_sdp (session, user_sdp, TRUE))
+ {
+ /* 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(user_sdp->str),
+ SOATAG_RTP_SORT(SOA_RTP_SORT_REMOTE),
+ SOATAG_RTP_SELECT(SOA_RTP_SELECT_ALL),
+ NUTAG_AUTOANSWER(0),
+ TAG_IF(reinvite,
+ NUTAG_INVITE_TIMER (TPSIP_REINVITE_TIMEOUT)),
+ TAG_END());
+ priv->pending_offer = FALSE;
+
+ rakia_media_session_change_state (
+ session,
+ reinvite? TPSIP_MEDIA_SESSION_STATE_REINVITE_SENT
+ : TPSIP_MEDIA_SESSION_STATE_INVITE_SENT);
+ }
+ else
+ WARNING ("cannot send a valid SDP offer, are there no streams?");
+
+ g_string_free (user_sdp, TRUE);
+}
+
+static void
+priv_session_respond (RakiaMediaSession *session)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+ GString *user_sdp;
+
+ g_return_if_fail (priv->nua_op != NULL);
+
+ user_sdp = g_string_new (NULL);
+
+ if (priv_session_local_sdp (session, user_sdp, FALSE))
+ {
+ msg_t *msg;
+
+ /* 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 (user_sdp->str),
+ 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, TPSIP_MEDIA_SESSION_STATE_ACTIVE);
+ }
+ else
+ {
+ WARNING ("cannot respond with a valid SDP answer, were all streams closed?");
+
+ priv_session_rollback (session);
+ }
+
+ g_string_free (user_sdp, TRUE);
+}
+
+static gboolean
+priv_is_codec_intersect_pending (RakiaMediaSession *session)
+{
+ RakiaMediaSessionPrivate *priv = TPSIP_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 = TPSIP_MEDIA_SESSION_GET_PRIVATE (session);
+
+ if (priv->local_non_ready != 0)
+ {
+ DEBUG("there are local streams not ready, postponed");
+ return;
+ }
+
+ switch (priv->state)
+ {
+ case TPSIP_MEDIA_SESSION_STATE_CREATED:
+ priv_session_invite (session, FALSE);
+ break;
+ case TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
+ if (priv->accepted
+ && !priv_is_codec_intersect_pending (session))
+ rakia_media_session_change_state (session,
+ TPSIP_MEDIA_SESSION_STATE_ACTIVE);
+ break;
+ case TPSIP_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 TPSIP_MEDIA_SESSION_STATE_REINVITE_RECEIVED:
+ if (!priv_is_codec_intersect_pending (session))
+ priv_session_respond (session);
+ break;
+ case TPSIP_MEDIA_SESSION_STATE_ACTIVE:
+ case TPSIP_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 = TPSIP_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;
+ DEBUG("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 = TPSIP_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 = TPSIP_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 TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED:
+ case TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED:
+ DEBUG("no codec intersection, closing the stream");
+ rakia_media_stream_close (stream);
+ break;
+ case TPSIP_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 TPSIP_MEDIA_SESSION_STATE_ACTIVE:
+ /* We've most likely rolled back from
+ * TPSIP_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,
+ RakiaMediaChannel *channel)
+{
+ g_assert (TPSIP_IS_MEDIA_CHANNEL (channel));
+ tp_svc_channel_type_streamed_media_emit_stream_state_changed(
+ channel,
+ rakia_media_stream_get_id (stream), state);
+}
+
+static void
+priv_stream_direction_changed_cb (RakiaMediaStream *stream,
+ guint direction,
+ guint pending_send_flags,
+ RakiaMediaChannel *channel)
+{
+ g_assert (TPSIP_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 = TPSIP_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:
+ MESSAGE ("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))
+ {
+ DEBUG("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 = TPSIP_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 (TPSIP_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),
+ priv->channel);
+ 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
new file mode 100644
index 0000000..158e23b
--- /dev/null
+++ b/rakia/media-session.h
@@ -0,0 +1,143 @@
+/*
+ * 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 __TPSIP_MEDIA_SESSION_H__
+#define __TPSIP_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 {
+ TPSIP_MEDIA_SESSION_STATE_CREATED = 0,
+ TPSIP_MEDIA_SESSION_STATE_INVITE_SENT,
+ TPSIP_MEDIA_SESSION_STATE_INVITE_RECEIVED,
+ TPSIP_MEDIA_SESSION_STATE_RESPONSE_RECEIVED,
+ TPSIP_MEDIA_SESSION_STATE_ACTIVE,
+ TPSIP_MEDIA_SESSION_STATE_REINVITE_SENT,
+ TPSIP_MEDIA_SESSION_STATE_REINVITE_RECEIVED,
+ TPSIP_MEDIA_SESSION_STATE_REINVITE_PENDING,
+ TPSIP_MEDIA_SESSION_STATE_ENDED
+} RakiaMediaSessionState;
+
+typedef struct _RakiaMediaSession RakiaMediaSession;
+typedef struct _RakiaMediaSessionClass RakiaMediaSessionClass;
+
+struct _RakiaMediaSessionClass {
+ GObjectClass parent_class;
+};
+
+struct _RakiaMediaSession {
+ GObject parent;
+};
+
+GType rakia_media_session_get_type(void);
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_MEDIA_SESSION \
+ (rakia_media_session_get_type())
+#define TPSIP_MEDIA_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_MEDIA_SESSION, RakiaMediaSession))
+#define TPSIP_MEDIA_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_MEDIA_SESSION, RakiaMediaSessionClass))
+#define TPSIP_IS_MEDIA_SESSION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_MEDIA_SESSION))
+#define TPSIP_IS_MEDIA_SESSION_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_MEDIA_SESSION))
+#define TPSIP_MEDIA_SESSION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_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_start_telephony_event (RakiaMediaSession *self,
+ guint stream_id,
+ guchar event,
+ GError **error);
+gboolean rakia_media_session_stop_telephony_event (RakiaMediaSession *self,
+ guint stream_id,
+ GError **error);
+
+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);
+
+#ifdef ENABLE_DEBUG
+
+#define SESSION_DEBUG(s, ...) rakia_media_session_debug (s, __VA_ARGS__)
+
+void rakia_media_session_debug (RakiaMediaSession *session,
+ const gchar *format, ...);
+
+#else
+
+#define SESSION_DEBUG(s, ...)
+
+#endif
+
+G_END_DECLS
+
+#endif /* #ifndef __TPSIP_MEDIA_SESSION_H__*/
diff --git a/rakia/media-stream.c b/rakia/media-stream.c
new file mode 100644
index 0000000..f8f659e
--- /dev/null
+++ b/rakia/media-stream.c
@@ -0,0 +1,2002 @@
+/*
+ * 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 TPSIP_DEBUG_MEDIA
+#include "rakia/debug.h"
+
+
+#define same_boolean(old, new) ((!(old)) == (!(new)))
+
+
+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 */
+typedef struct _RakiaMediaStreamPrivate RakiaMediaStreamPrivate;
+
+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' */
+
+ gchar *stream_sdp; /* SDP description of the stream */
+
+ 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 TPSIP_MEDIA_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_MEDIA_STREAM, RakiaMediaStreamPrivate))
+
+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_update_local_sdp(RakiaMediaStream *stream);
+static void priv_generate_sdp (RakiaMediaStream *stream);
+
+#if 0
+#ifdef ENABLE_DEBUG
+static const char *debug_tp_protocols[] = {
+ "TP_MEDIA_STREAM_PROTO_UDP (0)",
+ "TP_MEDIA_STREAM_PROTO_TCP (1)"
+};
+
+static const char *debug_tp_transports[] = {
+ "TP_MEDIA_STREAM_TRANSPORT_TYPE_LOCAL (0)",
+ "TP_MEDIA_STREAM_TRANSPORT_TYPE_DERIVED (1)",
+ "TP_MEDIA_STREAM_TRANSPORT_TYPE_RELAY (2)"
+};
+#endif /* ENABLE_DEBUG */
+#endif /* 0 */
+
+/***********************************************************************
+ * Set: Gobject interface
+ ***********************************************************************/
+
+static void
+rakia_media_stream_init (RakiaMediaStream *self)
+{
+ RakiaMediaStreamPrivate *priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ priv->playing = FALSE;
+ priv->sending = FALSE;
+
+ 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));
+
+ priv->native_cands_prepared = FALSE;
+ priv->native_codecs_prepared = FALSE;
+
+ priv->push_remote_cands_pending = FALSE;
+ priv->push_remote_codecs_pending = FALSE;
+}
+
+static void
+rakia_media_stream_constructed (GObject *obj)
+{
+ RakiaMediaStreamPrivate *priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (
+ TPSIP_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 = TPSIP_MEDIA_STREAM (object);
+ RakiaMediaStreamPrivate *priv = TPSIP_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 = TPSIP_MEDIA_STREAM (object);
+ RakiaMediaStreamPrivate *priv = TPSIP_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.",
+ TPSIP_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 = TPSIP_MEDIA_STREAM (object);
+ RakiaMediaStreamPrivate *priv = TPSIP_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 = TPSIP_MEDIA_STREAM (object);
+ RakiaMediaStreamPrivate *priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ /* free any data held directly by the object here */
+ g_free (priv->object_path);
+ g_free (priv->stream_sdp);
+
+ 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)
+{
+ /* Inform the connection manager of the current codec choice.
+ * -> note: not implemented by tp-gabble either (2006/May) */
+
+ DEBUG ("not implemented (ignoring)");
+
+ 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)
+{
+ DEBUG("StreamHandler.Error called: %u %s", errno, message);
+
+ rakia_media_stream_close (TPSIP_MEDIA_STREAM (iface));
+
+ 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 = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv;
+
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ DEBUG("enter");
+
+ priv->native_cands_prepared = TRUE;
+
+ if (priv->native_codecs_prepared)
+ priv_generate_sdp (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 *obj = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv;
+
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ DEBUG("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 = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv;
+ GPtrArray *candidates;
+ GValue candidate = { 0, };
+ GValue transport = { 0, };
+ gint tr_goodness;
+
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ if (priv->stream_sdp != NULL)
+ {
+ MESSAGE ("Stream %u: SDP already generated, ignoring native candidate '%s'", priv->id, candidate_id);
+ tp_svc_media_stream_handler_return_from_new_native_candidate (context);
+ return;
+ }
+
+ 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)
+ {
+ DEBUG("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));
+
+ SESSION_DEBUG(priv->session, "put native candidate '%s' from stream-engine 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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+ GValue val = { 0, };
+
+ SESSION_DEBUG(priv->session, "putting list of %d locally supported "
+ "codecs from stream-engine into cache", codecs->len);
+ g_value_init (&val, TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST);
+ g_value_set_static_boxed (&val, codecs);
+ g_value_copy (&val, &priv->native_codecs);
+
+ priv->native_codecs_prepared = TRUE;
+ if (priv->native_cands_prepared)
+ priv_generate_sdp (self);
+}
+
+static void
+rakia_media_stream_codecs_updated (TpSvcMediaStreamHandler *iface,
+ const GPtrArray *codecs,
+ DBusGMethodInvocation *context)
+{
+ RakiaMediaStream *self = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv = TPSIP_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" };
+
+ SESSION_DEBUG(priv->session,
+ "CodecsUpdated called before SetLocalCodecs or Ready");
+
+ dbus_g_method_return_error (context, &e);
+ }
+ else
+ {
+ GValue val = { 0, };
+
+ SESSION_DEBUG(priv->session, "putting list of %d locally supported "
+ "codecs from CodecsUpdated into cache", codecs->len);
+ g_value_init (&val, TP_ARRAY_TYPE_MEDIA_STREAM_HANDLER_CODEC_LIST);
+ g_value_set_static_boxed (&val, codecs);
+ g_value_copy (&val, &priv->native_codecs);
+
+ /* This doesn't use priv_generate_sdp because it short-circuits if
+ * priv->stream_sdp is already set. We want to update it.
+ */
+ if (priv->native_cands_prepared)
+ priv_update_local_sdp (self);
+
+ 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 = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv;
+
+ DEBUG ("enter");
+
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ if (priv->ready_received)
+ {
+ MESSAGE ("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);
+
+ priv->native_codecs_prepared = TRUE;
+ if (priv->native_cands_prepared)
+ priv_generate_sdp (obj);
+
+ 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 (TPSIP_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 = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv;
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (obj);
+
+ if (priv->state != state)
+ {
+ DEBUG("changing stream state from %u to %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 = TPSIP_MEDIA_STREAM (iface);
+ RakiaMediaStreamPrivate *priv;
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ DEBUG("got codec intersection containing %u codecs from stream-engine",
+ codecs->len);
+
+ /* Uncomment the line below if there's need to limit the local codec list
+ * with the intersection for later SDP negotiations.
+ * TODO: Make sure to update the SDP for the stream as well. */
+ /* 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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+ return priv->id;
+}
+
+guint
+rakia_media_stream_get_media_type (RakiaMediaStream *self)
+{
+ RakiaMediaStreamPrivate *priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+ return priv->media_type;
+}
+
+void
+rakia_media_stream_close (RakiaMediaStream *self)
+{
+ tp_svc_media_stream_handler_emit_close (self);
+}
+
+/**
+ * Described the local stream configuration in SDP (RFC2327),
+ * or NULL if stream not configured yet.
+ */
+const char *rakia_media_stream_local_sdp (RakiaMediaStream *obj)
+{
+ RakiaMediaStreamPrivate *priv;
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (obj);
+ return priv->stream_sdp;
+}
+
+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 = TPSIP_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)
+ {
+ DEBUG("the stream is rejected remotely");
+ return FALSE;
+ }
+
+ if (new_media->m_proto != sdp_proto_rtp)
+ {
+ WARNING ("Stream %u: the remote protocol is not RTP/AVP", priv->id);
+ return FALSE;
+ }
+
+ sdp_conn = sdp_media_connections (new_media);
+ if (sdp_conn == NULL)
+ {
+ WARNING ("Stream %u: no valid remote connections", priv->id);
+ return FALSE;
+ }
+
+ if (new_media->m_rtpmaps == NULL)
+ {
+ WARNING ("Stream %u: no remote codecs", priv->id);
+ 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)
+ {
+ DEBUG("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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ if (same_boolean (priv->playing, playing))
+ return;
+
+ DEBUG("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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ if (same_boolean(priv->sending, sending))
+ return;
+
+ DEBUG("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 = TPSIP_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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+ pending_send_flags = priv->pending_send_flags & pending_send_mask;
+
+ if ((direction & ~priv->direction & TP_MEDIA_STREAM_DIRECTION_SEND) != 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 & ~priv->direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE) != 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;
+
+ DEBUG("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)
+ priv_update_local_sdp (stream);
+}
+
+/*
+ * 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 = TPSIP_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;
+
+ DEBUG("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;
+ DEBUG("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 (TPSIP_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;
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+ g_assert (priv->stream_sdp == NULL || priv->ready_received);
+ return (priv->stream_sdp != NULL);
+}
+
+gboolean
+rakia_media_stream_is_codec_intersect_pending (RakiaMediaStream *self)
+{
+ RakiaMediaStreamPrivate *priv = TPSIP_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 = TPSIP_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_generate_sdp (RakiaMediaStream *self)
+{
+ RakiaMediaStreamPrivate *priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (self);
+
+ if (priv->stream_sdp != NULL)
+ return;
+
+ priv_update_local_sdp (self);
+
+ g_assert (priv->stream_sdp != NULL);
+
+ 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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ sdpmedia = priv->remote_media;
+ if (sdpmedia == NULL)
+ {
+ DEBUG("remote media description is not received yet");
+ return;
+ }
+
+ if (!priv->ready_received)
+ {
+ DEBUG("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_destroy (opt_params);
+ g_free (ptime);
+ g_free (max_ptime);
+
+ SESSION_DEBUG(priv->session, "passing %d remote codecs to stream engine",
+ 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 = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ media = priv->remote_media;
+ if (media == NULL)
+ {
+ DEBUG("remote media description is not received yet");
+ return;
+ }
+
+ if (!priv->ready_received)
+ {
+ DEBUG("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);
+
+ DEBUG("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);
+
+ DEBUG("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));
+
+ DEBUG("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;
+
+ DEBUG("enter");
+
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ if (priv->ready_received
+ && priv->native_candidate_id != NULL
+ && priv->remote_candidate_id != NULL)
+ {
+ DEBUG("emitting SetActiveCandidatePair for %s-%s",
+ priv->native_candidate_id, priv->remote_candidate_id);
+ tp_svc_media_stream_handler_emit_set_active_candidate_pair (
+ stream, priv->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_destroy (co_params);
+ co_params = NULL;
+ }
+}
+
+/**
+* Refreshes the local SDP based on Farsight stream, and current
+* object, state.
+*/
+static void
+priv_update_local_sdp(RakiaMediaStream *stream)
+{
+ RakiaMediaStreamPrivate *priv;
+ GString *mline;
+ GString *alines;
+ gchar *cline;
+ GValue transport = { 0 };
+ const GPtrArray *candidates;
+ 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_component;
+ /* guint tr_type; */
+ /* gdouble tr_pref; */
+ guint rtcp_port = 0;
+ gchar *rtcp_address = NULL;
+ const gchar *dirline;
+ int i;
+
+ priv = TPSIP_MEDIA_STREAM_GET_PRIVATE (stream);
+
+ 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;
+ const gchar *candidate_id;
+ const GPtrArray *ca_tports;
+ guint tr_proto = TP_MEDIA_STREAM_BASE_PROTO_UDP;
+ 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++)
+ {
+ g_value_set_static_boxed (&transport,
+ g_ptr_array_index (ca_tports, 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,
+ 3, &tr_proto,
+ 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;
+ }
+ }
+
+ 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)
+ {
+ g_free (priv->native_candidate_id);
+ priv->native_candidate_id = g_strdup (candidate_id);
+ break;
+ }
+ }
+ g_return_if_fail (i >= 0);
+ g_return_if_fail (tr_addr != NULL);
+ g_return_if_fail (tr_subtype != NULL);
+ g_return_if_fail (tr_profile != NULL);
+
+ mline = g_string_new ("m=");
+ g_string_append_printf (mline,
+ "%s %u %s/%s",
+ priv_media_type_to_str (priv->media_type),
+ tr_port,
+ tr_subtype,
+ tr_profile);
+
+ cline = g_strdup_printf ("c=IN %s %s\r\n",
+ (strchr (tr_addr, ':') == NULL)? "IP4" : "IP6",
+ tr_addr);
+
+ 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),
+ mline, alines);
+
+ g_free(priv->stream_sdp);
+ priv->stream_sdp = g_strconcat(mline->str, "\r\n",
+ cline,
+ alines->str,
+ NULL);
+
+ 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 (mline, TRUE);
+ g_free (cline);
+ g_string_free (alines, TRUE);
+
+ g_signal_emit (stream, signals[SIG_LOCAL_MEDIA_UPDATED], 0);
+}
+
+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
new file mode 100644
index 0000000..0e09a42
--- /dev/null
+++ b/rakia/media-stream.h
@@ -0,0 +1,92 @@
+/*
+ * 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 __TPSIP_MEDIA_STREAM_H__
+#define __TPSIP_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;
+
+struct _RakiaMediaStreamClass {
+ GObjectClass parent_class;
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _RakiaMediaStream {
+ GObject parent;
+};
+
+GType rakia_media_stream_get_type(void);
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_MEDIA_STREAM \
+ (rakia_media_stream_get_type())
+#define TPSIP_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_MEDIA_STREAM, RakiaMediaStream))
+#define TPSIP_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_MEDIA_STREAM, RakiaMediaStreamClass))
+#define TPSIP_IS_MEDIA_STREAM(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_MEDIA_STREAM))
+#define TPSIP_IS_MEDIA_STREAM_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_MEDIA_STREAM))
+#define TPSIP_MEDIA_STREAM_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_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);
+const char *rakia_media_stream_local_sdp (RakiaMediaStream *self);
+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 __TPSIP_MEDIA_STREAM_H__*/
diff --git a/rakia/signals-marshal.list b/rakia/signals-marshal.list
new file mode 100644
index 0000000..ec706c4
--- /dev/null
+++ b/rakia/signals-marshal.list
@@ -0,0 +1,2 @@
+BOOLEAN:POINTER,POINTER
+VOID:UINT,UINT
diff --git a/rakia/sofia-decls.h b/rakia/sofia-decls.h
new file mode 100644
index 0000000..7f04e24
--- /dev/null
+++ b/rakia/sofia-decls.h
@@ -0,0 +1,50 @@
+/*
+ * sofia-decls.h - A header file to pull in Sofia APIs
+ * Copyright (C) 2006-2009 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 _TPSIP_SOFIA_DECLS_H_
+#define _TPSIP_SOFIA_DECLS_H_
+
+/* note: As one Sofia-SIP NUA instance is created per SIP connection,
+ * RakiaConnection is used as the context pointer.
+ * See {top}/docs/design.txt for further information.
+ *
+ * Each NUA handle managed by Telepathy-SofiaSIP is bound to an object
+ * implementing RakiaEventTarget. The managed NUA handle survives at
+ * least as long as the bound object. When the object is destroyed,
+ * the handle is bound to a special end-of-life event handler object.
+ * Thus, any NUA handle has either the magic value of NULL, or
+ * pointing to an event target object attached to this handle.
+ */
+
+#define NUA_MAGIC_T struct _RakiaBaseConnection
+#define NUA_HMAGIC_T struct _RakiaEventTarget
+#define SU_ROOT_MAGIC_T struct _RakiaConnectionManager
+#define SU_TIMER_ARG_T struct _RakiaBaseConnection
+#define SU_WAKEUP_ARG_T void
+
+#define TPSIP_DEFAULT_STUN_PORT 3478
+
+/* Maximum defer timeout for deferrable Sofia timers */
+#define TPSIP_DEFER_TIMEOUT 30
+
+#include <sofia-sip/nua.h>
+#include <sofia-sip/su.h>
+
+#endif /* _TPSIP_SOFIA_DECLS_H_*/
diff --git a/rakia/text-channel.c b/rakia/text-channel.c
new file mode 100644
index 0000000..4d4bc8c
--- /dev/null
+++ b/rakia/text-channel.c
@@ -0,0 +1,955 @@
+/*
+ * sip-text-channel.c - Source for RakiaTextChannel
+ * Copyright (C) 2005-2008 Collabora Ltd.
+ * Copyright (C) 2005-2009 Nokia Corporation
+ * @author Martti Mela <first.surname@nokia.com>
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * Based on telepathy-gabble implementation (gabble-im-channel).
+ * @author See gabble-im-channel.c
+ *
+ * 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/text-channel.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <dbus/dbus-glib.h>
+#include <telepathy-glib/channel-iface.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-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+#include "rakia/event-target.h"
+#include "rakia/base-connection.h"
+
+#include <sofia-sip/sip_protos.h>
+#include <sofia-sip/sip_status.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_IM
+#include "rakia/debug.h"
+
+static gboolean
+rakia_text_channel_nua_r_message_cb (RakiaTextChannel *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo);
+
+static void channel_iface_init (gpointer, gpointer);
+static void destroyable_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (RakiaTextChannel, rakia_text_channel, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TPSIP_TYPE_EVENT_TARGET, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+ tp_dbus_properties_mixin_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT,
+ tp_message_mixin_text_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MESSAGES,
+ tp_message_mixin_messages_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DESTROYABLE,
+ destroyable_iface_init);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL);
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL));
+
+static const char *rakia_text_channel_interfaces[] = {
+ TP_IFACE_CHANNEL_INTERFACE_DESTROYABLE,
+ NULL
+};
+
+
+/* properties */
+enum
+{
+ PROP_CONNECTION = 1,
+ PROP_OBJECT_PATH,
+ PROP_CHANNEL_TYPE,
+ PROP_HANDLE_TYPE,
+ PROP_HANDLE,
+ PROP_TARGET_ID,
+ PROP_INITIATOR_HANDLE,
+ PROP_INITIATOR_ID,
+ PROP_REQUESTED,
+ PROP_INTERFACES,
+ PROP_CHANNEL_DESTROYED,
+ PROP_CHANNEL_PROPERTIES,
+ LAST_PROPERTY
+};
+
+
+/* private structures */
+
+typedef struct _RakiaTextPendingMessage RakiaTextPendingMessage;
+
+struct _RakiaTextPendingMessage
+{
+ nua_handle_t *nh;
+ gchar *token;
+ TpMessageSendingFlags flags;
+};
+
+typedef struct _RakiaTextChannelPrivate RakiaTextChannelPrivate;
+
+struct _RakiaTextChannelPrivate
+{
+ RakiaBaseConnection *conn;
+ gchar *object_path;
+ TpHandle handle;
+ TpHandle initiator;
+
+ guint sent_id;
+ GQueue *sending_messages;
+
+ gboolean closed;
+
+ gboolean dispose_has_run;
+};
+
+
+#define _rakia_text_pending_new0() \
+ (g_slice_new0(RakiaTextPendingMessage))
+
+#define TPSIP_TEXT_CHANNEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_TEXT_CHANNEL, RakiaTextChannelPrivate))
+
+static void rakia_text_pending_free (RakiaTextPendingMessage *msg,
+ TpHandleRepoIface *contact_handles)
+{
+ if (msg->nh)
+ nua_handle_unref (msg->nh);
+
+ g_free (msg->token);
+
+ g_slice_free (RakiaTextPendingMessage, msg);
+}
+
+static void
+rakia_text_channel_init (RakiaTextChannel *obj)
+{
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (obj);
+
+ DEBUG("enter");
+
+ priv->sending_messages = g_queue_new ();
+}
+
+static void rakia_text_channel_send_message (GObject *object,
+ TpMessage *message,
+ TpMessageSendingFlags flags);
+
+static void
+rakia_text_channel_constructed (GObject *obj)
+{
+ RakiaTextChannelPrivate *priv;
+ TpBaseConnection *base_conn;
+ TpHandleRepoIface *contact_handles;
+ TpDBusDaemon *bus;
+ TpChannelTextMessageType types[] = {
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+ };
+ const gchar * supported_content_types[] = {
+ "text/plain",
+ NULL
+ };
+ GObjectClass *parent_object_class =
+ G_OBJECT_CLASS (rakia_text_channel_parent_class);
+
+ if (parent_object_class->constructed != NULL)
+ parent_object_class->constructed (obj);
+
+ priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE(TPSIP_TEXT_CHANNEL(obj));
+ base_conn = (TpBaseConnection *) priv->conn;
+ contact_handles = tp_base_connection_get_handles (base_conn,
+ TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_ref (contact_handles, priv->handle);
+
+ g_assert (priv->initiator != 0);
+ tp_handle_ref (contact_handles, priv->initiator);
+
+ rakia_base_connection_add_auth_handler (priv->conn, TPSIP_EVENT_TARGET (obj));
+
+ g_signal_connect (obj,
+ "nua-event::nua_r_message",
+ G_CALLBACK (rakia_text_channel_nua_r_message_cb),
+ NULL);
+
+ tp_message_mixin_init (obj, G_STRUCT_OFFSET (RakiaTextChannel, message_mixin),
+ base_conn);
+
+ tp_message_mixin_implement_sending (obj, rakia_text_channel_send_message,
+ G_N_ELEMENTS (types), types, 0,
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_FAILURES |
+ TP_DELIVERY_REPORTING_SUPPORT_FLAG_RECEIVE_SUCCESSES,
+ supported_content_types);
+
+ bus = tp_base_connection_get_dbus_daemon (base_conn);
+ tp_dbus_daemon_register_object (bus, priv->object_path, obj);
+}
+
+
+static void rakia_text_channel_get_property(GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void rakia_text_channel_set_property(GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void rakia_text_channel_dispose(GObject *object);
+static void rakia_text_channel_finalize(GObject *object);
+
+static void
+rakia_text_channel_class_init(RakiaTextChannelClass *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-handle", NULL },
+ { "InitiatorID", "initiator-id", NULL },
+ { "Requested", "requested", NULL },
+ { NULL }
+ };
+ static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+ { TP_IFACE_CHANNEL,
+ tp_dbus_properties_mixin_getter_gobject_properties,
+ NULL,
+ channel_props,
+ },
+ { NULL }
+ };
+
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ DEBUG("enter");
+
+ g_type_class_add_private (klass, sizeof (RakiaTextChannelPrivate));
+
+ object_class->get_property = rakia_text_channel_get_property;
+ object_class->set_property = rakia_text_channel_set_property;
+
+ object_class->constructed = rakia_text_channel_constructed;
+
+ object_class->dispose = rakia_text_channel_dispose;
+ object_class->finalize = rakia_text_channel_finalize;
+
+ 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.",
+ TPSIP_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_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", "Peer's SIP URI",
+ "The URI string obtained by inspecting the peer handle",
+ 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-handle", "Initiator's handle",
+ "The contact who initiated 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_HANDLE,
+ param_spec);
+
+ param_spec = g_param_spec_string ("initiator-id", "Initiator's URI",
+ "The string 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);
+
+ klass->dbus_props_class.interfaces =
+ prop_interfaces;
+ tp_dbus_properties_mixin_class_init (object_class,
+ G_STRUCT_OFFSET (RakiaTextChannelClass, dbus_props_class));
+
+ tp_message_mixin_init_dbus_properties (object_class);
+}
+
+static void
+rakia_text_channel_get_property(GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaTextChannel *chan = TPSIP_TEXT_CHANNEL(object);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE(chan);
+ TpBaseConnection *base_conn = (TpBaseConnection *) 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_string(value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ break;
+
+ case PROP_HANDLE_TYPE:
+ g_value_set_uint(value, TP_HANDLE_TYPE_CONTACT);
+ break;
+
+ case PROP_HANDLE:
+ g_value_set_uint(value, priv->handle);
+ break;
+
+ case PROP_TARGET_ID:
+ {
+ TpHandleRepoIface *repo = tp_base_connection_get_handles (
+ base_conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_value_set_string (value, tp_handle_inspect (repo, priv->handle));
+ }
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ 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_assert (priv->initiator != 0);
+ 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_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",
+ NULL));
+ break;
+
+ case PROP_INTERFACES:
+ g_value_set_static_boxed (value, rakia_text_channel_interfaces);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+rakia_text_channel_set_property(GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaTextChannel *chan = TPSIP_TEXT_CHANNEL (object);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (chan);
+
+ switch (property_id)
+ {
+ case PROP_CONNECTION:
+ priv->conn = 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_CHANNEL_TYPE:
+ case PROP_HANDLE_TYPE:
+ /* this property is writable in the interface, but not actually
+ * meaningfully changable on this channel, so we do nothing */
+ break;
+
+ case PROP_HANDLE:
+ /* we don't ref it here because we don't necessarily have access to the
+ * contact repo yet - instead we ref it in the constructed */
+ priv->handle = g_value_get_uint(value);
+ break;
+
+ case PROP_INITIATOR_HANDLE:
+ /* similarly we can't ref this yet */
+ priv->initiator = g_value_get_uint (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+rakia_text_channel_dispose(GObject *object)
+{
+ RakiaTextChannel *self = TPSIP_TEXT_CHANNEL (object);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_handles;
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ if (!priv->closed)
+ {
+ priv->closed = TRUE;
+ tp_svc_channel_emit_closed (self);
+ }
+
+ contact_handles = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_handle_unref (contact_handles, priv->handle);
+
+ if (priv->initiator != 0)
+ tp_handle_unref (contact_handles, priv->initiator);
+
+ if (G_OBJECT_CLASS (rakia_text_channel_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_text_channel_parent_class)->dispose (object);
+}
+
+static void
+zap_pending_messages (GQueue *pending_messages,
+ TpHandleRepoIface *contact_handles)
+{
+ g_queue_foreach (pending_messages,
+ (GFunc) rakia_text_pending_free, contact_handles);
+ g_queue_clear (pending_messages);
+}
+
+static void
+rakia_text_channel_finalize(GObject *object)
+{
+ RakiaTextChannel *self = TPSIP_TEXT_CHANNEL (object);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_handles;
+
+ contact_handles = tp_base_connection_get_handles (
+ (TpBaseConnection *)priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ DEBUG ("%u pending outgoing message requests",
+ g_queue_get_length (priv->sending_messages));
+ zap_pending_messages (priv->sending_messages, contact_handles);
+ g_queue_free (priv->sending_messages);
+
+ g_free (priv->object_path);
+
+ tp_message_mixin_finalize (object);
+
+ G_OBJECT_CLASS (rakia_text_channel_parent_class)->finalize (object);
+}
+
+static gint rakia_acknowledged_messages_compare(gconstpointer msg,
+ gconstpointer id)
+{
+ RakiaTextPendingMessage *message = (RakiaTextPendingMessage *)msg;
+ nua_handle_t *nh = (nua_handle_t *) id;
+ return (message->nh != nh);
+}
+
+/**
+ * rakia_text_channel_close
+ *
+ * Implements DBus method Close
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_text_channel_close (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaTextChannel *self = TPSIP_TEXT_CHANNEL (iface);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE(self);
+
+ if (priv->closed)
+ {
+ DEBUG ("already closed, doing nothing");
+ }
+ else
+ {
+ if (!tp_message_mixin_has_pending_messages ((GObject *) self, NULL))
+ {
+ DEBUG ("actually closing, no pending messages");
+ priv->closed = TRUE;
+ }
+ else
+ {
+ DEBUG ("not really closing, there are pending messages left");
+
+ tp_message_mixin_set_rescued ((GObject *) self);
+
+ if (priv->initiator != priv->handle)
+ {
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+ ((TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ g_assert (priv->initiator != 0);
+ g_assert (priv->handle != 0);
+
+ tp_handle_unref (contact_repo, priv->initiator);
+ priv->initiator = priv->handle;
+ tp_handle_ref (contact_repo, priv->initiator);
+ }
+ }
+ tp_svc_channel_emit_closed (self);
+ }
+ tp_svc_channel_return_from_close (context);
+}
+
+/**
+ * rakia_text_channel_destroy
+ *
+ * Implements D-Bus method Destroy
+ * on interface org.freedesktop.Telepathy.Channel.Interface.Destroyable
+ */
+static void
+rakia_text_channel_destroy (TpSvcChannelInterfaceDestroyable *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaTextChannel *self = TPSIP_TEXT_CHANNEL (iface);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ TpHandleRepoIface *contact_handles;
+
+ contact_handles = tp_base_connection_get_handles (
+ (TpBaseConnection *) priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+ tp_message_mixin_clear ((GObject *) iface);
+
+ /* Close() and Destroy() have the same signature, so we can safely
+ * chain to the other function now */
+ rakia_text_channel_close ((TpSvcChannel *) self, context);
+}
+
+/**
+ * rakia_text_channel_get_channel_type
+ *
+ * Implements DBus method GetChannelType
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_text_channel_get_channel_type (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ DEBUG("enter");
+
+ tp_svc_channel_return_from_get_channel_type (context,
+ TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+
+/**
+ * rakia_text_channel_get_handle
+ *
+ * Implements DBus method GetHandle
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_text_channel_get_handle (TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ RakiaTextChannel *obj = TPSIP_TEXT_CHANNEL (iface);
+ RakiaTextChannelPrivate *priv;
+
+ DEBUG("enter");
+
+ priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE(obj);
+
+ tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+ priv->handle);
+}
+
+
+/**
+ * rakia_text_channel_get_interfaces
+ *
+ * Implements DBus method GetInterfaces
+ * on interface org.freedesktop.Telepathy.Channel
+ */
+static void
+rakia_text_channel_get_interfaces(TpSvcChannel *iface,
+ DBusGMethodInvocation *context)
+{
+ DEBUG("enter");
+ tp_svc_channel_return_from_get_interfaces (context,
+ rakia_text_channel_interfaces);
+}
+
+static void
+rakia_text_channel_send_message (GObject *object,
+ TpMessage *message,
+ TpMessageSendingFlags flags)
+{
+ RakiaTextChannel *self = TPSIP_TEXT_CHANNEL(object);
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ RakiaTextPendingMessage *msg = NULL;
+ nua_handle_t *msg_nh = NULL;
+ GError *error = NULL;
+ const GHashTable *part;
+ guint n_parts;
+ const gchar *content_type;
+ const gchar *text;
+
+ DEBUG("enter");
+
+#define INVALID_ARGUMENT(msg, ...) \
+ G_STMT_START { \
+ DEBUG (msg , ## __VA_ARGS__); \
+ g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, \
+ msg , ## __VA_ARGS__); \
+ goto fail; \
+ } G_STMT_END
+
+ part = tp_message_peek (message, 0);
+
+ if (tp_asv_lookup (part, "message-type") != NULL)
+ {
+ if (tp_asv_get_uint32 (part, "message-type", NULL) !=
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL)
+ INVALID_ARGUMENT ("invalid message type");
+ }
+
+ n_parts = tp_message_count_parts (message);
+
+ if (n_parts != 2)
+ INVALID_ARGUMENT ("message must contain exactly 1 part, not %u",
+ (n_parts - 1));
+
+ part = tp_message_peek (message, 1);
+ content_type = tp_asv_get_string (part, "content-type");
+ text = tp_asv_get_string (part, "content");
+
+ if (content_type == NULL || tp_strdiff (content_type, "text/plain"))
+ INVALID_ARGUMENT ("message must be text/plain");
+
+ if (text == NULL)
+ INVALID_ARGUMENT ("content must be a UTF-8 string");
+
+ /* Okay, it's valid. Let's send it. */
+
+ msg_nh = rakia_base_connection_create_handle (priv->conn, priv->handle);
+ if (msg_nh == NULL)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Request creation failed");
+ goto fail;
+ }
+
+ rakia_event_target_attach (msg_nh, (GObject *) self);
+
+ nua_message(msg_nh,
+ SIPTAG_CONTENT_TYPE_STR("text/plain"),
+ SIPTAG_PAYLOAD_STR(text),
+ TAG_END());
+
+ msg = _rakia_text_pending_new0 ();
+ msg->nh = msg_nh;
+ msg->token = g_strdup_printf ("%u", priv->sent_id++);
+ msg->flags = flags;
+
+ tp_message_mixin_sent (object, message, flags, msg->token, NULL);
+ g_queue_push_tail (priv->sending_messages, msg);
+
+ DEBUG ("message queued for delivery");
+ return;
+
+fail:
+ g_assert (error != NULL);
+ tp_message_mixin_sent (object, message, 0, NULL, error);
+ g_error_free (error);
+}
+
+static gchar *
+text_send_error_to_dbus_error (TpChannelTextSendError error)
+{
+ switch (error)
+ {
+ case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
+ return TP_ERROR_STR_OFFLINE;
+
+ case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
+ return TP_ERROR_STR_INVALID_HANDLE;
+
+ case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
+ return TP_ERROR_STR_PERMISSION_DENIED;
+
+ case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
+ return TP_ERROR_STR_INVALID_ARGUMENT;
+
+ case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
+ return TP_ERROR_STR_NOT_IMPLEMENTED;
+
+ case TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN:
+ default:
+ return TP_ERROR_STR_INVALID_ARGUMENT;
+ }
+
+ return NULL;
+}
+
+static void
+delivery_report (RakiaTextChannel *self,
+ const gchar *token,
+ TpDeliveryStatus status,
+ TpChannelTextSendError send_error)
+{
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ TpBaseConnection *base_conn;
+ TpMessage *msg;
+
+ base_conn = (TpBaseConnection *) priv->conn;
+
+ msg = tp_message_new (base_conn, 1, 1);
+
+ tp_message_set_handle (msg, 0, "message-sender", TP_HANDLE_TYPE_CONTACT,
+ priv->handle);
+
+ tp_message_set_uint32 (msg, 0, "message-type",
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_DELIVERY_REPORT);
+
+ tp_message_set_string (msg, 0, "delivery-token", token);
+ tp_message_set_uint32 (msg, 0, "delivery-status", status);
+
+ if (status == TP_DELIVERY_STATUS_TEMPORARILY_FAILED ||
+ status == TP_DELIVERY_STATUS_PERMANENTLY_FAILED)
+ {
+ if (send_error != TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN)
+ tp_message_set_uint32 (msg, 0, "delivery-error", send_error);
+
+ tp_message_set_string (msg, 0, "delivery-dbus-error",
+ text_send_error_to_dbus_error (send_error));
+ }
+
+ tp_message_mixin_take_received((GObject *) self, msg);
+}
+
+static gboolean
+rakia_text_channel_nua_r_message_cb (RakiaTextChannel *self,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ gpointer foo)
+{
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (self);
+ RakiaTextPendingMessage *msg;
+ TpHandleRepoIface *contact_repo;
+ TpChannelTextSendError send_error;
+ GList *node;
+
+ /* ignore provisional responses */
+ if (ev->status < 200)
+ return TRUE;
+
+ node = g_queue_find_custom (priv->sending_messages,
+ ev->nua_handle,
+ rakia_acknowledged_messages_compare);
+
+ /* Shouldn't happen... */
+ if (node == NULL)
+ {
+ WARNING ("message pending sent acknowledgement not found");
+ return FALSE;
+ }
+
+ msg = (RakiaTextPendingMessage *)node->data;
+
+ g_assert (msg != NULL);
+
+ /* FIXME: generate a delivery report */
+ if (ev->status >= 200 && ev->status < 300)
+ {
+ DEBUG ("message delivered");
+
+ if (msg->flags & TP_MESSAGE_SENDING_FLAG_REPORT_DELIVERY)
+ {
+ DEBUG ("Sending delivery report");
+ delivery_report (self, msg->token, TP_DELIVERY_STATUS_DELIVERED, 0);
+ }
+ }
+ else
+ {
+ switch (ev->status)
+ {
+ case 401:
+ case 403:
+ case 407:
+ case 603:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED;
+ break;
+ case 604:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT;
+ break;
+ case 405:
+ case 406:
+ case 415:
+ case 416:
+ case 488:
+ case 501:
+ case 505:
+ case 606:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED;
+ break;
+ case 410:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT /* TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE? */;
+ break;
+ case 404:
+ case 480:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE;
+ break;
+ case 413:
+ case 513:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG;
+ break;
+ default:
+ send_error = TP_CHANNEL_TEXT_SEND_ERROR_UNKNOWN;
+ }
+
+ delivery_report (self, msg->token, TP_DELIVERY_STATUS_PERMANENTLY_FAILED,
+ send_error);
+ }
+
+ g_queue_remove(priv->sending_messages, msg);
+
+ contact_repo = tp_base_connection_get_handles (
+ (TpBaseConnection *)(priv->conn), TP_HANDLE_TYPE_CONTACT);
+
+ rakia_text_pending_free(msg, contact_repo);
+
+ return TRUE;
+}
+
+void rakia_text_channel_receive(RakiaTextChannel *chan,
+ const sip_t *sip,
+ TpHandle sender,
+ const char *text,
+ gsize len)
+{
+ RakiaTextChannelPrivate *priv = TPSIP_TEXT_CHANNEL_GET_PRIVATE (chan);
+ TpMessage *msg;
+ TpBaseConnection *base_conn;
+ sip_call_id_t *hdr_call_id;
+ sip_cseq_t *hdr_cseq;
+ sip_date_t *hdr_date_sent;
+
+ base_conn = (TpBaseConnection *) priv->conn;
+ msg = tp_message_new (base_conn, 2, 2);
+
+ DEBUG ("Received message from contact %u: %s", sender, text);
+
+ /* Header */
+ tp_message_set_handle (msg, 0, "message-sender", TP_HANDLE_TYPE_CONTACT,
+ sender);
+ tp_message_set_int64 (msg, 0, "message-received", time (NULL));
+
+ hdr_date_sent = sip_date (sip);
+ if (hdr_date_sent != NULL)
+ {
+ tp_message_set_int64 (msg, 0, "message-sent",
+ hdr_date_sent->d_time - SU_TIME_EPOCH);
+ }
+
+ /* Create a message token out of globally unique SIP header values.
+ * As MESSAGE requests can be sent within a dialog, we have to append
+ * the Call-ID value with the sequence number in CSeq. */
+ hdr_call_id = sip_call_id (sip);
+ hdr_cseq = sip_cseq (sip);
+ if (hdr_call_id != NULL && hdr_cseq != NULL)
+ {
+ tp_message_set_string_printf (msg, 0, "message-token", "%s;cseq=%u",
+ hdr_call_id->i_id, (guint) hdr_cseq->cs_seq);
+ }
+
+ /* Body */
+ tp_message_set_string (msg, 1, "content-type", "text/plain");
+ tp_message_set_string (msg, 1, "content", text);
+
+ tp_message_mixin_take_received (G_OBJECT (chan), msg);
+}
+
+static void
+destroyable_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcChannelInterfaceDestroyableClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_interface_destroyable_implement_##x (\
+ klass, rakia_text_channel_##x)
+ IMPLEMENT(destroy);
+#undef IMPLEMENT
+}
+
+static void
+channel_iface_init(gpointer g_iface, gpointer iface_data)
+{
+ TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (\
+ klass, rakia_text_channel_##x)
+ IMPLEMENT(close);
+ IMPLEMENT(get_channel_type);
+ IMPLEMENT(get_handle);
+ IMPLEMENT(get_interfaces);
+#undef IMPLEMENT
+}
diff --git a/rakia/text-channel.h b/rakia/text-channel.h
new file mode 100644
index 0000000..e74eb1c
--- /dev/null
+++ b/rakia/text-channel.h
@@ -0,0 +1,72 @@
+/*
+ * sip-text-channel.h - Header for RakiaTextChannel
+ * Copyright (C) 2005-2008 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 __TPSIP_TEXT_CHANNEL_H__
+#define __TPSIP_TEXT_CHANNEL_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/handle.h>
+#include <telepathy-glib/message-mixin.h>
+
+#include <rakia/sofia-decls.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaTextChannel RakiaTextChannel;
+typedef struct _RakiaTextChannelClass RakiaTextChannelClass;
+
+struct _RakiaTextChannelClass {
+ GObjectClass parent_class;
+
+ TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _RakiaTextChannel {
+ GObject parent;
+
+ TpMessageMixin message_mixin;
+};
+
+GType rakia_text_channel_get_type(void);
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_TEXT_CHANNEL \
+ (rakia_text_channel_get_type())
+#define TPSIP_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_TEXT_CHANNEL, RakiaTextChannel))
+#define TPSIP_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_TEXT_CHANNEL, RakiaTextChannelClass))
+#define TPSIP_IS_TEXT_CHANNEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_TEXT_CHANNEL))
+#define TPSIP_IS_TEXT_CHANNEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_TEXT_CHANNEL))
+#define TPSIP_TEXT_CHANNEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_TYPE_TEXT_CHANNEL, RakiaTextChannelClass))
+
+void rakia_text_channel_receive (RakiaTextChannel *obj,
+ const sip_t *sip,
+ TpHandle sender,
+ const char *text,
+ gsize len);
+
+G_END_DECLS
+
+#endif /* #ifndef __TPSIP_TEXT_CHANNEL_H__*/
diff --git a/rakia/text-manager.c b/rakia/text-manager.c
new file mode 100644
index 0000000..731694f
--- /dev/null
+++ b/rakia/text-manager.c
@@ -0,0 +1,642 @@
+/*
+ * text-manager.c - Text channel manager for SIP
+ * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2011 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
+ */
+
+#include "config.h"
+
+#include "rakia/text-manager.h"
+
+#include <string.h>
+
+#include <telepathy-glib/channel-manager.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/interfaces.h>
+
+#include "rakia/text-channel.h"
+#include "rakia/base-connection.h"
+#include "rakia/handles.h"
+
+#include <sofia-sip/msg_header.h>
+#include <sofia-sip/sip_tag.h>
+#include <sofia-sip/sip_status.h>
+
+#define DEBUG_FLAG TPSIP_DEBUG_IM
+#include "rakia/debug.h"
+
+
+static void channel_manager_iface_init (gpointer g_iface, gpointer iface_data);
+static void connection_status_changed_cb (TpBaseConnection *conn,
+ guint status, guint reason, RakiaTextManager *self);
+static void rakia_text_manager_close_all (RakiaTextManager *self);
+
+G_DEFINE_TYPE_WITH_CODE (RakiaTextManager, rakia_text_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
+ channel_manager_iface_init))
+
+enum
+{
+ PROP_CONNECTION = 1,
+ LAST_PROPERTY
+};
+
+typedef struct _RakiaTextManagerPrivate RakiaTextManagerPrivate;
+struct _RakiaTextManagerPrivate
+{
+ TpBaseConnection *conn;
+ /* guint handle => RakiaTextChannel *channel */
+ GHashTable *channels;
+
+ gulong status_changed_id;
+ gulong message_received_id;
+
+ gboolean dispose_has_run;
+};
+
+#define TPSIP_TEXT_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TPSIP_TYPE_TEXT_MANAGER, RakiaTextManagerPrivate))
+
+static void
+rakia_text_manager_init (RakiaTextManager *fac)
+{
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+
+ priv->conn = NULL;
+ priv->channels = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL, g_object_unref);
+
+ priv->dispose_has_run = FALSE;
+}
+
+static void
+rakia_text_manager_constructed (GObject *object)
+{
+ RakiaTextManager *fac = TPSIP_TEXT_MANAGER (object);
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+ GObjectClass *parent_object_class =
+ G_OBJECT_CLASS (rakia_text_manager_parent_class);
+
+ if (parent_object_class->constructed != NULL)
+ parent_object_class->constructed (object);
+
+ priv->status_changed_id = g_signal_connect (priv->conn,
+ "status-changed", (GCallback) connection_status_changed_cb, object);
+}
+
+static void
+rakia_text_manager_dispose (GObject *object)
+{
+ RakiaTextManager *fac = TPSIP_TEXT_MANAGER (object);
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+
+ if (priv->dispose_has_run)
+ return;
+
+ priv->dispose_has_run = TRUE;
+
+ rakia_text_manager_close_all (fac);
+ g_assert (priv->channels == NULL);
+
+ if (G_OBJECT_CLASS (rakia_text_manager_parent_class)->dispose)
+ G_OBJECT_CLASS (rakia_text_manager_parent_class)->dispose (object);
+}
+
+static void
+rakia_text_manager_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaTextManager *fac = TPSIP_TEXT_MANAGER (object);
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ g_value_set_object (value, priv->conn);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_text_manager_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ RakiaTextManager *fac = TPSIP_TEXT_MANAGER (object);
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+
+ switch (property_id) {
+ case PROP_CONNECTION:
+ priv->conn = TP_BASE_CONNECTION (g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+rakia_text_manager_class_init (RakiaTextManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ g_type_class_add_private (klass, sizeof (RakiaTextManagerPrivate));
+
+ object_class->constructed = rakia_text_manager_constructed;
+ object_class->get_property = rakia_text_manager_get_property;
+ object_class->set_property = rakia_text_manager_set_property;
+ object_class->dispose = rakia_text_manager_dispose;
+
+ param_spec = g_param_spec_object ("connection",
+ "RakiaBaseConnection object",
+ "SIP connection that owns this text channel manager",
+ TPSIP_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);
+}
+
+static void
+rakia_text_manager_close_all (RakiaTextManager *fac)
+{
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+ GHashTable *channels;
+
+ if (priv->status_changed_id != 0)
+ {
+ g_signal_handler_disconnect (priv->conn,
+ priv->status_changed_id);
+ priv->status_changed_id = 0;
+ }
+
+ if (!priv->channels)
+ return;
+
+ channels = priv->channels;
+ priv->channels = NULL;
+
+ g_hash_table_destroy (channels);
+}
+
+struct _ForeachData
+{
+ TpExportableChannelFunc func;
+ gpointer user_data;
+};
+
+static void
+_foreach_slave (gpointer key, gpointer value, gpointer user_data)
+{
+ struct _ForeachData *data = (struct _ForeachData *)user_data;
+ TpExportableChannel *chan = TP_EXPORTABLE_CHANNEL (value);
+
+ data->func (chan, data->user_data);
+}
+
+static void
+rakia_text_manager_foreach_channel (TpChannelManager *manager,
+ TpExportableChannelFunc func,
+ gpointer user_data)
+{
+ RakiaTextManager *fac = TPSIP_TEXT_MANAGER (manager);
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+ struct _ForeachData data;
+
+ data.func = func;
+ data.user_data = user_data;
+
+ g_hash_table_foreach (priv->channels, _foreach_slave, &data);
+}
+
+/**
+ * text_channel_closed_cb:
+ *
+ * Signal callback for when a text channel is closed. Removes the references
+ * that #RakiaChannelManager holds to them.
+ */
+static void
+channel_closed (RakiaTextChannel *chan, gpointer user_data)
+{
+ RakiaTextManager *self = TPSIP_TEXT_MANAGER (user_data);
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (self);
+ TpHandle contact_handle;
+ gboolean really_destroyed = TRUE;
+
+ tp_channel_manager_emit_channel_closed_for_object (self,
+ (TpExportableChannel *) chan);
+
+ if (priv->channels == NULL)
+ return;
+
+ g_object_get (chan,
+ "handle", &contact_handle,
+ "channel-destroyed", &really_destroyed,
+ NULL);
+
+ if (really_destroyed)
+ {
+ DEBUG ("removing text channel with handle %u", contact_handle);
+ g_hash_table_remove (priv->channels, GINT_TO_POINTER (contact_handle));
+ }
+ else
+ {
+ DEBUG ("reopening channel with handle %u due to pending messages",
+ contact_handle);
+ tp_channel_manager_emit_new_channel (self,
+ (TpExportableChannel *) chan, NULL);
+ }
+}
+
+/**
+ * new_text_channel
+ *
+ * Creates a new empty RakiaTextChannel.
+ */
+static RakiaTextChannel *
+rakia_text_manager_new_channel (RakiaTextManager *fac,
+ TpHandle handle,
+ TpHandle initiator,
+ gpointer request_token)
+{
+ RakiaTextManagerPrivate *priv;
+ RakiaTextChannel *chan;
+ gchar *object_path;
+ TpBaseConnection *conn;
+ GSList *request_tokens;
+
+ priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+ conn = priv->conn;
+
+ object_path = g_strdup_printf ("%s/TextChannel%u",
+ conn->object_path, handle);
+
+ DEBUG ("object path %s", object_path);
+
+ chan = g_object_new (TPSIP_TYPE_TEXT_CHANNEL,
+ "connection", priv->conn,
+ "object-path", object_path,
+ "handle", handle,
+ "initiator-handle", initiator,
+ NULL);
+
+ g_free (object_path);
+
+ g_signal_connect (chan, "closed", (GCallback) channel_closed, fac);
+
+ g_hash_table_insert (priv->channels, GUINT_TO_POINTER (handle), chan);
+
+ if (request_token != NULL)
+ request_tokens = g_slist_prepend (NULL, request_token);
+ else
+ request_tokens = NULL;
+
+ tp_channel_manager_emit_new_channel (fac,
+ (TpExportableChannel *) chan, request_tokens);
+
+ g_slist_free (request_tokens);
+
+ return chan;
+}
+
+
+static const gchar * const text_channel_fixed_properties[] = {
+ TP_IFACE_CHANNEL ".ChannelType",
+ TP_IFACE_CHANNEL ".TargetHandleType",
+ NULL
+};
+
+static const gchar * const text_channel_allowed_properties[] = {
+ TP_IFACE_CHANNEL ".TargetHandle",
+ TP_IFACE_CHANNEL ".TargetID",
+ NULL
+};
+
+static void
+rakia_text_manager_type_foreach_channel_class (GType type,
+ TpChannelManagerTypeChannelClassFunc func,
+ gpointer user_data)
+{
+ GHashTable *table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) tp_g_value_slice_free);
+ GValue *value;
+
+ value = tp_g_value_slice_new (G_TYPE_STRING);
+ g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+ g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[0],
+ value);
+
+ value = tp_g_value_slice_new (G_TYPE_UINT);
+ g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+ g_hash_table_insert (table, (gchar *) text_channel_fixed_properties[1],
+ value);
+
+ func (type, table, text_channel_allowed_properties, user_data);
+
+ g_hash_table_destroy (table);
+}
+
+
+static gboolean
+rakia_text_manager_requestotron (RakiaTextManager *self,
+ gpointer request_token,
+ GHashTable *request_properties,
+ gboolean require_new)
+{
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (self);
+ TpBaseConnection *base_conn = (TpBaseConnection *) priv->conn;
+ TpHandle handle;
+ GError *error = NULL;
+ TpExportableChannel *channel;
+
+ if (tp_strdiff (tp_asv_get_string (request_properties,
+ TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_TEXT))
+ return FALSE;
+
+ if (tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandleType", NULL) != TP_HANDLE_TYPE_CONTACT)
+ return FALSE;
+
+ /* validity already checked by TpBaseConnection */
+ handle = tp_asv_get_uint32 (request_properties,
+ TP_IFACE_CHANNEL ".TargetHandle", NULL);
+ g_assert (handle != 0);
+
+ if (tp_channel_manager_asv_has_unknown_properties (request_properties,
+ text_channel_fixed_properties, text_channel_allowed_properties,
+ &error))
+ goto error;
+
+ channel = g_hash_table_lookup (priv->channels,
+ GUINT_TO_POINTER (handle));
+
+ if (channel == NULL)
+ {
+ rakia_text_manager_new_channel (self,
+ handle, base_conn->self_handle, request_token);
+ return TRUE;
+ }
+
+ if (require_new)
+ {
+ g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
+ "Already chatting with contact #%u in another channel", handle);
+ goto error;
+ }
+
+ tp_channel_manager_emit_request_already_satisfied (self, request_token,
+ channel);
+ return TRUE;
+
+error:
+ tp_channel_manager_emit_request_failed (self, request_token,
+ error->domain, error->code, error->message);
+ g_error_free (error);
+ return TRUE;
+}
+
+
+static gboolean
+rakia_text_manager_create_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ RakiaTextManager *self = TPSIP_TEXT_MANAGER (manager);
+
+ return rakia_text_manager_requestotron (self, request_token,
+ request_properties, TRUE);
+}
+
+
+static gboolean
+rakia_text_manager_request_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ RakiaTextManager *self = TPSIP_TEXT_MANAGER (manager);
+
+ return rakia_text_manager_requestotron (self, request_token,
+ request_properties, FALSE);
+}
+
+
+static gboolean
+rakia_text_manager_ensure_channel (TpChannelManager *manager,
+ gpointer request_token,
+ GHashTable *request_properties)
+{
+ RakiaTextManager *self = TPSIP_TEXT_MANAGER (manager);
+
+ return rakia_text_manager_requestotron (self, request_token,
+ request_properties, FALSE);
+}
+
+static inline RakiaTextChannel *
+rakia_text_manager_lookup_channel (RakiaTextManager *fac,
+ TpHandle handle)
+{
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (fac);
+
+ return g_hash_table_lookup (priv->channels,
+ GUINT_TO_POINTER(handle));
+}
+
+static gboolean
+rakia_nua_i_message_cb (TpBaseConnection *conn,
+ const RakiaNuaEvent *ev,
+ tagi_t tags[],
+ RakiaTextManager *fac)
+{
+ RakiaTextChannel *channel;
+ TpHandle handle;
+ const sip_t *sip = ev->sip;
+ const char *text = "";
+ gsize len = 0;
+ char *allocated_text = NULL;
+
+ /* Block anything else except text/plain messages (like isComposings) */
+ if (sip->sip_content_type
+ && (g_ascii_strcasecmp ("text/plain", sip->sip_content_type->c_type)))
+ {
+ nua_respond (ev->nua_handle,
+ SIP_415_UNSUPPORTED_MEDIA,
+ SIPTAG_ACCEPT_STR("text/plain"),
+ NUTAG_WITH_THIS(ev->nua),
+ TAG_END());
+ goto end;
+ }
+
+ /* If there is some text, assure it's in UTF-8 encoding */
+ if (sip->sip_payload && sip->sip_payload->pl_len > 0)
+ {
+ const char *charset = NULL;
+ if (sip->sip_content_type)
+ {
+ charset = msg_header_find_param (
+ (msg_common_t *) sip->sip_content_type, "charset");
+ }
+
+ /* Default charset is UTF-8, we only need to convert if it's a different one */
+ if (charset && g_ascii_strcasecmp (charset, "UTF-8"))
+ {
+ GError *error;
+ gsize in_len;
+ allocated_text = g_convert (
+ sip->sip_payload->pl_data, sip->sip_payload->pl_len,
+ "UTF-8", charset, &in_len, &len, &error);
+
+ if (allocated_text == NULL)
+ {
+ gint status;
+ const char *message = NULL;
+
+ MESSAGE ("character set conversion failed for the message body: %s", error->message);
+
+ if (error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+ {
+ status = 400;
+ message = "Invalid character sequence in the message body";
+ }
+ else
+ {
+ status = 500;
+ message = "Character set conversion failed for the message body";
+ }
+ nua_respond (ev->nua_handle,
+ status, message,
+ NUTAG_WITH_THIS(ev->nua),
+ TAG_END());
+
+ g_error_free (error);
+ goto end;
+ }
+
+ text = allocated_text;
+
+ if (in_len != sip->sip_payload->pl_len)
+ {
+ nua_respond (ev->nua_handle,
+ 400, "Incomplete character sequence at the "
+ "end of the message body",
+ NUTAG_WITH_THIS(ev->nua),
+ TAG_END());
+ goto end;
+ }
+ }
+ else
+ {
+ if (!g_utf8_validate (sip->sip_payload->pl_data,
+ sip->sip_payload->pl_len,
+ NULL))
+ {
+ nua_respond (ev->nua_handle,
+ 400, "Invalid character sequence in the message body",
+ NUTAG_WITH_THIS(ev->nua),
+ TAG_END());
+ goto end;
+ }
+ text = sip->sip_payload->pl_data;
+ len = (gsize) sip->sip_payload->pl_len;
+ }
+ }
+
+ handle = rakia_handle_by_requestor (conn, sip);
+
+ if (!handle)
+ {
+ nua_respond (ev->nua_handle,
+ 400, "Invalid From address",
+ NUTAG_WITH_THIS(ev->nua),
+ TAG_END());
+ goto end;
+ }
+
+ /* Send the final response immediately as recommended by RFC 3428 */
+ nua_respond (ev->nua_handle,
+ SIP_200_OK,
+ NUTAG_WITH_THIS(ev->nua),
+ TAG_END());
+
+ DEBUG("Got incoming message from <%s>",
+ rakia_handle_inspect (conn, handle));
+
+ channel = rakia_text_manager_lookup_channel (fac, handle);
+
+ if (!channel)
+ channel = rakia_text_manager_new_channel (fac,
+ handle, handle, NULL);
+
+ rakia_text_channel_receive (channel,
+ sip, handle, text, len);
+
+ rakia_handle_unref (conn, handle);
+
+end:
+ g_free (allocated_text);
+
+ return TRUE;
+}
+
+static void
+connection_status_changed_cb (TpBaseConnection *conn,
+ guint status,
+ guint reason,
+ RakiaTextManager *self)
+{
+ RakiaTextManagerPrivate *priv = TPSIP_TEXT_MANAGER_GET_PRIVATE (self);
+
+ switch (status)
+ {
+ case TP_CONNECTION_STATUS_CONNECTING:
+
+ priv->message_received_id = g_signal_connect (conn,
+ "nua-event::nua_i_message",
+ G_CALLBACK (rakia_nua_i_message_cb),
+ self);
+
+ break;
+ case TP_CONNECTION_STATUS_DISCONNECTED:
+ rakia_text_manager_close_all (self);
+
+ if (priv->message_received_id != 0)
+ {
+ g_signal_handler_disconnect (conn, priv->message_received_id);
+ priv->message_received_id = 0;
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+channel_manager_iface_init (gpointer g_iface, gpointer iface_data)
+{
+ TpChannelManagerIface *iface = g_iface;
+
+ iface->foreach_channel = rakia_text_manager_foreach_channel;
+ iface->type_foreach_channel_class =
+ rakia_text_manager_type_foreach_channel_class;
+ iface->create_channel = rakia_text_manager_create_channel;
+ iface->request_channel = rakia_text_manager_request_channel;
+ iface->ensure_channel = rakia_text_manager_ensure_channel;
+}
diff --git a/rakia/text-manager.h b/rakia/text-manager.h
new file mode 100644
index 0000000..fc30edf
--- /dev/null
+++ b/rakia/text-manager.h
@@ -0,0 +1,57 @@
+/*
+ * rakia/text-manager.h - Text channel manager for SIP
+ * Copyright (C) 2007 Collabora Ltd.
+ * Copyright (C) 2007-2011 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 __TPSIP_TEXT_MANAGER_H__
+#define __TPSIP_TEXT_MANAGER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RakiaTextManager RakiaTextManager;
+typedef struct _RakiaTextManagerClass RakiaTextManagerClass;
+
+struct _RakiaTextManagerClass {
+ GObjectClass parent_class;
+};
+
+struct _RakiaTextManager {
+ GObject parent;
+};
+
+GType rakia_text_manager_get_type(void);
+
+/* TYPE MACROS */
+#define TPSIP_TYPE_TEXT_MANAGER \
+ (rakia_text_manager_get_type())
+#define TPSIP_TEXT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), TPSIP_TYPE_TEXT_MANAGER, RakiaTextManager))
+#define TPSIP_TEXT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), TPSIP_TYPE_TEXT_MANAGER, RakiaTextManagerClass))
+#define TPSIP_IS_TEXT_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), TPSIP_TYPE_TEXT_MANAGER))
+#define TPSIP_IS_TEXT_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), TPSIP_TYPE_TEXT_MANAGER))
+#define TPSIP_TEXT_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TPSIP_TYPE_TEXT_MANAGER, RakiaTextManagerClass))
+
+G_END_DECLS
+
+#endif
diff --git a/rakia/util.c b/rakia/util.c
new file mode 100644
index 0000000..164ad1e
--- /dev/null
+++ b/rakia/util.c
@@ -0,0 +1,161 @@
+/*
+ * util.c - implementation of Telepathy-SofiaSIP utilities
+ * Copyright (C) 2009, 2010 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 "util.h"
+
+#include <string.h>
+
+gchar const *
+rakia_version_string (void)
+{
+ return "Telepathy-SofiaSIP/" TELEPATHY_SIP_VERSION;
+}
+
+static const guchar escape_table[256] =
+ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1,
+ /* Control characters except LF and CR.
+ * NOTE: null character is intentionally flagged */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 - 0x1f */
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x22 == '"' */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, /* 0x5c == '\\' */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* 0x7f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+
+/**
+ * rakia_string_append_quoted:
+ * @buf: a #GString to append the quoted text to
+ * @text: text to append as a quoted string
+ *
+ * Appends to @buf the content of @text as a quoted string accordingly to SIP
+ * or MIME syntax.
+ */
+void
+rakia_string_append_quoted (GString *buf, const gchar *text)
+{
+ const gchar *p;
+ gchar quoted_pair[2] = { '\\', };
+
+ g_string_append_c (buf, '"');
+
+ p = text;
+ while (*p)
+ {
+ const gchar *q;
+
+ /* Get the following text span to append verbatim */
+ for (q = p; !escape_table[(guchar) *q]; ++q)
+ { /* do nothing */ }
+ g_string_append_len (buf, p, q - p);
+
+ if (*q == '\0')
+ break;
+
+ quoted_pair[1] = *q;
+ g_string_append_len (buf, quoted_pair, 2);
+
+ p = q + 1;
+ }
+
+ g_string_append_c (buf, '"');
+}
+
+/**
+ * rakia_quote_string:
+ * @src: the source string
+ *
+ * Formats the content of @text as a quoted string accordingly to SIP
+ * or MIME syntax.
+ *
+ * Returns: a newly allocated quoted string.
+ * The string is to be freed with g_free().
+ */
+gchar *
+rakia_quote_string (const gchar *src)
+{
+ GString *buf;
+
+ buf = g_string_sized_new (2);
+
+ rakia_string_append_quoted (buf, src);
+
+ return g_string_free (buf, FALSE);
+}
+
+/**
+ * rakia_unquote_string:
+ * @src: the quoted string, including the encompassing double quotes
+ * @len: length of the string @src in bytes,
+ * or -1 if the string is null-terminated
+ *
+ * Extracts text out of a quoted string literal accordingly to SIP
+ * or MIME syntax, unescaping characters preceded by backspaces.
+ *
+ * Returns: a newly allocated unquoted string.
+ * The string is to be freed with g_free().
+ */
+gchar *
+rakia_unquote_string (const gchar *src, gssize len)
+{
+ gchar *res;
+ gchar *p;
+ gsize i;
+
+ g_return_val_if_fail (src != NULL, NULL);
+
+ if (len < 0)
+ len = strlen (src);
+
+ g_return_val_if_fail (len >= 2, NULL);
+ g_return_val_if_fail (src[0] == '"' && src[len - 1] == '"', NULL);
+
+ res = g_malloc (len - 2 + 1);
+
+ p = res;
+
+ for (i = 1; i < len - 1; ++i)
+ {
+ if (src[i] == '\\')
+ {
+ ++i;
+ if (G_UNLIKELY (i == len - 1))
+ {
+ g_critical ("the quoted string is missing the termination quote, improperly escaped?");
+ return NULL;
+ }
+ }
+ *p++ = src[i];
+ }
+
+ *p = '\0';
+
+ return res;
+}
diff --git a/rakia/util.h b/rakia/util.h
new file mode 100644
index 0000000..c4baef4
--- /dev/null
+++ b/rakia/util.h
@@ -0,0 +1,38 @@
+/*
+ * util.h - declarations for Telepathy-SofiaSIP utilities
+ * Copyright (C) 2009 Nokia Corporation
+ * @author Mikhail Zabaluev <mikhail.zabaluev@nokia.com>
+ *
+ * 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 TPSIP_UTIL_H_
+#define TPSIP_UTIL_H_
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar * rakia_quote_string (const gchar *src);
+
+gchar * rakia_unquote_string (const gchar *src, gssize len);
+
+void rakia_string_append_quoted (GString *buf, const gchar *text);
+
+gchar const *rakia_version_string ();
+
+G_END_DECLS
+
+#endif /* !TPSIP_UTIL_H_ */