libpurple/protocols/jabber/jingle/rtp.c

Tue, 04 Feb 2020 03:05:18 +0000

author
Gary Kramlich <grim@reaperworld.com>
date
Tue, 04 Feb 2020 03:05:18 +0000
changeset 40275
bba8505129d5
parent 40264
d253f767f6cc
parent 40270
88bc25476614
child 40350
72271baf92bc
permissions
-rw-r--r--

Merged in fbellet/pidgin/port-changes-from-branch-2.x.y-to-default (pull request #632)

Port changes from branch 2.x.y to default

Approved-by: Eion Robb <eionrobb@gmail.com>
Approved-by: John Bailey <rekkanoryo@rekkanoryo.org>
Approved-by: Gary Kramlich <grim@reaperworld.com>

/**
 * @file rtp.c
 *
 * purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */

#include "internal.h"

#ifdef USE_VV

#include "jabber.h"
#include "jingle.h"
#include "google/google_p2p.h"
#include "media.h"
#include "mediamanager.h"
#include "iceudp.h"
#include "rawudp.h"
#include "rtp.h"
#include "session.h"
#include "debug.h"

#include <string.h>

struct _JingleRtp
{
	JingleContent parent;
};

typedef struct
{
	gchar *media_type;
	gchar *ssrc;
} JingleRtpPrivate;

static JingleContent *jingle_rtp_parse_internal(PurpleXmlNode *rtp);
static PurpleXmlNode *jingle_rtp_to_xml_internal(JingleContent *rtp, PurpleXmlNode *content, JingleActionType action);
static void jingle_rtp_handle_action_internal(JingleContent *content, PurpleXmlNode *jingle, JingleActionType action);

static PurpleMedia *jingle_rtp_get_media(JingleSession *session);

enum {
	PROP_0,
	PROP_MEDIA_TYPE,
	PROP_SSRC,
	PROP_LAST
};

static GParamSpec *properties[PROP_LAST];

G_DEFINE_DYNAMIC_TYPE_EXTENDED(
	JingleRtp,
	jingle_rtp,
	JINGLE_TYPE_CONTENT,
	0,
	G_ADD_PRIVATE_DYNAMIC(JingleRtp)
);

/******************************************************************************
 * Helpers
 *****************************************************************************/
static JingleTransport *
jingle_rtp_candidates_to_transport(JingleSession *session, const gchar *type, guint generation, GList *candidates)
{
	JingleTransport *transport;

	transport = jingle_transport_create(type);
	if (!transport)
		return NULL;

	for (; candidates; candidates = g_list_next(candidates)) {
		PurpleMediaCandidate *candidate = candidates->data;
		if (purple_media_candidate_get_protocol(candidate) ==
				PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
			gchar *id = jabber_get_next_id(jingle_session_get_js(session));
			jingle_transport_add_local_candidate(transport, id, generation, candidate);
			g_free(id);
		}
	}

	return transport;
}

static void jingle_rtp_ready(JingleSession *session);

static void
jingle_rtp_candidates_prepared_cb(PurpleMedia *media,
		gchar *sid, gchar *name, JingleSession *session)
{
	JingleContent *content = jingle_session_find_content(
			session, sid, NULL);
	JingleTransport *oldtransport, *transport;
	GList *candidates;

	purple_debug_info("jingle-rtp", "jingle_rtp_candidates_prepared_cb\n");

	if (content == NULL) {
		purple_debug_error("jingle-rtp",
				"jingle_rtp_candidates_prepared_cb: "
				"Can't find session %s\n", sid);
		return;
	}

	oldtransport = jingle_content_get_transport(content);
	candidates = purple_media_get_local_candidates(media, sid, name);
	transport = jingle_rtp_candidates_to_transport(
			session, jingle_transport_get_transport_type(oldtransport),
			0, candidates);

	purple_media_candidate_list_free(candidates);
	g_object_unref(oldtransport);

	jingle_content_set_pending_transport(content, transport);
	jingle_content_accept_transport(content);

	jingle_rtp_ready(session);
}

static void
jingle_rtp_codecs_changed_cb(PurpleMedia *media, gchar *sid,
		JingleSession *session)
{
	purple_debug_info("jingle-rtp", "jingle_rtp_codecs_changed_cb: "
			"session_id: %s jingle_session: %p\n", sid, session);
	jingle_rtp_ready(session);
}

static void
jingle_rtp_new_candidate_cb(PurpleMedia *media, gchar *sid, gchar *name, PurpleMediaCandidate *candidate, JingleSession *session)
{
	JingleContent *content = jingle_session_find_content(session, sid, NULL);
	JingleTransport *transport;
	gchar *id;

	purple_debug_info("jingle-rtp", "jingle_rtp_new_candidate_cb\n");

	if (content == NULL) {
		purple_debug_error("jingle-rtp",
				"jingle_rtp_new_candidate_cb: "
				"Can't find session %s\n", sid);
		return;
	}

	transport = jingle_content_get_transport(content);

	id = jabber_get_next_id(jingle_session_get_js(session));
	jingle_transport_add_local_candidate(transport, id, 1, candidate);
	g_free(id);

	g_object_unref(transport);

	jabber_iq_send(jingle_session_to_packet(session, JINGLE_TRANSPORT_INFO));
}

static void
jingle_rtp_initiate_ack_cb(JabberStream *js, const char *from,
                           JabberIqType type, const char *id,
                           PurpleXmlNode *packet, gpointer data)
{
	JingleSession *session = data;

	if (type == JABBER_IQ_ERROR || purple_xmlnode_get_child(packet, "error")) {
		purple_media_end(jingle_rtp_get_media(session), NULL, NULL);
		g_object_unref(session);
		return;
	}
}

static void
jingle_rtp_state_changed_cb(PurpleMedia *media, PurpleMediaState state,
		gchar *sid, gchar *name, JingleSession *session)
{
	purple_debug_info("jingle-rtp", "state-changed: state %d "
			"id: %s name: %s\n", state, sid ? sid : "(null)",
			name ? name : "(null)");
}

static void
jingle_rtp_stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
		gchar *sid, gchar *name, gboolean local,
		JingleSession *session)
{
	purple_debug_info("jingle-rtp", "stream-info: type %d "
			"id: %s name: %s\n", type, sid ? sid : "(null)",
			name ? name : "(null)");

	g_return_if_fail(JINGLE_IS_SESSION(session));

	if (type == PURPLE_MEDIA_INFO_HANGUP ||
			type == PURPLE_MEDIA_INFO_REJECT) {
		jabber_iq_send(jingle_session_terminate_packet(
				session, type == PURPLE_MEDIA_INFO_HANGUP ?
				"success" : "decline"));

		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
				G_CALLBACK(jingle_rtp_state_changed_cb),
				session);
		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
				G_CALLBACK(jingle_rtp_stream_info_cb),
				session);
		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
				G_CALLBACK(jingle_rtp_new_candidate_cb),
				session);

		g_object_unref(session);
	/* The same signal is emited *four* times in case of acceptance
	 * by purple_media_stream_info() (stream acceptance, session
	 * acceptance, participant acceptance, and conference acceptance).
	 * We only react to the first one, where sid and name are given
	 * non-null values.
	 */
	} else if (type == PURPLE_MEDIA_INFO_ACCEPT && sid && name &&
			jingle_session_is_initiator(session) == FALSE) {

		jingle_rtp_ready(session);
	}
}

static void
jingle_rtp_ready(JingleSession *session)
{
	PurpleMedia *media = jingle_rtp_get_media(session);

	if (purple_media_candidates_prepared(media, NULL, NULL) &&
			purple_media_codecs_ready(media, NULL) &&
			(jingle_session_is_initiator(session) == TRUE ||
			purple_media_accepted(media, NULL, NULL))) {
		if (jingle_session_is_initiator(session)) {
			JabberIq *iq = jingle_session_to_packet(
					session, JINGLE_SESSION_INITIATE);
			jabber_iq_set_callback(iq,
					jingle_rtp_initiate_ack_cb, session);
			jabber_iq_send(iq);
		} else {
			jabber_iq_send(jingle_session_to_packet(session,
					JINGLE_SESSION_ACCEPT));
		}

		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
				G_CALLBACK(jingle_rtp_candidates_prepared_cb),
				session);
		g_signal_handlers_disconnect_by_func(G_OBJECT(media),
				G_CALLBACK(jingle_rtp_codecs_changed_cb),
				session);
		g_signal_connect(G_OBJECT(media), "new-candidate",
				G_CALLBACK(jingle_rtp_new_candidate_cb),
				session);
	}
}

static PurpleMedia *
jingle_rtp_create_media(JingleContent *content)
{
	JingleSession *session = jingle_content_get_session(content);
	JabberStream *js = jingle_session_get_js(session);
	gchar *remote_jid = jingle_session_get_remote_jid(session);

	PurpleMedia *media = purple_media_manager_create_media(
			purple_media_manager_get(),
			purple_connection_get_account(js->gc),
			"fsrtpconference", remote_jid,
			jingle_session_is_initiator(session));
	g_free(remote_jid);

	if (!media) {
		purple_debug_error("jingle-rtp", "Couldn't create media session\n");
		return NULL;
	}

	purple_media_set_protocol_data(media, session);

	/* connect callbacks */
	g_signal_connect(G_OBJECT(media), "candidates-prepared",
				 G_CALLBACK(jingle_rtp_candidates_prepared_cb), session);
	g_signal_connect(G_OBJECT(media), "codecs-changed",
				 G_CALLBACK(jingle_rtp_codecs_changed_cb), session);
	g_signal_connect(G_OBJECT(media), "state-changed",
				 G_CALLBACK(jingle_rtp_state_changed_cb), session);
	g_signal_connect(G_OBJECT(media), "stream-info",
			G_CALLBACK(jingle_rtp_stream_info_cb), session);

	g_object_unref(session);
	return media;
}

static gboolean
jingle_rtp_init_media(JingleContent *content)
{
	JingleSession *session = jingle_content_get_session(content);
	PurpleMedia *media = jingle_rtp_get_media(session);
	gchar *creator;
	gchar *media_type;
	gchar *remote_jid;
	gchar *senders;
	gchar *name;
	const gchar *transmitter;
	gboolean is_audio;
	gboolean is_creator;
	PurpleMediaSessionType type;
	JingleTransport *transport;
	GParameter *params = NULL;
	guint num_params;

	/* maybe this create ought to just be in initiate and handle initiate */
	if (media == NULL) {
		media = jingle_rtp_create_media(content);

		if (media == NULL)
			return FALSE;
	}

	media_type = jingle_rtp_get_media_type(content);
	if (media_type == NULL) {
		g_object_unref(session);
		return FALSE;
	}

	name = jingle_content_get_name(content);
	remote_jid = jingle_session_get_remote_jid(session);
	senders = jingle_content_get_senders(content);
	transport = jingle_content_get_transport(content);

	if (JINGLE_IS_RAWUDP(transport))
		transmitter = "rawudp";
	else if (JINGLE_IS_ICEUDP(transport))
		transmitter = "nice";
	else if (JINGLE_IS_GOOGLE_P2P(transport))
		transmitter = "nice";
	else
		transmitter = "notransmitter";
	g_object_unref(transport);

	is_audio = purple_strequal(media_type, "audio");

	if (purple_strequal(senders, "both"))
		type = is_audio ? PURPLE_MEDIA_AUDIO
				: PURPLE_MEDIA_VIDEO;
	else if (purple_strequal(senders, "initiator") ==
			jingle_session_is_initiator(session))
		type = is_audio ? PURPLE_MEDIA_SEND_AUDIO
				: PURPLE_MEDIA_SEND_VIDEO;
	else
		type = is_audio ? PURPLE_MEDIA_RECV_AUDIO
				: PURPLE_MEDIA_RECV_VIDEO;

	params =
		jingle_get_params(jingle_session_get_js(session), NULL, 0, 0, 0,
			NULL, NULL, &num_params);

	creator = jingle_content_get_creator(content);
	if (creator == NULL) {
		g_free(name);
		g_free(media_type);
		g_free(remote_jid);
		g_free(senders);
		g_free(params);
		g_object_unref(session);
		return FALSE;
	}

	if (purple_strequal(creator, "initiator"))
		is_creator = jingle_session_is_initiator(session);
	else
		is_creator = !jingle_session_is_initiator(session);
	g_free(creator);

	if(!purple_media_add_stream(media, name, remote_jid,
			type, is_creator, transmitter, num_params, params)) {
		purple_media_end(media, NULL, NULL);
		/* TODO: How much clean-up is necessary here? (does calling
		         purple_media_end lead to cleaning up Jingle structs?) */
		return FALSE;
	}

	g_free(name);
	g_free(media_type);
	g_free(remote_jid);
	g_free(senders);
	g_free(params);
	g_object_unref(session);

	return TRUE;
}

static GList *
jingle_rtp_parse_codecs(PurpleXmlNode *description)
{
	GList *codecs = NULL;
	PurpleXmlNode *codec_element = NULL;
	const char *encoding_name,*id, *clock_rate;
	PurpleMediaCodec *codec;
	const gchar *media = purple_xmlnode_get_attrib(description, "media");
	PurpleMediaSessionType type;

	if (media == NULL) {
		purple_debug_warning("jingle-rtp", "missing media type\n");
		return NULL;
	}

	if (purple_strequal(media, "video")) {
		type = PURPLE_MEDIA_VIDEO;
	} else if (purple_strequal(media, "audio")) {
		type = PURPLE_MEDIA_AUDIO;
	} else {
		purple_debug_warning("jingle-rtp", "unknown media type: %s\n",
				media);
		return NULL;
	}

	for (codec_element = purple_xmlnode_get_child(description, "payload-type") ;
		 codec_element ;
		 codec_element = purple_xmlnode_get_next_twin(codec_element)) {
		PurpleXmlNode *param;
		gchar *codec_str;
		encoding_name = purple_xmlnode_get_attrib(codec_element, "name");

		id = purple_xmlnode_get_attrib(codec_element, "id");
		clock_rate = purple_xmlnode_get_attrib(codec_element, "clockrate");

		codec = purple_media_codec_new(atoi(id), encoding_name,
				     type,
				     clock_rate ? atoi(clock_rate) : 0);

		for (param = purple_xmlnode_get_child(codec_element, "parameter");
				param; param = purple_xmlnode_get_next_twin(param)) {
			purple_media_codec_add_optional_parameter(codec,
					purple_xmlnode_get_attrib(param, "name"),
					purple_xmlnode_get_attrib(param, "value"));
		}

		codec_str = purple_media_codec_to_string(codec);
		purple_debug_info("jingle-rtp", "received codec: %s\n", codec_str);
		g_free(codec_str);

		codecs = g_list_append(codecs, codec);
	}
	return codecs;
}

static void
jingle_rtp_add_payloads(PurpleXmlNode *description, GList *codecs)
{
	for (; codecs ; codecs = codecs->next) {
		PurpleMediaCodec *codec = (PurpleMediaCodec*)codecs->data;
		GList *iter = purple_media_codec_get_optional_parameters(codec);
		gchar *id, *name, *clockrate, *channels;
		gchar *codec_str;
		PurpleXmlNode *payload = purple_xmlnode_new_child(description, "payload-type");

		id = g_strdup_printf("%d",
				purple_media_codec_get_id(codec));
		name = purple_media_codec_get_encoding_name(codec);
		clockrate = g_strdup_printf("%d",
				purple_media_codec_get_clock_rate(codec));
		channels = g_strdup_printf("%d",
				purple_media_codec_get_channels(codec));

		purple_xmlnode_set_attrib(payload, "name", name);
		purple_xmlnode_set_attrib(payload, "id", id);
		purple_xmlnode_set_attrib(payload, "clockrate", clockrate);
		purple_xmlnode_set_attrib(payload, "channels", channels);

		g_free(channels);
		g_free(clockrate);
		g_free(name);
		g_free(id);

		for (; iter; iter = g_list_next(iter)) {
			PurpleKeyValuePair *mparam = iter->data;
			PurpleXmlNode *param = purple_xmlnode_new_child(payload, "parameter");
			purple_xmlnode_set_attrib(param, "name", mparam->key);
			purple_xmlnode_set_attrib(param, "value", mparam->value);
		}

		codec_str = purple_media_codec_to_string(codec);
		purple_debug_info("jingle", "adding codec: %s\n", codec_str);
		g_free(codec_str);
	}
}

/******************************************************************************
 * JingleContent Implementation
 *****************************************************************************/
static PurpleXmlNode *
jingle_rtp_to_xml_internal(JingleContent *rtp, PurpleXmlNode *content, JingleActionType action)
{
	PurpleXmlNode *node = JINGLE_CONTENT_CLASS(jingle_rtp_parent_class)->to_xml(rtp, content, action);
	PurpleXmlNode *description = purple_xmlnode_get_child(node, "description");
	if (description != NULL) {
		JingleSession *session = jingle_content_get_session(rtp);
		PurpleMedia *media = jingle_rtp_get_media(session);
		gchar *media_type = jingle_rtp_get_media_type(rtp);
		gchar *ssrc = jingle_rtp_get_ssrc(rtp);
		gchar *name = jingle_content_get_name(rtp);
		GList *codecs = purple_media_get_codecs(media, name);

		purple_xmlnode_set_attrib(description, "media", media_type);

		if (ssrc != NULL)
			purple_xmlnode_set_attrib(description, "ssrc", ssrc);

		g_free(media_type);
		g_free(name);
		g_object_unref(session);

		jingle_rtp_add_payloads(description, codecs);
		purple_media_codec_list_free(codecs);
	}
	return node;
}

static JingleContent *
jingle_rtp_parse_internal(PurpleXmlNode *rtp)
{
	JingleContent *content = JINGLE_CONTENT_CLASS(jingle_rtp_parent_class)->parse(rtp);
	PurpleXmlNode *description = purple_xmlnode_get_child(rtp, "description");
	const gchar *media_type = purple_xmlnode_get_attrib(description, "media");
	const gchar *ssrc = purple_xmlnode_get_attrib(description, "ssrc");
	purple_debug_info("jingle-rtp", "rtp parse\n");
	g_object_set(content, "media-type", media_type, NULL);
	if (ssrc != NULL)
		g_object_set(content, "ssrc", ssrc, NULL);
	return content;
}

static void
jingle_rtp_handle_action_internal(JingleContent *content, PurpleXmlNode *xmlcontent, JingleActionType action)
{
	switch (action) {
		case JINGLE_SESSION_ACCEPT:
		case JINGLE_SESSION_INITIATE: {
			JingleSession *session;
			JingleTransport *transport;
			PurpleXmlNode *description;
			GList *candidates;
			GList *codecs;
			gchar *name;
			gchar *remote_jid;
			PurpleMedia *media;

			session = jingle_content_get_session(content);

			if (action == JINGLE_SESSION_INITIATE &&
					!jingle_rtp_init_media(content)) {
				/* XXX: send error */
				jabber_iq_send(jingle_session_terminate_packet(
						session, "general-error"));
				g_object_unref(session);
				break;
			}

			transport = jingle_transport_parse(
					purple_xmlnode_get_child(xmlcontent, "transport"));
			description = purple_xmlnode_get_child(xmlcontent, "description");
			candidates = jingle_transport_get_remote_candidates(transport);
			codecs = jingle_rtp_parse_codecs(description);
			name = jingle_content_get_name(content);
			remote_jid = jingle_session_get_remote_jid(session);

			media = jingle_rtp_get_media(session);
			purple_media_set_remote_codecs(media,
					name, remote_jid, codecs);
			purple_media_add_remote_candidates(media,
					name, remote_jid, candidates);

			if (action == JINGLE_SESSION_ACCEPT)
				purple_media_stream_info(media,
						PURPLE_MEDIA_INFO_ACCEPT,
						name, remote_jid, FALSE);

			g_free(remote_jid);
			g_free(name);
			g_object_unref(session);
			g_object_unref(transport);
			purple_media_candidate_list_free(candidates);
			purple_media_codec_list_free(codecs);
			break;
		}
		case JINGLE_SESSION_TERMINATE: {
			JingleSession *session = jingle_content_get_session(content);
			PurpleMedia *media = jingle_rtp_get_media(session);

			if (media != NULL) {
				purple_media_end(media, NULL, NULL);
			}

			g_object_unref(session);
			break;
		}
		case JINGLE_TRANSPORT_INFO: {
			JingleSession *session = jingle_content_get_session(content);
			JingleTransport *transport = jingle_transport_parse(
					purple_xmlnode_get_child(xmlcontent, "transport"));
			GList *candidates = jingle_transport_get_remote_candidates(transport);
			gchar *name = jingle_content_get_name(content);
			gchar *remote_jid =
					jingle_session_get_remote_jid(session);

			purple_media_add_remote_candidates(
					jingle_rtp_get_media(session),
					name, remote_jid, candidates);

			g_free(remote_jid);
			g_free(name);
			g_object_unref(session);
			g_object_unref(transport);
			purple_media_candidate_list_free(candidates);
			break;
		}
		case JINGLE_DESCRIPTION_INFO: {
			JingleSession *session =
					jingle_content_get_session(content);
			PurpleXmlNode *description = purple_xmlnode_get_child(
					xmlcontent, "description");
			GList *codecs, *iter, *iter2, *remote_codecs =
					jingle_rtp_parse_codecs(description);
			gchar *name = jingle_content_get_name(content);
			gchar *remote_jid =
					jingle_session_get_remote_jid(session);
			PurpleMedia *media;

			media = jingle_rtp_get_media(session);

			/*
			 * This may have problems if description-info is
			 * received without the optional parameters for a
			 * codec with configuration info (such as THEORA
			 * or H264). The local configuration info may be
			 * set for the remote codec.
			 *
			 * As of 2.6.3 there's no API to support getting
			 * the remote codecs specifically, just the
			 * intersection. Another option may be to cache
			 * the remote codecs received in initiate/accept.
			 */
			codecs = purple_media_get_codecs(media, name);

			for (iter = codecs; iter; iter = g_list_next(iter)) {
				guint id;

				id = purple_media_codec_get_id(iter->data);
				iter2 = remote_codecs;

				for (; iter2; iter2 = g_list_next(iter2)) {
					if (purple_media_codec_get_id(
							iter2->data) != id)
						continue;

					g_object_unref(iter->data);
					iter->data = iter2->data;
					remote_codecs = g_list_delete_link(
							remote_codecs, iter2);
					break;
				}
			}

			codecs = g_list_concat(codecs, remote_codecs);

			purple_media_set_remote_codecs(media,
					name, remote_jid, codecs);

			purple_media_codec_list_free (codecs);
			g_free(remote_jid);
			g_free(name);
			g_object_unref(session);
			break;
		}
		default:
			break;
	}
}


/******************************************************************************
 * GObject Stuff
 *****************************************************************************/
static void
jingle_rtp_init (JingleRtp *rtp)
{
}

static void
jingle_rtp_finalize (GObject *rtp)
{
	JingleRtpPrivate *priv = jingle_rtp_get_instance_private(JINGLE_RTP(rtp));
	purple_debug_info("jingle-rtp","jingle_rtp_finalize\n");

	g_free(priv->media_type);
	g_free(priv->ssrc);

	G_OBJECT_CLASS(jingle_rtp_parent_class)->finalize(rtp);
}

static void
jingle_rtp_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	JingleRtp *rtp = JINGLE_RTP(object);
	JingleRtpPrivate *priv = jingle_rtp_get_instance_private(rtp);

	switch (prop_id) {
		case PROP_MEDIA_TYPE:
			g_free(priv->media_type);
			priv->media_type = g_value_dup_string(value);
			break;
		case PROP_SSRC:
			g_free(priv->ssrc);
			priv->ssrc = g_value_dup_string(value);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
jingle_rtp_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
	JingleRtp *rtp = JINGLE_RTP(object);
	JingleRtpPrivate *priv = jingle_rtp_get_instance_private(rtp);

	switch (prop_id) {
		case PROP_MEDIA_TYPE:
			g_value_set_string(value, priv->media_type);
			break;
		case PROP_SSRC:
			g_value_set_string(value, priv->ssrc);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}


static void
jingle_rtp_class_finalize(JingleRtpClass *klass) {
}

static void
jingle_rtp_class_init (JingleRtpClass *klass)
{
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	JingleContentClass *content_class = JINGLE_CONTENT_CLASS(klass);

	obj_class->finalize = jingle_rtp_finalize;
	obj_class->set_property = jingle_rtp_set_property;
	obj_class->get_property = jingle_rtp_get_property;

	content_class->to_xml = jingle_rtp_to_xml_internal;
	content_class->parse = jingle_rtp_parse_internal;
	content_class->description_type = JINGLE_APP_RTP;
	content_class->handle_action = jingle_rtp_handle_action_internal;

	properties[PROP_MEDIA_TYPE] = g_param_spec_string("media-type",
			"Media Type",
			"The media type (\"audio\" or \"video\") for this rtp session.",
			NULL,
			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	properties[PROP_SSRC] = g_param_spec_string("ssrc",
			"ssrc",
			"The ssrc for this rtp session.",
			NULL,
			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties(obj_class, PROP_LAST, properties);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
void
jingle_rtp_register(PurplePlugin *plugin) {
	jingle_rtp_register_type(G_TYPE_MODULE(plugin));
}

gchar *
jingle_rtp_get_media_type(JingleContent *content)
{
	gchar *media_type;
	g_object_get(content, "media-type", &media_type, NULL);
	return media_type;
}

gchar *
jingle_rtp_get_ssrc(JingleContent *content)
{
	gchar *ssrc;
	g_object_get(content, "ssrc", &ssrc, NULL);
	return ssrc;
}

static PurpleMedia *
jingle_rtp_get_media(JingleSession *session)
{
	JabberStream *js = jingle_session_get_js(session);
	PurpleMedia *media = NULL;
	GList *iter = purple_media_manager_get_media_by_account(
			purple_media_manager_get(),
			purple_connection_get_account(js->gc));

	for (; iter; iter = g_list_delete_link(iter, iter)) {
		JingleSession *media_session =
				purple_media_get_protocol_data(iter->data);
		if (media_session == session) {
			media = iter->data;
			break;
		}
	}
	if (iter != NULL)
		g_list_free(iter);

	return media;
}

gboolean
jingle_rtp_initiate_media(JabberStream *js, const gchar *who,
		      PurpleMediaSessionType type)
{
	/* create content negotiation */
	JingleSession *session;
	JingleContent *content;
	JingleTransport *transport;
	JabberBuddy *jb;
	JabberBuddyResource *jbr;
	gboolean ret = FALSE;
	const gchar *transport_type;

	gchar *resource = NULL, *me = NULL, *sid = NULL;

	/* construct JID to send to */
	jb = jabber_buddy_find(js, who, FALSE);
	if (!jb) {
		purple_debug_error("jingle-rtp", "Could not find Jabber buddy\n");
		goto out;
	}

	resource = jabber_get_resource(who);
	jbr = jabber_buddy_find_resource(jb, resource);

	if (!jbr) {
		purple_debug_error("jingle-rtp", "Could not find buddy's resource - %s\n", resource);
		goto out;
	}

	if (jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_ICEUDP)) {
		transport_type = JINGLE_TRANSPORT_ICEUDP;
	} else if (jabber_resource_has_capability(jbr, JINGLE_TRANSPORT_RAWUDP)) {
		transport_type = JINGLE_TRANSPORT_RAWUDP;
	} else if (jabber_resource_has_capability(jbr, NS_GOOGLE_TRANSPORT_P2P)) {
		transport_type = NS_GOOGLE_TRANSPORT_P2P;
	} else {
		purple_debug_error("jingle-rtp", "Resource doesn't support "
				"the same transport types\n");
		goto out;
	}

	/* set ourselves as initiator */
	me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource);

	sid = jabber_get_next_id(js);
	session = jingle_session_create(js, sid, me, who, TRUE);


	if (type & PURPLE_MEDIA_AUDIO) {
		JingleRtpPrivate *priv = NULL;

		transport = jingle_transport_create(transport_type);
		content = jingle_content_create(JINGLE_APP_RTP, "initiator",
				"session", "audio-session", "both", transport);

		priv = jingle_rtp_get_instance_private(JINGLE_RTP(content));

		jingle_session_add_content(session, content);
		priv->media_type = g_strdup("audio");
		jingle_rtp_init_media(content);
		g_object_notify_by_pspec(G_OBJECT(content), properties[PROP_MEDIA_TYPE]);
	}
	if (type & PURPLE_MEDIA_VIDEO) {
		JingleRtpPrivate *priv = NULL;

		transport = jingle_transport_create(transport_type);
		content = jingle_content_create(JINGLE_APP_RTP, "initiator",
				"session", "video-session", "both", transport);

		priv = jingle_rtp_get_instance_private(JINGLE_RTP(content));

		jingle_session_add_content(session, content);
		priv->media_type = g_strdup("video");
		jingle_rtp_init_media(content);
		g_object_notify_by_pspec(G_OBJECT(content), properties[PROP_MEDIA_TYPE]);
	}

	if (jingle_rtp_get_media(session) == NULL) {
		goto out;
	}

	ret = TRUE;

out:
	g_free(me);
	g_free(resource);
	g_free(sid);
	return ret;
}

void
jingle_rtp_terminate_session(JabberStream *js, const gchar *who)
{
	JingleSession *session;
/* XXX: This may cause file transfers and xml sessions to stop as well */
	session = jingle_session_find_by_jid(js, who);

	if (session) {
		PurpleMedia *media = jingle_rtp_get_media(session);
		if (media) {
			purple_debug_info("jingle-rtp", "hanging up media\n");
			purple_media_stream_info(media,
					PURPLE_MEDIA_INFO_HANGUP,
					NULL, NULL, TRUE);
		}
	}
}

#endif /* USE_VV */

mercurial