diff libpurple/protocols/jabber/jingle.c @ 25666:e73b03097664

Moved Jingle message handlers from jabber.c to jingle.c.
author Mike Ruprecht <maiku@soc.pidgin.im>
date Mon, 02 Jun 2008 20:59:20 +0000
parents d048100a43ab
children 41d6d4217d21 a7be3074923b
line wrap: on
line diff
--- a/libpurple/protocols/jabber/jingle.c	Mon Jun 02 18:18:58 2008 +0000
+++ b/libpurple/protocols/jabber/jingle.c	Mon Jun 02 20:59:20 2008 +0000
@@ -21,6 +21,7 @@
 #include "purple.h"
 #include "jingle.h"
 #include "xmlnode.h"
+#include "iq.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -487,4 +488,492 @@
 	return jingle;
 }
 
+static void
+jabber_jingle_session_send_content_accept(JingleSession *session)
+{
+	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+					 JABBER_IQ_SET);
+	xmlnode *jingle = jabber_jingle_session_create_content_accept(session);
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+
+	xmlnode_insert_child(result->node, jingle);
+	jabber_iq_send(result);
+}
+
+static void
+jabber_jingle_session_send_session_accept(JingleSession *session)
+{
+	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+					 JABBER_IQ_SET);
+	xmlnode *jingle = jabber_jingle_session_create_session_accept(session);
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+
+	xmlnode_insert_child(result->node, jingle);
+	jabber_iq_send(result);
+	purple_debug_info("jabber", "Sent session accept, starting stream\n");
+	gst_element_set_state(purple_media_get_audio_pipeline(session->media), GST_STATE_PLAYING);
+
+	session->session_started = TRUE;
+}
+
+static void
+jabber_jingle_session_send_session_reject(JingleSession *session)
+{
+	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+					 JABBER_IQ_SET);
+	xmlnode *jingle = jabber_jingle_session_create_terminate(session,
+								 "decline", NULL);
+	xmlnode_set_attrib(result->node, "to", 
+			   jabber_jingle_session_get_remote_jid(session));
+	xmlnode_insert_child(result->node, jingle);
+	jabber_iq_send(result);
+	jabber_jingle_session_destroy(session);
+}
+
+static void
+jabber_jingle_session_send_session_terminate(JingleSession *session)
+{
+	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+					 JABBER_IQ_SET);
+	xmlnode *jingle = jabber_jingle_session_create_terminate(session,
+								 "no-error", NULL);
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+	xmlnode_insert_child(result->node, jingle);
+	jabber_iq_send(result);
+	jabber_jingle_session_destroy(session);
+}
+
+/* callback called when new local transport candidate(s) are available on the
+	Farsight stream */
+static void
+jabber_jingle_session_candidates_prepared(PurpleMedia *media, JingleSession *session)
+{
+	if (!jabber_jingle_session_is_initiator(session)) {
+		/* create transport-info package */
+		JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
+						 JABBER_IQ_SET);
+		xmlnode *jingle = jabber_jingle_session_create_transport_info(session);
+		purple_debug_info("jabber", "jabber_session_candidates_prepared: %d candidates\n",
+				  g_list_length(purple_media_get_local_audio_candidates(session->media)));
+		xmlnode_set_attrib(result->node, "to",
+				   jabber_jingle_session_get_remote_jid(session));
+
+		xmlnode_insert_child(result->node, jingle);
+		jabber_iq_send(result);
+	}
+}
+
+/* callback called when a pair of transport candidates (local and remote)
+	has been established */
+static void
+jabber_jingle_session_candidate_pair_established(PurpleMedia *media,
+						 FsCandidate *native_candidate,
+						 FsCandidate *remote_candidate,
+						 JingleSession *session)
+{	
+	purple_debug_info("jabber", "jabber_candidate_pair_established called\n");
+	/* if we are the initiator, we should send a content-modify message */
+	if (jabber_jingle_session_is_initiator(session)) {
+		JabberIq *result;
+		xmlnode *jingle;
+		
+		purple_debug_info("jabber", "we are the initiator, let's send content-modify\n");
+
+		result = jabber_iq_new(jabber_jingle_session_get_js(session), JABBER_IQ_SET);
+
+		/* shall change this to a "content-replace" */
+		jingle = jabber_jingle_session_create_content_replace(session,
+								      native_candidate,
+								      remote_candidate);
+		xmlnode_set_attrib(result->node, "to",
+				   jabber_jingle_session_get_remote_jid(session));
+		xmlnode_insert_child(result->node, jingle);
+		jabber_iq_send(result);
+	}
+}
+
+static gboolean
+jabber_jingle_session_initiate_media_internal(JingleSession *session,
+					      const char *initiator,
+					      const char *remote_jid)
+{
+	PurpleMedia *media = NULL;
+
+	media = purple_media_manager_create_media(purple_media_manager_get(), 
+						  session->js->gc, "fsrtpconference", remote_jid);
+
+	if (!media) {
+		purple_debug_error("jabber", "Couldn't create fsrtpconference\n");
+		return FALSE;
+	}
+
+	/* this will need to be changed to "nice" once the libnice transmitter is finished */
+	if (!purple_media_add_stream(media, remote_jid, PURPLE_MEDIA_AUDIO, "rawudp")) {
+		purple_debug_error("jabber", "Couldn't create audio stream\n");
+		purple_media_reject(media);
+		return FALSE;
+	}
+
+	jabber_jingle_session_set_remote_jid(session, remote_jid);
+	jabber_jingle_session_set_initiator(session, initiator);
+	jabber_jingle_session_set_media(session, media);
+
+	/* connect callbacks */
+	g_signal_connect_swapped(G_OBJECT(media), "accepted", 
+				 G_CALLBACK(jabber_jingle_session_send_session_accept), session);
+	g_signal_connect_swapped(G_OBJECT(media), "reject", 
+				 G_CALLBACK(jabber_jingle_session_send_session_reject), session);
+	g_signal_connect_swapped(G_OBJECT(media), "hangup", 
+				 G_CALLBACK(jabber_jingle_session_send_session_terminate), session);
+	g_signal_connect(G_OBJECT(media), "candidates-prepared", 
+				 G_CALLBACK(jabber_jingle_session_candidates_prepared), session);
+	g_signal_connect(G_OBJECT(media), "candidate-pair", 
+				 G_CALLBACK(jabber_jingle_session_candidate_pair_established), session);
+
+	purple_media_ready(media);
+
+	return TRUE;
+}
+
+static void
+jabber_jingle_session_initiate_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	const char *from = xmlnode_get_attrib(packet, "from");
+	JingleSession *session = jabber_jingle_session_find_by_jid(js, from);
+	PurpleMedia *media = session->media;
+	JabberIq *result;
+	xmlnode *jingle;
+
+	if (!strcmp(xmlnode_get_attrib(packet, "type"), "error")) {
+		purple_media_got_hangup(media);
+		return;
+	}
+
+	/* catch errors */
+	if (xmlnode_get_child(packet, "error")) {
+		purple_media_got_hangup(media);
+		return;
+	}
+
+	/* create transport-info package */
+	result = jabber_iq_new(jabber_jingle_session_get_js(session), JABBER_IQ_SET);
+	jingle = jabber_jingle_session_create_transport_info(session);
+	purple_debug_info("jabber", "jabber_session_candidates_prepared: %d candidates\n",
+			  g_list_length(purple_media_get_local_audio_candidates(session->media)));
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+
+	xmlnode_insert_child(result->node, jingle);
+	jabber_iq_send(result);
+}
+
+PurpleMedia *
+jabber_jingle_session_initiate_media(PurpleConnection *gc, const char *who, 
+				     PurpleMediaStreamType type)
+{
+	/* create content negotiation */
+	JabberStream *js = gc->proto_data;
+	JabberIq *request = jabber_iq_new(js, JABBER_IQ_SET);
+	xmlnode *jingle, *content, *description, *transport;
+	GList *codecs;
+	JingleSession *session;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	
+	char *jid = NULL, *me = NULL;
+
+	/* construct JID to send to */
+	jb = jabber_buddy_find(js, who, FALSE);
+	if (!jb) {
+		purple_debug_error("jabber", "Could not find Jabber buddy\n");
+		return NULL;
+	}
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if (!jbr) {
+		purple_debug_error("jabber", "Could not find buddy's resource\n");
+	}
+
+	if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) {
+		jid = g_strdup_printf("%s/%s", who, jbr->name);
+	} else {
+		jid = g_strdup(who);
+	}
+	
+	session = jabber_jingle_session_create(js);
+	/* set ourselves as initiator */
+	me = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource);
+
+	if (!jabber_jingle_session_initiate_media_internal(session, me, jid)) {
+		g_free(jid);
+		g_free(me);
+		jabber_jingle_session_destroy(session);
+		return NULL;
+	}
+
+	g_free(jid);
+	g_free(me);
+
+	codecs = purple_media_get_local_audio_codecs(session->media);
+
+	/* create request */
+	
+	xmlnode_set_attrib(request->node, "to", 
+			   jabber_jingle_session_get_remote_jid(session));
+	jingle = xmlnode_new_child(request->node, "jingle");
+	xmlnode_set_namespace(jingle, "urn:xmpp:tmp:jingle");
+	xmlnode_set_attrib(jingle, "action", "session-initiate");
+	/* get our JID and a session id... */
+	xmlnode_set_attrib(jingle, "initiator", jabber_jingle_session_get_initiator(session));
+	xmlnode_set_attrib(jingle, "sid", jabber_jingle_session_get_id(session));
+	
+	content = xmlnode_new_child(jingle, "content");
+	xmlnode_set_attrib(content, "name", "audio-content");
+	xmlnode_set_attrib(content, "profile", "RTP/AVP");
+
+	description = jabber_jingle_session_create_description(session);
+	xmlnode_insert_child(content, description);
+
+	transport = xmlnode_new_child(content, "transport");
+	xmlnode_set_namespace(transport, "urn:xmpp:tmp:jingle:transports:ice-udp");
+
+	jabber_iq_set_callback(request, jabber_jingle_session_initiate_result_cb, NULL);
+
+	/* send request to other part */	
+	jabber_iq_send(request);
+
+	fs_codec_list_destroy(codecs);
+
+	return session->media;
+}
+
+void
+jabber_jingle_session_handle_content_replace(JabberStream *js, xmlnode *packet)
+{
+	xmlnode *jingle = xmlnode_get_child(packet, "jingle");
+	const char *sid = xmlnode_get_attrib(jingle, "sid");
+	JingleSession *session = jabber_jingle_session_find_by_id(js, sid);
+
+	if (!jabber_jingle_session_is_initiator(session) && session->session_started) {
+		JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
+		JabberIq *accept = jabber_iq_new(js, JABBER_IQ_SET);
+		xmlnode *content_accept = NULL;
+
+		/* send acknowledement */
+		xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id"));
+		xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
+		jabber_iq_send(result);
+
+		/* send content-accept */
+		content_accept = jabber_jingle_session_create_content_accept(session);
+		xmlnode_set_attrib(accept->node, "id", xmlnode_get_attrib(packet, "id"));
+		xmlnode_set_attrib(accept->node, "to", xmlnode_get_attrib(packet, "from"));
+		xmlnode_insert_child(accept->node, content_accept);
+
+		jabber_iq_send(accept);
+	}
+}
+
+void
+jabber_jingle_session_handle_session_accept(JabberStream *js, xmlnode *packet)
+{
+	JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	xmlnode *jingle = xmlnode_get_child(packet, "jingle");
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	const char *sid = xmlnode_get_attrib(jingle, "sid");
+	const char *action = xmlnode_get_attrib(jingle, "action");
+	JingleSession *session = jabber_jingle_session_find_by_id(js, sid);
+	GList *remote_codecs = NULL;
+	GList *remote_transports = NULL;
+	GList *codec_intersection;
+	FsCodec *top = NULL;
+	xmlnode *description = NULL;
+	xmlnode *transport = NULL;
+
+	/* We should probably check validity of the incoming XML... */
+
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+
+	description = xmlnode_get_child(content, "description");
+	transport = xmlnode_get_child(content, "transport");
+
+	/* fetch codecs from remote party */
+	purple_debug_info("jabber", "get codecs from session-accept\n");
+	remote_codecs = jabber_jingle_get_codecs(description);
+	purple_debug_info("jabber", "get transport candidates from session accept\n");
+	remote_transports = jabber_jingle_get_candidates(transport);
+
+	purple_debug_info("jabber", "Got %d codecs from responder\n",
+			  g_list_length(remote_codecs));
+	purple_debug_info("jabber", "Got %d transport candidates from responder\n",
+			  g_list_length(remote_transports));
+
+	purple_debug_info("jabber", "Setting remote codecs on stream\n");
+
+	purple_media_set_remote_audio_codecs(session->media, 
+					     jabber_jingle_session_get_remote_jid(session),
+					     remote_codecs);
+
+	codec_intersection = purple_media_get_negotiated_audio_codecs(session->media);
+	purple_debug_info("jabber", "codec_intersection contains %d elems\n",
+			  g_list_length(codec_intersection));
+	/* get the top codec */
+	if (g_list_length(codec_intersection) > 0) {
+		top = (FsCodec *) codec_intersection->data;
+		purple_debug_info("jabber", "Found a suitable codec on stream = %d\n",
+				  top->id);
+
+		/* we have found a suitable codec, but we will not start the stream
+		   just yet, wait for transport negotiation to complete... */
+	}
+	/* if we also got transport candidates, add them to our streams
+	   list of known remote candidates */
+	if (g_list_length(remote_transports) > 0) {
+		purple_media_add_remote_audio_candidates(session->media,
+							 jabber_jingle_session_get_remote_jid(session),
+							 remote_transports);
+		fs_candidate_list_destroy(remote_transports);
+	}
+	if (g_list_length(codec_intersection) == 0 &&
+			g_list_length(remote_transports)) {
+		/* we didn't get any candidates and the codec intersection is empty,
+		   this means this was not a content-accept message and we couldn't
+		   find any suitable codecs, should return error and hang up */
+
+	}
+
+	g_list_free(codec_intersection);
+
+	if (!strcmp(action, "session-accept")) {
+		purple_media_got_accept(jabber_jingle_session_get_media(session));
+		purple_debug_info("jabber", "Got session-accept, starting stream\n");
+		gst_element_set_state(purple_media_get_audio_pipeline(session->media),
+				      GST_STATE_PLAYING);
+	}
+
+	jabber_iq_send(result);
+
+	session->session_started = TRUE;
+}
+
+void 
+jabber_jingle_session_handle_session_initiate(JabberStream *js, xmlnode *packet)
+{
+	JingleSession *session = NULL;
+	xmlnode *jingle = xmlnode_get_child(packet, "jingle");
+	xmlnode *content = NULL;
+	xmlnode *description = NULL;
+	const char *sid = NULL;
+	const char *initiator = NULL;
+	GList *codecs = NULL;
+	JabberIq *result = NULL;
+
+	if (!jingle) {
+		purple_debug_error("jabber", "Malformed request");
+		return;
+	}
+
+	sid = xmlnode_get_attrib(jingle, "sid");
+	initiator = xmlnode_get_attrib(jingle, "initiator");
+
+	if (jabber_jingle_session_find_by_id(js, sid)) {
+		/* This should only happen if you start a session with yourself */
+		purple_debug_error("jabber", "Jingle session with id={%s} already exists\n", sid);
+		return;
+	}
+	session = jabber_jingle_session_create_by_id(js, sid);
+
+	/* init media */
+	content = xmlnode_get_child(jingle, "content");
+	if (!content) {
+		purple_debug_error("jabber", "jingle tag must contain content tag\n");
+		/* should send error here */
+		return;
+	}
+
+	description = xmlnode_get_child(content, "description");
+
+	if (!description) {
+		purple_debug_error("jabber", "content tag must contain description tag\n");
+		/* we should create an error iq here */
+		return;
+	}
+
+	if (!jabber_jingle_session_initiate_media_internal(session, initiator, initiator)) {
+		purple_debug_error("jabber", "Couldn't start media session with %s\n", initiator);
+		jabber_jingle_session_destroy(session);
+		/* we should create an error iq here */
+		return;
+	}
+
+	codecs = jabber_jingle_get_codecs(description);
+
+	purple_media_set_remote_audio_codecs(session->media, initiator, codecs);
+
+	result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
+	jabber_iq_send(result);
+}
+
+void
+jabber_jingle_session_handle_session_terminate(JabberStream *js, xmlnode *packet)
+{
+	JabberIq *result = jabber_iq_new(js, JABBER_IQ_SET);
+	xmlnode *jingle = xmlnode_get_child(packet, "jingle");
+	const char *sid = xmlnode_get_attrib(jingle, "sid");
+	JingleSession *session = jabber_jingle_session_find_by_id(js, sid);
+
+	if (!session) {
+		purple_debug_error("jabber", "jabber_handle_session_terminate couldn't find session\n");
+		return;
+	}
+
+	xmlnode_set_attrib(result->node, "to",
+			   jabber_jingle_session_get_remote_jid(session));
+	xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id"));
+
+
+
+	/* maybe we should look at the reasoncode to determine if it was
+	   a hangup or a reject, and call different callbacks to purple_media */
+	gst_element_set_state(purple_media_get_audio_pipeline(session->media), GST_STATE_NULL);
+
+	purple_media_got_hangup(jabber_jingle_session_get_media(session));
+	jabber_iq_send(result);
+	jabber_jingle_session_destroy(session);
+}
+
+void
+jabber_jingle_session_handle_transport_info(JabberStream *js, xmlnode *packet)
+{
+	JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT);
+	xmlnode *jingle = xmlnode_get_child(packet, "jingle");
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	xmlnode *transport = xmlnode_get_child(content, "transport");
+	GList *remote_candidates = jabber_jingle_get_candidates(transport);
+	const char *sid = xmlnode_get_attrib(jingle, "sid");
+	JingleSession *session = jabber_jingle_session_find_by_id(js, sid);
+
+	if (!session)
+		purple_debug_error("jabber", "jabber_handle_session_candidates couldn't find session\n");
+
+	/* send acknowledement */
+	xmlnode_set_attrib(result->node, "id", xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
+	jabber_iq_send(result);
+
+	/* add candidates to our list of remote candidates */
+	if (g_list_length(remote_candidates) > 0) {
+		purple_media_add_remote_audio_candidates(session->media,
+							 xmlnode_get_attrib(packet, "from"),
+							 remote_candidates);
+		fs_candidate_list_destroy(remote_candidates);
+	}
+}
+
 #endif /* USE_VV */