changeset 26014:bd598b606ca4

Restructure Jingle code to more easily support multiple application types. Actually negotiate a rawudp transport rather than pretending to use iceudp.
author Mike Ruprecht <maiku@soc.pidgin.im>
date Sun, 19 Oct 2008 04:37:23 +0000
parents 5a774d0817d8
children 8bfa1f01f035
files libpurple/marshallers.list libpurple/media.c libpurple/media.h libpurple/mediamanager.h libpurple/protocols/jabber/Makefile.am libpurple/protocols/jabber/Makefile.mingw libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/iq.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/jabber/jingle.c libpurple/protocols/jabber/jingle.h libpurple/protocols/jabber/jingle/content.c libpurple/protocols/jabber/jingle/content.h libpurple/protocols/jabber/jingle/jingle.c libpurple/protocols/jabber/jingle/jingle.h libpurple/protocols/jabber/jingle/session.c libpurple/protocols/jabber/jingle/session.h libpurple/protocols/jabber/jingle/transport.c libpurple/protocols/jabber/jingle/transport.h pidgin/gtkconv.c pidgin/gtkmedia.c
diffstat 22 files changed, 2113 insertions(+), 1593 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/marshallers.list	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/marshallers.list	Sun Oct 19 04:37:23 2008 +0000
@@ -1,3 +1,4 @@
 VOID:BOXED,BOXED
 VOID:POINTER,POINTER,OBJECT
 BOOLEAN:OBJECT
+VOID:STRING,STRING
--- a/libpurple/media.c	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/media.c	Sun Oct 19 04:37:23 2008 +0000
@@ -210,8 +210,8 @@
 					 G_TYPE_POINTER, FS_TYPE_CANDIDATE);
 	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
-					 g_cclosure_marshal_VOID__VOID,
-					 G_TYPE_NONE, 0);
+					 purple_smarshal_VOID__STRING_STRING,
+					 G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
 	purple_media_signals[CANDIDATE_PAIR] = g_signal_new("candidate-pair", G_TYPE_FROM_CLASS(klass),
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
 					 purple_smarshal_VOID__BOXED_BOXED,
@@ -675,7 +675,7 @@
 					if (session->session == fssession) {
 						g_signal_emit(session->media,
 								purple_media_signals[CODECS_READY],
-								0, &session->id);
+								0, session->id);
 						g_list_free(sessions);
 						break;
 					}
@@ -1003,7 +1003,8 @@
 	g_object_get(stream, "participant", &participant, NULL);
 	g_object_get(participant, "cname", &name, NULL);
 	g_object_unref(participant);
-	g_signal_emit(session->media, purple_media_signals[CANDIDATES_PREPARED], 0);
+	g_signal_emit(session->media, purple_media_signals[CANDIDATES_PREPARED],
+			0, session->id, name);
 	g_free(name);
 }
 
--- a/libpurple/media.h	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/media.h	Sun Oct 19 04:37:23 2008 +0000
@@ -27,6 +27,8 @@
 #ifndef __MEDIA_H_
 #define __MEDIA_H_
 
+#include "internal.h"
+
 #ifdef USE_VV
 
 #include <gst/gst.h>
--- a/libpurple/mediamanager.h	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/mediamanager.h	Sun Oct 19 04:37:23 2008 +0000
@@ -27,6 +27,8 @@
 #ifndef __MEDIA_MANAGER_H_
 #define __MEDIA_MANAGER_H_
 
+#include "internal.h"
+
 #ifdef USE_VV
 
 #include <glib.h>
--- a/libpurple/protocols/jabber/Makefile.am	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Sun Oct 19 04:37:23 2008 +0000
@@ -19,8 +19,18 @@
 			  iq.h \
 			  jabber.c \
 			  jabber.h \
-			  jingle.c \
-			  jingle.h \
+			  jingle/jingle.c \
+			  jingle/jingle.h \
+			  jingle/content.c \
+			  jingle/content.h \
+			  jingle/rawudp.c \
+			  jingle/rawudp.h \
+			  jingle/rtp.c \
+			  jingle/rtp.h \
+			  jingle/session.c \
+			  jingle/session.h \
+			  jingle/transport.c \
+			  jingle/transport.h \
 			  jutil.c \
 			  jutil.h \
 			  message.c \
--- a/libpurple/protocols/jabber/Makefile.mingw	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Sun Oct 19 04:37:23 2008 +0000
@@ -52,6 +52,12 @@
 			google.c \
 			iq.c \
 			jabber.c \
+			jingle/jingle.c \
+			jingle/content.c \
+			jingle/rawudp.c \
+			jingle/rtp.c \
+			jingle/session.c \
+			jingle/transport.c \
 			jutil.c \
 			message.c \
 			oob.c \
--- a/libpurple/protocols/jabber/disco.c	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/protocols/jabber/disco.c	Sun Oct 19 04:37:23 2008 +0000
@@ -28,6 +28,7 @@
 #include "iq.h"
 #include "disco.h"
 #include "jabber.h"
+#include "jingle/jingle.h"
 #include "presence.h"
 #include "roster.h"
 #include "pep.h"
@@ -157,10 +158,10 @@
 			SUPPORT_FEATURE("http://www.google.com/transport/p2p");
 			SUPPORT_FEATURE("http://www.google.com/transport/raw-udp");
 			SUPPORT_FEATURE("http://www.google.com/session/phone");
-			SUPPORT_FEATURE("urn:xmpp:tmp:jingle");
-			SUPPORT_FEATURE("urn:xmpp:tmp:jingle:apps:rtp#audio");
-			SUPPORT_FEATURE("urn:xmpp:tmp:jingle:apps:rtp#video");
-			SUPPORT_FEATURE("urn:xmpp:tmp:jingle:transports:ice-udp");
+			SUPPORT_FEATURE(JINGLE);
+			SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_AUDIO);
+			SUPPORT_FEATURE(JINGLE_APP_RTP_SUPPORT_VIDEO);
+			SUPPORT_FEATURE(JINGLE_TRANSPORT_RAWUDP);
 #endif
 		} else {
 			const char *ext = NULL;
--- a/libpurple/protocols/jabber/iq.c	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/protocols/jabber/iq.c	Sun Oct 19 04:37:23 2008 +0000
@@ -28,7 +28,7 @@
 #include "disco.h"
 #include "google.h"
 #include "iq.h"
-#include "jingle.h"
+#include "jingle/jingle.h"
 #include "oob.h"
 #include "roster.h"
 #include "si.h"
@@ -360,8 +360,8 @@
 	}
 	
 #ifdef USE_VV
-	if (xmlnode_get_child_with_namespace(packet, "jingle", "urn:xmpp:tmp:jingle")) {
-		jabber_jingle_session_parse(js, packet);
+	if (xmlnode_get_child_with_namespace(packet, "jingle", JINGLE)) {
+		jingle_parse(js, packet);
 		return;
 	}
 #endif
@@ -406,7 +406,7 @@
 	jabber_iq_register_handler("jabber:iq:register", jabber_register_parse);
 	jabber_iq_register_handler("urn:xmpp:ping", urn_xmpp_ping_parse);
 #ifdef USE_VV
-	jabber_iq_register_handler("urn:xmpp:tmp:jingle", jabber_jingle_session_parse);
+	jabber_iq_register_handler(JINGLE, jingle_parse);
 #endif
 }
 
--- a/libpurple/protocols/jabber/jabber.c	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Oct 19 04:37:23 2008 +0000
@@ -56,13 +56,13 @@
 #include "xdata.h"
 #include "pep.h"
 #include "adhoccommands.h"
-#include "jingle.h"
+
+#include "jingle/jingle.h"
+#include "jingle/rtp.h"
 
 #ifdef USE_VV
 #include <gst/farsight/fs-conference-iface.h>
 
-#define XEP_0167_AUDIO_CAP "urn:xmpp:tmp:jingle:apps:rtp#audio"
-#define XEP_0167_VIDEO_CAP "urn:xmpp:tmp:jingle:apps:rtp#video"
 #define GTALK_CAP "http://www.google.com/session/phone"
 
 #endif
@@ -1285,7 +1285,7 @@
 
 #ifdef USE_VV
 	/* Close all of the open Jingle sessions on this stream */
-	jabber_jingle_session_terminate_sessions(js);
+	jingle_terminate_sessions(js);
 #endif
 
 	/* Don't perform any actions on the ssl connection
@@ -1923,7 +1923,7 @@
 		return;
 
 #ifdef USE_VV
-	jabber_jingle_session_terminate_session_media(js, who);
+	jingle_rtp_terminate_session(js, who);
 #endif
 	if((jb = jabber_buddy_find(js, who, TRUE)) &&
 			(jbr = jabber_buddy_find_resource(jb, jid->resource))) {
@@ -2408,7 +2408,7 @@
 jabber_initiate_media(PurpleConnection *gc, const char *who, 
 		      PurpleMediaSessionType type)
 {
-	return jabber_jingle_session_initiate_media(gc->proto_data, who, type);
+	return jingle_rtp_initiate_media(gc->proto_data, who, type);
 }
 
 gboolean jabber_can_do_media(PurpleConnection *gc, const char *who, 
@@ -2432,18 +2432,18 @@
 	if (type == (PURPLE_MEDIA_AUDIO | PURPLE_MEDIA_VIDEO)) {
 		purple_debug_info("jabber", 
 				  "Checking audio/video XEP support for %s\n", who);
-		return (jabber_buddy_has_capability(jb, XEP_0167_AUDIO_CAP) ||
+		return (jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_AUDIO) ||
 				jabber_buddy_has_capability(jb, GTALK_CAP)) && 
-				jabber_buddy_has_capability(jb, XEP_0167_VIDEO_CAP);
+				jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_VIDEO);
 	} else if (type == (PURPLE_MEDIA_AUDIO)) {
 		purple_debug_info("jabber", 
 				  "Checking audio XEP support for %s\n", who);
-		return jabber_buddy_has_capability(jb, XEP_0167_AUDIO_CAP) ||
+		return jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_AUDIO) ||
 				jabber_buddy_has_capability(jb, GTALK_CAP);
 	} else if (type == (PURPLE_MEDIA_VIDEO)) {
 		purple_debug_info("jabber", 
 				  "Checking video XEP support for %s\n", who);
-		return jabber_buddy_has_capability(jb, XEP_0167_VIDEO_CAP);
+		return jabber_buddy_has_capability(jb, JINGLE_APP_RTP_SUPPORT_VIDEO);
 	}
 
 	return FALSE;
--- a/libpurple/protocols/jabber/jabber.h	Fri Sep 05 02:18:15 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun Oct 19 04:37:23 2008 +0000
@@ -247,6 +247,7 @@
 #ifdef USE_VV
 	/* keep a hash table of JingleSessions */
 	GHashTable *sessions;
+	GHashTable *medias;
 #endif
 };
 
--- a/libpurple/protocols/jabber/jingle.c	Fri Sep 05 02:18:15 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1520 +0,0 @@
-/*
- * purple - Jabber Protocol Plugin
- *
- * 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 "config.h"
-#include "purple.h"
-#include "jingle.h"
-#include "xmlnode.h"
-#include "iq.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <glib.h>
-
-#ifdef USE_VV
-
-#include <gst/farsight/fs-candidate.h>
-
-#define JINGLE "urn:xmpp:tmp:jingle"
-#define JINGLE_RTP "urn:xmpp:tmp:jingle:apps:rtp"
-#define JINGLE_RTP_INFO "urn:xmpp:tmp:jingle:apps:rtp:info"
-#define TRANSPORT_ICEUDP "urn:xmpp:tmp:jingle:transports:ice-udp"
-
-typedef enum {
-	PENDING,
-	GOT_ACK,
-	ACCEPTED,
-	ACTIVE
-} JingleSessionState;
-
-typedef struct {
-	char *id;
-	JabberStream *js;
-	PurpleMedia *media;
-	char *remote_jid;
-	char *initiator;
-	gboolean is_initiator;
-	JingleSessionState state;
-	GHashTable *contents;	/* JingleSessionContent table */
-} JingleSession;
-
-typedef struct {
-	gchar *name;
-	JingleSession *session;
-	gchar *creator;
-	gchar *sender;
-	gchar *transport_type;
-	gchar *type;
-	gchar *subtype;
-} JingleSessionContent;
-
-static void
-jabber_jingle_session_content_create_internal(JingleSession *session,
-					      const gchar *name,
-					      const gchar *creator,
-					      const gchar *sender,
-					      const gchar *transport_type,
-					      const gchar *type,
-					      const gchar *subtype)
-{
-	JingleSessionContent *content = g_new0(JingleSessionContent, 1);
-	content->session = session;
-	content->name = g_strdup(name);
-	content->creator = g_strdup(creator);
-	content->sender = g_strdup(sender);
-	content->transport_type = g_strdup(transport_type);
-	content->type = g_strdup(type);
-	content->subtype = g_strdup(subtype);
-
-	if (!session->contents) {
-		purple_debug_info("jingle", "Creating hash table for contents\n");
-		session->contents = g_hash_table_new(g_str_hash, g_str_equal);
-	}
-	purple_debug_info("jingle", "inserting content with name == \"%s\" into table\n",
-			  content->name);
-	g_hash_table_insert(session->contents, content->name, content);
-}
-
-static void
-jabber_jingle_session_destroy_content(JingleSessionContent *content)
-{
-	purple_debug_info("jingle", "destroying content with name == \"%s\"\n",
-			  content->name);
-	g_hash_table_remove(content->session->contents, content->name);
-	g_free(content->name);
-	g_free(content->creator);
-	g_free(content->sender);
-	g_free(content->transport_type);
-	g_free(content->type);
-	g_free(content->subtype);
-	g_free(content);
-}
-
-static const gchar *
-jabber_jingle_session_content_get_name(const JingleSessionContent *jsc)
-{
-	return jsc->name;
-}
-
-static JingleSession *
-jabber_jingle_session_content_get_session(const JingleSessionContent *jsc)
-{
-	return jsc->session;
-}
-
-static const gchar *
-jabber_jingle_session_content_get_creator(const JingleSessionContent *jsc)
-{
-	return jsc->creator;
-}
-
-static const gchar *
-jabber_jingle_session_content_get_sender(const JingleSessionContent *jsc)
-{
-	return jsc->sender;
-}
-
-static const gchar *
-jabber_jingle_session_content_get_transport_type(const JingleSessionContent *jsc)
-{
-	return jsc->transport_type;
-}
-
-static gboolean
-jabber_jingle_session_content_is_transport_type(const JingleSessionContent *jsc,
-						const gchar *transport_type)
-{
-	return !strcmp(jabber_jingle_session_content_get_transport_type(jsc),
-			transport_type);
-}
-
-static const gchar *
-jabber_jingle_session_content_get_type(const JingleSessionContent *jsc)
-{
-	return jsc->type;
-}
-
-static gboolean
-jabber_jingle_session_content_is_type(const JingleSessionContent *jsc,
-				      const gchar *type)
-{
-	return !strcmp(jabber_jingle_session_content_get_type(jsc), type);
-}
-
-static gchar *
-jabber_jingle_session_content_get_subtype(const JingleSessionContent *jsc)
-{
-	return jsc->subtype;
-}
-
-static gboolean
-jabber_jingle_session_content_is_vv_type(const JingleSessionContent *jsc,
-					 const gchar *media_type)
-{
-	return jabber_jingle_session_content_is_type(jsc, JINGLE_RTP) &&
-			!strcmp(jabber_jingle_session_content_get_subtype(jsc),
-				media_type);
-}
-
-static void
-jabber_jingle_session_content_set_sender(JingleSessionContent *jsc,
-					    const char *sender)
-{
-	if (jsc->sender)
-		g_free(jsc->sender);
-	jsc->sender = g_strdup(sender);
-}
-
-static gboolean
-jabber_jingle_session_equal(gconstpointer a, gconstpointer b)
-{
-	purple_debug_info("jingle", 
-					  "jabber_jingle_session_equal, comparing %s and %s\n",
-					  ((JingleSession *)a)->id,
-					  ((JingleSession *)b)->id);
-	return !strcmp(((JingleSession *) a)->id, ((JingleSession *) b)->id);
-}
-
-static JingleSession *
-jabber_jingle_session_create_internal(JabberStream *js,
-									  const char *id)
-{
-    JingleSession *sess = g_new0(JingleSession, 1);
-	sess->js = js;
-		
-	if (id) {
-		sess->id = g_strdup(id);
-	} else if (js) {
-		/* init the session ID... */
-		sess->id = jabber_get_next_id(js);
-	}
-	
-	/* insert it into the hash table */
-	if (!js->sessions) {
-		purple_debug_info("jingle", "Creating hash table for sessions\n");
-		js->sessions = g_hash_table_new(g_str_hash, g_str_equal);
-	}
-	purple_debug_info("jingle", "inserting session with key: %s into table\n",
-					  sess->id);
-	g_hash_table_insert(js->sessions, sess->id, sess);
-
-	sess->state = PENDING;
-
-	return sess;
-}
-
-static JabberStream *
-jabber_jingle_session_get_js(const JingleSession *sess)
-{
-	return sess->js;
-}
-
-static JingleSession *
-jabber_jingle_session_create(JabberStream *js)
-{
-	JingleSession *sess = jabber_jingle_session_create_internal(js, NULL);
-	sess->is_initiator = TRUE;	
-	return sess;
-}
-
-static JingleSession *
-jabber_jingle_session_create_by_id(JabberStream *js, const char *id)
-{
-	JingleSession *sess = jabber_jingle_session_create_internal(js, id);
-	sess->is_initiator = FALSE;
-	return sess;
-}
-
-static const char *
-jabber_jingle_session_get_id(const JingleSession *sess)
-{
-	return sess->id;
-}
-
-static void
-jabber_jingle_session_destroy(JingleSession *sess)
-{
-	GList *contents;
-	g_hash_table_remove(sess->js->sessions, sess->id);
-	g_free(sess->id);
-	g_free(sess->remote_jid);
-	g_free(sess->initiator);
-
-	if (sess->media)
-		g_object_unref(sess->media);
-
-	for (contents = g_hash_table_get_values(sess->contents); contents;
-			contents = g_list_delete_link(contents, contents))
-		jabber_jingle_session_destroy_content(contents->data);
-
-	g_free(sess);
-}
-
-static JingleSession *
-jabber_jingle_session_find_by_id(JabberStream *js, const char *id)
-{
-	purple_debug_info("jingle", "find_by_id %s\n", id);
-	purple_debug_info("jingle", "lookup: %p\n", (js->sessions) ?
-			  g_hash_table_lookup(js->sessions, id) : NULL);  
-	return (JingleSession *) (js->sessions) ?
-			  g_hash_table_lookup(js->sessions, id) : NULL;
-}
-
-static JingleSession *
-jabber_jingle_session_find_by_jid(JabberStream *js, const char *jid)
-{
-	GList *values = (js->sessions) ? 
-			g_hash_table_get_values(js->sessions) : NULL;
-	gboolean use_bare = strchr(jid, '/') == NULL;
-
-	for (; values; values = g_list_delete_link(values, values)) {
-		JingleSession *session = (JingleSession *)values->data;
-		gchar *cmp_jid = use_bare ? jabber_get_bare_jid(session->remote_jid)
-					  : g_strdup(session->remote_jid);
-		if (!strcmp(jid, cmp_jid)) {
-			g_free(cmp_jid);
-			g_list_free(values);
-			return session;
-		}
-		g_free(cmp_jid);
-	}
-
-	return NULL;	
-}
-
-static GList *
-jabber_jingle_get_codecs(xmlnode *description)
-{
-	GList *codecs = NULL;
-	xmlnode *codec_element = NULL;
-	const char *encoding_name,*id, *clock_rate;
-	FsCodec *codec;
-	const gchar *media = xmlnode_get_attrib(description, "media");
-	FsMediaType type = !strcmp(media, "video") ? FS_MEDIA_TYPE_VIDEO :
-			!strcmp(media, "audio") ? FS_MEDIA_TYPE_AUDIO : 0;
-
-	for (codec_element = xmlnode_get_child(description, "payload-type") ;
-		 codec_element ;
-		 codec_element = xmlnode_get_next_twin(codec_element)) {
-		xmlnode *param;
-		gchar *codec_str;
-		encoding_name = xmlnode_get_attrib(codec_element, "name");
-
-		id = xmlnode_get_attrib(codec_element, "id");
-		clock_rate = xmlnode_get_attrib(codec_element, "clockrate");
-
-		codec = fs_codec_new(atoi(id), encoding_name, 
-				     type, 
-				     clock_rate ? atoi(clock_rate) : 0);
-
-		for (param = xmlnode_get_child(codec_element, "parameter");
-				param; param = xmlnode_get_next_twin(param)) {
-			fs_codec_add_optional_parameter(codec,
-					xmlnode_get_attrib(param, "name"),
-					xmlnode_get_attrib(param, "value"));
-		}
-
-		codec_str = fs_codec_to_string(codec);
-		purple_debug_fatal("jingle", "received codec: %s\n", codec_str);
-		g_free(codec_str);
-
-		codecs = g_list_append(codecs, codec);
-	}
-	return codecs;
-}
-
-static GList *
-jabber_jingle_get_candidates(const xmlnode *transport)
-{
-	GList *candidates = NULL;
-	xmlnode *candidate = NULL;
-	FsCandidate *c;
-	
-	for (candidate = xmlnode_get_child(transport, "candidate") ;
-		 candidate ;
-		 candidate = xmlnode_get_next_twin(candidate)) {
-		const char *type = xmlnode_get_attrib(candidate, "type");
-		c = fs_candidate_new(xmlnode_get_attrib(candidate, "component"), 
-							atoi(xmlnode_get_attrib(candidate, "component")),
-							strcmp(type, "host") == 0 ?
-							FS_CANDIDATE_TYPE_HOST :
-							strcmp(type, "prflx") == 0 ?
-							FS_CANDIDATE_TYPE_PRFLX :
-							strcmp(type, "relay") == 0 ?
-							FS_CANDIDATE_TYPE_RELAY :
-							strcmp(type, "srflx") == 0 ?
-							FS_CANDIDATE_TYPE_SRFLX : 0,
-							strcmp(xmlnode_get_attrib(candidate, "protocol"),
-							  "udp") == 0 ? 
-				 				FS_NETWORK_PROTOCOL_UDP :
-				 				FS_NETWORK_PROTOCOL_TCP,
-							xmlnode_get_attrib(candidate, "ip"),
-							atoi(xmlnode_get_attrib(candidate, "port")));
-		candidates = g_list_append(candidates, c);
-	}
-	
-	return candidates;
-}
-
-static JingleSessionContent *
-jabber_jingle_session_get_content(const JingleSession *session,
-				  const char *name)
-{
-	return (JingleSession *) name ?
-			g_hash_table_lookup(session->contents, name) : NULL;
-}
-
-static GList *
-jabber_jingle_session_get_contents(const JingleSession *session)
-{
-	return g_hash_table_get_values(session->contents);
-}
-
-static PurpleMedia *
-jabber_jingle_session_get_media(const JingleSession *sess)
-{
-	return sess->media;
-}
-
-static void
-jabber_jingle_session_set_media(JingleSession *sess, PurpleMedia *media)
-{
-	sess->media = media;
-}
-
-static const char *
-jabber_jingle_session_get_remote_jid(const JingleSession *sess)
-{
-	return sess->remote_jid;
-}
-
-static void
-jabber_jingle_session_set_remote_jid(JingleSession *sess, 
-				     const char *remote_jid)
-{
-	if (sess->remote_jid)
-		g_free(sess->remote_jid);
-	sess->remote_jid = g_strdup(remote_jid);
-}
-
-static JingleSessionState
-jabber_jingle_session_get_state(JingleSession *sess)
-{
-	return sess->state;
-}
-
-static void
-jabber_jingle_session_set_state(JingleSession *sess,
-				JingleSessionState state)
-{
-	sess->state = state;
-}
-				
-
-static const char *
-jabber_jingle_session_get_initiator(const JingleSession *sess)
-{
-	return sess->initiator;
-}
-
-static void
-jabber_jingle_session_set_initiator(JingleSession *sess,
-				    const char *initiator)
-{
-	if (sess->initiator)
-		g_free(sess->initiator);
-	sess->initiator = g_strdup(initiator);
-}
-
-static gboolean
-jabber_jingle_session_is_initiator(const JingleSession *sess)
-{
-	return sess->is_initiator;
-}
-
-static void
-jabber_jingle_session_add_payload_types(const JingleSessionContent *jsc,
-					xmlnode *description,
-					GList *codecs)
-{
-	for (; codecs ; codecs = codecs->next) {
-		FsCodec *codec = (FsCodec*)codecs->data;
-		GList *iter = codec->optional_params;
-		char id[8], clockrate[10], channels[10];
-		gchar *codec_str;
-		xmlnode *payload = xmlnode_new_child(description, "payload-type");
-		
-		g_snprintf(id, sizeof(id), "%d", codec->id);
-		g_snprintf(clockrate, sizeof(clockrate), "%d", codec->clock_rate);
-		g_snprintf(channels, sizeof(channels), "%d", codec->channels);
-		
-		xmlnode_set_attrib(payload, "name", codec->encoding_name);
-		xmlnode_set_attrib(payload, "id", id);
-		xmlnode_set_attrib(payload, "clockrate", clockrate);
-		xmlnode_set_attrib(payload, "channels", channels);
-
-		for (; iter; iter = g_list_next(iter)) {
-			FsCodecParameter *fsparam = iter->data;
-			xmlnode *param = xmlnode_new_child(payload, "parameter");
-			xmlnode_set_attrib(param, "name", fsparam->name);
-			xmlnode_set_attrib(param, "value", fsparam->value);
-		}
-
-		codec_str = fs_codec_to_string(codec);
-		purple_debug_fatal("jingle", "adding codec: %s\n", codec_str);
-		g_free(codec_str);
-	}
-}
-
-static xmlnode *
-jabber_jingle_session_add_description_vv(const JingleSessionContent *jsc,
-					 xmlnode *description)
-{
-	xmlnode_set_attrib(description, "media", 
-			jabber_jingle_session_content_get_subtype(jsc));
-	return description;
-}
-
-static xmlnode *
-jabber_jingle_session_add_description(const JingleSessionContent *jsc,
-				      xmlnode *content)
-{
-	xmlnode *description = xmlnode_new_child(content, "description");
-	xmlnode_set_namespace(description,
-			jabber_jingle_session_content_get_type(jsc));
-
-	if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP))
-		return jabber_jingle_session_add_description_vv(jsc, description);
-	else
-		return description;
-}
-
-static xmlnode *
-jabber_jingle_session_add_candidate_iceudp(xmlnode *transport,
-					   FsCandidate *c,
-					   FsCandidate *remote)
-{
-	char port[8];
-	char prio[8];
-	char component[8];
-	xmlnode *candidate = xmlnode_new_child(transport, "candidate");
-	
-	g_snprintf(port, sizeof(port), "%d", c->port);
-	g_snprintf(prio, sizeof(prio), "%d", c->priority);
-	g_snprintf(component, sizeof(component), "%d", c->component_id);
-	
-	xmlnode_set_attrib(candidate, "component", component);
-	xmlnode_set_attrib(candidate, "foundation", "1"); /* what about this? */
-	xmlnode_set_attrib(candidate, "generation", "0"); /* ? */
-	xmlnode_set_attrib(candidate, "ip", c->ip);
-	xmlnode_set_attrib(candidate, "network", "0"); /* ? */
-	xmlnode_set_attrib(candidate, "port", port);
-	xmlnode_set_attrib(candidate, "priority", prio); /* Is this correct? */
-	xmlnode_set_attrib(candidate, "protocol",
-			   c->proto == FS_NETWORK_PROTOCOL_UDP ?
-			   "udp" : "tcp");
-	if (c->username)
-		xmlnode_set_attrib(transport, "ufrag", c->username);
-	if (c->password)
-		xmlnode_set_attrib(transport, "pwd", c->password);
-	
-	xmlnode_set_attrib(candidate, "type", 
-			   c->type == FS_CANDIDATE_TYPE_HOST ? 
-			   "host" :
-			   c->type == FS_CANDIDATE_TYPE_PRFLX ? 
-			   "prflx" :
-		       	   c->type == FS_CANDIDATE_TYPE_RELAY ? 
-			   "relay" :
-			   c->type == FS_CANDIDATE_TYPE_SRFLX ?
-			   "srflx" : NULL);
-
-	/* relay */
-	if (c->type == FS_CANDIDATE_TYPE_RELAY) {
-		/* set rel-addr and rel-port? How? */
-	}
-
-	if (remote) {
-		char remote_port[8];
-		g_snprintf(remote_port, sizeof(remote_port), "%d", remote->port);
-		xmlnode_set_attrib(candidate, "rem-addr", remote->ip);
-		xmlnode_set_attrib(candidate, "rem-port", remote_port);
-	}
-
-	return candidate;
-}
-
-static xmlnode *
-jabber_jingle_session_add_transport(const JingleSessionContent *jsc,
-				    xmlnode *content)
-{
-	xmlnode *transport = xmlnode_new_child(content, "transport");
-	const gchar *transport_type = jabber_jingle_session_content_get_transport_type(jsc);
-	xmlnode_set_namespace(transport, transport_type);
-	return transport;
-}
-
-static xmlnode *
-jabber_jingle_session_add_content(const JingleSessionContent *jsc,
-				  xmlnode *jingle)
-{
-	xmlnode *content = xmlnode_new_child(jingle, "content");
-	xmlnode_set_attrib(content, "creator",
-			   jabber_jingle_session_content_get_creator(jsc));
-	xmlnode_set_attrib(content, "name",
-			   jabber_jingle_session_content_get_name(jsc));
-	xmlnode_set_attrib(content, "sender",
-			   jabber_jingle_session_content_get_sender(jsc));
-	return content;
-}
-
-
-static xmlnode *
-jabber_jingle_session_add_jingle(const JingleSession *sess,
-				 JabberIq *iq, const char *action)
-{
-	xmlnode *jingle = iq ? xmlnode_new_child(iq->node, "jingle") : 
-				xmlnode_new("jingle");
-	xmlnode_set_namespace(jingle, JINGLE);
-	xmlnode_set_attrib(jingle, "action", action);
-	xmlnode_set_attrib(jingle, "initiator", 
-			   jabber_jingle_session_get_initiator(sess));
-	if (jabber_jingle_session_is_initiator(sess))
-		xmlnode_set_attrib(jingle, "responder",
-				jabber_jingle_session_get_remote_jid(sess));
-	else {
-		gchar *responder = g_strdup_printf("%s@%s/%s",
-				sess->js->user->node,
-				sess->js->user->domain,
-				sess->js->user->resource);
-		xmlnode_set_attrib(jingle, "responder", responder);
-		g_free(responder);
-	}
-	xmlnode_set_attrib(jingle, "sid", jabber_jingle_session_get_id(sess));
-	
-	return jingle;
-}
-
-static JabberIq *
-jabber_jingle_session_create_ack(JingleSession *session, xmlnode *jingle)
-{
-	JabberIq *result = jabber_iq_new(
-			jabber_jingle_session_get_js(session),
-			JABBER_IQ_RESULT);
-	xmlnode *packet = xmlnode_get_parent(jingle);
-	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
-	xmlnode_set_attrib(result->node, "from", xmlnode_get_attrib(packet, "to"));
-	xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
-	return result;
-}
-
-static JabberIq *
-jabber_jingle_session_create_iq(const JingleSession *session)
-{
-	JabberIq *result = jabber_iq_new(jabber_jingle_session_get_js(session),
-					 JABBER_IQ_SET);
-	gchar *from = g_strdup_printf("%s@%s/%s", session->js->user->node,
-				      session->js->user->domain,
-				      session->js->user->resource);
-	xmlnode_set_attrib(result->node, "from", from);
-	g_free(from);
-	xmlnode_set_attrib(result->node, "to",
-			   jabber_jingle_session_get_remote_jid(session));
-	return result;
-}
-#if 0
-static xmlnode *
-jabber_jingle_session_create_content_accept(const JingleSession *sess)
-{
-	xmlnode *jingle = 
-		jabber_jingle_session_add_jingle(sess, NULL, "content-accept");
-
-	xmlnode *content = xmlnode_new_child(jingle, "content");
-	xmlnode *description = jabber_jingle_session_create_description(sess);
-
-	xmlnode_set_attrib(content, "creator", "initiator");
-	xmlnode_set_attrib(content, "name", "audio-content");
-	xmlnode_set_attrib(content, "profile", "RTP/AVP");
-
-	xmlnode_insert_child(content, description);
-
-	return jingle;
-}
-
-static xmlnode *
-jabber_jingle_session_create_content_replace(const JingleSession *sess,
-					     FsCandidate *native_candidate,
-					     FsCandidate *remote_candidate)
-{
-	xmlnode *jingle = 
-		jabber_jingle_session_add_jingle(sess, NULL, "content-replace");
-	xmlnode *content = NULL;
-	xmlnode *transport = NULL;
-
-	purple_debug_info("jingle", "creating content-modify for native candidate %s " \
-			  ", remote candidate %s\n", native_candidate->candidate_id,
-			  remote_candidate->candidate_id);
-
-	content = xmlnode_new_child(jingle, "content");
-	xmlnode_set_attrib(content, "creator", "initiator");
-	xmlnode_set_attrib(content, "name", "audio-content");
-	xmlnode_set_attrib(content, "profile", "RTP/AVP");
-	
-	/* get top codec from codec_intersection to put here... */
-	/* later on this should probably handle changing codec */
-
-	xmlnode_insert_child(content, jabber_jingle_session_create_description(sess));
-
-	transport = xmlnode_new_child(content, "transport");
-	xmlnode_set_namespace(transport, TRANSPORT_ICEUDP);
-	jabber_jingle_session_add_candidate_iceudp(transport, native_candidate,
-						   remote_candidate);
-
-	purple_debug_info("jingle", "End create content modify\n");
-	
-	return jingle;
-}
-#endif
-
-static JabberIq *
-jabber_jingle_session_create_session_accept(const JingleSession *session)
-{
-	PurpleMedia *media = jabber_jingle_session_get_media(session);
-	const gchar *remote_jid = jabber_jingle_session_get_remote_jid(session);
-	JabberIq *request = jabber_jingle_session_create_iq(session);
-	xmlnode *jingle =
-		jabber_jingle_session_add_jingle(session, request,
-						 "session-accept");
-	GList *contents = jabber_jingle_session_get_contents(session);
-
-	for (; contents; contents = contents->next) {
-		JingleSessionContent *jsc = contents->data;
-		const gchar *content_name = jabber_jingle_session_content_get_name(jsc);
-		xmlnode *content = jabber_jingle_session_add_content(jsc, jingle);
-		xmlnode *description = jabber_jingle_session_add_description(jsc, content);
-		xmlnode *transport = jabber_jingle_session_add_transport(jsc, content);
-		if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) {
-			GList *codecs = purple_media_get_negotiated_codecs(media,
-									   content_name);
-			jabber_jingle_session_add_payload_types(jsc, description, codecs);
-			fs_codec_list_destroy(codecs);
-		}
-		if (jabber_jingle_session_content_is_transport_type(jsc, TRANSPORT_ICEUDP)) {
-			jabber_jingle_session_add_candidate_iceudp(transport, 
-					purple_media_get_local_candidate(media, content_name,
-									 remote_jid),
-					purple_media_get_remote_candidate(media, content_name,
-									  remote_jid));
-		}
-	}
-
-	return request;
-}
-
-static JabberIq *
-jabber_jingle_session_create_session_info(const JingleSession *session,
-					  const gchar *type)
-{
-	JabberIq *request = jabber_jingle_session_create_iq(session);
-	xmlnode *jingle =
-		jabber_jingle_session_add_jingle(session, request,
-						 "session-info");
-	xmlnode *info = xmlnode_new_child(jingle, type);
-	xmlnode_set_namespace(info, JINGLE_RTP_INFO);
-	return request;
-}
-
-static JabberIq *
-jabber_jingle_session_create_session_initiate(const JingleSession *session)
-{
-	JabberIq *request = jabber_jingle_session_create_iq(session);
-	xmlnode *jingle =
-		jabber_jingle_session_add_jingle(session, request,
-						 "session-initiate");
-	GList *contents = jabber_jingle_session_get_contents(session);
-
-	for (; contents; contents = contents->next) {
-		JingleSessionContent *jsc = contents->data;
-		xmlnode *content = jabber_jingle_session_add_content(jsc, jingle);
-		xmlnode *description = jabber_jingle_session_add_description(jsc, content);
-		if (jabber_jingle_session_content_is_type(jsc, JINGLE_RTP)) {
-			PurpleMedia *media = jabber_jingle_session_get_media(session);
-			const gchar *content_name =
-					jabber_jingle_session_content_get_name(jsc);
-			GList *codecs = purple_media_get_local_codecs(media, content_name);
-			jabber_jingle_session_add_payload_types(jsc, description, codecs);
-			fs_codec_list_destroy(codecs);
-		}
-		jabber_jingle_session_add_transport(jsc, content);
-	}
-
-	return request;
-}
-
-static JabberIq *
-jabber_jingle_session_create_session_terminate(const JingleSession *sess,
-					       const char *reasoncode,
-					       const char *reasontext)
-{
-	JabberIq *request = jabber_jingle_session_create_iq(sess);
-	xmlnode *jingle = 
-		jabber_jingle_session_add_jingle(sess, request,
-						 "session-terminate");
-	xmlnode *reason = xmlnode_new_child(jingle, "reason");
-	xmlnode_new_child(reason, reasoncode);
-	if (reasontext) {
-		xmlnode *text = xmlnode_new_child(reason, "text");
-		xmlnode_insert_data(text, reasontext, strlen(reasontext));
-	}
-	
-	return request;
-}
-
-static JabberIq *
-jabber_jingle_session_create_transport_info(const JingleSessionContent *jsc,
-					    FsCandidate *candidate)
-{
-	JingleSession *session = 
-			jabber_jingle_session_content_get_session(jsc);
-	JabberIq *request = jabber_jingle_session_create_iq(session);
-	xmlnode *jingle =
-		jabber_jingle_session_add_jingle(session, request,
-						 "transport-info");
-	xmlnode *content = jabber_jingle_session_add_content(jsc, jingle);
-	xmlnode *transport = jabber_jingle_session_add_transport(jsc, content);
-	jabber_jingle_session_add_candidate_iceudp(transport, candidate, NULL);
-	return request;
-}
-#if 0
-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);
-}
-#endif
-
-static void
-jabber_jingle_session_accept(JingleSession *session)
-{
-	if (jabber_jingle_session_get_state(session) == ACCEPTED &&
-			purple_media_candidates_prepared(
-				jabber_jingle_session_get_media(session),
-				jabber_jingle_session_get_remote_jid(session))) {
-		jabber_iq_send(jabber_jingle_session_create_session_accept(session));
-		
-		purple_debug_info("jingle", "Sent session accept.\n");
-		jabber_jingle_session_set_state(session, ACTIVE);
-	}
-}
-
-static void
-jabber_jingle_session_send_session_accept(JingleSession *session)
-{
-	/* create transport-info packages */
-	PurpleMedia *media = jabber_jingle_session_get_media(session);
-	GList *contents = jabber_jingle_session_get_contents(session);
-	const gchar *remote_jid = jabber_jingle_session_get_remote_jid(session);
-	for (; contents; contents = contents->next) {
-		JingleSessionContent *jsc = contents->data;
-		GList *candidates = purple_media_get_local_candidates(
-				media,
-				jabber_jingle_session_content_get_name(jsc),
-				remote_jid);
-		purple_debug_info("jingle",
-				  "jabber_session_candidates_prepared: %d candidates\n",
-				  g_list_length(candidates));
-		for (; candidates; candidates = candidates->next) {
-			FsCandidate *candidate = candidates->data;
-			JabberIq *result = jabber_jingle_session_create_transport_info(jsc,
-					candidate);
-			jabber_iq_send(result);
-		}
-		fs_candidate_list_destroy(candidates);
-
-		purple_debug_info("jingle", "codec intersection: %i\n",
-				g_list_length(purple_media_get_negotiated_codecs(media,
-				jabber_jingle_session_content_get_name(jsc))));
-	}
-
-	jabber_jingle_session_set_state(session, ACCEPTED);
-	jabber_jingle_session_accept(session);
-}
-
-static void
-jabber_jingle_session_send_session_reject(JingleSession *session)
-{
-	jabber_iq_send(jabber_jingle_session_create_session_terminate(session,
-			"decline", NULL));
-	jabber_jingle_session_destroy(session);
-}
-
-static void
-jabber_jingle_session_send_session_terminate(JingleSession *session)
-{
-	jabber_iq_send(jabber_jingle_session_create_session_terminate(session,
-			"no-error", NULL));
-	jabber_jingle_session_destroy(session);
-}
-
-static void
-jabber_jingle_session_content_create_media(JingleSession *session,
-					     PurpleMediaSessionType type)
-{
-	gchar sender[10] = "";
-
-	if (type & PURPLE_MEDIA_AUDIO) {
-		if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_SEND_AUDIO)
-			strcpy(sender, "initiator");
-		else if ((type & PURPLE_MEDIA_AUDIO) == PURPLE_MEDIA_RECV_AUDIO)
-			strcpy(sender, "responder");
-		else
-			strcpy(sender, "both");
-		jabber_jingle_session_content_create_internal(session,
-				"audio-content", "initiator", sender,
-				TRANSPORT_ICEUDP, JINGLE_RTP, "audio");
-	}
-	if (type & PURPLE_MEDIA_VIDEO) {
-		if ((type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_SEND_VIDEO)
-			strcpy(sender, "initiator");
-		else if ((type & PURPLE_MEDIA_VIDEO) == PURPLE_MEDIA_RECV_VIDEO)
-			strcpy(sender, "responder");
-		else
-			strcpy(sender, "both");
-		jabber_jingle_session_content_create_internal(session,
-				"video-content", "initiator", sender,
-				TRANSPORT_ICEUDP, JINGLE_RTP, "video");
-	}
-}
-
-static void
-jabber_jingle_session_content_create_parse(JingleSession *session,
-					   xmlnode *content)
-{
-	xmlnode *description = xmlnode_get_child(content, "description");
-	xmlnode *transport = xmlnode_get_child(content, "transport");
-
-	const gchar *creator = xmlnode_get_attrib(content, "creator");
-	const gchar *sender = xmlnode_get_attrib(content, "sender");
-	const gchar *subtype = xmlnode_get_attrib(description, "media");
-
-	jabber_jingle_session_content_create_internal(session,
-						      xmlnode_get_attrib(content, "name"),
-						      creator ? creator : "initiator",
-						      sender ? sender : "both",
-						      xmlnode_get_namespace(transport),
-						      xmlnode_get_namespace(description),
-						      subtype);
-}
-
-static void
-jabber_jingle_session_new_candidate_cb(PurpleMedia *media,
-				       const gchar *session_id,
-				       const gchar *name,
-				       FsCandidate *candidate,
-				       JingleSession *session)
-{
-	if (jabber_jingle_session_get_state(session) == GOT_ACK ||
-			jabber_jingle_session_get_state(session) == ACTIVE) {
-		JingleSessionContent *jsc = jabber_jingle_session_get_content(session,
-									      session_id);
-		jabber_iq_send(jabber_jingle_session_create_transport_info(jsc,
-				candidate));
-	}
-}
-
-/* callback called when a pair of transport candidates (local and remote)
-	has been established */
-static void
-jabber_jingle_session_candidate_pair_established_cb(PurpleMedia *media,
-						    FsCandidate *native_candidate,
-						    FsCandidate *remote_candidate,
-						    JingleSession *session)
-{
-	if (!jabber_jingle_session_is_initiator(session)) {
-		jabber_jingle_session_accept(session);
-	}
-}
-
-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;
-	GList *contents;
-
-	if (!session) {
-		/* respond with an error here */
-		purple_debug_error("jingle", "Received session-initiate ack"
-				   " to nonexistent session\n");
-		return;
-	}
-
-	media = session->media;
-
-	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 packages */
-	contents = jabber_jingle_session_get_contents(session);
-	for (; contents; contents = contents->next) {
-		JingleSessionContent *jsc = contents->data;
-		GList *candidates = purple_media_get_local_candidates(
-				jabber_jingle_session_get_media(session),
-				jabber_jingle_session_content_get_name(jsc),
-				jabber_jingle_session_get_remote_jid(session));
-		purple_debug_info("jingle",
-				  "jabber_session_candidates_prepared: %d candidates\n",
-				  g_list_length(candidates));
-		for (; candidates; candidates = candidates->next) {
-			FsCandidate *candidate = candidates->data;
-			JabberIq *result = jabber_jingle_session_create_transport_info(jsc,
-					candidate);
-			jabber_iq_send(result);
-		}
-		fs_candidate_list_destroy(candidates);
-	}
-
-	jabber_jingle_session_set_state(session, GOT_ACK);
-}
-
-static void
-jabber_jingle_session_codecs_ready_cb(PurpleMedia *media,
-				      const gchar *sess_id,
-				      JingleSession *session)
-{
-	GList *contents = jabber_jingle_session_get_contents(session);
-	for (; contents; contents = g_list_delete_link(contents, contents)) {
-		JingleSessionContent *jsc = contents->data;
-		if (!purple_media_codecs_ready(media,
-				jabber_jingle_session_content_get_name(jsc))) {
-			break;
-		}
-	}
-
-	if (contents != NULL)
-		g_list_free(contents);
-	else if (jabber_jingle_session_is_initiator(session)
-			&& jabber_jingle_session_get_state(session) == PENDING) {
-		JabberIq *request;
-
-		/* create request */
-		request = jabber_jingle_session_create_session_initiate(session);
-		jabber_iq_set_callback(request, jabber_jingle_session_initiate_result_cb, NULL);
-
-		/* send request to other part */	
-		jabber_iq_send(request);
-	} else {
-		jabber_jingle_session_accept(session);
-	}
-}
-
-static gboolean
-jabber_jingle_session_initiate_media_internal(JingleSession *session,
-					      const char *initiator,
-					      const char *remote_jid)
-{
-	PurpleMedia *media = NULL;
-	GList *contents = jabber_jingle_session_get_contents(session);
-
-	media = purple_media_manager_create_media(purple_media_manager_get(), 
-						  session->js->gc, "fsrtpconference", remote_jid);
-
-	jabber_jingle_session_set_remote_jid(session, remote_jid);
-	jabber_jingle_session_set_initiator(session, initiator);
-
-	if (!media) {
-		purple_debug_error("jingle", "Couldn't create media session\n");
-		return FALSE;
-	}
-
-	jabber_jingle_session_set_media(session, media);
-
-	for (; contents; contents = g_list_delete_link(contents, contents)) {
-		JingleSessionContent *jsc = contents->data;
-		gboolean result = FALSE;
-		const gchar *sender = jabber_jingle_session_content_get_sender(jsc);
-		FsStreamDirection direction = FS_DIRECTION_NONE;
-
-		if (!strcmp(sender, "initiator"))
-			direction = FS_DIRECTION_SEND;
-		else if(!strcmp(sender, "responder"))
-			direction = FS_DIRECTION_RECV;
-		else
-			direction = FS_DIRECTION_BOTH;
-
-		if (!jabber_jingle_session_is_initiator(session)
-				&& direction != FS_DIRECTION_BOTH) {
-			if (direction == FS_DIRECTION_SEND)
-				direction = FS_DIRECTION_RECV;
-			else
-				direction = FS_DIRECTION_SEND;
-		}
-
-		/* these will need to be changed to "nice" once the libnice transmitter is finished */
-		if (jabber_jingle_session_content_is_vv_type(jsc, "audio")) {
-			result = purple_media_add_stream(media, "audio-content", remote_jid,
-					purple_media_from_fs(FS_MEDIA_TYPE_AUDIO, direction),
-					"rawudp", 0, NULL);
-			purple_debug_info("jingle", "Created Jingle audio session\n");
-		}
-		else if (jabber_jingle_session_content_is_vv_type(jsc, "video")) {
-			result = purple_media_add_stream(media, "video-content", remote_jid,
-					purple_media_from_fs(FS_MEDIA_TYPE_VIDEO, direction),
-					"rawudp", 0, NULL);
-			purple_debug_info("jingle", "Created Jingle video session\n");
-		}
-
-		if (!result) {
-			purple_debug_error("jingle", "Couldn't create stream\n");
-			purple_media_hangup(media);
-			return FALSE;
-		}
-	}
-
-	/* 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), "new-candidate", 
-				 G_CALLBACK(jabber_jingle_session_new_candidate_cb), session);
-	g_signal_connect(G_OBJECT(media), "candidate-pair", 
-				 G_CALLBACK(jabber_jingle_session_candidate_pair_established_cb), session);
-	g_signal_connect(G_OBJECT(media), "codecs-ready", 
-				 G_CALLBACK(jabber_jingle_session_codecs_ready_cb), session);
-
-	purple_media_ready(media);
-
-	return TRUE;
-}
-
-PurpleMedia *
-jabber_jingle_session_initiate_media(JabberStream *js, const char *who, 
-				     PurpleMediaSessionType type)
-{
-	/* create content negotiation */
-	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("jingle", "Could not find Jabber buddy\n");
-		return NULL;
-	}
-	jbr = jabber_buddy_find_resource(jb, NULL);
-	if (!jbr) {
-		purple_debug_error("jingle", "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);
-	jabber_jingle_session_content_create_media(session, type);
-
-	/* 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);
-
-	return session->media;
-}
-
-void
-jabber_jingle_session_terminate_session_media(JabberStream *js, const gchar *who)
-{
-	JingleSession *session;
-
-	session = jabber_jingle_session_find_by_jid(js, who);
-
-	if (session)
-		purple_media_hangup(session->media);
-}
-
-void
-jabber_jingle_session_terminate_sessions(JabberStream *js)
-{
-	GList *values = js->sessions ?
-			g_hash_table_get_values(js->sessions) : NULL;
-
-	for (; values; values = g_list_delete_link(values, values)) {
-		JingleSession *session = (JingleSession *)values->data;
-		purple_media_hangup(session->media);
-	}
-}
-
-static void
-jabber_jingle_session_handle_content_replace(JingleSession *session, xmlnode *jingle)
-{
-#if 0
-	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);
-	}
-#endif
-}
-
-static void
-jabber_jingle_session_handle_session_accept(JingleSession *session, xmlnode *jingle)
-{
-	xmlnode *content = xmlnode_get_child(jingle, "content");
-	const char *action = xmlnode_get_attrib(jingle, "action");
-	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... */
-
-	for (content = xmlnode_get_child(jingle, "content"); content;
-			content = xmlnode_get_next_twin(content)) {
-		description = xmlnode_get_child(content, "description");
-		transport = xmlnode_get_child(content, "transport");
-
-		/* fetch codecs from remote party */
-		purple_debug_info("jingle", "get codecs from session-accept\n");
-		remote_codecs = jabber_jingle_get_codecs(description);
-		purple_debug_info("jingle", "get transport candidates from session accept\n");
-		remote_transports = jabber_jingle_get_candidates(transport);
-
-		purple_debug_info("jingle", "Got %d codecs from responder\n",
-				  g_list_length(remote_codecs));
-		purple_debug_info("jingle", "Got %d transport candidates from responder\n",
-				  g_list_length(remote_transports));
-
-		purple_debug_info("jingle", "Setting remote codecs on stream\n");
-
-		if (!purple_media_set_remote_codecs(session->media,
-						    xmlnode_get_attrib(content, "name"),
-						    jabber_jingle_session_get_remote_jid(session),
-						    remote_codecs)) {
-			purple_media_reject(jabber_jingle_session_get_media(session));
-			return;
-		}
-
-		codec_intersection = purple_media_get_negotiated_codecs(session->media,
-									xmlnode_get_attrib(content, "name"));
-		purple_debug_info("jingle", "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("jingle", "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... */
-			purple_media_set_send_codec(
-					jabber_jingle_session_get_media(session),
-					xmlnode_get_attrib(content, "name"),
-					codec_intersection->data);
-		}
-		/* 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_candidates(session->media,
-							   xmlnode_get_attrib(content, "name"),
-							   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 */
-
-		}
-
-		fs_codec_list_destroy(codec_intersection);
-
-	}
-
-	if (!strcmp(action, "session-accept")) {
-		purple_media_got_accept(jabber_jingle_session_get_media(session));
-		purple_debug_info("jingle", "Got session-accept\n");
-	}
-
-	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
-
-	jabber_jingle_session_set_state(session, ACTIVE);
-}
-
-static void
-jabber_jingle_session_handle_session_info(JingleSession *session, xmlnode *jingle)
-{
-	purple_debug_info("jingle", "got session-info\n");
-	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
-}
-
-static void 
-jabber_jingle_session_handle_session_initiate(JingleSession *session, xmlnode *jingle)
-{
-	xmlnode *content = NULL;
-	xmlnode *description = NULL;
-	xmlnode *transport = NULL;
-	const char *initiator = NULL;
-	GList *codecs = NULL;
-
-	if (!jingle) {
-		purple_debug_error("jingle", "Malformed request\n");
-		return;
-	}
-
-	initiator = xmlnode_get_attrib(jingle, "initiator");
-
-	for (content = xmlnode_get_child(jingle, "content"); content;
-			content = xmlnode_get_next_twin(content)) {
-		/* init media */
-		if (!content) {
-			purple_debug_error("jingle", "jingle tag must contain content tag\n");
-			/* should send error here */
-			return;
-		}
-
-		description = xmlnode_get_child(content, "description");
-
-		if (!description) {
-			purple_debug_error("jingle", "content tag must contain description tag\n");
-			/* we should create an error iq here */
-			return;
-		}
-
-		transport = xmlnode_get_child(content, "transport");
-
-		if (!transport) {
-			purple_debug_error("jingle", "content tag must contain transport tag\n");
-			/* we should create an error iq here */
-			return;
-		}
-
-		jabber_jingle_session_content_create_parse(session, content);
-	}
-
-	if (!jabber_jingle_session_initiate_media_internal(session, initiator, initiator)) {
-		purple_debug_error("jingle", "Couldn't start media session with %s\n", initiator);
-		jabber_jingle_session_send_session_reject(session);
-		return;
-	}
-
-	for (content = xmlnode_get_child(jingle, "content"); content;
-			content = xmlnode_get_next_twin(content)) {
-		GList *codec_intersection = NULL;
-
-		/* init media */
-		if (!content) {
-			purple_debug_error("jingle", "jingle tag must contain content tag\n");
-			/* should send error here */
-			return;
-		}
-
-		description = xmlnode_get_child(content, "description");
-
-		if (!description) {
-			purple_debug_error("jingle", "content tag must contain description tag\n");
-			/* we should create an error iq here */
-			return;
-		}
-		codecs = jabber_jingle_get_codecs(description);
-
-		purple_media_set_remote_codecs(session->media,
-					       xmlnode_get_attrib(content, "name"),
-					       initiator, codecs);
-
-		codec_intersection = purple_media_get_negotiated_codecs(session->media,
-				xmlnode_get_attrib(content, "name"));
-		purple_debug_info("jingle", "codec intersection: %i\n",
-				g_list_length(codec_intersection));
-
-		if (g_list_length(codec_intersection) > 0) {
-			purple_media_set_send_codec(
-					jabber_jingle_session_get_media(session),
-					xmlnode_get_attrib(content, "name"),
-					codec_intersection->data);
-		}
-	}
-	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
-	jabber_iq_send(jabber_jingle_session_create_session_info(session, "ringing"));
-
-	purple_media_got_request(jabber_jingle_session_get_media(session));
-}
-
-static void
-jabber_jingle_session_handle_session_terminate(JingleSession *session, xmlnode *jingle)
-{
-	if (!session) {
-		purple_debug_error("jingle", "jabber_handle_session_terminate couldn't find session\n");
-		return;
-	}
-
-	/* maybe we should look at the reasoncode to determine if it was
-	   a hangup or a reject, and call different callbacks to purple_media */
-	purple_media_got_hangup(jabber_jingle_session_get_media(session));
-	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
-	jabber_jingle_session_destroy(session);
-}
-
-static void
-jabber_jingle_session_handle_transport_info(JingleSession *session, xmlnode *jingle)
-{
-	xmlnode *content = xmlnode_get_child(jingle, "content");
-	xmlnode *transport = xmlnode_get_child(content, "transport");
-	GList *remote_candidates = jabber_jingle_get_candidates(transport);
-
-	if (!session)
-		purple_debug_error("jingle", "jabber_handle_session_candidates couldn't find session\n");
-
-	/* send acknowledement */
-	jabber_iq_send(jabber_jingle_session_create_ack(session, jingle));
-
-	/* add candidates to our list of remote candidates */
-	if (g_list_length(remote_candidates) > 0) {
-		purple_media_add_remote_candidates(session->media,
-						   xmlnode_get_attrib(content, "name"),
-						   xmlnode_get_attrib(xmlnode_get_parent(jingle), "from"),
-						   remote_candidates);
-		fs_candidate_list_destroy(remote_candidates);
-	}
-}
-
-void
-jabber_jingle_session_parse(JabberStream *js, xmlnode *packet)
-{
-	const gchar *type = xmlnode_get_attrib(packet, "type");
-	xmlnode *jingle;
-	const gchar *action;
-	const char *sid;
-	JingleSession *session;
-
-	if (!type || strcmp(type, "set")) {
-		/* send iq error here */
-		return;
-	}
-
-	/* is this a Jingle package? */
-	if (!(jingle = xmlnode_get_child(packet, "jingle"))) {
-		/* send iq error here */
-		return;
-	}
-
-	if (!(action = xmlnode_get_attrib(jingle, "action"))) {
-		/* send iq error here */
-		return;
-	}
-
-	purple_debug_info("jabber", "got Jingle package action = %s\n",
-			  action);
-
-	if (!(sid = xmlnode_get_attrib(jingle, "sid"))) {
-		/* send iq error here */
-		return;
-	}
-
-	if (!(session = jabber_jingle_session_find_by_id(js, sid))
-			&& strcmp(action, "session-initiate")) {
-		purple_debug_error("jingle", "jabber_jingle_session_parse couldn't find session\n");
-		/* send iq error here */
-		return;
-	}
-
-	if (!strcmp(action, "session-initiate")) {
-		if (session) {
-			/* This should only happen if you start a session with yourself */
-			purple_debug_error("jingle", "Jingle session with "
-					"id={%s} already exists\n", sid);
-			/* send iq error */
-		} else if ((session = jabber_jingle_session_find_by_jid(js,
-				xmlnode_get_attrib(packet, "from")))) {
-			purple_debug_fatal("jingle", "Jingle session with "
-					"jid={%s} already exists\n",
-					xmlnode_get_attrib(packet, "from"));
-			/* send jingle redirect packet */
-			return;
-		} else {
-			session = jabber_jingle_session_create_by_id(js, sid);
-			jabber_jingle_session_handle_session_initiate(session, jingle);
-		}
-	} else if (!strcmp(action, "session-accept")
-			   || !strcmp(action, "content-accept")) {
-		jabber_jingle_session_handle_session_accept(session, jingle);
-	} else if (!strcmp(action, "session-info")) {
-		jabber_jingle_session_handle_session_info(session, jingle);
-	} else if (!strcmp(action, "session-terminate")) {
-		jabber_jingle_session_handle_session_terminate(session, jingle);
-	} else if (!strcmp(action, "transport-info")) {
-		jabber_jingle_session_handle_transport_info(session, jingle);
-	} else if (!strcmp(action, "content-replace")) {
-		jabber_jingle_session_handle_content_replace(session, jingle);
-	}
-}
-
-#endif /* USE_VV */
-
--- a/libpurple/protocols/jabber/jingle.h	Fri Sep 05 02:18:15 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * 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 Library 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 02110-1301,  USA
- */
- 
-#ifndef JINGLE_H
-#define JINGLE_H
-
-#include "config.h"
-#include "jabber.h"
-#include "media.h"
-
-#include <glib.h>
-#include <glib-object.h>
-
-/*
- * When Jingle content types other than voice and video are implemented,
- * this #ifdef others surrounding Jingle code should be changed to just
- * be around the voice and video specific parts.
- */
-#ifdef USE_VV
-
-G_BEGIN_DECLS
-
-void jabber_jingle_session_parse(JabberStream *js, xmlnode *packet);
-
-PurpleMedia *jabber_jingle_session_initiate_media(JabberStream *js,
-						  const char *who,
-						  PurpleMediaSessionType type);
-
-void jabber_jingle_session_terminate_session_media(JabberStream *js, const gchar *who);
-void jabber_jingle_session_terminate_sessions(JabberStream *js);
-
-G_END_DECLS
-
-#endif /* USE_VV */
-
-#endif /* JINGLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/content.c	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,455 @@
+/**
+ * @file content.c
+ *
+ * purple
+ *
+ * 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 "config.h"
+#include "debug.h"
+#include "content.h"
+#include "jingle.h"
+
+#include <string.h>
+
+struct _JingleContentPrivate
+{
+	JingleSession *session;
+	gchar *description_type;
+	gchar *creator;
+	gchar *disposition;
+	gchar *name;
+	gchar *senders;
+	JingleTransport *transport;
+	JingleTransport *pending_transport;
+};
+
+#define JINGLE_CONTENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_CONTENT, JingleContentPrivate))
+
+static void jingle_content_class_init (JingleContentClass *klass);
+static void jingle_content_init (JingleContent *content);
+static void jingle_content_finalize (GObject *object);
+static void jingle_content_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_content_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static xmlnode *jingle_content_to_xml_internal(JingleContent *content, xmlnode *jingle, JingleActionType action);
+static JingleContent *jingle_content_parse_internal(xmlnode *content);
+
+static GObjectClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+	PROP_SESSION,
+	PROP_CREATOR,
+	PROP_DISPOSITION,
+	PROP_NAME,
+	PROP_SENDERS,
+	PROP_TRANSPORT,
+	PROP_PENDING_TRANSPORT,
+};
+
+GType
+jingle_content_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleContentClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_content_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleContent),
+			0,
+			(GInstanceInitFunc) jingle_content_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "JingleContent", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_content_class_init (JingleContentClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = jingle_content_finalize;
+	gobject_class->set_property = jingle_content_set_property;
+	gobject_class->get_property = jingle_content_get_property;
+	klass->to_xml = jingle_content_to_xml_internal;
+	klass->parse = jingle_content_parse_internal;
+
+	g_object_class_install_property(gobject_class, PROP_SESSION,
+			g_param_spec_object("session",
+			"Jingle Session",
+			"The jingle session parent of this content.",
+			JINGLE_TYPE_SESSION,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_CREATOR,
+			g_param_spec_string("creator",
+			"Creator",
+			"The participant that created this content.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_DISPOSITION,
+			g_param_spec_string("disposition",
+			"Disposition",
+			"The disposition of the content.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_NAME,
+			g_param_spec_string("name",
+			"Name",
+			"The name of this content.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_SENDERS,
+			g_param_spec_string("senders",
+			"Senders",
+			"The sender of this content.",
+			NULL,
+			G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_TRANSPORT,
+			g_param_spec_object("transport",
+			"transport",
+			"The transport of this content.",
+			JINGLE_TYPE_TRANSPORT,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_PENDING_TRANSPORT,
+			g_param_spec_object("pending-transport",
+			"Pending transport",
+			"The pending transport contained within this content",
+			JINGLE_TYPE_TRANSPORT,
+			G_PARAM_READWRITE));
+
+	g_type_class_add_private(klass, sizeof(JingleContentPrivate));
+}
+
+static void
+jingle_content_init (JingleContent *content)
+{
+	content->priv = JINGLE_CONTENT_GET_PRIVATE(content);
+	memset(content->priv, 0, sizeof(content->priv));
+}
+
+static void
+jingle_content_finalize (GObject *content)
+{
+	JingleContentPrivate *priv = JINGLE_CONTENT_GET_PRIVATE(content);
+	purple_debug_info("jingle","jingle_content_finalize\n");
+	
+	g_free(priv->description_type);
+	g_free(priv->creator);
+	g_free(priv->disposition);
+	g_free(priv->name);
+	g_free(priv->senders);
+	g_object_unref(priv->transport);
+	if (priv->pending_transport)
+		g_object_unref(priv->pending_transport);
+
+	parent_class->finalize(content);
+}
+
+static void
+jingle_content_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleContent *content;
+	g_return_if_fail(JINGLE_IS_CONTENT(object));
+
+	content = JINGLE_CONTENT(object);
+
+	switch (prop_id) {
+		case PROP_SESSION:
+			content->priv->session = g_value_get_object(value);
+			break;
+		case PROP_CREATOR:
+			g_free(content->priv->creator);
+			content->priv->creator = g_value_dup_string(value);
+			break;
+		case PROP_DISPOSITION:
+			g_free(content->priv->disposition);
+			content->priv->disposition = g_value_dup_string(value);
+			break;
+		case PROP_NAME:
+			g_free(content->priv->name);
+			content->priv->name = g_value_dup_string(value);
+			break;
+		case PROP_SENDERS:
+			g_free(content->priv->senders);
+			content->priv->senders = g_value_dup_string(value);
+			break;
+		case PROP_TRANSPORT:
+			if (content->priv->transport)
+				g_object_unref(content->priv->transport);
+			content->priv->transport = g_value_get_object(value);
+			g_object_ref(content->priv->transport);
+			break;
+		case PROP_PENDING_TRANSPORT:
+			if (content->priv->pending_transport)
+				g_object_unref(content->priv->pending_transport);
+			content->priv->pending_transport = g_value_get_object(value);
+			g_object_ref(content->priv->pending_transport);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_content_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleContent *content;
+	g_return_if_fail(JINGLE_IS_CONTENT(object));
+	
+	content = JINGLE_CONTENT(object);
+
+	switch (prop_id) {
+		case PROP_SESSION:
+			g_value_set_object(value, content->priv->session);
+			break;
+		case PROP_CREATOR:
+			g_value_set_string(value, content->priv->creator);
+			break;
+		case PROP_DISPOSITION:
+			g_value_set_string(value, content->priv->disposition);
+			break;
+		case PROP_NAME:
+			g_value_set_string(value, content->priv->name);
+			break;
+		case PROP_SENDERS:
+			g_value_set_string(value, content->priv->senders);
+			break;
+		case PROP_TRANSPORT:
+			g_value_set_object(value, content->priv->transport);
+			break;
+		case PROP_PENDING_TRANSPORT:
+			g_value_set_object(value, content->priv->pending_transport);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+JingleContent *
+jingle_content_create(const gchar *type, const gchar *creator,
+		const gchar *disposition, const gchar *name,
+		const gchar *senders, JingleTransport *transport)
+{
+	
+
+	JingleContent *content = g_object_new(jingle_get_type(type),
+			"creator", creator,
+			"disposition", disposition != NULL ? disposition : "session",
+			"name", name,
+			"senders", senders != NULL ? senders : "both",
+			"transport", transport,
+			NULL);
+	return content;
+}
+
+JingleSession *jingle_content_get_session(JingleContent *content)
+{
+	JingleSession *session;
+	g_object_get(content, "session", &session, NULL);
+	return session;
+}
+
+const gchar *
+jingle_content_get_description_type(JingleContent *content)
+{
+	return JINGLE_CONTENT_GET_CLASS(content)->description_type;
+}
+
+gchar *
+jingle_content_get_creator(JingleContent *content)
+{
+	gchar *creator;
+	g_object_get(content, "creator", &creator, NULL);
+	return creator;
+}
+
+gchar *
+jingle_content_get_disposition(JingleContent *content)
+{
+	gchar *disposition;
+	g_object_get(content, "disposition", &disposition, NULL);
+	return disposition;
+}
+
+gchar *
+jingle_content_get_name(JingleContent *content)
+{
+	gchar *name;
+	g_object_get(content, "name", &name, NULL);
+	return name;
+}
+
+gchar *
+jingle_content_get_senders(JingleContent *content)
+{
+	gchar *senders;
+	g_object_get(content, "senders", &senders, NULL);
+	return senders;
+}
+
+JingleTransport *
+jingle_content_get_transport(JingleContent *content)
+{
+	JingleTransport *transport;
+	g_object_get(content, "transport", &transport, NULL);
+	return transport;
+}
+
+void
+jingle_content_set_session(JingleContent *content, JingleSession *session)
+{
+	JINGLE_IS_CONTENT(content);
+	JINGLE_IS_SESSION(session);
+	g_object_set(content, "session", session, NULL);
+}
+
+JingleTransport *
+jingle_content_get_pending_transport(JingleContent *content)
+{
+	JingleTransport *pending_transport;
+	g_object_get(content, "pending_transport", &pending_transport, NULL);
+	return pending_transport;
+}
+
+void
+jingle_content_set_pending_transport(JingleContent *content, JingleTransport *transport)
+{
+	g_object_set(content, "pending-transport", transport, NULL);
+}
+
+void
+jingle_content_accept_transport(JingleContent *content)
+{
+	if (content->priv->transport)
+		g_object_unref(content->priv->transport);
+	content->priv->transport = content->priv->pending_transport;
+	content->priv->pending_transport = NULL;
+}
+
+void
+jingle_content_remove_pending_transport(JingleContent *content)
+{
+	if (content->priv->pending_transport) {
+		g_object_unref(content->priv->pending_transport);
+		content->priv->pending_transport = NULL;
+	}
+}
+
+void
+jingle_content_modify(JingleContent *content, const gchar *senders)
+{
+	g_object_set(content, "senders", senders, NULL);
+}
+
+static JingleContent *
+jingle_content_parse_internal(xmlnode *content)
+{
+	xmlnode *description = xmlnode_get_child(content, "description");
+	const gchar *type = xmlnode_get_namespace(description);
+	const gchar *creator = xmlnode_get_attrib(content, "creator");
+	const gchar *disposition = xmlnode_get_attrib(content, "disposition");
+	const gchar *senders = xmlnode_get_attrib(content, "senders");
+	const gchar *name = xmlnode_get_attrib(content, "name");
+	JingleTransport *transport =
+			jingle_transport_parse(xmlnode_get_child(content, "transport"));
+
+	if (senders == NULL)
+		senders = "both";
+
+	return jingle_content_create(type, creator, disposition, name, senders, transport);
+}
+
+JingleContent *
+jingle_content_parse(xmlnode *content)
+{
+	const gchar *type = xmlnode_get_namespace(xmlnode_get_child(content, "description"));
+	return JINGLE_CONTENT_CLASS(g_type_class_ref(jingle_get_type(type)))->parse(content);
+}
+
+static xmlnode *
+jingle_content_to_xml_internal(JingleContent *content, xmlnode *jingle, JingleActionType action)
+{
+	xmlnode *node = xmlnode_new_child(jingle, "content");
+	gchar *creator = jingle_content_get_creator(content);
+	gchar *name = jingle_content_get_name(content);
+	gchar *senders = jingle_content_get_senders(content);
+	gchar *disposition = jingle_content_get_disposition(content);
+
+	xmlnode_set_attrib(node, "creator", creator);
+	xmlnode_set_attrib(node, "name", name);
+	xmlnode_set_attrib(node, "senders", senders);
+	if (strcmp("session", disposition))
+		xmlnode_set_attrib(node, "disposition", disposition);
+
+	g_free(disposition);
+	g_free(senders);
+	g_free(name);
+	g_free(creator);
+
+	if (action != JINGLE_CONTENT_REMOVE) {
+		JingleTransport *transport;
+
+		if (action != JINGLE_TRANSPORT_ACCEPT &&
+				action != JINGLE_TRANSPORT_INFO &&
+				action != JINGLE_TRANSPORT_REJECT &&
+				action != JINGLE_TRANSPORT_REPLACE) {
+			xmlnode *description = xmlnode_new_child(node, "description");
+
+			xmlnode_set_namespace(description,
+					jingle_content_get_description_type(content));
+		}
+
+		if (action != JINGLE_TRANSPORT_REJECT && action == JINGLE_TRANSPORT_REPLACE)
+			transport = jingle_content_get_pending_transport(content);
+		else
+			transport = jingle_content_get_transport(content);
+
+		jingle_transport_to_xml(transport, node, action);
+	}
+
+	return node;
+}
+
+xmlnode *
+jingle_content_to_xml(JingleContent *content, xmlnode *jingle, JingleActionType action)
+{
+	g_return_val_if_fail(JINGLE_IS_CONTENT(content), NULL);
+	return JINGLE_CONTENT_GET_CLASS(content)->to_xml(content, jingle, action);
+}
+
+void
+jingle_content_handle_action(JingleContent *content, xmlnode *jingle, JingleActionType action)
+{
+	g_return_if_fail(JINGLE_IS_CONTENT(content));
+	JINGLE_CONTENT_GET_CLASS(content)->handle_action(content, jingle, action);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/content.h	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,117 @@
+/**
+ * @file content.h
+ *
+ * purple
+ *
+ * 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
+ */
+
+#ifndef JINGLE_CONTENT_H
+#define JINGLE_CONTENT_H
+
+
+#include "jabber.h"
+#include "jingle.h"
+#include "session.h"
+#include "transport.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_CONTENT            (jingle_content_get_type())
+#define JINGLE_CONTENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_CONTENT, JingleContent))
+#define JINGLE_CONTENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_CONTENT, JingleContentClass))
+#define JINGLE_IS_CONTENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_CONTENT))
+#define JINGLE_IS_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_CONTENT))
+#define JINGLE_CONTENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_CONTENT, JingleContentClass))
+
+/** @copydoc _JingleContent */
+typedef struct _JingleContent JingleContent;
+/** @copydoc _JingleContentClass */
+typedef struct _JingleContentClass JingleContentClass;
+/** @copydoc _JingleContentPrivate */
+typedef struct _JingleContentPrivate JingleContentPrivate;
+
+/** The content class */
+struct _JingleContentClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+
+	xmlnode *(*to_xml) (JingleContent *content, xmlnode *jingle, JingleActionType action);
+	JingleContent *(*parse) (xmlnode *content);
+	void (*handle_action) (JingleContent *content, xmlnode *jingle, JingleActionType action);
+	const gchar *description_type;
+};
+
+/** The content class's private data */
+struct _JingleContent
+{
+	GObject parent;                /**< The parent of this object. */
+	JingleContentPrivate *priv;      /**< The private data of this object. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the content class's GType
+ *
+ * @return The content class's GType.
+ */
+GType jingle_content_get_type(void);
+
+JingleContent *jingle_content_create(const gchar *type, const gchar *creator,
+		const gchar *disposition, const gchar *name,
+		const gchar *senders, JingleTransport *transport);
+
+JingleSession *jingle_content_get_session(JingleContent *content);
+const gchar *jingle_content_get_description_type(JingleContent *content);
+gchar *jingle_content_get_creator(JingleContent *content);
+gchar *jingle_content_get_disposition(JingleContent *content);
+gchar *jingle_content_get_name(JingleContent *content);
+gchar *jingle_content_get_senders(JingleContent *content);
+JingleTransport *jingle_content_get_transport(JingleContent *content);
+JingleTransport *jingle_content_get_pending_transport(JingleContent *content);
+
+void jingle_content_set_session(JingleContent *content, JingleSession *session);
+void jingle_content_set_pending_transport(JingleContent *content, JingleTransport *transport);
+void jingle_content_accept_transport(JingleContent *content);
+void jingle_content_remove_pending_transport(JingleContent *content);
+void jingle_content_modify(JingleContent *content, const gchar *senders);
+
+#define jingle_content_create_content_accept(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_ACCEPT)
+#define jingle_content_create_content_add(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_ADD)
+#define jingle_content_create_content_modify(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_MODIFY)
+#define jingle_content_create_content_remove(session) \
+	jingle_session_to_packet(session, JINGLE_CONTENT_REMOVE)
+
+JingleContent *jingle_content_parse(xmlnode *content);
+xmlnode *jingle_content_to_xml(JingleContent *content, xmlnode *jingle, JingleActionType action);
+void jingle_content_handle_action(JingleContent *content, xmlnode *jingle, JingleActionType action);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_CONTENT_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,432 @@
+/*
+ * @file jingle.c
+ *
+ * purple - Jabber Protocol Plugin
+ *
+ * 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 "config.h"
+#include "content.h"
+#include "debug.h"
+#include "jingle.h"
+#include <string.h>
+#include "session.h"
+#include "rawudp.h"
+#include "rtp.h"
+
+const gchar *
+jingle_get_action_name(JingleActionType action)
+{
+	switch (action) {
+		case JINGLE_CONTENT_ACCEPT:
+			return "content-accept";
+		case JINGLE_CONTENT_ADD:
+			return "content-add";
+		case JINGLE_CONTENT_MODIFY:
+			return "content-modify";
+		case JINGLE_CONTENT_REMOVE:
+			return "content-remove";
+		case JINGLE_SESSION_ACCEPT:
+			return "session-accept";
+		case JINGLE_SESSION_INFO:
+			return "session-info";
+		case JINGLE_SESSION_INITIATE:
+			return "session-initiate";
+		case JINGLE_SESSION_TERMINATE:
+			return "session-terminate";
+		case JINGLE_TRANSPORT_ACCEPT:
+			return "transport-accept";
+		case JINGLE_TRANSPORT_INFO:
+			return "transport-info";
+		case JINGLE_TRANSPORT_REPLACE:
+			return "transport-replace";
+		default:
+			return "unknown-type";
+	}
+}
+
+JingleActionType
+jingle_get_action_type(const gchar *action)
+{
+	if (!strcmp(action, "content-accept"))
+		return JINGLE_CONTENT_ACCEPT;
+	else if (!strcmp(action, "content-add"))
+		return JINGLE_CONTENT_ADD;
+	else if (!strcmp(action, "content-modify"))
+		return JINGLE_CONTENT_MODIFY;
+	else if (!strcmp(action, "content-remove"))
+		return JINGLE_CONTENT_REMOVE;
+	else if (!strcmp(action, "session-accept"))
+		return JINGLE_SESSION_ACCEPT;
+	else if (!strcmp(action, "session-info"))
+		return JINGLE_SESSION_INFO;
+	else if (!strcmp(action, "session-initiate"))
+		return JINGLE_SESSION_INITIATE;
+	else if (!strcmp(action, "session-terminate"))
+		return JINGLE_SESSION_TERMINATE;
+	else if (!strcmp(action, "transport-accept"))
+		return JINGLE_TRANSPORT_ACCEPT;
+	else if (!strcmp(action, "transport-info"))
+		return JINGLE_TRANSPORT_INFO;
+	else if (!strcmp(action, "transport-replace"))
+		return JINGLE_TRANSPORT_REPLACE;
+	else
+		return JINGLE_UNKNOWN_TYPE;
+}
+
+GType
+jingle_get_type(const gchar *type)
+{
+	if (!strcmp(type, JINGLE_APP_RTP))
+		return JINGLE_TYPE_RTP;
+#if 0
+	else if (!strcmp(type, JINGLE_APP_FT))
+		return JINGLE_TYPE_FT;
+	else if (!strcmp(type, JINGLE_APP_XML))
+		return JINGLE_TYPE_XML;
+#endif
+	else if (!strcmp(type, JINGLE_TRANSPORT_RAWUDP))
+		return JINGLE_TYPE_RAWUDP;
+#if 0
+	else if (!strcmp(type, JINGLE_TRANSPORT_ICEUDP))
+		return JINGLE_TYPE_ICEUDP;
+	else if (!strcmp(type, JINGLE_TRANSPORT_SOCKS))
+		return JINGLE_TYPE_SOCKS;
+	else if (!strcmp(type, JINGLE_TRANSPORT_IBB))
+		return JINGLE_TYPE_IBB;
+#endif
+	else
+		return G_TYPE_NONE;
+}
+
+static void
+jingle_handle_content_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		jingle_session_accept_content(session, name, creator);
+		/* signal here */
+	}
+}
+
+static void
+jingle_handle_content_add(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		JingleContent *pending_content =
+				jingle_content_parse(content);
+		if (pending_content == NULL) {
+			purple_debug_error("jingle",
+					"Error parsing \"content-add\" content.\n");
+			/* XXX: send error here */
+		} else {
+			jingle_session_add_pending_content(session,
+					pending_content);
+		}
+	}
+
+	/* XXX: signal here */
+}
+
+static void
+jingle_handle_content_modify(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *local_content = jingle_session_find_content(session, name, creator);
+
+		if (content != NULL) {
+			const gchar *senders = xmlnode_get_attrib(content, "senders");
+			gchar *local_senders = jingle_content_get_senders(local_content);
+			if (strcmp(senders, local_senders))
+				jingle_content_modify(local_content, senders);
+			g_free(local_senders);
+		} else {
+			purple_debug_error("jingle", "content_modify: unknown content\n");
+			/* XXX: send error */
+		}
+	}
+}
+
+static void
+jingle_handle_content_reject(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		jingle_session_remove_pending_content(session, name, creator);
+		/* signal here */
+	}
+}
+
+static void
+jingle_handle_content_remove(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		jingle_session_remove_content(session, name, creator);
+	}
+}
+
+static void
+jingle_handle_session_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	jingle_session_accept_session(session);
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+		if (content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_content_handle_action(content, jingle,
+					JINGLE_SESSION_ACCEPT);
+		}
+	}
+}
+
+static void
+jingle_handle_session_info(JingleSession *session, xmlnode *jingle)
+{
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+	/* XXX: call signal */
+}
+
+static void
+jingle_handle_session_initiate(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		JingleContent *parsed_content = jingle_content_parse(content);
+		if (parsed_content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_session_add_content(session, parsed_content);
+			jingle_content_handle_action(parsed_content, jingle,
+					JINGLE_SESSION_INITIATE);
+		}
+	}
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+}
+
+static void
+jingle_handle_session_terminate(JingleSession *session, xmlnode *jingle)
+{
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	jingle_session_handle_action(session, jingle,
+			JINGLE_SESSION_TERMINATE);
+	/* display reason? */
+	g_object_unref(session);
+}
+
+static void
+jingle_handle_transport_accept(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+		jingle_content_accept_transport(content);
+	}
+}
+
+static void
+jingle_handle_transport_info(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+		if (content == NULL) {
+			purple_debug_error("jingle", "Error parsing content\n");
+			/* XXX: send error */
+		} else {
+			jingle_content_handle_action(content, jingle,
+					JINGLE_TRANSPORT_INFO);
+		}
+	}
+}
+
+static void
+jingle_handle_transport_reject(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+	
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+		jingle_content_remove_pending_transport(content);
+	}
+}
+
+static void
+jingle_handle_transport_replace(JingleSession *session, xmlnode *jingle)
+{
+	xmlnode *content = xmlnode_get_child(jingle, "content");
+
+	jabber_iq_send(jingle_session_create_ack(session, jingle));
+
+	for (; content; content = xmlnode_get_next_twin(content)) {
+		const gchar *name = xmlnode_get_attrib(content, "name");
+		const gchar *creator = xmlnode_get_attrib(content, "creator");
+		xmlnode *xmltransport = xmlnode_get_child(content, "transport");
+		JingleTransport *transport = jingle_transport_parse(xmltransport);
+		JingleContent *content = jingle_session_find_content(session, name, creator);
+
+		jingle_content_set_pending_transport(content, transport);
+	}
+}
+
+
+void
+jingle_parse(JabberStream *js, xmlnode *packet)
+{
+	const gchar *type = xmlnode_get_attrib(packet, "type");
+	xmlnode *jingle;
+	const gchar *action;
+	const gchar *sid;
+	JingleSession *session;
+
+	if (!type || strcmp(type, "set")) {
+		/* send iq error here */
+		return;
+	}
+
+	/* is this a Jingle package? */
+	if (!(jingle = xmlnode_get_child(packet, "jingle"))) {
+		/* send iq error here */
+		return;
+	}
+
+	if (!(action = xmlnode_get_attrib(jingle, "action"))) {
+		/* send iq error here */
+		return;
+	}
+
+	purple_debug_info("jabber", "got Jingle package action = %s\n",
+			  action);
+
+	if (!(sid = xmlnode_get_attrib(jingle, "sid"))) {
+		/* send iq error here */
+		return;
+	}
+
+	if (!(session = jingle_session_find_by_sid(js, sid))
+			&& strcmp(action, "session-initiate")) {
+		purple_debug_error("jingle", "jabber_jingle_session_parse couldn't find session\n");
+		/* send iq error here */
+		return;
+	}
+
+	if (!strcmp(action, "session-initiate")) {
+		if (session) {
+			/* This should only happen if you start a session with yourself */
+			purple_debug_error("jingle", "Jingle session with "
+					"id={%s} already exists\n", sid);
+			/* send iq error */
+		} else if ((session = jingle_session_find_by_jid(js,
+				xmlnode_get_attrib(packet, "from")))) {
+			purple_debug_fatal("jingle", "Jingle session with "
+					"jid={%s} already exists\n",
+					xmlnode_get_attrib(packet, "from"));
+			/* send jingle redirect packet */
+			return;
+		} else {
+			session = jingle_session_create(js, sid,
+					xmlnode_get_attrib(packet, "to"),
+					xmlnode_get_attrib(packet, "from"), FALSE);
+			jingle_handle_session_initiate(session, jingle);
+		}
+	} else if (!strcmp(action, "content-accept")) {
+		jingle_handle_content_accept(session, jingle);
+	} else if (!strcmp(action, "content-add")) {
+		jingle_handle_content_add(session, jingle);
+	} else if (!strcmp(action, "content-modify")) {
+		jingle_handle_content_modify(session, jingle);
+	} else if (!strcmp(action, "content-reject")) {
+		jingle_handle_content_reject(session, jingle);
+	} else if (!strcmp(action, "content-remove")) {
+		jingle_handle_content_remove(session, jingle);
+	} else if (!strcmp(action, "session-accept")) {
+		jingle_handle_session_accept(session, jingle);
+	} else if (!strcmp(action, "session-info")) {
+		jingle_handle_session_info(session, jingle);
+	} else if (!strcmp(action, "session-terminate")) {
+		jingle_handle_session_terminate(session, jingle);
+	} else if (!strcmp(action, "transport-accept")) {
+		jingle_handle_transport_accept(session, jingle);
+	} else if (!strcmp(action, "transport-info")) {
+		jingle_handle_transport_info(session, jingle);
+	} else if (!strcmp(action, "transport-reject")) {
+		jingle_handle_transport_reject(session, jingle);
+	} else if (!strcmp(action, "transport-replace")) {
+		jingle_handle_transport_replace(session, jingle);
+	}
+}
+
+void
+jingle_terminate_sessions(JabberStream *js)
+{
+	GList *values = js->sessions ?
+			g_hash_table_get_values(js->sessions) : NULL;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		JingleSession *session = (JingleSession *)values->data;
+		g_object_unref(session);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.h	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,81 @@
+/*
+ * @file jingle.h
+ *
+ * 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 Library 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 02110-1301,  USA
+ */
+ 
+#ifndef JINGLE_H
+#define JINGLE_H
+
+#include "jabber.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define JINGLE "urn:xmpp:jingle:0"
+#define JINGLE_ERROR "urn:xmpp:jingle:errors:0"
+#define JINGLE_APP_FT "urn:xmpp:jingle:apps:file-transfer:0"
+#define JINGLE_APP_RTP "urn:xmpp:jingle:apps:rtp:0"
+#define JINGLE_APP_RTP_ERROR "urn:xmpp:jingle:apps:rtp:errors:0"
+#define JINGLE_APP_RTP_INFO "urn:xmpp:jingle:apps:rtp:info:0"
+#define JINGLE_APP_RTP_SUPPORT_AUDIO "urn:xmpp:jingle:apps:rtp:audio"
+#define JINGLE_APP_RTP_SUPPORT_VIDEO "urn:xmpp:jingle:apps:rtp:video"
+#define JINGLE_APP_XML "urn:xmpp:tmp:jingle:apps:xmlstream"
+#define JINGLE_DTMF "urn:xmpp:jingle:dtmf:0"
+#define JINGLE_TRANSPORT_SOCKS "urn:xmpp:jingle:transports:bytestreams:0"
+#define JINGLE_TRANSPORT_IBB "urn:xmpp:jingle:transports:ibb:0"
+#define JINGLE_TRANSPORT_ICEUDP "urn:xmpp:jingle:transports:ice-udp:0"
+#define JINGLE_TRANSPORT_RAWUDP "urn:xmpp:jingle:transports:raw-udp:0"
+#define JINGLE_TRANSPORT_RAWUDP_INFO "urn:xmpp:jingle:transports:raw-udp:info:0"
+
+typedef enum {
+	JINGLE_UNKNOWN_TYPE,
+	JINGLE_CONTENT_ACCEPT,
+	JINGLE_CONTENT_ADD,
+	JINGLE_CONTENT_MODIFY,
+	JINGLE_CONTENT_REJECT,
+	JINGLE_CONTENT_REMOVE,
+	JINGLE_SESSION_ACCEPT,
+	JINGLE_SESSION_INFO,
+	JINGLE_SESSION_INITIATE,
+	JINGLE_SESSION_TERMINATE,
+	JINGLE_TRANSPORT_ACCEPT,
+	JINGLE_TRANSPORT_INFO,
+	JINGLE_TRANSPORT_REJECT,
+	JINGLE_TRANSPORT_REPLACE,
+} JingleActionType;
+
+const gchar *jingle_get_action_name(JingleActionType action);
+JingleActionType jingle_get_action_type(const gchar *action);
+
+GType jingle_get_type(const gchar *type);
+
+void jingle_parse(JabberStream *js, xmlnode *packet);
+
+void jingle_terminate_sessions(JabberStream *js);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/session.c	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,588 @@
+/**
+ * @file session.c
+ *
+ * purple
+ *
+ * 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 "config.h"
+#include "content.h"
+#include "debug.h"
+#include "session.h"
+#include "jingle.h"
+
+#include <string.h>
+
+struct _JingleSessionPrivate
+{
+	gchar *sid;
+	JabberStream *js;
+	gchar *remote_jid;
+	gchar *local_jid;
+	gboolean is_initiator;
+	gboolean state;
+	GList *contents;
+	GList *pending_contents;
+};
+
+#define JINGLE_SESSION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_SESSION, JingleSessionPrivate))
+
+static void jingle_session_class_init (JingleSessionClass *klass);
+static void jingle_session_init (JingleSession *session);
+static void jingle_session_finalize (GObject *object);
+static void jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+
+static GObjectClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+	PROP_SID,
+	PROP_JS,
+	PROP_REMOTE_JID,
+	PROP_LOCAL_JID,
+	PROP_IS_INITIATOR,
+	PROP_STATE,
+	PROP_CONTENTS,
+	PROP_PENDING_CONTENTS,
+};
+
+GType
+jingle_session_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleSessionClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_session_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleSession),
+			0,
+			(GInstanceInitFunc) jingle_session_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "JingleSession", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_session_class_init (JingleSessionClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+	
+	gobject_class->finalize = jingle_session_finalize;
+	gobject_class->set_property = jingle_session_set_property;
+	gobject_class->get_property = jingle_session_get_property;
+
+	g_object_class_install_property(gobject_class, PROP_SID,
+			g_param_spec_string("sid",
+			"Session ID",
+			"The unique session ID of the Jingle Session.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_JS,
+			g_param_spec_pointer("js",
+			"JabberStream",
+			"The Jabber stream associated with this session.",
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_REMOTE_JID,
+			g_param_spec_string("remote-jid",
+			"Remote JID",
+			"The JID of the remote participant.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_LOCAL_JID,
+			g_param_spec_string("local-jid",
+			"Local JID",
+			"The JID of the local participant.",
+			NULL,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_IS_INITIATOR,
+			g_param_spec_boolean("is-initiator",
+			"Is Initiator",
+			"Whether or not the local JID is the initiator of the session.",
+			FALSE,
+			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+
+	g_object_class_install_property(gobject_class, PROP_STATE,
+			g_param_spec_boolean("state",
+			"State",
+			"The state of the session (PENDING=FALSE, ACTIVE=TRUE).",
+			FALSE,
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_CONTENTS,
+			g_param_spec_pointer("contents",
+			"Contents",
+			"The active contents contained within this session",
+			G_PARAM_READABLE));
+
+	g_object_class_install_property(gobject_class, PROP_PENDING_CONTENTS,
+			g_param_spec_pointer("pending-contents",
+			"Pending contents",
+			"The pending contents contained within this session",
+			G_PARAM_READABLE));
+
+	g_type_class_add_private(klass, sizeof(JingleSessionPrivate));
+}
+
+static void
+jingle_session_init (JingleSession *session)
+{
+	session->priv = JINGLE_SESSION_GET_PRIVATE(session);
+	memset(session->priv, 0, sizeof(session->priv));
+}
+
+static void
+jingle_session_finalize (GObject *session)
+{
+	JingleSessionPrivate *priv = JINGLE_SESSION_GET_PRIVATE(session);
+	purple_debug_info("jingle","jingle_session_finalize\n");
+
+	g_hash_table_remove(priv->js->sessions, priv->sid);
+
+	g_free(priv->sid);
+	g_free(priv->remote_jid);
+	g_free(priv->local_jid);
+
+	for (; priv->contents; priv->contents =
+			g_list_delete_link(priv->contents, priv->contents)) {
+		g_object_unref(priv->contents->data);
+	}
+	for (; priv->pending_contents; priv->pending_contents =
+			g_list_delete_link(priv->pending_contents, priv->pending_contents)) {
+		g_object_unref(priv->pending_contents->data);
+	}
+
+	parent_class->finalize(session);
+}
+
+static void
+jingle_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleSession *session;
+	g_return_if_fail(JINGLE_IS_SESSION(object));
+
+	session = JINGLE_SESSION(object);
+
+	switch (prop_id) {
+		case PROP_SID:
+			g_free(session->priv->sid);
+			session->priv->sid = g_value_dup_string(value);
+			break;
+		case PROP_JS:
+			session->priv->js = g_value_get_pointer(value);
+			break;
+		case PROP_REMOTE_JID:
+			g_free(session->priv->remote_jid);
+			session->priv->remote_jid = g_value_dup_string(value);
+			break;
+		case PROP_LOCAL_JID:
+			g_free(session->priv->local_jid);
+			session->priv->local_jid = g_value_dup_string(value);
+			break;
+		case PROP_IS_INITIATOR:
+			session->priv->is_initiator = g_value_get_boolean(value);
+			break;
+		case PROP_STATE:
+			session->priv->state = g_value_get_boolean(value);
+			break;
+		case PROP_CONTENTS:
+			session->priv->contents = g_value_get_pointer(value);
+			break;
+		case PROP_PENDING_CONTENTS:
+			session->priv->pending_contents = g_value_get_pointer(value);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleSession *session;
+	g_return_if_fail(JINGLE_IS_SESSION(object));
+	
+	session = JINGLE_SESSION(object);
+
+	switch (prop_id) {
+		case PROP_SID:
+			g_value_set_string(value, session->priv->sid);
+			break;
+		case PROP_JS:
+			g_value_set_pointer(value, session->priv->js);
+			break;
+		case PROP_REMOTE_JID:
+			g_value_set_string(value, session->priv->remote_jid);
+			break;
+		case PROP_LOCAL_JID:
+			g_value_set_string(value, session->priv->local_jid);
+			break;
+		case PROP_IS_INITIATOR:
+			g_value_set_boolean(value, session->priv->is_initiator);
+			break;
+		case PROP_STATE:
+			g_value_set_boolean(value, session->priv->state);
+			break;
+		case PROP_CONTENTS:
+			g_value_set_pointer(value, session->priv->contents);
+			break;
+		case PROP_PENDING_CONTENTS:
+			g_value_set_pointer(value, session->priv->pending_contents);
+			break;
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+
+JingleSession *
+jingle_session_create(JabberStream *js, const gchar *sid,
+			const gchar *local_jid, const gchar *remote_jid,
+			gboolean is_initiator)
+{
+	JingleSession *session = g_object_new(jingle_session_get_type(),
+			"js", js,
+			"sid", sid,
+			"local-jid", local_jid,
+			"remote-jid", remote_jid,
+			"is_initiator", is_initiator,
+			NULL);
+
+	/* insert it into the hash table */
+	if (!js->sessions) {
+		purple_debug_info("jingle",
+				"Creating hash table for sessions\n");
+		js->sessions = g_hash_table_new(g_str_hash, g_str_equal);
+	}
+	purple_debug_info("jingle",
+			"inserting session with key: %s into table\n", sid);
+	g_hash_table_insert(js->sessions, g_strdup(sid), session);
+
+	return session;
+}
+
+JabberStream *
+jingle_session_get_js(JingleSession *session)
+{
+	JabberStream *js;
+	g_object_get(session, "js", &js, NULL);
+	return js;
+}
+
+gchar *
+jingle_session_get_sid(JingleSession *session)
+{
+	gchar *sid;
+	g_object_get(session, "sid", &sid, NULL);
+	return sid;
+}
+
+gchar *
+jingle_session_get_local_jid(JingleSession *session)
+{
+	gchar *local_jid;
+	g_object_get(session, "local-jid", &local_jid, NULL);
+	return local_jid;
+}
+
+gchar *
+jingle_session_get_remote_jid(JingleSession *session)
+{
+	gchar *remote_jid;
+	g_object_get(session, "remote-jid", &remote_jid, NULL);
+	return remote_jid;
+}
+
+gboolean
+jingle_session_is_initiator(JingleSession *session)
+{
+	gboolean is_initiator;
+	g_object_get(session, "is-initiator", &is_initiator, NULL);
+	return is_initiator;
+}
+
+gboolean
+jingle_session_get_state(JingleSession *session)
+{
+	gboolean state;
+	g_object_get(session, "state", &state, NULL);
+	return state;
+}
+
+GList *
+jingle_session_get_contents(JingleSession *session)
+{
+	GList *contents;
+	g_object_get(session, "contents", &contents, NULL);
+	return contents;
+}
+
+GList *
+jingle_session_get_pending_contents(JingleSession *session)
+{
+	GList *pending_contents;
+	g_object_get(session, "pending-contents", &pending_contents, NULL);
+	return pending_contents;
+}
+
+JingleSession *
+jingle_session_find_by_sid(JabberStream *js, const gchar *sid)
+{
+	purple_debug_info("jingle", "find_by_id %s\n", sid);
+	purple_debug_info("jingle", "lookup: %p\n", (js->sessions) ?
+			  g_hash_table_lookup(js->sessions, sid) : NULL);  
+	return (JingleSession *) (js->sessions) ?
+			  g_hash_table_lookup(js->sessions, sid) : NULL;
+}
+
+JingleSession *
+jingle_session_find_by_jid(JabberStream *js, const gchar *jid)
+{
+	GList *values = (js->sessions) ? 
+			g_hash_table_get_values(js->sessions) : NULL;
+	gboolean use_bare = strchr(jid, '/') == NULL;
+
+	for (; values; values = g_list_delete_link(values, values)) {
+		JingleSession *session = (JingleSession *)values->data;
+		gchar *remote_jid = jingle_session_get_remote_jid(session);
+		gchar *cmp_jid = use_bare ? jabber_get_bare_jid(remote_jid)
+					  : g_strdup(remote_jid);
+		g_free(remote_jid);
+		if (!strcmp(jid, cmp_jid)) {
+			g_free(cmp_jid);
+			g_list_free(values);
+			return session;
+		}
+		g_free(cmp_jid);
+	}
+
+	return NULL;	
+}
+
+static xmlnode *
+jingle_add_jingle_packet(JingleSession *session,
+			 JabberIq *iq, JingleActionType action)
+{
+	xmlnode *jingle = iq ?
+			xmlnode_new_child(iq->node, "jingle") :
+			xmlnode_new("jingle");
+	gchar *local_jid = jingle_session_get_local_jid(session);
+	gchar *remote_jid = jingle_session_get_remote_jid(session);
+
+	xmlnode_set_namespace(jingle, JINGLE);
+	xmlnode_set_attrib(jingle, "action", jingle_get_action_name(action));
+
+	if (jingle_session_is_initiator(session)) {
+		xmlnode_set_attrib(jingle, "initiator",
+				jingle_session_get_local_jid(session));
+		xmlnode_set_attrib(jingle, "responder",
+				jingle_session_get_remote_jid(session));
+	} else {
+		xmlnode_set_attrib(jingle, "initiator",
+				jingle_session_get_remote_jid(session));
+		xmlnode_set_attrib(jingle, "responder",
+				jingle_session_get_local_jid(session));
+	}
+
+	g_free(local_jid);
+	g_free(remote_jid);
+
+	xmlnode_set_attrib(jingle, "sid", jingle_session_get_sid(session));
+	
+	return jingle;
+}
+
+JabberIq *
+jingle_session_create_ack(JingleSession *session, const xmlnode *jingle)
+{
+	JabberIq *result = jabber_iq_new(
+			jingle_session_get_js(session),
+			JABBER_IQ_RESULT);
+	xmlnode *packet = xmlnode_get_parent(jingle);
+	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
+	xmlnode_set_attrib(result->node, "from", xmlnode_get_attrib(packet, "to"));
+	xmlnode_set_attrib(result->node, "to", xmlnode_get_attrib(packet, "from"));
+	return result;
+}
+
+static JabberIq *
+jingle_create_iq(JingleSession *session)
+{
+	JabberStream *js = jingle_session_get_js(session);
+	JabberIq *result = jabber_iq_new(js, JABBER_IQ_SET);
+	gchar *from = jingle_session_get_local_jid(session);
+	gchar *to = jingle_session_get_remote_jid(session);
+
+	xmlnode_set_attrib(result->node, "from", from);
+	xmlnode_set_attrib(result->node, "to", to);
+
+	g_free(from);
+	g_free(to);
+	return result;
+}
+
+xmlnode *
+jingle_session_to_xml(JingleSession *session, xmlnode *jingle, JingleActionType action)
+{
+	if (action != JINGLE_SESSION_INFO && action != JINGLE_SESSION_TERMINATE) {
+		GList *iter;
+		if (action == JINGLE_CONTENT_ACCEPT
+				|| action == JINGLE_CONTENT_ADD
+				|| action == JINGLE_CONTENT_REMOVE)
+			iter = jingle_session_get_pending_contents(session);
+		else
+			iter = jingle_session_get_contents(session);
+
+		for (; iter; iter = g_list_next(iter)) {
+			jingle_content_to_xml(iter->data, jingle, action);
+		}
+	}
+	return jingle;
+}
+
+JabberIq *
+jingle_session_to_packet(JingleSession *session, JingleActionType action)
+{
+	JabberIq *iq = jingle_create_iq(session);
+	xmlnode *jingle = jingle_add_jingle_packet(session, iq, action);
+	jingle_session_to_xml(session, jingle, action);
+	return iq;
+}
+
+void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action)
+{
+	GList *iter;
+	if (action == JINGLE_CONTENT_ADD || action == JINGLE_CONTENT_REMOVE)
+		iter = jingle_session_get_pending_contents(session);
+	else
+		iter = jingle_session_get_contents(session);
+
+	for (; iter; iter = g_list_next(iter)) {
+		jingle_content_handle_action(iter->data, jingle, action);
+	}
+}
+
+JingleContent *
+jingle_session_find_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	GList *iter = session->priv->contents;
+	for (; iter; iter = g_list_next(iter)) {
+		JingleContent *content = iter->data;
+		gchar *cname = jingle_content_get_name(content);
+		gchar *ccreator = jingle_content_get_creator(content);
+		gboolean result = (!strcmp(name, cname) && !strcmp(creator, ccreator));
+
+		g_free(cname);
+		g_free(ccreator);
+
+		if (result == TRUE)
+			return content;
+	}
+	return NULL;
+}
+
+JingleContent *
+jingle_session_find_pending_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	GList *iter = session->priv->pending_contents;
+	for (; iter; iter = g_list_next(iter)) {
+		JingleContent *content = iter->data;
+		gchar *cname = jingle_content_get_name(content);
+		gchar *ccreator = jingle_content_get_creator(content);
+		gboolean result = (!strcmp(name, cname) && !strcmp(creator, ccreator));
+
+		g_free(cname);
+		g_free(ccreator);
+
+		if (result == TRUE)
+			return content;
+	}
+	return NULL;
+}
+
+void
+jingle_session_add_content(JingleSession *session, JingleContent* content)
+{
+	session->priv->contents =
+			g_list_append(session->priv->contents, content);
+	jingle_content_set_session(content, session);
+}
+
+void
+jingle_session_remove_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	JingleContent *content =
+			jingle_session_find_content(session, name, creator);
+
+	if (content) {
+		session->priv->contents =
+				g_list_remove(session->priv->contents, content);
+		g_object_unref(content);
+	}
+}
+
+void
+jingle_session_add_pending_content(JingleSession *session, JingleContent* content)
+{
+	session->priv->pending_contents =
+			g_list_append(session->priv->pending_contents, content);
+	jingle_content_set_session(content, session);
+}
+
+void
+jingle_session_remove_pending_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	JingleContent *content = jingle_session_find_pending_content(session, name, creator);
+
+	if (content) {
+		session->priv->pending_contents =
+				g_list_remove(session->priv->pending_contents, content);
+		g_object_unref(content);
+	}
+}
+
+void
+jingle_session_accept_content(JingleSession *session, const gchar *name, const gchar *creator)
+{
+	JingleContent *content = jingle_session_find_pending_content(session, name, creator);
+
+	if (content) {
+		g_object_ref(content);
+		jingle_session_remove_pending_content(session, name, creator);
+		jingle_session_add_content(session, content);
+	}
+}
+
+void
+jingle_session_accept_session(JingleSession *session)
+{
+	session->priv->state = TRUE;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/session.h	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,122 @@
+/**
+ * @file session.h
+ *
+ * purple
+ *
+ * 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
+ */
+
+#ifndef JINGLE_SESSION_H
+#define JINGLE_SESSION_H
+
+#include "iq.h"
+#include "jabber.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_SESSION            (jingle_session_get_type())
+#define JINGLE_SESSION(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_SESSION, JingleSession))
+#define JINGLE_SESSION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_SESSION, JingleSessionClass))
+#define JINGLE_IS_SESSION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_SESSION))
+#define JINGLE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_SESSION))
+#define JINGLE_SESSION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_SESSION, JingleSessionClass))
+
+/** @copydoc _JingleSession */
+typedef struct _JingleSession JingleSession;
+/** @copydoc _JingleSessionClass */
+typedef struct _JingleSessionClass JingleSessionClass;
+/** @copydoc _JingleSessionPrivate */
+typedef struct _JingleSessionPrivate JingleSessionPrivate;
+
+/** The session class */
+struct _JingleSessionClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+};
+
+/** The session class's private data */
+struct _JingleSession
+{
+	GObject parent;                /**< The parent of this object. */
+	JingleSessionPrivate *priv;      /**< The private data of this object. */
+};
+
+struct _JingleContent;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the session class's GType
+ *
+ * @return The session class's GType.
+ */
+GType jingle_session_get_type(void);
+
+JingleSession *jingle_session_create(JabberStream *js, const gchar *sid,
+				     const gchar *local_jid, const gchar *remote_jid,
+				     gboolean is_initiator);
+JabberStream *jingle_session_get_js(JingleSession *session);
+gchar *jingle_session_get_sid(JingleSession *session);
+gchar *jingle_session_get_local_jid(JingleSession *session);
+gchar *jingle_session_get_remote_jid(JingleSession *session);
+gboolean jingle_session_is_initiator(JingleSession *session);
+gboolean jingle_session_get_state(JingleSession *session);
+
+GList *jingle_session_get_contents(JingleSession *session);
+GList *jingle_session_get_pending_contents(JingleSession *session);
+
+JingleSession *jingle_session_find_by_sid(JabberStream *js, const gchar *sid);
+JingleSession *jingle_session_find_by_jid(JabberStream *js, const gchar *jid);
+
+JabberIq *jingle_session_create_ack(JingleSession *session, const xmlnode *jingle);
+xmlnode *jingle_session_to_xml(JingleSession *session, xmlnode *parent, JingleActionType action);
+JabberIq *jingle_session_to_packet(JingleSession *session, JingleActionType action);
+
+void jingle_session_handle_action(JingleSession *session, xmlnode *jingle, JingleActionType action);
+
+#define jingle_session_create_session_accept(session) \
+	jingle_session_to_packet(session, JINGLE_SESSION_ACCEPT)
+#define jingle_session_create_session_info(session) \
+	jingle_session_to_packet(session, JINGLE_SESSION_INFO)
+#define jingle_session_create_session_initiate(session) \
+	jingle_session_to_packet(session, JINGLE_SESSION_INITIATE)
+#define jingle_session_create_session_terminate(session) \
+	jingle_session_to_packet(session, JINGLE_SESSION_TERMINATE)
+
+struct _JingleContent *jingle_session_find_content(JingleSession *session,
+					const gchar *name, const gchar *creator);
+struct _JingleContent *jingle_session_find_pending_content(JingleSession *session,
+					const gchar *name, const gchar *creator);
+
+void jingle_session_add_content(JingleSession *session, struct _JingleContent* content);
+void jingle_session_remove_content(JingleSession *session, const gchar *name, const gchar *creator);
+void jingle_session_add_pending_content(JingleSession *session, struct _JingleContent* content);
+void jingle_session_remove_pending_content(JingleSession *session, const gchar *name, const gchar *creator);
+void jingle_session_accept_content(JingleSession *session, const gchar *name, const gchar *creator);
+void jingle_session_accept_session(JingleSession *session);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_SESSION_H */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/transport.c	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,172 @@
+/**
+ * @file transport.c
+ *
+ * purple
+ *
+ * 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 "transport.h"
+#include "jingle.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct _JingleTransportPrivate
+{
+	void *dummy;
+};
+
+#define JINGLE_TRANSPORT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), JINGLE_TYPE_TRANSPORT, JingleTransportPrivate))
+
+static void jingle_transport_class_init (JingleTransportClass *klass);
+static void jingle_transport_init (JingleTransport *transport);
+static void jingle_transport_finalize (GObject *object);
+static void jingle_transport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+static void jingle_transport_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+JingleTransport *jingle_transport_parse_internal(xmlnode *transport);
+xmlnode *jingle_transport_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action);
+
+static GObjectClass *parent_class = NULL;
+
+enum {
+	PROP_0,
+};
+
+GType
+jingle_transport_get_type()
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(JingleTransportClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) jingle_transport_class_init,
+			NULL,
+			NULL,
+			sizeof(JingleTransport),
+			0,
+			(GInstanceInitFunc) jingle_transport_init,
+			NULL
+		};
+		type = g_type_register_static(G_TYPE_OBJECT, "JingleTransport", &info, 0);
+	}
+	return type;
+}
+
+static void
+jingle_transport_class_init (JingleTransportClass *klass)
+{
+	GObjectClass *gobject_class = (GObjectClass*)klass;
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = jingle_transport_finalize;
+	gobject_class->set_property = jingle_transport_set_property;
+	gobject_class->get_property = jingle_transport_get_property;
+	klass->to_xml = jingle_transport_to_xml_internal;
+	klass->parse = jingle_transport_parse_internal;
+
+	g_type_class_add_private(klass, sizeof(JingleTransportPrivate));
+}
+
+static void
+jingle_transport_init (JingleTransport *transport)
+{
+	transport->priv = JINGLE_TRANSPORT_GET_PRIVATE(transport);
+	memset(transport->priv, 0, sizeof(transport->priv));
+}
+
+static void
+jingle_transport_finalize (GObject *transport)
+{
+	/* JingleTransportPrivate *priv = JINGLE_TRANSPORT_GET_PRIVATE(transport); */
+	purple_debug_info("jingle","jingle_transport_finalize\n");
+
+	parent_class->finalize(transport);
+}
+
+static void
+jingle_transport_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	JingleTransport *transport;
+	g_return_if_fail(JINGLE_IS_TRANSPORT(object));
+
+	transport = JINGLE_TRANSPORT(object);
+
+	switch (prop_id) {
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+jingle_transport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	JingleTransport *transport;
+	g_return_if_fail(JINGLE_IS_TRANSPORT(object));
+	
+	transport = JINGLE_TRANSPORT(object);
+
+	switch (prop_id) {
+		default:	
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);	
+			break;
+	}
+}
+
+JingleTransport *
+jingle_transport_create(const gchar *type)
+{
+	return g_object_new(jingle_get_type(type), NULL);
+}
+
+const gchar *
+jingle_transport_get_transport_type(JingleTransport *transport)
+{
+	return JINGLE_TRANSPORT_GET_CLASS(transport)->transport_type;
+}
+
+JingleTransport *
+jingle_transport_parse_internal(xmlnode *transport)
+{
+	const gchar *type = xmlnode_get_namespace(transport);
+	return jingle_transport_create(type);
+}
+
+xmlnode *
+jingle_transport_to_xml_internal(JingleTransport *transport, xmlnode *content, JingleActionType action)
+{
+	xmlnode *node = xmlnode_new_child(content, "transport");
+	xmlnode_set_namespace(node, jingle_transport_get_transport_type(transport));
+	return node;
+}
+
+JingleTransport *
+jingle_transport_parse(xmlnode *transport)
+{
+	const gchar *type = xmlnode_get_namespace(transport);
+	return JINGLE_TRANSPORT_CLASS(g_type_class_ref(jingle_get_type(type)))->parse(transport);
+}
+
+xmlnode *
+jingle_transport_to_xml(JingleTransport *transport, xmlnode *content, JingleActionType action)
+{
+	g_return_val_if_fail(JINGLE_IS_TRANSPORT(transport), NULL);
+	return JINGLE_TRANSPORT_GET_CLASS(transport)->to_xml(transport, content, action);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/jingle/transport.h	Sun Oct 19 04:37:23 2008 +0000
@@ -0,0 +1,95 @@
+/**
+ * @file transport.h
+ *
+ * purple
+ *
+ * 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
+ */
+
+#ifndef JINGLE_TRANSPORT_H
+#define JINGLE_TRANSPORT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "jingle.h"
+#include "xmlnode.h"
+
+G_BEGIN_DECLS
+
+#define JINGLE_TYPE_TRANSPORT            (jingle_transport_get_type())
+#define JINGLE_TRANSPORT(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), JINGLE_TYPE_TRANSPORT, JingleTransport))
+#define JINGLE_TRANSPORT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), JINGLE_TYPE_TRANSPORT, JingleTransportClass))
+#define JINGLE_IS_TRANSPORT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), JINGLE_TYPE_TRANSPORT))
+#define JINGLE_IS_TRANSPORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), JINGLE_TYPE_TRANSPORT))
+#define JINGLE_TRANSPORT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), JINGLE_TYPE_TRANSPORT, JingleTransportClass))
+
+/** @copydoc _JingleTransport */
+typedef struct _JingleTransport JingleTransport;
+/** @copydoc _JingleTransportClass */
+typedef struct _JingleTransportClass JingleTransportClass;
+/** @copydoc _JingleTransportPrivate */
+typedef struct _JingleTransportPrivate JingleTransportPrivate;
+
+/** The transport class */
+struct _JingleTransportClass
+{
+	GObjectClass parent_class;     /**< The parent class. */
+
+	const gchar *transport_type;
+	xmlnode *(*to_xml) (JingleTransport *transport, xmlnode *content, JingleActionType action);
+	JingleTransport *(*parse) (xmlnode *transport);
+};
+
+/** The transport class's private data */
+struct _JingleTransport
+{
+	GObject parent;                /**< The parent of this object. */
+	JingleTransportPrivate *priv;      /**< The private data of this object. */
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Gets the transport class's GType
+ *
+ * @return The transport class's GType.
+ */
+GType jingle_transport_get_type(void);
+
+JingleTransport *jingle_transport_create(const gchar *type);
+const gchar *jingle_transport_get_transport_type(JingleTransport *transport);
+void jingle_transport_add_candidate();
+
+#define jingle_transport_create_transport_accept(session) \
+	jingle_session_to_packet(session, JINGLE_TRANSPORT_ACCEPT)
+#define jingle_transport_create_transport_info(session) \
+	jingle_session_to_packet(session, JINGLE_TRANSPORT_INFO)
+#define jingle_transport_create_transport_replace(session) \
+	jingle_session_to_packet(session, JINGLE_TRANSPORT_REPLACE)
+
+JingleTransport *jingle_transport_parse(xmlnode *transport);
+xmlnode *jingle_transport_to_xml(JingleTransport *transport, xmlnode *content, JingleActionType action);
+
+#ifdef __cplusplus
+}
+#endif
+
+G_END_DECLS
+
+#endif /* JINGLE_TRANSPORT_H */
+
--- a/pidgin/gtkconv.c	Fri Sep 05 02:18:15 2008 +0000
+++ b/pidgin/gtkconv.c	Sun Oct 19 04:37:23 2008 +0000
@@ -7770,6 +7770,8 @@
 	}
 
 	gtkmedia = pidgin_media_new(media);
+	g_object_unref(media);
+
 	gtk_box_pack_start(GTK_BOX(gtkconv->topvbox), gtkmedia, FALSE, FALSE, 0);
 	gtk_widget_show(gtkmedia);
 	g_signal_connect(G_OBJECT(gtkmedia), "message", G_CALLBACK(pidgin_gtkmedia_message_cb), conv);
--- a/pidgin/gtkmedia.c	Fri Sep 05 02:18:15 2008 +0000
+++ b/pidgin/gtkmedia.c	Sun Oct 19 04:37:23 2008 +0000
@@ -249,6 +249,7 @@
 pidgin_media_finalize (GObject *media)
 {
 	PidginMedia *gtkmedia = PIDGIN_MEDIA(media);
+	purple_debug_info("gtkmedia", "pidgin_media_finalize\n");
 	if (gtkmedia->priv->media) {
 		pidgin_media_disconnect_levels(gtkmedia->priv->media, gtkmedia);
 		g_object_unref(gtkmedia->priv->media);