changeset 30888:9fccebaac7c5

merge of 'a05232ffa430529ccf3e31b0b2006324a28ef9bd' and 'df86722dad2f9f8e11e9f107a7ff3abc05b98594'
author ivan.komarov@soc.pidgin.im
date Mon, 01 Nov 2010 07:12:08 +0000
parents 204827933880 (current diff) c9fe019788a6 (diff)
children d87c49564564 d9e21370fca0 7c5a78a2cc0d
files libpurple/protocols/msn/dialog.c libpurple/protocols/msn/dialog.h libpurple/protocols/msn/sync.c libpurple/protocols/msn/sync.h
diffstat 65 files changed, 4123 insertions(+), 3106 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Nov 01 07:10:36 2010 +0000
+++ b/ChangeLog	Mon Nov 01 07:12:08 2010 +0000
@@ -1,6 +1,16 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
 version 2.7.6 (??/??/????):
+	MSN:
+	* Added support for MSNP16:
+	* Added support for "Multi Points of Presence" (MPOP), which let users
+	  to sign on multiple places at the same time.
+	* Added extended capabilities support (no one implemented).
+	* Merged the work done on the Google SoC:
+	* A major rewrite of SLP code:
+	* Reworked the data transfer architecture.
+	  (http://developer.pidgin.im/wiki/SlpArchitecture)
+	* A lot of love to MSN prpl.
 
 version 2.7.5 (10/31/2010):
 	General:
--- a/NEWS	Mon Nov 01 07:10:36 2010 +0000
+++ b/NEWS	Mon Nov 01 07:12:08 2010 +0000
@@ -3,6 +3,13 @@
 Our development blog is available at: http://planet.pidgin.im
 
 2.7.6 (??/??/????):
+	Jorge: In this release I have merged two branches where I have spent
+	most of my time in the last months, the MSNP16 and SLP-rewrite.  I 
+	hope you all will enjoy the hability to be connected on multiple
+	instances at the same time. I also hope that the SLP rewrite
+	fix a lot of old bugs that we have in the tracker. I am really 
+	happy with this rewrite because there was untoched code from almost
+	5 years ago. I hope you like this release!
 
 2.7.5 (10/31/2010):
 	John: A bugfix release for all of you!  This time we fixed a bunch of
--- a/libpurple/protocols/msn/Makefile.am	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Mon Nov 01 07:12:08 2010 +0000
@@ -12,8 +12,6 @@
 	command.h \
 	contact.c\
 	contact.h\
-	dialog.c \
-	dialog.h \
 	directconn.c \
 	directconn.h \
 	error.c \
@@ -36,6 +34,8 @@
 	object.h \
 	oim.c\
 	oim.h\
+	p2p.c \
+	p2p.h \
 	page.c \
 	page.h \
 	servconn.c \
@@ -50,14 +50,16 @@
 	slplink.h \
 	slpmsg.c \
 	slpmsg.h \
+	slpmsg_part.c \
+	slpmsg_part.h \
 	soap.c \
 	soap.h \
 	state.c \
 	state.h \
+	sbconn.c \
+	sbconn.h \
 	switchboard.c \
 	switchboard.h \
-	sync.c \
-	sync.h \
 	table.c \
 	table.h \
 	transaction.c \
@@ -66,6 +68,8 @@
 	user.h \
 	userlist.c \
 	userlist.h \
+	xfer.c \
+	xfer.h \
 	msnutils.c \
 	msnutils.h
 
--- a/libpurple/protocols/msn/Makefile.mingw	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.mingw	Mon Nov 01 07:12:08 2010 +0000
@@ -40,7 +40,6 @@
 C_SRC =			cmdproc.c \
 			command.c \
 			contact.c\
-			dialog.c \
 			directconn.c \
 			error.c \
 			group.c \
@@ -52,6 +51,7 @@
 			notification.c \
 			object.c \
 			oim.c\
+			p2p.c \
 			page.c \
 			servconn.c \
 			session.c \
@@ -59,14 +59,16 @@
 			slpcall.c \
 			slplink.c \
 			slpmsg.c \
+			slpmsg_part.c \
 			soap.c\
 			state.c \
+			sbconn.c \
 			switchboard.c \
-			sync.c \
 			table.c \
 			transaction.c \
 			user.c \
 			userlist.c \
+			xfer.c \
 			msnutils.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
--- a/libpurple/protocols/msn/cmdproc.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,8 +21,12 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+
 #include "cmdproc.h"
+#include "error.h"
 
 MsnCmdProc *
 msn_cmdproc_new(MsnSession *session)
@@ -122,7 +126,8 @@
 		return;
 	}
 
-	msn_history_add(cmdproc->history, trans);
+	if (trans->saveable)
+		msn_history_add(cmdproc->history, trans);
 
 	data = msn_transaction_to_string(trans);
 
@@ -155,75 +160,6 @@
 }
 
 void
-msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command,
-					   const char *format, ...)
-{
-	MsnServConn *servconn;
-	char *data;
-	char *params = NULL;
-	va_list arg;
-	size_t len;
-
-	g_return_if_fail(cmdproc != NULL);
-	g_return_if_fail(command != NULL);
-
-	servconn = cmdproc->servconn;
-
-	if (!servconn->connected)
-		return;
-
-	if (format != NULL)
-	{
-		va_start(arg, format);
-		params = g_strdup_vprintf(format, arg);
-		va_end(arg);
-	}
-
-	if (params != NULL)
-		data = g_strdup_printf("%s %s\r\n", command, params);
-	else
-		data = g_strdup_printf("%s\r\n", command);
-
-	g_free(params);
-
-	len = strlen(data);
-
-	show_debug_cmd(cmdproc, FALSE, data);
-
-	msn_servconn_write(servconn, data, len);
-
-	g_free(data);
-}
-
-void
-msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command,
-				 const char *format, ...)
-{
-	MsnTransaction *trans;
-	va_list arg;
-
-	g_return_if_fail(cmdproc != NULL);
-	g_return_if_fail(command != NULL);
-
-	if (!cmdproc->servconn->connected)
-		return;
-
-	trans = g_new0(MsnTransaction, 1);
-
-	trans->cmdproc = cmdproc;
-	trans->command = g_strdup(command);
-
-	if (format != NULL)
-	{
-		va_start(arg, format);
-		trans->params = g_strdup_vprintf(format, arg);
-		va_end(arg);
-	}
-
-	msn_cmdproc_send_trans(cmdproc, trans);
-}
-
-void
 msn_cmdproc_process_payload(MsnCmdProc *cmdproc, char *payload,
 							int payload_len)
 {
@@ -243,58 +179,71 @@
 msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	MsnMsgTypeCb cb;
-	const char *messageId = NULL;
+	const char *message_id = NULL;
 
 	/* Multi-part messages */
-	if ((messageId = msn_message_get_attr(msg, "Message-ID")) != NULL) {
-		const char *chunk_text = msn_message_get_attr(msg, "Chunks");
+	message_id = msn_message_get_header_value(msg, "Message-ID");
+	if (message_id != NULL) {
+		/* This is the first in a series of chunks */
+
+		const char *chunk_text = msn_message_get_header_value(msg, "Chunks");
 		guint chunk;
 		if (chunk_text != NULL) {
 			chunk = strtol(chunk_text, NULL, 10);
-			/* 1024 chunks of ~1300 bytes is ~1MB, which seems OK to prevent 
+			/* 1024 chunks of ~1300 bytes is ~1MB, which seems OK to prevent
 			   some random client causing pidgin to hog a ton of memory.
 			   Probably should figure out the maximum that the official client
 			   actually supports, though. */
 			if (chunk > 0 && chunk < 1024) {
 				msg->total_chunks = chunk;
 				msg->received_chunks = 1;
-				g_hash_table_insert(cmdproc->multiparts, (gpointer)messageId, msn_message_ref(msg));
-				purple_debug_info("msn", "Received chunked message, messageId: '%s', total chunks: %d\n",
-				                  messageId, chunk);
+				g_hash_table_insert(cmdproc->multiparts, (gpointer)message_id, msn_message_ref(msg));
+				purple_debug_info("msn", "Received chunked message, message_id: '%s', total chunks: %d\n",
+				                  message_id, chunk);
 			} else {
-				purple_debug_error("msn", "MessageId '%s' has too many chunks: %d\n", messageId, chunk);
+				purple_debug_error("msn", "MessageId '%s' has too many chunks: %d\n", message_id, chunk);
 			}
 			return;
 		} else {
-			chunk_text = msn_message_get_attr(msg, "Chunk");
+			chunk_text = msn_message_get_header_value(msg, "Chunk");
 			if (chunk_text != NULL) {
-				MsnMessage *first = g_hash_table_lookup(cmdproc->multiparts, messageId);
+				/* This is one chunk in a series of chunks */
+
+				MsnMessage *first = g_hash_table_lookup(cmdproc->multiparts, message_id);
 				chunk = strtol(chunk_text, NULL, 10);
 				if (first == NULL) {
 					purple_debug_error("msn",
-					                   "Unable to find first chunk of messageId '%s' to correspond with chunk %d.\n",
-					                   messageId, chunk+1);
-				} else if (first->received_chunks == chunk) {
-					/* Chunk is from 1 to total-1 (doesn't count first one) */
-					purple_debug_info("msn", "Received chunk %d of %d, messageId: '%s'\n",
-					                  chunk+1, first->total_chunks, messageId);
-					first->body = g_realloc(first->body, first->body_len + msg->body_len);
-					memcpy(first->body + first->body_len, msg->body, msg->body_len);
-					first->body_len += msg->body_len;
-					first->received_chunks++;
-					if (first->received_chunks != first->total_chunks)
-						return;
-					else
-						/* We're done! Send it along... The caller takes care of
-						   freeing the old one. */
-						msg = first;
-				} else {
-					/* TODO: Can you legitimately receive chunks out of order? */
-					g_hash_table_remove(cmdproc->multiparts, messageId);
+					                   "Unable to find first chunk of message_id '%s' to correspond with chunk %d.\n",
+					                   message_id, chunk + 1);
+				} else if (first->received_chunks != chunk) {
+					/*
+					 * We received an out of order chunk number (i.e. not the
+					 * next one in the sequence).  Not sure if this can happen
+					 * legitimately, but we definitely don't handle it right
+					 * now.
+					 */
+					g_hash_table_remove(cmdproc->multiparts, message_id);
 					return;
 				}
+
+				/* Chunk is from 1 to total-1 (doesn't count first one) */
+				purple_debug_info("msn", "Received chunk %d of %d, message_id: '%s'\n",
+						chunk + 1, first->total_chunks, message_id);
+				first->body = g_realloc(first->body, first->body_len + msg->body_len);
+				memcpy(first->body + first->body_len, msg->body, msg->body_len);
+				first->body_len += msg->body_len;
+				first->received_chunks++;
+				if (first->received_chunks != first->total_chunks)
+					/* We're waiting for more chunks */
+					return;
+
+				/*
+				 * We have all the chunks for this message, great!  Send
+				 * it along... The caller takes care of freeing the old one.
+				 */
+				msg = first;
 			} else {
-				purple_debug_error("msn", "Received MessageId '%s' with no chunk number!\n", messageId);
+				purple_debug_error("msn", "Received MessageId '%s' with no chunk number!\n", message_id);
 			}
 		}
 	}
@@ -314,8 +263,8 @@
 		purple_debug_warning("msn", "Unhandled content-type '%s'\n",
 						   msn_message_get_content_type(msg));
 
-	if (messageId != NULL)
-		g_hash_table_remove(cmdproc->multiparts, messageId);
+	if (message_id != NULL)
+		g_hash_table_remove(cmdproc->multiparts, message_id);
 }
 
 void
--- a/libpurple/protocols/msn/cmdproc.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/cmdproc.h	Mon Nov 01 07:12:08 2010 +0000
@@ -27,7 +27,6 @@
 typedef struct _MsnCmdProc MsnCmdProc;
 
 #include "command.h"
-#include "error.h"
 #include "history.h"
 #include "servconn.h"
 #include "session.h"
@@ -51,18 +50,45 @@
 	void *data; /**< Extra data, like the switchboard. */
 };
 
+/**
+ * Creates a MsnCmdProc structure.
+ *
+ * @param session 	The session to associate with.
+ *
+ * @return A new MsnCmdProc structure.
+ */
 MsnCmdProc *msn_cmdproc_new(MsnSession *session);
+
+/**
+ * Destroys an MsnCmdProc.
+ *
+ * @param cmdproc 	The object structure.
+ */
 void msn_cmdproc_destroy(MsnCmdProc *cmdproc);
 
+/**
+ * Process the queued transactions.
+ *
+ * @param cmdproc 	The MsnCmdProc.
+ */
 void msn_cmdproc_process_queue(MsnCmdProc *cmdproc);
 
+/**
+ * Sends transaction using this servconn.
+ *
+ * @param cmdproc 	The MsnCmdProc to be used.
+ * @param trans 	The MsnTransaction to be sent.
+ */
 void msn_cmdproc_send_trans(MsnCmdProc *cmdproc, MsnTransaction *trans);
+
+/**
+ * Add a transaction to the queue to be processed latter.
+ *
+ * @param cmdproc 	The MsnCmdProc in which the transaction will be queued.
+ * @param trans 	The MsnTransaction to be queued.
+ */
 void msn_cmdproc_queue_trans(MsnCmdProc *cmdproc,
 							 MsnTransaction *trans);
-void msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command,
-					  const char *format, ...);
-void msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command,
-							const char *format, ...);
 
 void msn_cmdproc_process_msg(MsnCmdProc *cmdproc,
 							 MsnMessage *msg);
--- a/libpurple/protocols/msn/command.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/command.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,7 +21,8 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+#include "internal.h"
+
 #include "command.h"
 
 static gboolean
--- a/libpurple/protocols/msn/command.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/command.h	Mon Nov 01 07:12:08 2010 +0000
@@ -54,9 +54,39 @@
 	void *payload_cbdata;
 };
 
+/**
+ * Create a command object from the incoming string and ref it.
+ *
+ * @param string 	The incoming string.
+ *
+ * @return 			A MsnCommand object.
+ */
 MsnCommand *msn_command_from_string(const char *string);
+
+/**
+ * Destroy a MsnCommand object if its ref count is zero, otherwise
+ * just unref it.
+ *
+ * @param cmd 	The MsnCommand to be destroyed.
+ */
 void msn_command_destroy(MsnCommand *cmd);
+
+/**
+ * Increment the ref count.
+ *
+ * @param cmd 	The MsnCommand to be ref.
+ *
+ * @return 		The ref command.
+ */
 MsnCommand *msn_command_ref(MsnCommand *cmd);
+
+/**
+ * Decrement the ref count. If the count goes to 0, destroy it.
+ *
+ * @param cmd	The MsnCommand to be unref.
+ *
+ * @return 		The ref command.
+ */
 MsnCommand *msn_command_unref(MsnCommand *cmd);
 
 #endif /* MSN_COMMAND_H */
--- a/libpurple/protocols/msn/contact.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/contact.c	Mon Nov 01 07:12:08 2010 +0000
@@ -24,12 +24,16 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA
  */
 
-#include "msn.h"
+#include "internal.h"
+#include "debug.h"
+
 #include "contact.h"
 #include "xmlnode.h"
 #include "group.h"
+#include "msnutils.h"
 #include "soap.h"
 #include "nexus.h"
+#include "user.h"
 
 const char *MsnSoapPartnerScenarioText[] =
 {
@@ -1172,7 +1176,7 @@
 		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL);
 		msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL);
 
-		if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) {
+		if (msn_user_is_in_list(user, MSN_LIST_PL)) {
 			msn_del_contact_from_list(state->session, NULL, state->who, MSN_LIST_PL);
 			return;
 		}
--- a/libpurple/protocols/msn/dialog.c	Mon Nov 01 07:10:36 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/**
- * @file dialog.c Dialog functions
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "msn.h"
-#include "dialog.h"
-
-typedef struct
-{
-	PurpleConnection *gc;
-	char *who;
-	char *group;
-	gboolean add;
-
-} MsnAddRemData;
-
-/* Remove the buddy referenced by the MsnAddRemData before the serverside list is changed.
- * If the buddy will be added, he'll be added back; if he will be removed, he won't be. */
-/* Actually with our MSNP14 code that isn't true yet, he won't be added back :( */
-static void
-msn_complete_sync_issue(MsnAddRemData *data)
-{
-	PurpleBuddy *buddy;
-	PurpleGroup *group = NULL;
-
-	if (data->group != NULL)
-		group = purple_find_group(data->group);
-
-	if (group != NULL)
-		buddy = purple_find_buddy_in_group(purple_connection_get_account(data->gc), data->who, group);
-	else
-		buddy = purple_find_buddy(purple_connection_get_account(data->gc), data->who);
-
-	if (buddy != NULL)
-		purple_blist_remove_buddy(buddy);
-}
-
-
-static void
-msn_add_cb(MsnAddRemData *data)
-{
-#if 0
-	/* this *should* be necessary !! */
-	msn_complete_sync_issue(data);
-#endif
-
-	if (g_list_find(purple_connections_get_all(), data->gc) != NULL)
-	{
-		MsnSession *session = data->gc->proto_data;
-		MsnUserList *userlist = session->userlist;
-
-		msn_userlist_add_buddy(userlist, data->who, data->group);
-	}
-
-	g_free(data->group);
-	g_free(data->who);
-	g_free(data);
-}
-
-static void
-msn_rem_cb(MsnAddRemData *data)
-{
-	msn_complete_sync_issue(data);
-
-	if (g_list_find(purple_connections_get_all(), data->gc) != NULL)
-	{
-		MsnSession *session = data->gc->proto_data;
-		MsnUserList *userlist = session->userlist;
-
-		if (data->group == NULL) {
-			msn_userlist_rem_buddy_from_list(userlist, data->who, MSN_LIST_FL);
-		} else {
-			g_free(data->group);
-		}
-	}
-
-	g_free(data->who);
-	g_free(data);
-}
-
-void
-msn_show_sync_issue(MsnSession *session, const char *passport,
-					const char *group_name)
-{
-	PurpleConnection *gc;
-	PurpleAccount *account;
-	MsnAddRemData *data;
-	char *msg, *reason;
-
-	account = session->account;
-	gc = purple_account_get_connection(account);
-
-	data        = g_new0(MsnAddRemData, 1);
-	data->who   = g_strdup(passport);
-	data->group = g_strdup(group_name);
-	data->gc    = gc;
-
-	msg = g_strdup_printf(_("Buddy list synchronization issue in %s (%s)"),
-						  purple_account_get_username(account),
-						  purple_account_get_protocol_name(account));
-
-	if (group_name != NULL)
-	{
-		reason = g_strdup_printf(_("%s on the local list is "
-								   "inside the group \"%s\" but not on "
-								   "the server list. "
-								   "Do you want this buddy to be added?"),
-								 passport, group_name);
-	}
-	else
-	{
-		reason = g_strdup_printf(_("%s is on the local list but "
-								   "not on the server list. "
-								   "Do you want this buddy to be added?"),
-								 passport);
-	}
-
-	purple_request_action(gc, NULL, msg, reason, PURPLE_DEFAULT_ACTION_NONE,
-						purple_connection_get_account(gc), data->who, NULL,
-						data, 2,
-						_("Yes"), G_CALLBACK(msn_add_cb),
-						_("No"), G_CALLBACK(msn_rem_cb));
-
-	g_free(reason);
-	g_free(msg);
-}
--- a/libpurple/protocols/msn/dialog.h	Mon Nov 01 07:10:36 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/**
- * @file dialog.h Dialog functions
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-#ifndef MSN_DIALOG_H
-#define MSN_DIALOG_H
-
-void msn_show_sync_issue(MsnSession *session, const char *passport,
-						 const char *group_name);
-
-#endif /* MSN_DIALOG_H */
--- a/libpurple/protocols/msn/directconn.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/directconn.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,30 +21,20 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+
 #include "msn.h"
 #include "directconn.h"
 
 #include "slp.h"
 #include "slpmsg.h"
+#include "p2p.h"
 
-#pragma pack(push,1)
-typedef struct {
-	guint32 session_id;
-	guint32 seq_id;
-	guint64 offset;
-	guint64 total_size;
-	guint32 length;
-	guint32 flags;
-	guint32 ack_id;
-	guint32 ack_uid;
-	guint64 ack_size;
-/*	guint8  body[1]; */
-} MsnDcContext;
-#pragma pack(pop)
-
-#define DC_PACKET_HEADER_SIZE sizeof(MsnDcContext)
 #define DC_MAX_BODY_SIZE      8*1024
-#define DC_MAX_PACKET_SIZE    (DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE)
+#define DC_MAX_PACKET_SIZE    (P2P_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE)
 
 static void
 msn_dc_calculate_nonce_hash(MsnDirectConnNonceType type,
@@ -121,8 +111,12 @@
 {
 	g_free(p->data);
 
+#if 0
 	if (p->msg)
 		msn_message_unref(p->msg);
+#endif
+	if (p->part)
+		msn_slpmsgpart_unref(p->part);
 
 	g_free(p);
 }
@@ -191,7 +185,7 @@
 	if (slplink) {
 		slplink->dc = NULL;
 		if (slplink->swboard == NULL)
-			msn_slplink_destroy(slplink);
+			msn_slplink_unref(slplink);
 	}
 
 	g_free(dc->msg_body);
@@ -353,7 +347,7 @@
 		if (queue) {
 			while (!g_queue_is_empty(queue)) {
 				MsnDirectConnPacket *p = g_queue_pop_head(queue);
-				msn_slplink_send_msg(slplink, p->msg);
+				msn_slplink_send_msgpart(slplink, (MsnSlpMessage*)p->part->ack_data);
 				msn_dc_destroy_packet(p);
 			}
 			g_queue_free(queue);
@@ -363,51 +357,6 @@
 }
 
 static void
-msn_dc_parse_binary_header(MsnDirectConn *dc)
-{
-	MsnSlpHeader *h;
-	MsnDcContext *context;
-
-	g_return_if_fail(dc != NULL);
-
-	h = &dc->header;
-	/* Skip packet size */
-	context = (MsnDcContext *)(dc->in_buffer + 4);
-
-	h->session_id = GUINT32_FROM_LE(context->session_id);
-	h->id = GUINT32_FROM_LE(context->seq_id);
-	h->offset = GUINT64_FROM_LE(context->offset);
-	h->total_size = GUINT64_FROM_LE(context->total_size);
-	h->length = GUINT32_FROM_LE(context->length);
-	h->flags = GUINT32_FROM_LE(context->flags);
-	h->ack_id = GUINT32_FROM_LE(context->ack_id);
-	h->ack_sub_id = GUINT32_FROM_LE(context->ack_uid);
-	h->ack_size = GUINT64_FROM_LE(context->ack_size);
-}
-
-static const gchar *
-msn_dc_serialize_binary_header(MsnDirectConn *dc) {
-	MsnSlpHeader *h;
-	static MsnDcContext bin_header;
-
-	g_return_val_if_fail(dc != NULL, NULL);
-
-	h = &dc->header;
-
-	bin_header.session_id = GUINT32_TO_LE(h->session_id);
-	bin_header.seq_id = GUINT32_TO_LE(h->id);
-	bin_header.offset = GUINT64_TO_LE(h->offset);
-	bin_header.total_size = GUINT64_TO_LE(h->total_size);
-	bin_header.length = GUINT32_TO_LE(h->length);
-	bin_header.flags = GUINT32_TO_LE(h->flags);
-	bin_header.ack_id = GUINT32_TO_LE(h->ack_id);
-	bin_header.ack_uid = GUINT32_TO_LE(h->ack_sub_id);
-	bin_header.ack_size = GUINT64_TO_LE(h->ack_size);
-
-	return (const gchar *)&bin_header;
-}
-
-static void
 msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond)
 {
 	MsnDirectConn *dc = data;
@@ -502,10 +451,11 @@
 {
 	const gchar *h;
 
-	h = msn_dc_serialize_binary_header(dc);
-	memcpy(p->data, h, DC_PACKET_HEADER_SIZE);
+	h = (gchar*)  msn_p2p_header_to_wire(&dc->header);
 
-	memcpy(p->data + offsetof(MsnDcContext, ack_id), dc->nonce, 16);
+	memcpy(p->data, h, P2P_PACKET_HEADER_SIZE);
+
+	memcpy(p->data + offsetof(MsnP2PHeader, ack_id), dc->nonce, 16);
 
 	msn_dc_enqueue_packet(dc, p);
 }
@@ -515,7 +465,7 @@
 {
 	MsnDirectConnPacket *p;
 
-	p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE);
+	p = msn_dc_new_packet(P2P_PACKET_HEADER_SIZE);
 
 	dc->header.session_id = 0;
 	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
@@ -532,7 +482,7 @@
 {
 	MsnDirectConnPacket *p;
 
-	p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE);
+	p = msn_dc_new_packet(P2P_PACKET_HEADER_SIZE);
 
 	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
 	dc->header.length = 0;
@@ -546,10 +496,10 @@
 	guchar nonce[16];
 	gchar  nonce_hash[37];
 
-	if (packet_length != DC_PACKET_HEADER_SIZE)
+	if (packet_length != P2P_PACKET_HEADER_SIZE)
 		return FALSE;
 
-	memcpy(nonce, dc->in_buffer + 4 + offsetof(MsnDcContext, ack_id), 16);
+	memcpy(nonce, dc->in_buffer + 4 + offsetof(MsnP2PHeader, ack_id), 16);
 
 	if (dc->nonce_type == DC_NONCE_PLAIN) {
 		if (memcmp(dc->nonce, nonce, 16) == 0) {
@@ -589,6 +539,14 @@
 static void
 msn_dc_send_packet_cb(MsnDirectConnPacket *p)
 {
+	if (p->part != NULL && p->part->ack_cb != NULL)
+		p->part->ack_cb(p->part, p->part->ack_data);
+}
+
+#if 0
+static void
+msn_dc_send_packet_cb(MsnDirectConnPacket *p)
+{
 	if (p->msg != NULL && p->msg->ack_cb != NULL)
 		p->msg->ack_cb(p->msg, p->msg->ack_data);
 }
@@ -599,21 +557,42 @@
 	MsnDirectConnPacket *p;
 	guint32 length;
 
-	length = msg->body_len + DC_PACKET_HEADER_SIZE;
+	length = msg->body_len + P2P_PACKET_HEADER_SIZE;
 	p = msn_dc_new_packet(length);
 
-	memcpy(p->data, &msg->msnslp_header, DC_PACKET_HEADER_SIZE);
-	memcpy(p->data + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len);
+	memcpy(p->data, msg->slpmsg->header, P2P_PACKET_HEADER_SIZE);
+	memcpy(p->data + P2P_PACKET_HEADER_SIZE, msg->body, msg->body_len);
 
 	p->sent_cb = msn_dc_send_packet_cb;
 	p->msg = msn_message_ref(msg);
 
 	msn_dc_enqueue_packet(dc, p);
 }
+#endif
+
+void
+msn_dc_enqueue_part(MsnDirectConn *dc, MsnSlpMessagePart *part)
+{
+	MsnDirectConnPacket *p;
+	guint32 length;
+
+	length = part->size + P2P_PACKET_HEADER_SIZE;
+	p = msn_dc_new_packet(length);
+
+	memcpy(p->data, part->header, P2P_PACKET_HEADER_SIZE);
+	memcpy(p->data + P2P_PACKET_HEADER_SIZE, part->buffer, part->size);
+
+	p->sent_cb = msn_dc_send_packet_cb;
+	p->part = part;
+
+	msn_dc_enqueue_packet(dc, p);
+}
 
 static int
 msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length)
 {
+	MsnSlpMessagePart *part;
+
 	g_return_val_if_fail(dc != NULL, DC_PROCESS_ERROR);
 
 	switch (dc->state) {
@@ -650,12 +629,9 @@
 		break;
 
 	case DC_STATE_ESTABLISHED:
-		msn_slplink_process_msg(
-			dc->slplink,
-			&dc->header,
-			dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE,
-			dc->header.length
-		);
+
+		part = msn_slpmsgpart_new_from_data(dc->in_buffer + 4, dc->header.length);
+		msn_slplink_process_msg(dc->slplink, part);
 
 		/*
 		if (dc->num_calls == 0) {
@@ -727,7 +703,15 @@
 			return;
 
 		if (dc->state != DC_STATE_FOO) {
-			msn_dc_parse_binary_header(dc);
+			MsnP2PHeader *context;
+			MsnP2PHeader *h;
+			
+			/* Skip packet size */
+			context = (MsnP2PHeader *)(dc->in_buffer + 4);
+
+			h = msn_p2p_header_from_wire(context);
+			memcpy(&dc->header, h, P2P_PACKET_HEADER_SIZE);
+			g_free(h);
 		}
 
 		switch (msn_dc_process_packet(dc, packet_length)) {
--- a/libpurple/protocols/msn/directconn.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/directconn.h	Mon Nov 01 07:12:08 2010 +0000
@@ -30,10 +30,13 @@
 #include "proxy.h"
 #include "circbuffer.h"
 
-#include "msg.h"
 #include "slp.h"
 #include "slplink.h"
 #include "slpmsg.h"
+#include "slpmsg_part.h"
+#include "p2p.h"
+
+#define MSN_DCCONN_MAX_SIZE 1352
 
 typedef enum
 {
@@ -68,7 +71,7 @@
 	guchar      *data;
 
 	void        (*sent_cb)(struct _MsnDirectConnPacket*);
-	MsnMessage  *msg;
+	MsnSlpMessagePart *part;
 };
 
 struct _MsnDirectConn
@@ -100,7 +103,7 @@
 	GQueue  *out_queue; /**< The outgoing packet queue */
 	int     msg_pos;    /**< The position of next byte to be sent in the actual packet */
 
-	MsnSlpHeader    header; /**< SLP header for parsing / serializing */
+	MsnP2PHeader    header; /**< SLP header for parsing / serializing */
 
 	/** The callback used for sending information to the peer about the opened socket */
 	void (*send_connection_info_msg_cb)(MsnDirectConn *);
@@ -124,8 +127,12 @@
 /*
  * Queues an MSN message to be sent via direct connection.
  */
+#if 0
 void
 msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg);
+#endif
+void
+msn_dc_enqueue_part(MsnDirectConn *dc, MsnSlpMessagePart *part);
 
 /*
  * Creates, initializes, and returns a new MsnDirectConn structure.
--- a/libpurple/protocols/msn/error.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/error.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,9 +21,23 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+/* Masca: can we get rid of the sync issue dialog? */
+#include "request.h"
+
 #include "error.h"
 
+typedef struct
+{
+	PurpleConnection *gc;
+	char *who;
+	char *group;
+	gboolean add;
+
+} MsnAddRemData;
+
 const char *
 msn_error_get_text(unsigned int type, gboolean *debug)
 {
@@ -264,3 +278,115 @@
 	g_free(buf);
 }
 
+/* Remove the buddy referenced by the MsnAddRemData before the serverside list
+ * is changed.  If the buddy will be added, he'll be added back; if he will be
+ * removed, he won't be. */
+/* Actually with our MSNP14 code that isn't true yet, he won't be added back :(
+ * */
+static void
+msn_complete_sync_issue(MsnAddRemData *data)
+{
+	PurpleBuddy *buddy;
+	PurpleGroup *group = NULL;
+
+	if (data->group != NULL)
+		group = purple_find_group(data->group);
+
+	if (group != NULL)
+		buddy = purple_find_buddy_in_group(purple_connection_get_account(data->gc), data->who, group);
+	else
+		buddy = purple_find_buddy(purple_connection_get_account(data->gc), data->who);
+
+	if (buddy != NULL)
+		purple_blist_remove_buddy(buddy);
+}
+
+
+static void
+msn_add_cb(MsnAddRemData *data)
+{
+#if 0
+	/* this *should* be necessary !! */
+	msn_complete_sync_issue(data);
+#endif
+
+	if (g_list_find(purple_connections_get_all(), data->gc) != NULL)
+	{
+		MsnSession *session = data->gc->proto_data;
+		MsnUserList *userlist = session->userlist;
+
+		msn_userlist_add_buddy(userlist, data->who, data->group);
+	}
+
+	g_free(data->group);
+	g_free(data->who);
+	g_free(data);
+}
+
+static void
+msn_rem_cb(MsnAddRemData *data)
+{
+	msn_complete_sync_issue(data);
+
+	if (g_list_find(purple_connections_get_all(), data->gc) != NULL)
+	{
+		MsnSession *session = data->gc->proto_data;
+		MsnUserList *userlist = session->userlist;
+
+		if (data->group == NULL) {
+			msn_userlist_rem_buddy_from_list(userlist, data->who, MSN_LIST_FL);
+		} else {
+			g_free(data->group);
+		}
+	}
+
+	g_free(data->who);
+	g_free(data);
+}
+
+void
+msn_error_sync_issue(MsnSession *session, const char *passport,
+					const char *group_name)
+{
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	MsnAddRemData *data;
+	char *msg, *reason;
+
+	account = session->account;
+	gc = purple_account_get_connection(account);
+
+	data        = g_new0(MsnAddRemData, 1);
+	data->who   = g_strdup(passport);
+	data->group = g_strdup(group_name);
+	data->gc    = gc;
+
+	msg = g_strdup_printf(_("Buddy list synchronization issue in %s (%s)"),
+						  purple_account_get_username(account),
+						  purple_account_get_protocol_name(account));
+
+	if (group_name != NULL)
+	{ 
+		reason = g_strdup_printf(_("%s on the local list is "
+								   "inside the group \"%s\" but not on "
+								   "the server list. "
+								   "Do you want this buddy to be added?"),
+								 passport, group_name);
+	}
+	else
+	{ 
+		reason = g_strdup_printf(_("%s is on the local list but "
+								   "not on the server list. "
+								   "Do you want this buddy to be added?"),
+								 passport);
+	}
+
+	purple_request_action(gc, NULL, msg, reason, PURPLE_DEFAULT_ACTION_NONE,
+						purple_connection_get_account(gc), data->who, NULL,
+						data, 2,
+						_("Yes"), G_CALLBACK(msn_add_cb),
+						_("No"), G_CALLBACK(msn_rem_cb));
+
+	g_free(reason);
+	g_free(msg);
+}
--- a/libpurple/protocols/msn/error.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/error.h	Mon Nov 01 07:12:08 2010 +0000
@@ -44,4 +44,14 @@
  */
 void msn_error_handle(MsnSession *session, unsigned int type);
 
+/**
+ * Show the sync issue in a dialog using request api
+ *
+ * @param sesion 		MsnSession associated to this error.
+ * @param passport 		The passport associated with the error.
+ * @param group_name 	The group in the buddy is suppoused to be
+ */
+void msn_error_sync_issue(MsnSession *session, const char *passport,
+						 const char *group_name);
+
 #endif /* MSN_ERROR_H */
--- a/libpurple/protocols/msn/group.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/group.h	Mon Nov 01 07:12:08 2010 +0000
@@ -26,7 +26,7 @@
 
 typedef struct _MsnGroup  MsnGroup;
 
-#include <stdio.h>
+#include "internal.h"
 
 #include "session.h"
 #include "user.h"
--- a/libpurple/protocols/msn/history.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/history.h	Mon Nov 01 07:12:08 2010 +0000
@@ -24,6 +24,8 @@
 #ifndef MSN_HISTORY_H
 #define MSN_HISTORY_H
 
+#include "internal.h"
+
 typedef struct _MsnHistory MsnHistory;
 
 #include "transaction.h"
--- a/libpurple/protocols/msn/httpconn.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/httpconn.h	Mon Nov 01 07:12:08 2010 +0000
@@ -28,6 +28,7 @@
 
 #include "circbuffer.h"
 #include "servconn.h"
+#include "session.h"
 
 /**
  * An HTTP Connection.
--- a/libpurple/protocols/msn/msg.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/msg.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,9 +21,15 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "internal.h"
+#include "debug.h"
+
 #include "msn.h"
 #include "msg.h"
 #include "msnutils.h"
+#include "slpmsg.h"
+#include "slpmsg_part.h"
 
 MsnMessage *
 msn_message_new(MsnMsgType type)
@@ -36,7 +42,7 @@
 	if (purple_debug_is_verbose())
 		purple_debug_info("msn", "message new (%p)(%d)\n", msg, type);
 
-	msg->attr_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+	msg->header_table = g_hash_table_new_full(g_str_hash, g_str_equal,
 											g_free, g_free);
 
 	msn_message_ref(msg);
@@ -64,8 +70,9 @@
 	g_free(msg->content_type);
 	g_free(msg->charset);
 
-	g_hash_table_destroy(msg->attr_table);
-	g_list_free(msg->attr_list);
+	g_hash_table_destroy(msg->header_table);
+	g_list_free(msg->header_list);
+	msn_slpmsgpart_destroy(msg->part);
 
 	g_free(msg);
 }
@@ -112,11 +119,11 @@
 
 	msg = msn_message_new(MSN_MSG_TEXT);
 	msg->retries = 1;
-	msn_message_set_attr(msg, "User-Agent", PACKAGE_NAME "/" VERSION);
+	msn_message_set_header(msg, "User-Agent", PACKAGE_NAME "/" VERSION);
 	msn_message_set_content_type(msg, "text/plain");
 	msn_message_set_charset(msg, "UTF-8");
 	msn_message_set_flag(msg, 'A');
-	msn_message_set_attr(msg, "X-MMS-IM-Format",
+	msn_message_set_header(msg, "X-MMS-IM-Format",
 						 "FN=Segoe%20UI; EF=; CO=0; CS=1;PF=0");
 
 	message_cr = purple_str_add_cr(message);
@@ -133,7 +140,7 @@
 
 	msg = msn_message_new(MSN_MSG_SLP);
 
-	msn_message_set_attr(msg, "User-Agent", NULL);
+	msn_message_set_header(msg, "User-Agent", NULL);
 
 	msg->msnslp_message = TRUE;
 
@@ -157,46 +164,6 @@
 }
 
 void
-msn_message_parse_slp_body(MsnMessage *msg, const char *body, size_t len)
-{
-	MsnSlpHeader header;
-	const char *tmp;
-	int body_len;
-
-	tmp = body;
-
-	if (len < sizeof(header)) {
-		g_return_if_reached();
-	}
-
-	/* Import the header. */
-	memcpy(&header, tmp, sizeof(header));
-	tmp += sizeof(header);
-
-	msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
-	msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
-	msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
-	msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size);
-	msg->msnslp_header.length     = GUINT32_FROM_LE(header.length);
-	msg->msnslp_header.flags      = GUINT32_FROM_LE(header.flags);
-	msg->msnslp_header.ack_id     = GUINT32_FROM_LE(header.ack_id);
-	msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id);
-	msg->msnslp_header.ack_size   = GUINT64_FROM_LE(header.ack_size);
-
-	/* Import the body. */
-	body_len = len - (tmp - body);
-	/* msg->body_len = msg->msnslp_header.length; */
-
-	if (body_len > 0) {
-		msg->body_len = len - (tmp - body);
-		msg->body = g_malloc(msg->body_len + 1);
-		memcpy(msg->body, tmp, msg->body_len);
-		msg->body[msg->body_len] = '\0';
-		tmp += body_len;
-	}
-}
-
-void
 msn_message_parse_payload(MsnMessage *msg,
 						  const char *payload, size_t payload_len,
 						  const char *line_dem,const char *body_dem)
@@ -211,7 +178,7 @@
 	memcpy(tmp_base, payload, payload_len);
 	tmp_base[payload_len] = '\0';
 
-	/* Parse the attributes. */
+	/* Find the end of the headers */
 	end = strstr(tmp, body_dem);
 	/* TODO? some clients use \r delimiters instead of \r\n, the official client
 	 * doesn't send such messages, but does handle receiving them. We'll just
@@ -222,8 +189,8 @@
 	}
 	*end = '\0';
 
+	/* Split the headers and parse each one */
 	elems = g_strsplit(tmp, line_dem, 0);
-
 	for (cur = elems; *cur != NULL; cur++)
 	{
 		const char *key, *value;
@@ -240,7 +207,7 @@
 			if (!strcmp(key, "boundary")) {
 				char *end = strchr(value, '\"');
 				*end = '\0';
-				msn_message_set_attr(msg, key, value);
+				msn_message_set_header(msg, key, value);
 			}
 
 			g_strfreev(tokens);
@@ -278,12 +245,11 @@
 		}
 		else
 		{
-			msn_message_set_attr(msg, key, value);
+			msn_message_set_header(msg, key, value);
 		}
 
 		g_strfreev(tokens);
 	}
-
 	g_strfreev(elems);
 
 	/* Proceed to the end of the "\r\n\r\n" */
@@ -293,70 +259,26 @@
 	content_type = msn_message_get_content_type(msg);
 
 	if (content_type != NULL &&
-		!strcmp(content_type, "application/x-msnmsgrp2p"))
-	{
-		MsnSlpHeader header;
-		MsnSlpFooter footer;
-		int body_len;
-
-		if (payload_len - (tmp - tmp_base) < sizeof(header)) {
-			g_free(tmp_base);
-			g_return_if_reached();
-		}
-
+		!strcmp(content_type, "application/x-msnmsgrp2p")) {
 		msg->msnslp_message = TRUE;
-
-		/* Import the header. */
-		memcpy(&header, tmp, sizeof(header));
-		tmp += sizeof(header);
-
-		msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id);
-		msg->msnslp_header.id         = GUINT32_FROM_LE(header.id);
-		msg->msnslp_header.offset     = GUINT64_FROM_LE(header.offset);
-		msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size);
-		msg->msnslp_header.length     = GUINT32_FROM_LE(header.length);
-		msg->msnslp_header.flags      = GUINT32_FROM_LE(header.flags);
-		msg->msnslp_header.ack_id     = GUINT32_FROM_LE(header.ack_id);
-		msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id);
-		msg->msnslp_header.ack_size   = GUINT64_FROM_LE(header.ack_size);
-
-		body_len = payload_len - (tmp - tmp_base) - sizeof(footer);
+		msg->part = msn_slpmsgpart_new_from_data(tmp, payload_len - (tmp - tmp_base));
+	}
 
-		/* Import the body. */
-		if (body_len > 0) {
-			msg->body_len = body_len;
-			g_free(msg->body);
-			msg->body = g_malloc(msg->body_len + 1);
-			memcpy(msg->body, tmp, msg->body_len);
-			msg->body[msg->body_len] = '\0';
-			tmp += body_len;
-		}
-
-		/* Import the footer. */
-		if (body_len >= 0) {
-			memcpy(&footer, tmp, sizeof(footer));
-			tmp += sizeof(footer);
-			msg->msnslp_footer.value = GUINT32_FROM_BE(footer.value);
-		}
+	if (payload_len - (tmp - tmp_base) > 0) {
+		msg->body_len = payload_len - (tmp - tmp_base);
+		g_free(msg->body);
+		msg->body = g_malloc(msg->body_len + 1);
+		memcpy(msg->body, tmp, msg->body_len);
+		msg->body[msg->body_len] = '\0';
 	}
-	else
-	{
-		if (payload_len - (tmp - tmp_base) > 0) {
-			msg->body_len = payload_len - (tmp - tmp_base);
-			g_free(msg->body);
-			msg->body = g_malloc(msg->body_len + 1);
-			memcpy(msg->body, tmp, msg->body_len);
-			msg->body[msg->body_len] = '\0';
-		}
-		
-		if ((!content_type || !strcmp(content_type, "text/plain"))
+
+	if ((!content_type || !strcmp(content_type, "text/plain"))
 			&& msg->charset == NULL) {
-			char *body = g_convert(msg->body, msg->body_len, "UTF-8",
-			                       "ISO-8859-1", NULL, &msg->body_len, NULL);
-			g_free(msg->body);
-			msg->body = body;
-			msg->charset = g_strdup("UTF-8");
-		}
+		char *body = g_convert(msg->body, msg->body_len, "UTF-8",
+				"ISO-8859-1", NULL, &msg->body_len, NULL);
+		g_free(msg->body);
+		msg->body = body;
+		msg->charset = g_strdup("UTF-8");
 	}
 
 	g_free(tmp_base);
@@ -381,43 +303,10 @@
 char *
 msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size)
 {
-	MsnSlpHeader header;
-
-	char *tmp, *base;
-	const void *body;
-	size_t len, body_len;
-
-	g_return_val_if_fail(msg != NULL, NULL);
-
-	len = MSN_BUF_LEN;
-
-	base = tmp = g_malloc(len + 1);
-
-	body = msn_message_get_bin_data(msg, &body_len);
+	char *tmp;
 
-	header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
-	header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
-	header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
-	header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size);
-	header.length     = GUINT32_TO_LE(msg->msnslp_header.length);
-	header.flags      = GUINT32_TO_LE(msg->msnslp_header.flags);
-	header.ack_id     = GUINT32_TO_LE(msg->msnslp_header.ack_id);
-	header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id);
-	header.ack_size   = GUINT64_TO_LE(msg->msnslp_header.ack_size);
-
-	memcpy(tmp, &header, 48);
-	tmp += 48;
-
-	if (body != NULL)
-	{
-		memcpy(tmp, body, body_len);
-		tmp += body_len;
-	}
-
-	if (ret_size != NULL)
-		*ret_size = tmp - base;
-
-	return base;
+	tmp = msn_slpmsgpart_serialize(msg->part, ret_size);
+	return tmp;
 }
 
 char *
@@ -454,13 +343,13 @@
 
 	n += strlen(n);
 
-	for (l = msg->attr_list; l != NULL; l = l->next)
+	for (l = msg->header_list; l != NULL; l = l->next)
 	{
 		const char *key;
 		const char *value;
 
 		key = l->data;
-		value = msn_message_get_attr(msg, key);
+		value = msn_message_get_header_value(msg, key);
 
 		g_snprintf(n, end - n, "%s: %s\r\n", key, value);
 		n += strlen(n);
@@ -472,33 +361,13 @@
 
 	if (msg->msnslp_message)
 	{
-		MsnSlpHeader header;
-		MsnSlpFooter footer;
-
-		header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id);
-		header.id         = GUINT32_TO_LE(msg->msnslp_header.id);
-		header.offset     = GUINT64_TO_LE(msg->msnslp_header.offset);
-		header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size);
-		header.length     = GUINT32_TO_LE(msg->msnslp_header.length);
-		header.flags      = GUINT32_TO_LE(msg->msnslp_header.flags);
-		header.ack_id     = GUINT32_TO_LE(msg->msnslp_header.ack_id);
-		header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id);
-		header.ack_size   = GUINT64_TO_LE(msg->msnslp_header.ack_size);
+		size_t siz;
+		char *body;
+		
+		body = msn_slpmsgpart_serialize(msg->part, &siz);
 
-		memcpy(n, &header, 48);
-		n += 48;
-
-		if (body != NULL)
-		{
-			memcpy(n, body, body_len);
-
-			n += body_len;
-		}
-
-		footer.value = GUINT32_TO_BE(msg->msnslp_footer.value);
-
-		memcpy(n, &footer, 4);
-		n += 4;
+		memcpy(n, body, siz);
+		n += siz;
 	}
 	else
 	{
@@ -610,15 +479,15 @@
 }
 
 void
-msn_message_set_attr(MsnMessage *msg, const char *attr, const char *value)
+msn_message_set_header(MsnMessage *msg, const char *name, const char *value)
 {
 	const char *temp;
-	char *new_attr;
+	char *new_name;
 
 	g_return_if_fail(msg != NULL);
-	g_return_if_fail(attr != NULL);
+	g_return_if_fail(name != NULL);
 
-	temp = msn_message_get_attr(msg, attr);
+	temp = msn_message_get_header_value(msg, name);
 
 	if (value == NULL)
 	{
@@ -626,37 +495,37 @@
 		{
 			GList *l;
 
-			for (l = msg->attr_list; l != NULL; l = l->next)
+			for (l = msg->header_list; l != NULL; l = l->next)
 			{
-				if (!g_ascii_strcasecmp(l->data, attr))
+				if (!g_ascii_strcasecmp(l->data, name))
 				{
-					msg->attr_list = g_list_remove(msg->attr_list, l->data);
+					msg->header_list = g_list_remove(msg->header_list, l->data);
 
 					break;
 				}
 			}
 
-			g_hash_table_remove(msg->attr_table, attr);
+			g_hash_table_remove(msg->header_table, name);
 		}
 
 		return;
 	}
 
-	new_attr = g_strdup(attr);
+	new_name = g_strdup(name);
 
-	g_hash_table_insert(msg->attr_table, new_attr, g_strdup(value));
+	g_hash_table_insert(msg->header_table, new_name, g_strdup(value));
 
 	if (temp == NULL)
-		msg->attr_list = g_list_append(msg->attr_list, new_attr);
+		msg->header_list = g_list_append(msg->header_list, new_name);
 }
 
 const char *
-msn_message_get_attr(const MsnMessage *msg, const char *attr)
+msn_message_get_header_value(const MsnMessage *msg, const char *name)
 {
 	g_return_val_if_fail(msg != NULL, NULL);
-	g_return_val_if_fail(attr != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
 
-	return g_hash_table_lookup(msg->attr_table, attr);
+	return g_hash_table_lookup(msg->header_table, name);
 }
 
 GHashTable *
@@ -741,13 +610,13 @@
 				   msg->content_type, msg->charset);
 	}
 
-	for (l = msg->attr_list; l; l = l->next)
+	for (l = msg->header_list; l; l = l->next)
 	{
 		char *key;
 		const char *value;
 
 		key = l->data;
-		value = msn_message_get_attr(msg, key);
+		value = msn_message_get_header_value(msg, key);
 
 		g_string_append_printf(str, "%s: %s\r\n", key, value);
 	}
@@ -758,15 +627,15 @@
 
 	if (msg->msnslp_message)
 	{
-		g_string_append_printf(str, "Session ID: %u\r\n", msg->msnslp_header.session_id);
-		g_string_append_printf(str, "ID:         %u\r\n", msg->msnslp_header.id);
-		g_string_append_printf(str, "Offset:     %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.offset);
-		g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.total_size);
-		g_string_append_printf(str, "Length:     %u\r\n", msg->msnslp_header.length);
-		g_string_append_printf(str, "Flags:      0x%x\r\n", msg->msnslp_header.flags);
-		g_string_append_printf(str, "ACK ID:     %u\r\n", msg->msnslp_header.ack_id);
-		g_string_append_printf(str, "SUB ID:     %u\r\n", msg->msnslp_header.ack_sub_id);
-		g_string_append_printf(str, "ACK Size:   %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.ack_size);
+		g_string_append_printf(str, "Session ID: %u\r\n", msg->part->header->session_id);
+		g_string_append_printf(str, "ID:         %u\r\n", msg->part->header->id);
+		g_string_append_printf(str, "Offset:     %" G_GUINT64_FORMAT "\r\n", msg->part->header->offset);
+		g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->part->header->total_size);
+		g_string_append_printf(str, "Length:     %u\r\n", msg->part->header->length);
+		g_string_append_printf(str, "Flags:      0x%x\r\n", msg->part->header->flags);
+		g_string_append_printf(str, "ACK ID:     %u\r\n", msg->part->header->ack_id);
+		g_string_append_printf(str, "SUB ID:     %u\r\n", msg->part->header->ack_sub_id);
+		g_string_append_printf(str, "ACK Size:   %" G_GUINT64_FORMAT "\r\n", msg->part->header->ack_size);
 
 		if (purple_debug_is_verbose() && body != NULL)
 		{
@@ -783,17 +652,27 @@
 			else
 			{
 				int i;
-				for (i = 0; i < msg->body_len; i++)
+				int bin_len;
+				
+				if (msg->part->footer->value == P2P_APPID_SESION)
+					bin_len = P2P_PACKET_HEADER_SIZE;
+				else
+					bin_len = body_len;
+
+				for (i = 0; i < bin_len; i++)
 				{
 					g_string_append_printf(str, "%.2hhX ", body[i]);
 					if ((i % 16) == 15)
 						g_string_append(str, "\r\n");
 				}
+
+				if (bin_len == P2P_PACKET_HEADER_SIZE)
+					g_string_append_printf(str, "%s ", body + P2P_PACKET_HEADER_SIZE);
 				g_string_append(str, "\r\n");
 			}
 		}
 
-		g_string_append_printf(str, "Footer:     %u\r\n", msg->msnslp_footer.value);
+		g_string_append_printf(str, "Footer:     0x%08X\r\n", msg->part->footer->value);
 	}
 	else
 	{
@@ -817,7 +696,6 @@
 {
 	PurpleConnection *gc;
 	const char *body;
-	char *body_str;
 	char *body_enc;
 	char *body_final;
 	size_t body_len;
@@ -827,9 +705,7 @@
 	gc = cmdproc->session->account->gc;
 
 	body = msn_message_get_bin_data(msg, &body_len);
-	body_str = g_strndup(body, body_len);
-	body_enc = g_markup_escape_text(body_str, -1);
-	g_free(body_str);
+	body_enc = g_markup_escape_text(body, body_len);
 
 	passport = msg->remote_user;
 
@@ -840,13 +716,13 @@
 	}
 
 #if 0
-	if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "User-Agent")) != NULL)
 	{
 		purple_debug_misc("msn", "User-Agent = '%s'\n", value);
 	}
 #endif
 
-	if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "X-MMS-IM-Format")) != NULL)
 	{
 		char *pre, *post;
 
@@ -887,8 +763,9 @@
 				swboard->flag |= MSN_SB_FLAG_IM;
 			}
 		}
-		else
+		else if (!g_str_equal(passport, purple_account_get_username(gc->account)))
 		{
+			/* Don't im ourselves ... */
 			serv_got_im(gc, passport, body_final, 0, time(NULL));
 			if (swboard->conv == NULL)
 			{
@@ -914,7 +791,7 @@
 	gc = cmdproc->session->account->gc;
 	passport = msg->remote_user;
 
-	if (msn_message_get_attr(msg, "TypingUser") == NULL)
+	if (msn_message_get_header_value(msg, "TypingUser") == NULL)
 		return;
 
 	if (cmdproc->servconn->type == MSN_SERVCONN_SB) {
@@ -1035,6 +912,149 @@
 }
 
 void
+msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	const char *data;
+	gsize len;
+
+	session = cmdproc->servconn->session;
+	slplink = msn_session_get_slplink(session, msg->remote_user);
+
+	if (slplink->swboard == NULL)
+	{
+		/*
+		 * We will need swboard in order to change its flags.  If its
+		 * NULL, something has probably gone wrong earlier on.  I
+		 * didn't want to do this, but MSN 7 is somehow causing us
+		 * to crash here, I couldn't reproduce it to debug more,
+		 * and people are reporting bugs. Hopefully this doesn't
+		 * cause more crashes. Stu.
+		 */
+		if (cmdproc->data == NULL)
+			g_warning("msn_p2p_msg cmdproc->data was NULL\n");
+		else {
+			slplink->swboard = (MsnSwitchBoard *)cmdproc->data;
+			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
+		}
+	}
+
+	data = msn_message_get_bin_data(msg, &len);
+
+	if (msg->part) {
+		msn_slplink_process_msg(slplink, msg->part);
+	}
+	else /* This should never happen. */
+		purple_debug_fatal("msn", "P2P message without a Part.\n");
+}
+
+static void
+got_emoticon(MsnSlpCall *slpcall,
+			 const guchar *data, gsize size)
+{
+	PurpleConversation *conv;
+	MsnSwitchBoard *swboard;
+
+	swboard = slpcall->slplink->swboard;
+	conv = swboard->conv;
+
+	if (conv) {
+		/* FIXME: it would be better if we wrote the data as we received it
+		   instead of all at once, calling write multiple times and
+		   close once at the very end
+		 */
+		purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size);
+		purple_conv_custom_smiley_close(conv, slpcall->data_info );
+	}
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info);
+}
+
+void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
+{
+	MsnSession *session;
+	MsnSlpLink *slplink;
+	MsnSwitchBoard *swboard;
+	MsnObject *obj;
+	char **tokens;
+	char *smile, *body_str;
+	const char *body, *who, *sha1;
+	guint tok;
+	size_t body_len;
+
+	PurpleConversation *conv;
+
+	session = cmdproc->servconn->session;
+
+	if (!purple_account_get_bool(session->account, "custom_smileys", TRUE))
+		return;
+
+	swboard = cmdproc->data;
+	conv = swboard->conv;
+
+	body = msn_message_get_bin_data(msg, &body_len);
+	if (!body || !body_len)
+		return;
+	body_str = g_strndup(body, body_len);
+
+	/* MSN Messenger 7 may send more than one MSNObject in a single message...
+	 * Maybe 10 tokens is a reasonable max value. */
+	tokens = g_strsplit(body_str, "\t", 10);
+
+	g_free(body_str);
+
+	for (tok = 0; tok < 9; tok += 2) {
+		if (tokens[tok] == NULL || tokens[tok + 1] == NULL) {
+			break;
+		}
+
+		smile = tokens[tok];
+		obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1]));
+
+		if (obj == NULL)
+			break;
+
+		who = msn_object_get_creator(obj);
+		sha1 = msn_object_get_sha1(obj);
+
+		slplink = msn_session_get_slplink(session, who);
+		if (slplink->swboard != swboard) {
+			if (slplink->swboard != NULL)
+				/*
+				 * Apparently we're using a different switchboard now or
+				 * something?  I don't know if this is normal, but it
+				 * definitely happens.  So make sure the old switchboard
+				 * doesn't still have a reference to us.
+				 */
+				slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink);
+			slplink->swboard = swboard;
+			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
+		}
+
+		/* If the conversation doesn't exist then this is a custom smiley
+		 * used in the first message in a MSN conversation: we need to create
+		 * the conversation now, otherwise the custom smiley won't be shown.
+		 * This happens because every GtkIMHtml has its own smiley tree: if
+		 * the conversation doesn't exist then we cannot associate the new
+		 * smiley with its GtkIMHtml widget. */
+		if (!conv) {
+			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who);
+		}
+
+		if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) {
+			msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj);
+		}
+
+		msn_object_destroy(obj);
+		obj =   NULL;
+		who =   NULL;
+		sha1 = NULL;
+	}
+	g_strfreev(tokens);
+}
+
+void
 msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	GHashTable *body;
--- a/libpurple/protocols/msn/msg.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/msg.h	Mon Nov 01 07:12:08 2010 +0000
@@ -58,6 +58,8 @@
 #include "session.h"
 #include "transaction.h"
 #include "user.h"
+#include "slpmsg.h"
+#include "slpmsg_part.h"
 
 typedef void (*MsnMsgCb)(MsnMessage *, void *data);
 
@@ -67,24 +69,6 @@
 #define MSG_OIM_BODY_DEM	"\n\n"
 #define MSG_OIM_LINE_DEM	"\n"
 
-typedef struct
-{
-	guint32 session_id;
-	guint32 id;
-	guint64 offset;
-	guint64 total_size;
-	guint32 length;
-	guint32 flags;
-	guint32 ack_id;
-	guint32 ack_sub_id;
-	guint64 ack_size;
-} MsnSlpHeader;
-
-typedef struct
-{
-	guint32 value;
-} MsnSlpFooter;
-
 /**
  * A message.
  */
@@ -95,6 +79,8 @@
 	MsnMsgType type;
 
 	gboolean msnslp_message;
+	MsnSlpMessage *slpmsg;
+	MsnSlpMessagePart *part;
 
 	char *remote_user;
 	char flag;
@@ -106,11 +92,8 @@
 	guint total_chunks;   /**< How many chunks in this multi-part message */
 	guint received_chunks; /**< How many chunks we've received so far */
 
-	MsnSlpHeader msnslp_header;
-	MsnSlpFooter msnslp_footer;
-
-	GHashTable *attr_table;
-	GList *attr_list;
+	GHashTable *header_table;
+	GList *header_list;
 
 	gboolean ack_ref;           /**< A flag that states if this message has
 								  been ref'ed for using it in a callback. */
@@ -295,24 +278,24 @@
 const char *msn_message_get_charset(const MsnMessage *msg);
 
 /**
- * Sets an attribute in a message.
+ * Sets a header in a message.
  *
- * @param msg   The message.
- * @param attr  The attribute name.
- * @param value The attribute value.
+ * @param msg    The message.
+ * @param header The header name.
+ * @param value  The header value.
  */
-void msn_message_set_attr(MsnMessage *msg, const char *attr,
+void msn_message_set_header(MsnMessage *msg, const char *name,
 						  const char *value);
 
 /**
- * Returns an attribute from a message.
+ * Returns the value of a header from a message.
  *
- * @param msg  The message.
- * @param attr The attribute.
+ * @param msg    The message.
+ * @param header The header value.
  *
  * @return The value, or @c NULL if not found.
  */
-const char *msn_message_get_attr(const MsnMessage *msg, const char *attr);
+const char *msn_message_get_header_value(const MsnMessage *msg, const char *name);
 
 /**
  * Parses the body and returns it in the form of a hashtable.
@@ -326,9 +309,6 @@
 void msn_message_show_readable(MsnMessage *msg, const char *info,
 							   gboolean text_body);
 
-void msn_message_parse_slp_body(MsnMessage *msg, const char *body,
-								size_t len);
-
 char *msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size);
 
 char *msn_message_to_string(MsnMessage *msg);
@@ -337,8 +317,32 @@
 
 void msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
 
+/**
+ * Processes peer to peer messages.
+ *
+ * @param cmdproc The command processor.
+ * @param msg     The message.
+ */
+void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
+/**
+ * Processes emoticon messages.
+ *
+ * @param cmdproc The command processor.
+ * @param msg     The message.
+ */
+void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
 void msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
 
+/**
+ * Processes INVITE messages.
+ *
+ * @param cmdproc The command processor.
+ * @param msg     The message.
+ */
+void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
+
 void msn_handwritten_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
 
 #endif /* MSN_MSG_H */
--- a/libpurple/protocols/msn/msn.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/msn.c	Mon Nov 01 07:12:08 2010 +0000
@@ -23,7 +23,11 @@
  */
 #define PHOTO_SUPPORT 1
 
-#include "msn.h"
+#include "internal.h"
+
+#include "debug.h"
+#include "request.h"
+
 #include "accountopt.h"
 #include "contact.h"
 #include "msg.h"
@@ -40,10 +44,10 @@
 #include "msnutils.h"
 #include "version.h"
 
+#include "error.h"
 #include "msg.h"
 #include "switchboard.h"
 #include "notification.h"
-#include "sync.h"
 #include "slplink.h"
 
 #if PHOTO_SUPPORT
@@ -116,29 +120,6 @@
 	return buf;
 }
 
-gboolean
-msn_email_is_valid(const char *passport)
-{
-	if (purple_email_is_valid(passport)) {
-		/* Special characters aren't allowed in domains, so only go to '@' */
-		while (*passport != '@') {
-			if (*passport == '/')
-				return FALSE;
-			else if (*passport == '?')
-				return FALSE;
-			else if (*passport == '=')
-				return FALSE;
-			/* MSN also doesn't like colons, but that's checked already */
-
-			passport++;
-		}
-
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
 static gboolean
 msn_send_attention(PurpleConnection *gc, const char *username, guint type)
 {
@@ -267,9 +248,9 @@
 {
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
+	MsnTransaction *trans;
 	PurpleAccount *account;
 	const char *real_alias;
-	MsnTransaction *trans;
 	struct public_alias_closure *closure;
 
 	session = purple_connection_get_protocol_data(pc);
@@ -360,19 +341,21 @@
 {
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
+	MsnTransaction *trans;
 
 	session = gc->proto_data;
 	cmdproc = session->notification->cmdproc;
 
 	if (entry == NULL || *entry == '\0')
 	{
-		msn_cmdproc_send(cmdproc, "PRP", "%s", type);
+		trans = msn_transaction_new(cmdproc, "PRP", "%s", type);
 	}
 	else
 	{
-		msn_cmdproc_send(cmdproc, "PRP", "%s %s", type,
+		trans = msn_transaction_new(cmdproc, "PRP", "%s %s", type,
 						 purple_url_encode(entry));
 	}
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 static void
@@ -478,7 +461,7 @@
 
 	tmp = g_strdup_printf(_("Set friendly name for %s."),
 	                      purple_account_get_username(account));
-	purple_request_input(gc, _("Set your friendly name."), tmp,
+	purple_request_input(gc, _("Set Friendly Name"), tmp,
 					   _("This is the name that other MSN buddies will "
 						 "see you as."),
 					   purple_connection_get_display_name(gc), FALSE, FALSE, NULL,
@@ -489,6 +472,111 @@
 	g_free(tmp);
 }
 
+typedef struct MsnLocationData {
+	PurpleAccount *account;
+	MsnSession *session;
+	PurpleRequestFieldGroup *group;
+} MsnLocationData;
+
+static void
+update_endpoint_cb(MsnLocationData *data, PurpleRequestFields *fields)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	const char *old_name;
+	const char *name;
+	GList *others;
+
+	session = data->session;
+	account = data->account;
+
+	/* Update the current location's name */
+	old_name = purple_account_get_string(account, "endpoint-name", NULL);
+	name = purple_request_fields_get_string(fields, "endpoint-name");
+	if (!g_str_equal(old_name, name)) {
+		purple_account_set_string(account, "endpoint-name", name);
+		msn_notification_send_uux_private_endpointdata(session);
+	}
+
+	/* Sign out other locations */
+	for (others = purple_request_field_group_get_fields(data->group);
+	     others;
+	     others = g_list_next(others)) {
+		PurpleRequestField *field = others->data;
+		if (purple_request_field_get_type(field) != PURPLE_REQUEST_FIELD_BOOLEAN)
+			continue;
+		if (purple_request_field_bool_get_value(field)) {
+			const char *id = purple_request_field_get_id(field);
+			char *user;
+			purple_debug_info("msn", "Disconnecting Endpoint %s\n", id);
+
+			user = g_strdup_printf("%s;%s", purple_account_get_username(account), id);
+			msn_notification_send_uun(session, user, MSN_UNIFIED_NOTIFICATION_MPOP, "goawyplzthxbye");
+			g_free(user);
+		}
+	}
+
+	g_free(data);
+}
+
+static void
+msn_show_locations(PurplePluginAction *action)
+{
+	PurpleConnection *pc;
+	PurpleAccount *account;
+	MsnSession *session;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	GSList *l;
+	MsnLocationData *data;
+
+	pc = (PurpleConnection *)action->context;
+	account = purple_connection_get_account(pc);
+	session = purple_connection_get_protocol_data(pc);
+
+	fields = purple_request_fields_new();
+
+	group = purple_request_field_group_new(_("This Location"));
+	purple_request_fields_add_group(fields, group);
+	field = purple_request_field_label_new("endpoint-label", _("This is the name that identifies this location"));
+	purple_request_field_group_add_field(group, field);
+	field = purple_request_field_string_new("endpoint-name",
+	                                        _("Name"),
+	                                        purple_account_get_string(account, "endpoint-name", NULL),
+	                                        FALSE);
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	group = purple_request_field_group_new(_("Other Locations"));
+	purple_request_fields_add_group(fields, group);
+	field = purple_request_field_label_new("others-label", _("You can sign out from other locations here"));
+	purple_request_field_group_add_field(group, field);
+
+	for (l = session->user->endpoints; l; l = l->next) {
+		MsnUserEndpoint *ep = l->data;
+
+		if (g_str_equal(ep->id, session->guid))
+			/* Don't add myself to the list */
+			continue;
+
+		field = purple_request_field_bool_new(ep->id, ep->name, FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
+
+	data = g_new0(MsnLocationData, 1);
+	data->account = account;
+	data->session = session;
+	data->group = group;
+
+	purple_request_fields(pc, NULL, NULL, NULL,
+	                      fields,
+	                      _("OK"), G_CALLBACK(update_endpoint_cb),
+	                      _("Cancel"), G_CALLBACK(g_free),
+	                      account, NULL, NULL,
+	                      data);
+}
+
 static void
 msn_show_set_home_phone(PurplePluginAction *action)
 {
@@ -656,6 +744,7 @@
 	PurpleAccount *account;
 	MsnSession *session;
 	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
 
 	account = purple_connection_get_account(gc);
 	session = gc->proto_data;
@@ -663,9 +752,11 @@
 
 	if (account->perm_deny == PURPLE_PRIVACY_ALLOW_ALL ||
 	    account->perm_deny == PURPLE_PRIVACY_DENY_USERS)
-		msn_cmdproc_send(cmdproc, "BLP", "%s", "AL");
+		trans = msn_transaction_new(cmdproc, "BLP", "%s", "AL");
 	else
-		msn_cmdproc_send(cmdproc, "BLP", "%s", "BL");
+		trans = msn_transaction_new(cmdproc, "BLP", "%s", "BL");
+
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 static void
@@ -709,9 +800,7 @@
 static void
 t_msn_xfer_init(PurpleXfer *xfer)
 {
-	MsnSlpLink *slplink = xfer->data;
-	msn_slplink_request_ft(slplink, xfer);
-	msn_slplink_unref(slplink);
+	msn_request_ft(xfer);
 }
 
 static void
@@ -1068,6 +1157,11 @@
 	m = g_list_append(m, act);
 	m = g_list_append(m, NULL);
 
+	act = purple_plugin_action_new(_("View Locations..."),
+	                               msn_show_locations);
+	m = g_list_append(m, act);
+	m = g_list_append(m, NULL);
+
 	act = purple_plugin_action_new(_("Set Home Phone Number..."),
 								 msn_show_set_home_phone);
 	m = g_list_append(m, act);
@@ -1202,6 +1296,13 @@
 	username = purple_account_get_string(account, "display-name", NULL);
 	purple_connection_set_display_name(gc, username);
 
+	if (purple_account_get_string(account, "endpoint-name", NULL) == NULL) {
+		GHashTable *ui_info = purple_core_get_ui_info();
+		const gchar *ui_name = ui_info ? g_hash_table_lookup(ui_info, "name") : NULL;
+		purple_account_set_string(account, "endpoint-name",
+				ui_name && *ui_name ? ui_name : PACKAGE_NAME);
+	}
+
 	if (!msn_session_connect(session, host, port, http_method))
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -1412,7 +1513,7 @@
 
 		msg = msn_message_new_plain(msgtext);
 		msg->remote_user = g_strdup(who);
-		msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
+		msn_message_set_header(msg, "X-MMS-IM-Format", msgformat);
 
 		g_free(msgformat);
 		g_free(msgtext);
@@ -1445,7 +1546,7 @@
 			body_enc = g_markup_escape_text(body_str, -1);
 			g_free(body_str);
 
-			format = msn_message_get_attr(msg, "X-MMS-IM-Format");
+			format = msn_message_get_header_value(msg, "X-MMS-IM-Format");
 			msn_parse_format(format, &pre, &post);
 			body_str = g_strdup_printf("%s%s%s", pre ? pre :  "",
 									   body_enc ? body_enc : "", post ? post : "");
@@ -1520,7 +1621,7 @@
 	msg = msn_message_new(MSN_MSG_TYPING);
 	msn_message_set_content_type(msg, "text/x-msmsgscontrol");
 	msn_message_set_flag(msg, 'U');
-	msn_message_set_attr(msg, "TypingUser",
+	msn_message_set_header(msg, "TypingUser",
 						 purple_account_get_username(account));
 	msn_message_set_bin_data(msg, "\r\n", 2);
 
@@ -1576,7 +1677,7 @@
 		MsnUser *user2 = msn_userlist_find_user(userlist, who);
 		if (user2 != NULL) {
 			/* User already in userlist, so just update it. */
-			msn_user_destroy(user);
+			msn_user_unref(user);
 			user = user2;
 		} else {
 			msn_userlist_add_user(userlist, user);
@@ -1596,7 +1697,7 @@
 
 		/* Remove from local list */
 		purple_blist_remove_buddy(buddy);
-		msn_user_destroy(user);
+		msn_user_unref(user);
 	}
 	g_free(group);
 }
@@ -1927,7 +2028,7 @@
 	}
 
 	msg = msn_message_new_plain(msgtext);
-	msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat);
+	msn_message_set_header(msg, "X-MMS-IM-Format", msgformat);
 
 	smileys = msn_msg_grab_emoticons(msg->body, username);
 	while (smileys) {
@@ -1967,6 +2068,7 @@
 msn_keepalive(PurpleConnection *gc)
 {
 	MsnSession *session;
+	MsnTransaction *trans;
 
 	session = gc->proto_data;
 
@@ -1976,7 +2078,9 @@
 
 		cmdproc = session->notification->cmdproc;
 
-		msn_cmdproc_send_quick(cmdproc, "PNG", NULL, NULL);
+		trans = msn_transaction_new(cmdproc, "PNG", NULL);
+		msn_transaction_set_saveable(trans, FALSE);
+		msn_cmdproc_send_trans(cmdproc, trans);
 	}
 }
 
@@ -2712,7 +2816,6 @@
 {
 	msn_notification_init();
 	msn_switchboard_init();
-	msn_sync_init();
 
 	return TRUE;
 }
@@ -2721,7 +2824,6 @@
 {
 	msn_notification_end();
 	msn_switchboard_end();
-	msn_sync_end();
 
 	return TRUE;
 }
--- a/libpurple/protocols/msn/msn.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/msn.h	Mon Nov 01 07:12:08 2010 +0000
@@ -26,16 +26,6 @@
 
 typedef enum
 {
-	MSN_LIST_FL_OP = 0x01,
-	MSN_LIST_AL_OP = 0x02,
-	MSN_LIST_BL_OP = 0x04,
-	MSN_LIST_RL_OP = 0x08,
-	MSN_LIST_PL_OP = 0x10
-} MsnListOp;
-#define MSN_LIST_OP_MASK	0x07
-
-typedef enum
-{
 	MSN_CLIENT_CAP_WIN_MOBILE = 0x0000001,
 	MSN_CLIENT_CAP_INK_GIF    = 0x0000004,
 	MSN_CLIENT_CAP_INK_ISF    = 0x0000008,
@@ -83,23 +73,7 @@
 
 #include "internal.h"
 
-#include "account.h"
-#include "accountopt.h"
-#include "blist.h"
-#include "connection.h"
-#include "conversation.h"
-#include "debug.h"
-#include "cipher.h"
-#include "notify.h"
-#include "privacy.h"
-#include "proxy.h"
-#include "prpl.h"
-#include "request.h"
-#include "servconn.h"
-#include "sslconn.h"
-#include "util.h"
-
-#include "ft.h"
+#include "session.h"
 
 #include "msg.h"
 
@@ -109,9 +83,9 @@
 #define MSN_SERVER "messenger.hotmail.com"
 #define MSN_HTTPCONN_SERVER "gateway.messenger.hotmail.com"
 #define MSN_PORT 1863
-#define WLM_PROT_VER		15
+#define WLM_PROT_VER		16
 
-#define WLM_MAX_PROTOCOL	15
+#define WLM_MAX_PROTOCOL	16
 #define WLM_MIN_PROTOCOL	15
 
 #define MSN_TYPING_RECV_TIMEOUT 6
@@ -134,16 +108,14 @@
 /* Index into attention_types */
 #define MSN_NUDGE 0
 
-#define MSN_CLIENT_ID_VERSION      MSN_CLIENT_VER_7_0
+#define MSN_CLIENT_ID_VERSION      MSN_CLIENT_VER_9_0
 #define MSN_CLIENT_ID_CAPABILITIES (MSN_CLIENT_CAP_PACKET|MSN_CLIENT_CAP_INK_GIF|MSN_CLIENT_CAP_VOICEIM)
+#define MSN_CLIENT_ID_EXT_CAPS     (0)
 
 #define MSN_CLIENT_ID \
 	((MSN_CLIENT_ID_VERSION    << 24) | \
 	 (MSN_CLIENT_ID_CAPABILITIES))
 
-#define MSN_CLIENT_EXT_ID 0
-
-gboolean msn_email_is_valid(const char *passport);
 void
 msn_set_public_alias(PurpleConnection *gc, const char *alias,
                      PurpleSetPublicAliasSuccessCallback success_cb,
--- a/libpurple/protocols/msn/msnutils.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/msnutils.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,18 +21,19 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "internal.h"
+
 #include "msn.h"
 #include "msnutils.h"
 
 #include "cipher.h"
 
-char *rand_guid(void);
-
 /**************************************************************************
  * Util
  **************************************************************************/
 char *
-rand_guid()
+rand_guid(void)
 {
 	return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X",
 			rand() % 0xAAFF + 0x1111,
@@ -476,6 +477,29 @@
 	*ret_port = port;
 }
 
+gboolean
+msn_email_is_valid(const char *passport)
+{
+	if (purple_email_is_valid(passport)) {
+		/* Special characters aren't allowed in domains, so only go to '@' */
+		while (*passport != '@') {
+			if (*passport == '/')
+				return FALSE;
+			else if (*passport == '?')
+				return FALSE;
+			else if (*passport == '=')
+				return FALSE;
+			/* MSN also doesn't like colons, but that's checked already */
+
+			passport++;
+		}
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 /***************************************************************************
  * MSN Challenge Computing Function
  ***************************************************************************/
--- a/libpurple/protocols/msn/msnutils.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/msnutils.h	Mon Nov 01 07:12:08 2010 +0000
@@ -54,7 +54,32 @@
  */
 void msn_import_html(const char *html, char **attributes, char **message);
 
+/**
+ * Parses a socket string.
+ *
+ * @param str		A host:port string.
+ * @param ret_host 	Return string value of the host.
+ * @param ret_port	Return integer value of the port.
+ */
 void msn_parse_socket(const char *str, char **ret_host, int *ret_port);
+
+/**
+ * Verify if the email is a vaild passport.
+ *
+ * @param passport 	The email
+ *
+ * @return True if it is a valid passport, else FALSE
+ */
+gboolean msn_email_is_valid(const char *passport);
+
+/**
+ * Handle MSN Challenge Computation
+ * This algorithm references
+ * http://imfreedom.org/wiki/index.php/MSN:NS/Challenges
+ *
+ * @param input 	Challenge input.
+ * @param output 	Callenge output.
+ */
 void msn_handle_chl(char *input, char *output);
 
 #endif /* MSN_UTILS_H */
--- a/libpurple/protocols/msn/nexus.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/nexus.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,7 +21,11 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+
 #include "soap.h"
 #include "nexus.h"
 #include "notification.h"
--- a/libpurple/protocols/msn/nexus.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/nexus.h	Mon Nov 01 07:12:08 2010 +0000
@@ -24,6 +24,8 @@
 #ifndef MSN_NEXUS_H
 #define MSN_NEXUS_H
 
+#include "internal.h"
+
 typedef struct _MsnNexus MsnNexus;
 typedef struct _MsnTicketToken MsnTicketToken;
 typedef struct _MsnUsrKey MsnUsrKey;
--- a/libpurple/protocols/msn/notification.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/notification.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,17 +21,19 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "cipher.h"
+#include "core.h"
+#include "debug.h"
+
 #include "notification.h"
+
 #include "contact.h"
-#include "state.h"
 #include "error.h"
 #include "msnutils.h"
-#include "page.h"
-
+#include "state.h"
 #include "userlist.h"
-#include "sync.h"
-#include "slplink.h"
 
 static MsnTable *cbs_table;
 
@@ -92,6 +94,8 @@
 {
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
+	MsnTransaction *trans;
+	PurpleAccount *account;
 	GString *vers;
 	const char *ver_str;
 	int i;
@@ -100,6 +104,7 @@
 
 	cmdproc = servconn->cmdproc;
 	session = servconn->session;
+	account = session->account;
 
 	vers = g_string_new("");
 
@@ -115,7 +120,8 @@
 
 	/* Skip the initial space */
 	ver_str = (vers->str + 1);
-	msn_cmdproc_send(cmdproc, "VER", "%s", ver_str);
+	trans = msn_transaction_new(cmdproc, "VER", "%s", ver_str);
+	msn_cmdproc_send_trans(cmdproc, trans);
 
 	g_string_free(vers, TRUE);
 }
@@ -154,30 +160,40 @@
 msn_got_login_params(MsnSession *session, const char *ticket, const char *response)
 {
 	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
 
 	cmdproc = session->notification->cmdproc;
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_END);
 
-	msn_cmdproc_send(cmdproc, "USR", "SSO S %s %s", ticket, response);
+	if (session->protocol_ver >= 16)
+		trans = msn_transaction_new(cmdproc, "USR", "SSO S %s %s %s", ticket, response, session->guid);
+	else
+		trans = msn_transaction_new(cmdproc, "USR", "SSO S %s %s", ticket, response);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 static void
 cvr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	PurpleAccount *account;
+	MsnTransaction *trans;
 
 	account = cmdproc->session->account;
 
-	msn_cmdproc_send(cmdproc, "USR", "SSO I %s", purple_account_get_username(account));
+	trans = msn_transaction_new(cmdproc, "USR", "SSO I %s", purple_account_get_username(account));
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 static void
 usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session;
+	PurpleAccount *account;
 
 	session = cmdproc->session;
+	account = session->account;
 
 	if (!g_ascii_strcasecmp(cmd->params[1], "OK"))
 	{
@@ -227,22 +243,25 @@
 ver_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session;
+	MsnTransaction *trans;
 	PurpleAccount *account;
 	gboolean protocol_supported = FALSE;
-	char proto_str[8];
+	int proto_ver;
 	size_t i;
 
 	session = cmdproc->session;
 	account = session->account;
 
-	g_snprintf(proto_str, sizeof(proto_str), "MSNP%d", session->protocol_ver);
-
+	session->protocol_ver = 0;
 	for (i = 1; i < cmd->param_count; i++)
 	{
-		if (!strcmp(cmd->params[i], proto_str))
-		{
-			protocol_supported = TRUE;
-			break;
+		if (sscanf(cmd->params[i], "MSNP%d", &proto_ver) == 1) {
+			if (proto_ver >= WLM_MIN_PROTOCOL
+			 && proto_ver <= WLM_MAX_PROTOCOL
+			 && proto_ver > session->protocol_ver) {
+				protocol_supported = TRUE;
+				session->protocol_ver = proto_ver;
+			}
 		}
 	}
 
@@ -253,15 +272,18 @@
 		return;
 	}
 
+	purple_debug_info("msn", "Negotiated protocol version %d with the server.\n", session->protocol_ver);
+
 	/*
 	 * Windows Live Messenger 8.5
 	 * Notice :CVR String discriminate!
 	 * reference of http://www.microsoft.com/globaldev/reference/oslocversion.mspx
 	 * to see the Local ID
 	 */
-	msn_cmdproc_send(cmdproc, "CVR",
+	trans = msn_transaction_new(cmdproc, "CVR",
 					"0x0409 winnt 5.1 i386 MSNMSGR 8.5.1302 BC01 %s",
 					 purple_account_get_username(account));
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 /**************************************************************************
@@ -283,12 +305,16 @@
 void
 msn_notification_close(MsnNotification *notification)
 {
+	MsnTransaction *trans;
+
 	g_return_if_fail(notification != NULL);
 
 	if (!notification->in_use)
 		return;
 
-	msn_cmdproc_send_quick(notification->cmdproc, "OUT", NULL, NULL);
+	trans = msn_transaction_new(notification->cmdproc, "OUT", NULL);
+	msn_transaction_set_saveable(trans, FALSE);
+	msn_cmdproc_send_trans(notification->cmdproc, trans);
 
 	msn_notification_disconnect(notification);
 }
@@ -996,15 +1022,18 @@
 {
 	MsnSession *session;
 	PurpleAccount *account;
+	PurpleConnection *gc;
 	MsnUser *user;
 	MsnObject *msnobj = NULL;
-	unsigned long clientid;
+	unsigned long clientid, extcaps;
+	char *extcap_str;
 	int networkid = 0;
 	const char *state, *passport;
 	char *friendly;
 
 	session = cmdproc->session;
 	account = session->account;
+	gc = purple_account_get_connection(account);
 
 	state    = cmd->params[1];
 	passport = cmd->params[2];
@@ -1018,7 +1047,11 @@
 		/* Yahoo! Buddy, looks like */
 		networkid = atoi(cmd->params[3]);
 		friendly = g_strdup(purple_url_decode(cmd->params[4]));
-		clientid = strtoul(cmd->params[5], NULL, 10);
+		clientid = strtoul(cmd->params[5], &extcap_str, 10);
+		if (session->protocol_ver >= 16 && extcap_str && *extcap_str)
+			extcaps = strtoul(extcap_str+1, NULL, 10);
+		else
+			extcaps = 0;
 
 		/* cmd->params[7] seems to be a URL to a Yahoo! icon:
 				https://sec.yimg.com/i/us/nt/b/purpley.1.0.png
@@ -1028,7 +1061,11 @@
 		/* MSNP14+ with Display Picture object */
 		networkid = atoi(cmd->params[3]);
 		friendly = g_strdup(purple_url_decode(cmd->params[4]));
-		clientid = strtoul(cmd->params[5], NULL, 10);
+		clientid = strtoul(cmd->params[5], &extcap_str, 10);
+		if (session->protocol_ver >= 16 && extcap_str && *extcap_str)
+			extcaps = strtoul(extcap_str+1, NULL, 10);
+		else
+			extcaps = 0;
 		msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[6]));
 	} else if (cmd->param_count == 6) {
 		/* Yes, this is 5. The friendly name could start with a number,
@@ -1037,17 +1074,29 @@
 			/* MSNP14 without Display Picture object */
 			networkid = atoi(cmd->params[3]);
 			friendly = g_strdup(purple_url_decode(cmd->params[4]));
-			clientid = strtoul(cmd->params[5], NULL, 10);
+			clientid = strtoul(cmd->params[5], &extcap_str, 10);
+			if (session->protocol_ver >= 16 && extcap_str && *extcap_str)
+				extcaps = strtoul(extcap_str+1, NULL, 10);
+			else
+				extcaps = 0;
 		} else {
 			/* MSNP8+ with Display Picture object */
 			friendly = g_strdup(purple_url_decode(cmd->params[3]));
-			clientid = strtoul(cmd->params[4], NULL, 10);
+			clientid = strtoul(cmd->params[4], &extcap_str, 10);
+			if (session->protocol_ver >= 16 && extcap_str && *extcap_str)
+				extcaps = strtoul(extcap_str+1, NULL, 10);
+			else
+				extcaps = 0;
 			msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5]));
 		}
 	} else if (cmd->param_count == 5) {
 		/* MSNP8+ without Display Picture object */
 		friendly = g_strdup(purple_url_decode(cmd->params[3]));
-		clientid = strtoul(cmd->params[4], NULL, 10);
+		clientid = strtoul(cmd->params[4], &extcap_str, 10);
+		if (session->protocol_ver >= 16 && extcap_str && *extcap_str)
+			extcaps = strtoul(extcap_str+1, NULL, 10);
+		else
+			extcaps = 0;
 	} else {
 		purple_debug_warning("msn", "Received ILN with unknown number of parameters.\n");
 		return;
@@ -1062,6 +1111,7 @@
 
 	user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE) || (user->extinfo && user->extinfo->phone_mobile && user->extinfo->phone_mobile[0] == '+');
 	msn_user_set_clientid(user, clientid);
+	msn_user_set_extcaps(user, extcaps);
 	msn_user_set_network(user, networkid);
 
 	msn_user_set_state(user, state);
@@ -1197,21 +1247,27 @@
 {
 	MsnSession *session;
 	PurpleAccount *account;
+	PurpleConnection *gc;
 	MsnUser *user;
 	MsnObject *msnobj;
-	unsigned long clientid;
+	unsigned long clientid, extcaps;
+	char *extcap_str;
 	int networkid;
 	const char *state, *passport, *friendly;
 
 	session = cmdproc->session;
 	account = session->account;
+	gc = purple_account_get_connection(account);
 
 	state    = cmd->params[0];
 	passport = cmd->params[1];
 	networkid = atoi(cmd->params[2]);
 	friendly = purple_url_decode(cmd->params[3]);
 
-	user = msn_userlist_find_user(session->userlist, passport);
+	if (g_str_equal(passport, session->user->passport))
+		user = session->user;
+	else
+		user = msn_userlist_find_user(session->userlist, passport);
 	if (user == NULL) return;
 
 	if (msn_user_set_friendly_name(user, friendly))
@@ -1229,10 +1285,16 @@
 		msn_user_set_object(user, NULL);
 	}
 
-	clientid = strtoul(cmd->params[4], NULL, 10);
+	clientid = strtoul(cmd->params[4], &extcap_str, 10);
+	if (session->protocol_ver >= 16 && extcap_str && *extcap_str)
+		extcaps = strtoul(extcap_str+1, NULL, 10);
+	else
+		extcaps = 0;
+
 	user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE) || (user->extinfo && user->extinfo->phone_mobile && user->extinfo->phone_mobile[0] == '+');
 
 	msn_user_set_clientid(user, clientid);
+	msn_user_set_extcaps(user, extcaps);
 	msn_user_set_network(user, networkid);
 
 	msn_user_set_state(user, state);
@@ -1379,13 +1441,11 @@
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
 	const char *session_id;
-	const char *auth_key;
 	char *host;
 	int port;
 
 	session = cmdproc->session;
 	session_id = cmd->params[0];
-	auth_key = cmd->params[3];
 
 	msn_parse_socket(cmd->params[1], &host, &port);
 
@@ -1395,8 +1455,8 @@
 	swboard = msn_switchboard_new(session);
 
 	msn_switchboard_set_invited(swboard, TRUE);
-	msn_switchboard_set_session_id(swboard, session_id);
-	msn_switchboard_set_auth_key(swboard, auth_key);
+	msn_switchboard_set_session_id(swboard, cmd->params[0]);
+	msn_switchboard_set_auth_key(swboard, cmd->params[3]);
 	swboard->im_user = g_strdup(cmd->params[4]);
 	/* msn_switchboard_add_user(swboard, cmd->params[4]); */
 
@@ -1502,6 +1562,77 @@
 	/*get the payload content*/
 }
 
+static void
+parse_user_endpoints(MsnUser *user, xmlnode *payloadNode)
+{
+	xmlnode *epNode, *capsNode;
+	MsnUserEndpoint data;
+	const char *id;
+	char *caps, *tmp;
+
+	purple_debug_info("msn", "Get EndpointData\n");
+
+	for (epNode = xmlnode_get_child(payloadNode, "EndpointData");
+	     epNode;
+	     epNode = xmlnode_get_next_twin(epNode)) {
+		id = xmlnode_get_attrib(epNode, "id");
+		capsNode = xmlnode_get_child(epNode, "Capabilities");
+
+		if (capsNode != NULL) {
+			caps = xmlnode_get_data(capsNode);
+
+			data.clientid = strtoul(caps, &tmp, 10);
+			if (tmp && *tmp)
+				data.extcaps = strtoul(tmp + 1, NULL, 10);
+			else
+				data.extcaps = 0;
+
+			g_free(caps);
+
+		} else {
+			data.clientid = 0;
+			data.extcaps = 0;
+		}
+
+		msn_user_set_endpoint_data(user, id, &data);
+	}
+
+	/* Need to shortcut this check, probably... */
+	if (user == user->userlist->session->user) {
+		for (epNode = xmlnode_get_child(payloadNode, "PrivateEndpointData");
+		     epNode;
+		     epNode = xmlnode_get_next_twin(epNode)) {
+			MsnUserEndpoint *ep;
+			xmlnode *nameNode, *clientNode;
+
+			/*	<PrivateEndpointData id='{GUID}'>
+					<EpName>Endpoint Name</EpName>
+					<Idle>true/false</Idle>
+					<ClientType>1</ClientType>
+					<State>NLN</State>
+				</PrivateEndpointData>
+			*/
+			id = xmlnode_get_attrib(epNode, "id");
+			ep = msn_user_get_endpoint_data(user, id);
+
+			if (ep != NULL) {
+				nameNode = xmlnode_get_child(epNode, "EpName");
+				if (nameNode != NULL) {
+					g_free(ep->name);
+					ep->name = xmlnode_get_data(nameNode);
+				}
+
+				clientNode = xmlnode_get_child(epNode, "ClientType");
+				if (clientNode != NULL) {
+					tmp = xmlnode_get_data(clientNode);
+					ep->type = strtoul(tmp, NULL, 10);
+					g_free(tmp);
+				}
+			}
+		}
+	}
+}
+
 static void parse_currentmedia(MsnUser *user, const char *cmedia)
 {
 	char **cmedia_array;
@@ -1565,16 +1696,22 @@
 			 size_t len)
 {
 	MsnSession *session;
+	PurpleAccount *account;
 	MsnUser *user;
 	const char *passport;
-	char *str;
+	xmlnode *payloadNode;
+	char *psm_str, *str;
 
 	session = cmdproc->session;
+	account = session->account;
 
 	passport = cmd->params[0];
-	user = msn_userlist_find_user(session->userlist, passport);
+	if (g_str_equal(passport, session->user->passport))
+		user = session->user;
+	else
+		user = msn_userlist_find_user(session->userlist, passport);
 	if (user == NULL) {
-		str = g_strndup(payload, len);
+		char *str = g_strndup(payload, len);
 		purple_debug_info("msn", "unknown user %s, payload is %s\n",
 			passport, str);
 		g_free(str);
@@ -1593,13 +1730,28 @@
 	}
 
 	if (len != 0) {
-		str = msn_get_psm(cmd->payload,len);
-		msn_user_set_statusline(user, str);
-		g_free(str);
-
-		str = msn_get_currentmedia(cmd->payload, len);
+		payloadNode = xmlnode_from_str(payload, len);
+		if (!payloadNode) {
+			purple_debug_error("msn", "UBX XML parse Error!\n");
+
+			msn_user_set_statusline(user, NULL);
+
+			msn_user_update(user);
+			return;
+		}
+
+		psm_str = msn_get_psm(payloadNode);
+		msn_user_set_statusline(user, psm_str);
+		g_free(psm_str);
+
+		str = msn_get_currentmedia(payloadNode);
 		parse_currentmedia(user, str);
 		g_free(str);
+
+		parse_user_endpoints(user, payloadNode);
+
+		xmlnode_free(payloadNode);
+
 	} else {
 		msn_user_set_statusline(user, NULL);
 	}
@@ -1632,6 +1784,161 @@
 	cmd->payload_len = atoi(cmd->params[1]);
 }
 
+void
+msn_notification_send_uux(MsnSession *session, const char *payload)
+{
+	MsnTransaction *trans;
+	MsnCmdProc *cmdproc;
+	size_t len = strlen(payload);
+
+	cmdproc = session->notification->cmdproc;
+	purple_debug_misc("msn", "Sending UUX command with payload: %s\n", payload);
+	trans = msn_transaction_new(cmdproc, "UUX", "%" G_GSIZE_FORMAT, len);
+	msn_transaction_set_payload(trans, payload, len);
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+void msn_notification_send_uux_endpointdata(MsnSession *session)
+{
+	xmlnode *epDataNode;
+	xmlnode *capNode;
+	char *caps;
+	char *payload;
+	int length;
+
+	epDataNode = xmlnode_new("EndpointData");
+
+	capNode = xmlnode_new_child(epDataNode, "Capabilities");
+	if (session->protocol_ver >= 16)
+		caps = g_strdup_printf("%d:%02d", MSN_CLIENT_ID_CAPABILITIES, MSN_CLIENT_ID_EXT_CAPS);
+	else
+		caps = g_strdup_printf("%d", MSN_CLIENT_ID_CAPABILITIES);
+	xmlnode_insert_data(capNode, caps, -1);
+	g_free(caps);
+
+	payload = xmlnode_to_str(epDataNode, &length);
+
+	msn_notification_send_uux(session, payload);
+
+	xmlnode_free(epDataNode);
+	g_free(payload);
+}
+
+void msn_notification_send_uux_private_endpointdata(MsnSession *session)
+{
+	xmlnode *private;
+	const char *name;
+	xmlnode *epname;
+	xmlnode *idle;
+	GHashTable *ui_info;
+	const gchar *ui_type;
+	xmlnode *client_type;
+	xmlnode *state;
+	char *payload;
+	int length;
+
+	private = xmlnode_new("PrivateEndpointData");
+
+	name = purple_account_get_string(session->account, "endpoint-name", NULL);
+	epname = xmlnode_new_child(private, "EpName");
+	xmlnode_insert_data(epname, name, -1);
+
+	idle = xmlnode_new_child(private, "Idle");
+	xmlnode_insert_data(idle, "false", -1);
+
+	/* ClientType info (from amsn guys):
+		0: None
+		1: Computer
+		2: Website
+		3: Mobile / none
+		4: Xbox / phone /mobile
+		9: MsnGroup
+		32: Email member, currently Yahoo!
+	*/
+	client_type = xmlnode_new_child(private, "ClientType");
+	ui_info = purple_core_get_ui_info();
+	ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL;
+	if (ui_type) {
+		if (strcmp(ui_type, "pc") == 0)
+			xmlnode_insert_data(client_type, "1", -1);
+		else if (strcmp(ui_type, "web") == 0)
+			xmlnode_insert_data(client_type, "2", -1);
+		else if (strcmp(ui_type, "phone") == 0)
+			xmlnode_insert_data(client_type, "3", -1);
+		else if (strcmp(ui_type, "handheld") == 0)
+			xmlnode_insert_data(client_type, "3", -1);
+		else
+			xmlnode_insert_data(client_type, "1", -1);
+	}
+	else
+		xmlnode_insert_data(client_type, "1", -1);
+
+	state = xmlnode_new_child(private, "State");
+	xmlnode_insert_data(state, msn_state_get_text(msn_state_from_account(session->account)), -1);
+
+	payload = xmlnode_to_str(private, &length);
+
+	msn_notification_send_uux(session, payload);
+
+	xmlnode_free(private);
+	g_free(payload);
+}
+
+static void
+ubn_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
+			 size_t len)
+{
+	/* Do Nothing, right now. */
+	if (payload != NULL)
+		purple_debug_info("msn", "UBN payload:\n%s\n", payload);
+}
+
+static void
+ubn_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	purple_debug_misc("msn", "UBN received from %s.\n", cmd->params[0]);
+	cmdproc->last_cmd->payload_cb  = ubn_cmd_post;
+	cmd->payload_len = atoi(cmd->params[2]);
+}
+
+static void
+uun_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload,
+			 size_t len)
+{
+	/* Do Nothing, right now. */
+	if (payload != NULL)
+		purple_debug_info("msn", "UUN payload:\n%s\n", payload);
+}
+
+static void
+uun_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
+{
+	if (strcmp(cmd->params[1], "OK") != 0) {
+		purple_debug_misc("msn", "UUN received.\n");
+		cmdproc->last_cmd->payload_cb = uun_cmd_post;
+		cmd->payload_len = atoi(cmd->params[1]);
+	}
+	else
+		purple_debug_misc("msn", "UUN OK received.\n");
+}
+
+void
+msn_notification_send_uun(MsnSession *session, const char *user,
+                          MsnUnifiedNotificationType type, const char *payload)
+{
+	MsnTransaction *trans;
+	MsnCmdProc *cmdproc;
+	size_t len = strlen(payload);
+
+	cmdproc = session->notification->cmdproc;
+	purple_debug_misc("msn", "Sending UUN command %d to %s with payload: %s\n",
+	                  type, user, payload);
+	trans = msn_transaction_new(cmdproc, "UUN", "%s %d %" G_GSIZE_FORMAT,
+	                            user, type, len);
+	msn_transaction_set_payload(trans, payload, len);
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
 /**************************************************************************
  * Message Types
  **************************************************************************/
@@ -1651,39 +1958,39 @@
 		/* This isn't an official message. */
 		return;
 
-	if ((value = msn_message_get_attr(msg, "kv")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "kv")) != NULL)
 	{
 		g_free(session->passport_info.kv);
 		session->passport_info.kv = g_strdup(value);
 	}
 
-	if ((value = msn_message_get_attr(msg, "sid")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "sid")) != NULL)
 	{
 		g_free(session->passport_info.sid);
 		session->passport_info.sid = g_strdup(value);
 	}
 
-	if ((value = msn_message_get_attr(msg, "MSPAuth")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "MSPAuth")) != NULL)
 	{
 		g_free(session->passport_info.mspauth);
 		session->passport_info.mspauth = g_strdup(value);
 	}
 
-	if ((value = msn_message_get_attr(msg, "ClientIP")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "ClientIP")) != NULL)
 	{
 		g_free(session->passport_info.client_ip);
 		session->passport_info.client_ip = g_strdup(value);
 	}
 
-	if ((value = msn_message_get_attr(msg, "ClientPort")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "ClientPort")) != NULL)
 	{
 		session->passport_info.client_port = ntohs(atoi(value));
 	}
 
-	if ((value = msn_message_get_attr(msg, "LoginTime")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "LoginTime")) != NULL)
 		session->passport_info.sl = atol(value);
 
-	if ((value = msn_message_get_attr(msg, "EmailEnabled")) != NULL)
+	if ((value = msn_message_get_header_value(msg, "EmailEnabled")) != NULL)
 		session->passport_info.email_enabled = (gboolean)atol(value);
 
 #ifdef MSN_PARTIAL_LISTS
@@ -2087,6 +2394,9 @@
 	msn_table_add_cmd(cbs_table, NULL, "UBX", ubx_cmd);
 	msn_table_add_cmd(cbs_table, NULL, "UUX", uux_cmd);
 
+	msn_table_add_cmd(cbs_table, NULL, "UBN", ubn_cmd);
+	msn_table_add_cmd(cbs_table, NULL, "UUN", uun_cmd);
+
 	msn_table_add_cmd(cbs_table, NULL, "URL", url_cmd);
 
 	msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd);
--- a/libpurple/protocols/msn/notification.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/notification.h	Mon Nov 01 07:12:08 2010 +0000
@@ -49,6 +49,7 @@
 #include "servconn.h"
 #include "state.h"
 #include "user.h"
+#include "userlist.h"
 
 struct _MsnNotification
 {
@@ -66,6 +67,15 @@
 
 typedef void (*MsnFqyCb)(MsnSession *session, const char *passport, MsnNetwork network, gpointer data);
 
+/* Type used for msn_notification_send_uun */
+typedef enum {
+	MSN_UNIFIED_NOTIFICATION_SHARED_FOLDERS = 1,
+	MSN_UNIFIED_NOTIFICATION_UNKNOWN1 = 2,
+	MSN_UNIFIED_NOTIFICATION_P2P = 3,
+	MSN_UNIFIED_NOTIFICATION_MPOP = 4
+
+} MsnUnifiedNotificationType;
+
 void uum_send_msg(MsnSession *session, MsnMessage *msg);
 
 void msn_notification_end(void);
@@ -87,6 +97,17 @@
 void msn_notification_disconnect(MsnNotification *notification);
 void msn_notification_dump_contact(MsnSession *session);
 
+void msn_notification_send_uux(MsnSession *session, const char *payload);
+
+void msn_notification_send_uux_endpointdata(MsnSession *session);
+
+void msn_notification_send_uux_private_endpointdata(MsnSession *session);
+
+void msn_notification_send_uun(MsnSession *session,
+                               const char *user,
+                               MsnUnifiedNotificationType type,
+                               const char *payload);
+
 /**
  * Closes a notification.
  *
--- a/libpurple/protocols/msn/object.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/object.c	Mon Nov 01 07:12:08 2010 +0000
@@ -400,7 +400,7 @@
 	return obj->url1;
 }
 
-static MsnObject *
+MsnObject *
 msn_object_find_local(const char *sha1)
 {
 	GList *l;
--- a/libpurple/protocols/msn/object.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/object.h	Mon Nov 01 07:12:08 2010 +0000
@@ -269,6 +269,8 @@
  */
 const char *msn_object_get_url1(const MsnObject *obj);
 
+MsnObject * msn_object_find_local(const char *sha1);
+
 void msn_object_set_local(MsnObject *obj);
 
 #endif /* MSN_OBJECT_H */
--- a/libpurple/protocols/msn/oim.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/oim.c	Mon Nov 01 07:12:08 2010 +0000
@@ -23,7 +23,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+
 #include "soap.h"
 #include "oim.h"
 #include "msnutils.h"
@@ -618,7 +621,7 @@
 	                          MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM);
 	purple_debug_info("msn", "oim body:{%s}\n", message->body);
 
-	boundary = msn_message_get_attr(message, "boundary");
+	boundary = msn_message_get_header_value(message, "boundary");
 
 	if (boundary != NULL) {
 		char *bounds;
@@ -656,7 +659,7 @@
 		decode_msg = (char *)purple_base64_decode(message->body, &body_len);
 	}
 
-	from = msn_message_get_attr(message, "X-OIM-originatingSource");
+	from = msn_message_get_header_value(message, "X-OIM-originatingSource");
 
 	/* Match number to user's mobile number, FROM is a phone number
 	   if the other side pages you using your phone number */
@@ -671,7 +674,7 @@
 	if (passport == NULL) {
 		char *start, *end;
 
-		from = msn_message_get_attr(message, "From");
+		from = msn_message_get_header_value(message, "From");
 
 		tokens = g_strsplit(from, " ", 2);
 		if (tokens[1] != NULL)
@@ -690,7 +693,7 @@
 		g_strfreev(tokens);
 	}
 
-	date = msn_message_get_attr(message, "Date");
+	date = msn_message_get_header_value(message, "Date");
 	stamp = msn_oim_parse_timestamp(date);
 	purple_debug_info("msn", "oim Date:{%s},passport{%s}\n",
 	                  date, passport);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/p2p.c	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,76 @@
+#include "internal.h"
+
+#include "p2p.h"
+
+MsnP2PHeader *
+msn_p2p_header_from_wire(MsnP2PHeader *wire)
+{
+	MsnP2PHeader *header;
+
+	header = g_new(MsnP2PHeader, 1);
+
+	header->session_id = GUINT32_FROM_LE(wire->session_id);
+	header->id         = GUINT32_FROM_LE(wire->id);
+	header->offset     = GUINT64_FROM_LE(wire->offset);
+	header->total_size = GUINT64_FROM_LE(wire->total_size);
+	header->length     = GUINT32_FROM_LE(wire->length);
+	header->flags      = GUINT32_FROM_LE(wire->flags);
+	header->ack_id     = GUINT32_FROM_LE(wire->ack_id);
+	header->ack_sub_id = GUINT32_FROM_LE(wire->ack_sub_id);
+	header->ack_size   = GUINT64_FROM_LE(wire->ack_size);
+
+	return header;
+}
+
+MsnP2PHeader *
+msn_p2p_header_to_wire(MsnP2PHeader *header)
+{
+	MsnP2PHeader *wire;
+	
+	wire = g_new(MsnP2PHeader, 1);
+
+	wire->session_id = GUINT32_TO_LE(header->session_id);
+	wire->id         = GUINT32_TO_LE(header->id);
+	wire->offset     = GUINT64_TO_LE(header->offset);
+	wire->total_size = GUINT64_TO_LE(header->total_size);
+	wire->length     = GUINT32_TO_LE(header->length);
+	wire->flags      = GUINT32_TO_LE(header->flags);
+	wire->ack_id     = GUINT32_TO_LE(header->ack_id);
+	wire->ack_sub_id = GUINT32_TO_LE(header->ack_sub_id);
+	wire->ack_size   = GUINT64_TO_LE(header->ack_size);
+
+	return wire;
+
+}
+
+MsnP2PFooter *
+msn_p2p_footer_from_wire(MsnP2PFooter *wire)
+{
+	MsnP2PFooter *footer;
+
+	footer = g_new(MsnP2PFooter, 1);
+
+	footer->value = GUINT32_FROM_BE(wire->value);
+
+	return footer;
+}
+
+MsnP2PFooter *
+msn_p2p_footer_to_wire(MsnP2PFooter *footer)
+{
+	MsnP2PFooter *wire;
+
+	wire = g_new(MsnP2PFooter, 1);
+
+	wire->value = GUINT32_TO_BE(footer->value);
+
+	return wire;
+}
+
+gboolean
+msn_p2p_msg_is_data(const MsnP2PHeaderFlag flags)
+{
+	return (flags == P2P_MSN_OBJ_DATA ||
+	        flags == (P2P_WML2009_COMP | P2P_MSN_OBJ_DATA) ||
+	        flags == P2P_FILE_DATA);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/p2p.h	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,88 @@
+#ifndef MSN_P2P_H
+#define MSN_P2P_H
+
+
+#pragma pack(push,1)
+typedef struct {
+	guint32 session_id;
+	guint32 id;
+	/**
+	 * In a MsnSlpMessage:
+	 * For outgoing messages this is the number of bytes from buffer that
+	 * have already been sent out.  For incoming messages this is the
+	 * number of bytes that have been written to buffer.
+	 */
+	guint64 offset;
+	guint64 total_size;
+	guint32 length;
+	guint32 flags;
+	guint32 ack_id;
+	guint32 ack_sub_id;
+	guint64 ack_size;
+/*	guint8  body[1]; */
+} MsnP2PHeader;
+#pragma pack(pop)
+
+#pragma pack(push,1)
+typedef struct {
+	guint8  header_len;
+	guint8  opcode;
+	guint16 message_len;
+	guint32 base_id;
+} MsnP2Pv2Header;
+#pragma pack(pop)
+
+typedef struct
+{
+	guint32 value;
+} MsnP2PFooter;
+
+typedef enum
+{
+	P2P_NO_FLAG         = 0x0,        /**< No flags specified */
+	P2P_OUT_OF_ORDER    = 0x1,        /**< Chunk out-of-order */
+	P2P_ACK             = 0x2,        /**< Acknowledgement */
+	P2P_PENDING_INVITE  = 0x4,        /**< There is a pending invite */
+	P2P_BINARY_ERROR    = 0x8,        /**< Error on the binary level */
+	P2P_FILE            = 0x10,       /**< File */
+	P2P_MSN_OBJ_DATA    = 0x20,       /**< MsnObject data */
+	P2P_CLOSE           = 0x40,       /**< Close session */
+	P2P_TLP_ERROR       = 0x80,       /**< Error at transport layer protocol */
+	P2P_DC_HANDSHAKE    = 0x100,      /**< Direct Handshake */
+	P2P_WML2009_COMP    = 0x1000000,  /**< Compatibility with WLM 2009 */
+	P2P_FILE_DATA       = 0x1000030   /**< File transfer data */
+} MsnP2PHeaderFlag;
+/* Info From:
+ * http://msnpiki.msnfanatic.com/index.php/MSNC:P2Pv1_Headers#Flags
+ * http://trac.kmess.org/changeset/ba04d0c825769d23370511031c47f6be75fe9b86
+ * #7180
+ */
+
+typedef enum
+{
+	P2P_APPID_SESION    = 0x0,        /**< Negotiating session */
+	P2P_APPID_OBJ       = 0x1,        /**< MsnObject (Display or Emoticon) */
+	P2P_APPID_FILE      = 0x2,        /**< File transfer */
+	P2P_APPID_EMOTE     = 0xB,        /**< CustomEmoticon */
+	P2P_APPID_DISPLAY   = 0xC         /**< Display Image */
+} MsnP2PAppId;
+
+#define P2P_PACKET_HEADER_SIZE sizeof(MsnP2PHeader)
+#define P2P_PACKET_FOOTER_SIZE sizeof(MsnP2PFooter)
+
+MsnP2PHeader *
+msn_p2p_header_from_wire(MsnP2PHeader *wire);
+
+MsnP2PHeader *
+msn_p2p_header_to_wire(MsnP2PHeader *header);
+
+MsnP2PFooter *
+msn_p2p_footer_from_wire(MsnP2PFooter *wire);
+
+MsnP2PFooter *
+msn_p2p_footer_to_wire(MsnP2PFooter *footer);
+
+gboolean
+msn_p2p_msg_is_data(const MsnP2PHeaderFlag flags);
+
+#endif /* MSN_P2P_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/sbconn.c	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,151 @@
+#include "internal.h"
+#include "debug.h"
+
+#include "msg.h"
+#include "sbconn.h"
+
+void msn_sbconn_send_part(MsnSlpLink *slplink, MsnSlpMessagePart *part)
+{
+	MsnMessage *msg;
+	const char *passport;
+	char *data;
+	size_t size;
+
+	msg = msn_message_new_msnslp();
+
+	passport = purple_normalize(slplink->session->account, slplink->remote_user);
+	msn_message_set_header(msg, "P2P-Dest", passport);
+
+	data = msn_slpmsgpart_serialize(part, &size);
+	msg->part = msn_slpmsgpart_ref(part);
+
+	msn_message_set_bin_data(msg, data, size);
+
+	if (slplink->swboard == NULL)
+	{
+		slplink->swboard = msn_session_get_swboard(slplink->session,
+				slplink->remote_user, MSN_SB_FLAG_FT);
+
+		g_return_if_fail(slplink->swboard != NULL);
+
+		/* If swboard is destroyed we will be too */
+		slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
+	}
+
+	msn_switchboard_send_msg(slplink->swboard, msg, TRUE);
+}
+
+/** Called when a message times out. */
+static void
+msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans)
+{
+	MsnMessage *msg;
+
+	msg = trans->data;
+
+	msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT);
+}
+
+static void
+release_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
+{
+	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
+	char *payload;
+	gsize payload_len;
+	char flag;
+
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	cmdproc = swboard->cmdproc;
+
+	payload = msn_message_gen_payload(msg, &payload_len);
+
+	if (purple_debug_is_verbose()) {
+		purple_debug_info("msn", "SB length:{%" G_GSIZE_FORMAT "}\n", payload_len);
+		msn_message_show_readable(msg, "SB SEND", FALSE);
+	}
+
+	flag = msn_message_get_flag(msg);
+	trans = msn_transaction_new(cmdproc, "MSG", "%c %" G_GSIZE_FORMAT,
+								flag, payload_len);
+
+	/* Data for callbacks */
+	msn_transaction_set_data(trans, msg);
+
+	if (flag != 'U') {
+		if (msg->type == MSN_MSG_TEXT)
+		{
+			msg->ack_ref = TRUE;
+			msn_message_ref(msg);
+			swboard->ack_list = g_list_append(swboard->ack_list, msg);
+			msn_transaction_set_timeout_cb(trans, msg_timeout);
+		}
+		else if (msg->type == MSN_MSG_SLP)
+		{
+			msg->ack_ref = TRUE;
+			msn_message_ref(msg);
+			swboard->ack_list = g_list_append(swboard->ack_list, msg);
+			msn_transaction_set_timeout_cb(trans, msg_timeout);
+#if 0
+			if (msg->ack_cb != NULL)
+			{
+				msn_transaction_add_cb(trans, "ACK", msg_ack);
+				msn_transaction_add_cb(trans, "NAK", msg_nak);
+			}
+#endif
+		}
+	}
+
+	trans->payload = payload;
+	trans->payload_len = payload_len;
+
+	msg->trans = trans;
+
+	msn_cmdproc_send_trans(cmdproc, trans);
+}
+
+static void
+queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	purple_debug_info("msn", "Appending message to queue.\n");
+
+	g_queue_push_tail(swboard->msg_queue, msg);
+
+	msn_message_ref(msg);
+}
+
+void
+msn_sbconn_process_queue(MsnSwitchBoard *swboard)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(swboard != NULL);
+
+	purple_debug_info("msn", "Processing queue\n");
+
+	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)
+	{
+		purple_debug_info("msn", "Sending message\n");
+		release_msg(swboard, msg);
+		msn_message_unref(msg);
+	}
+}
+
+void
+msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg,
+						 gboolean queue)
+{
+	g_return_if_fail(swboard != NULL);
+	g_return_if_fail(msg     != NULL);
+
+	purple_debug_info("msn", "switchboard send msg..\n");
+	if (msn_switchboard_can_send(swboard))
+		release_msg(swboard, msg);
+	else if (queue)
+		queue_msg(swboard, msg);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/sbconn.h	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,17 @@
+#ifndef MSN_SBCONN_H
+#define MSN_SBCONN_H
+
+#include "msg.h"
+#include "slplink.h"
+
+#define MSN_SBCONN_MAX_SIZE 1202
+
+void msn_sbconn_send_part(MsnSlpLink *slplink, MsnSlpMessagePart *part);
+
+void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg,
+						 gboolean queue);
+
+void
+msn_sbconn_process_queue(MsnSwitchBoard *swboard);
+
+#endif /* MSN_SBCONN_H */
--- a/libpurple/protocols/msn/servconn.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/servconn.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,7 +21,9 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+#include "internal.h"
+#include "debug.h"
+
 #include "servconn.h"
 #include "error.h"
 
--- a/libpurple/protocols/msn/servconn.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/servconn.h	Mon Nov 01 07:12:08 2010 +0000
@@ -46,6 +46,7 @@
 	MSN_SERVCONN_SB
 } MsnServConnType;
 
+#include "internal.h"
 #include "proxy.h"
 
 #include "cmdproc.h"
--- a/libpurple/protocols/msn/session.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/session.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,13 +21,16 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+
+#include "error.h"
+#include "msnutils.h"
 #include "session.h"
 #include "notification.h"
 #include "oim.h"
 
-#include "dialog.h"
-
 MsnSession *
 msn_session_new(PurpleAccount *account)
 {
@@ -45,7 +48,9 @@
 								 purple_account_get_username(account), NULL);
 	session->oim = msn_oim_new(session);
 
-	session->protocol_ver = WLM_PROT_VER;
+	session->protocol_ver = 0;
+
+	session->guid = rand_guid();
 
 	return session;
 }
@@ -72,14 +77,11 @@
 		g_hash_table_destroy(session->soap_table);
 
 	while (session->slplinks != NULL)
-		msn_slplink_destroy(session->slplinks->data);
+		msn_slplink_unref(session->slplinks->data);
 
 	while (session->switches != NULL)
 		msn_switchboard_destroy(session->switches->data);
 
-	if (session->sync != NULL)
-		msn_sync_destroy(session->sync);
-
 	if (session->oim != NULL)
 		msn_oim_destroy(session->oim);
 
@@ -87,7 +89,7 @@
 		msn_nexus_destroy(session->nexus);
 
 	if (session->user != NULL)
-		msn_user_destroy(session->user);
+		msn_user_unref(session->user);
 
 	if (session->notification != NULL)
 		msn_notification_destroy(session->notification);
@@ -95,6 +97,7 @@
 	msn_userlist_destroy(session->userlist);
 
 	g_free(session->psm);
+	g_free(session->guid);
 	g_free(session->abch_cachekey);
 #if 0
 	g_free(session->blocked_text);
@@ -329,7 +332,7 @@
 			if (!found) {
 				if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) {
 					/* The user is not on the server list */
-					msn_show_sync_issue(session, buddy_name, group_name);
+					msn_error_sync_issue(session, buddy_name, group_name);
 				} else {
 					/* The user is not in that group on the server list */
 					to_remove = g_list_prepend(to_remove, buddy);
@@ -483,6 +486,11 @@
 		msn_session_sync_users(session);
 	}
 
+	if (session->protocol_ver >= 16) {
+		/* TODO: Send this when updating status instead? */
+		msn_notification_send_uux_endpointdata(session);
+		msn_notification_send_uux_private_endpointdata(session);
+	}
 	msn_change_status(session);
 }
 
--- a/libpurple/protocols/msn/session.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/session.h	Mon Nov 01 07:12:08 2010 +0000
@@ -61,15 +61,12 @@
 
 #define MSN_LOGIN_FQY_TIMEOUT 30
 
-#include "group.h"
-#include "httpconn.h"
+#define MSN_LOGIN_FQY_TIMEOUT 30
+
 #include "nexus.h"
 #include "notification.h"
 #include "oim.h"
-#include "slpcall.h"
-#include "sslconn.h"
 #include "switchboard.h"
-#include "sync.h"
 #include "user.h"
 #include "userlist.h"
 
@@ -92,7 +89,6 @@
 	MsnNotification *notification;
 	MsnNexus        *nexus;
 	MsnOim          *oim;
-	MsnSync         *sync;
 	MsnUserList     *userlist;
 	char            *abch_cachekey;
 
@@ -122,6 +118,7 @@
 
 	GHashTable *soap_table;
 	guint soap_cleanup_handle;
+	char *guid;
 
 	GSList *url_datas; /**< PurpleUtilFetchUrlData to be cancelled on exit */
 };
--- a/libpurple/protocols/msn/slp.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slp.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,7 +21,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+
 #include "slp.h"
 #include "slpcall.h"
 #include "slpmsg.h"
@@ -29,16 +32,14 @@
 
 #include "object.h"
 #include "user.h"
-#include "switchboard.h"
+#include "sbconn.h"
 #include "directconn.h"
-
-#include "smiley.h"
+#include "p2p.h"
+#include "xfer.h"
 
 /* seconds to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20
 
-static void request_user_display(MsnUser *user);
-
 typedef struct {
 	MsnSession *session;
 	const char *remote_user;
@@ -46,167 +47,6 @@
 } MsnFetchUserDisplayData;
 
 /**************************************************************************
- * Util
- **************************************************************************/
-
-static char *
-get_token(const char *str, const char *start, const char *end)
-{
-	const char *c, *c2;
-
-	if ((c = strstr(str, start)) == NULL)
-		return NULL;
-
-	c += strlen(start);
-
-	if (end != NULL)
-	{
-		if ((c2 = strstr(c, end)) == NULL)
-			return NULL;
-
-		return g_strndup(c, c2 - c);
-	}
-	else
-	{
-		/* This has to be changed */
-		return g_strdup(c);
-	}
-
-}
-
-/**************************************************************************
- * Xfer
- **************************************************************************/
-
-static void
-msn_xfer_init(PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	/* MsnSlpLink *slplink; */
-	char *content;
-
-	purple_debug_info("msn", "xfer_init\n");
-
-	slpcall = xfer->data;
-
-	/* Send Ok */
-	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-							  slpcall->session_id);
-
-	msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
-			content);
-
-	g_free(content);
-	msn_slplink_send_queued_slpmsgs(slpcall->slplink);
-}
-
-void
-msn_xfer_cancel(PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	char *content;
-
-	g_return_if_fail(xfer != NULL);
-	g_return_if_fail(xfer->data != NULL);
-
-	slpcall = xfer->data;
-
-	if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
-	{
-		if (slpcall->started)
-		{
-			msn_slpcall_close(slpcall);
-		}
-		else
-		{
-			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-									slpcall->session_id);
-
-			msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
-						content);
-
-			g_free(content);
-			msn_slplink_send_queued_slpmsgs(slpcall->slplink);
-
-			if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
-				slpcall->wasted = TRUE;
-			else
-				msn_slpcall_destroy(slpcall);
-		}
-	}
-}
-
-gssize
-msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-
-	g_return_val_if_fail(xfer != NULL, -1);
-	g_return_val_if_fail(data != NULL, -1);
-	g_return_val_if_fail(len > 0, -1);
-
-	g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1);
-
-	slpcall = xfer->data;
-	/* Not sure I trust it'll be there */
-	g_return_val_if_fail(slpcall != NULL, -1);
-
-	g_return_val_if_fail(slpcall->xfer_msg != NULL, -1);
-
-	slpcall->u.outgoing.len = len;
-	slpcall->u.outgoing.data = data;
-	msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg);
-	msn_message_unref(slpcall->xfer_msg->msg);
-	return MIN(1202, len);
-}
-
-gssize
-msn_xfer_read(guchar **data, PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	gsize len;
-
-	g_return_val_if_fail(xfer != NULL, -1);
-	g_return_val_if_fail(data != NULL, -1);
-
-	g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1);
-
-	slpcall = xfer->data;
-	/* Not sure I trust it'll be there */
-	g_return_val_if_fail(slpcall != NULL, -1);
-
-	/* Just pass up the whole GByteArray. We'll make another. */
-	*data = slpcall->u.incoming_data->data;
-	len = slpcall->u.incoming_data->len;
-
-	g_byte_array_free(slpcall->u.incoming_data, FALSE);
-	slpcall->u.incoming_data = g_byte_array_new();
-
-	return len;
-}
-
-void
-msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session)
-{
-	if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) &&
-		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) &&
-		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL))
-	{
-		purple_xfer_cancel_remote(slpcall->xfer);
-	}
-}
-
-void
-msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body,
-					  gsize size)
-{
-	PurpleXfer *xfer = slpcall->xfer;
-
-	purple_xfer_set_completed(xfer, TRUE);
-	purple_xfer_end(xfer);
-}
-
-/**************************************************************************
  * SLP Control
  **************************************************************************/
 
@@ -250,1037 +90,10 @@
 	msn_slplink_queue_slpmsg(slplink, slpmsg);
 }
 
-/* XXX: this could be improved if we tracked custom smileys
- * per-protocol, per-account, per-session or (ideally) per-conversation
- */
-static PurpleStoredImage *
-find_valid_emoticon(PurpleAccount *account, const char *path)
-{
-	GList *smileys;
-
-	if (!purple_account_get_bool(account, "custom_smileys", TRUE))
-		return NULL;
-
-	smileys = purple_smileys_get_all();
-
-	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
-		PurpleSmiley *smiley;
-		PurpleStoredImage *img;
-
-		smiley = smileys->data;
-		img = purple_smiley_get_stored_image(smiley);
-
-		if (purple_strequal(path, purple_imgstore_get_filename(img))) {
-			g_list_free(smileys);
-			return img;
-		}
-
-		purple_imgstore_unref(img);
-	}
-
-	purple_debug_error("msn", "Received illegal request for file %s\n", path);
-	return NULL;
-}
-
-static char *
-parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype)
-{
-	char *nonce;
-
-	*ntype = DC_NONCE_UNKNOWN;
-
-	nonce = get_token(content, "Hashed-Nonce: {", "}\r\n");
-	if (nonce) {
-		*ntype = DC_NONCE_SHA1;
-	} else {
-		guint32 n1, n6;
-		guint16 n2, n3, n4, n5;
-		nonce = get_token(content, "Nonce: {", "}\r\n");
-		if (nonce
-		 && sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%04hx%08x",
-		           &n1, &n2, &n3, &n4, &n5, &n6) == 6) {
-			*ntype = DC_NONCE_PLAIN;
-			g_free(nonce);
-			nonce = g_malloc(16);
-			*(guint32 *)(nonce +  0) = GUINT32_TO_LE(n1);
-			*(guint16 *)(nonce +  4) = GUINT16_TO_LE(n2);
-			*(guint16 *)(nonce +  6) = GUINT16_TO_LE(n3);
-			*(guint16 *)(nonce +  8) = GUINT16_TO_BE(n4);
-			*(guint16 *)(nonce + 10) = GUINT16_TO_BE(n5);
-			*(guint32 *)(nonce + 12) = GUINT32_TO_BE(n6);
-		} else {
-			/* Invalid nonce, so ignore request */
-			g_free(nonce);
-			nonce = NULL;
-		}
-	}
-
-	return nonce;
-}
-
-static void
-msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content)
-{
-	/* A direct connection negotiation response */
-	char *bridge;
-	char *nonce;
-	char *listening;
-	MsnDirectConn *dc = slpcall->slplink->dc;
-	MsnDirectConnNonceType ntype;
-
-	purple_debug_info("msn", "process_transresp\n");
-
-	/* Direct connections are disabled. */
-	if (!purple_account_get_bool(slpcall->slplink->session->account, "direct_connect", TRUE))
-		return;
-
-	g_return_if_fail(dc != NULL);
-	g_return_if_fail(dc->state == DC_STATE_CLOSED);
-
-	bridge = get_token(content, "Bridge: ", "\r\n");
-	nonce = parse_dc_nonce(content, &ntype);
-	listening = get_token(content, "Listening: ", "\r\n");
-	if (listening && bridge && !strcmp(bridge, "TCPv1")) {
-		/* Ok, the client supports direct TCP connection */
-
-		/* We always need this. */
-		if (ntype == DC_NONCE_SHA1) {
-			strncpy(dc->remote_nonce, nonce, 36);
-			dc->remote_nonce[36] = '\0';
-		}
-
-		if (!strcasecmp(listening, "false")) {
-			if (dc->listen_data != NULL) {
-				/*
-				 * We'll listen for incoming connections but
-				 * the listening socket isn't ready yet so we cannot
-				 * send the INVITE packet now. Put the slpcall into waiting mode
-				 * and let the callback send the invite.
-				 */
-				slpcall->wait_for_socket = TRUE;
-
-			} else if (dc->listenfd != -1) {
-				/* The listening socket is ready. Send the INVITE here. */
-				msn_dc_send_invite(dc);
-
-			} else {
-				/* We weren't able to create a listener either. Use SB. */
-				msn_dc_fallback_to_sb(dc);
-			}
-
-		} else {
-			/*
-			 * We should connect to the client so parse
-			 * IP/port from response.
-			 */
-			char *ip, *port_str;
-			int port = 0;
-
-			if (ntype == DC_NONCE_PLAIN) {
-				/* Only needed for listening side. */
-				memcpy(dc->nonce, nonce, 16);
-			}
-
-			/* Cancel any listen attempts because we don't need them. */
-			if (dc->listenfd_handle != 0) {
-				purple_input_remove(dc->listenfd_handle);
-				dc->listenfd_handle = 0;
-			}
-			if (dc->connect_timeout_handle != 0) {
-				purple_timeout_remove(dc->connect_timeout_handle);
-				dc->connect_timeout_handle = 0;
-			}
-			if (dc->listenfd != -1) {
-				purple_network_remove_port_mapping(dc->listenfd);
-				close(dc->listenfd);
-				dc->listenfd = -1;
-			}
-			if (dc->listen_data != NULL) {
-				purple_network_listen_cancel(dc->listen_data);
-				dc->listen_data = NULL;
-			}
-
-			/* Save external IP/port for later use. We'll try local connection first. */
-			dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n");
-			port_str = get_token(content, "IPv4External-Port: ", "\r\n");
-			if (port_str) {
-				dc->ext_port = atoi(port_str);
-				g_free(port_str);
-			}
-
-			ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
-			port_str = get_token(content, "IPv4Internal-Port: ", "\r\n");
-			if (port_str) {
-				port = atoi(port_str);
-				g_free(port_str);
-			}
-
-			if (ip && port) {
-				/* Try internal address first */
-				dc->connect_data = purple_proxy_connect(
-					NULL,
-					slpcall->slplink->session->account,
-					ip,
-					port,
-					msn_dc_connected_to_peer_cb,
-					dc
-				);
-
-				if (dc->connect_data) {
-					/* Add connect timeout handle */
-					dc->connect_timeout_handle = purple_timeout_add_seconds(
-						DC_OUTGOING_TIMEOUT,
-						msn_dc_outgoing_connection_timeout_cb,
-						dc
-					);
-				} else {
-					/*
-					 * Connection failed
-					 * Try external IP/port (if specified)
-					 */
-					msn_dc_outgoing_connection_timeout_cb(dc);
-				}
-
-			} else {
-				/*
-				 * Omitted or invalid internal IP address / port
-				 * Try external IP/port (if specified)
-				 */
-				msn_dc_outgoing_connection_timeout_cb(dc);
-			}
-
-			g_free(ip);
-		}
-
-	} else {
-		/*
-		 * Invalid direct connect invitation or
-		 * TCP connection is not supported
-		 */
-	}
-
-	g_free(listening);
-	g_free(nonce);
-	g_free(bridge);
-
-	return;
-}
-
-static void
-got_sessionreq(MsnSlpCall *slpcall, const char *branch,
-			   const char *euf_guid, const char *context)
-{
-	gboolean accepted = FALSE;
-
-	if (!strcmp(euf_guid, MSN_OBJ_GUID))
-	{
-		/* Emoticon or UserDisplay */
-		char *content;
-		gsize len;
-		MsnSlpLink *slplink;
-		MsnSlpMessage *slpmsg;
-		MsnObject *obj;
-		char *msnobj_data;
-		PurpleStoredImage *img = NULL;
-		int type;
-
-		/* Send Ok */
-		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-								  slpcall->session_id);
-
-		msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
-				content);
-
-		g_free(content);
-
-		slplink = slpcall->slplink;
-
-		msnobj_data = (char *)purple_base64_decode(context, &len);
-		obj = msn_object_new_from_string(msnobj_data);
-		type = msn_object_get_type(obj);
-		g_free(msnobj_data);
-		if (type == MSN_OBJECT_EMOTICON) {
-			img = find_valid_emoticon(slplink->session->account, obj->location);
-		} else if (type == MSN_OBJECT_USERTILE) {
-			img = msn_object_get_image(obj);
-			if (img)
-				purple_imgstore_ref(img);
-		}
-		msn_object_destroy(obj);
-
-		if (img != NULL) {
-			/* DATA PREP */
-			slpmsg = msn_slpmsg_new(slplink);
-			slpmsg->slpcall = slpcall;
-			slpmsg->session_id = slpcall->session_id;
-			msn_slpmsg_set_body(slpmsg, NULL, 4);
-			slpmsg->info = "SLP DATA PREP";
-			msn_slplink_queue_slpmsg(slplink, slpmsg);
-
-			/* DATA */
-			slpmsg = msn_slpmsg_new(slplink);
-			slpmsg->slpcall = slpcall;
-			slpmsg->flags = 0x20;
-			slpmsg->info = "SLP DATA";
-			msn_slpmsg_set_image(slpmsg, img);
-			msn_slplink_queue_slpmsg(slplink, slpmsg);
-			purple_imgstore_unref(img);
-
-			accepted = TRUE;
-
-		} else {
-			purple_debug_error("msn", "Wrong object.\n");
-		}
-	}
-
-	else if (!strcmp(euf_guid, MSN_FT_GUID))
-	{
-		/* File Transfer */
-		PurpleAccount *account;
-		PurpleXfer *xfer;
-		MsnFileContext *header;
-		gsize bin_len;
-		guint32 file_size;
-		char *file_name;
-
-		account = slpcall->slplink->session->account;
-
-		slpcall->end_cb = msn_xfer_end_cb;
-		slpcall->branch = g_strdup(branch);
-
-		slpcall->pending = TRUE;
-
-		xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE,
-							 slpcall->slplink->remote_user);
-
-		header = (MsnFileContext *)purple_base64_decode(context, &bin_len);
-		if (header != NULL && bin_len >= sizeof(MsnFileContext) - 1 &&
-			(header->version == 2 ||
-			 (header->version == 3 && header->length == sizeof(MsnFileContext) + 63))) {
-			file_size = GUINT64_FROM_LE(header->file_size);
-
-			file_name = g_convert((const gchar *)&header->file_name,
-			                      MAX_FILE_NAME_LEN * 2,
-			                      "UTF-8", "UTF-16LE",
-			                      NULL, NULL, NULL);
-
-			purple_xfer_set_filename(xfer, file_name ? file_name : "");
-			g_free(file_name);
-			purple_xfer_set_size(xfer, file_size);
-			purple_xfer_set_init_fnc(xfer, msn_xfer_init);
-			purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel);
-			purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel);
-			purple_xfer_set_read_fnc(xfer, msn_xfer_read);
-			purple_xfer_set_write_fnc(xfer, msn_xfer_write);
-
-			slpcall->u.incoming_data = g_byte_array_new();
-
-			slpcall->xfer = xfer;
-			purple_xfer_ref(slpcall->xfer);
-
-			xfer->data = slpcall;
-
-			if (header->type == 0 && bin_len >= sizeof(MsnFileContext)) {
-				purple_xfer_set_thumbnail(xfer, &header->preview,
-				                          bin_len - sizeof(MsnFileContext),
-				    					  "image/png");
-			}
-
-			purple_xfer_request(xfer);
-		}
-		g_free(header);
-
-		accepted = TRUE;
-
-	} else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) {
-		purple_debug_info("msn", "Cam request.\n");
-		if (slpcall && slpcall->slplink &&
-				slpcall->slplink->session) {
-			PurpleConversation *conv;
-			gchar *from = slpcall->slplink->remote_user;
-			conv = purple_find_conversation_with_account(
-					PURPLE_CONV_TYPE_IM, from,
-					slpcall->slplink->session->account);
-			if (conv) {
-				char *buf;
-				buf = g_strdup_printf(
-						_("%s requests to view your "
-						"webcam, but this request is "
-						"not yet supported."), from);
-				purple_conversation_write(conv, NULL, buf,
-						PURPLE_MESSAGE_SYSTEM |
-						PURPLE_MESSAGE_NOTIFY,
-						time(NULL));
-				g_free(buf);
-			}
-		}
-
-	} else if (!strcmp(euf_guid, MSN_CAM_GUID)) {
-		purple_debug_info("msn", "Cam invite.\n");
-		if (slpcall && slpcall->slplink &&
-				slpcall->slplink->session) {
-			PurpleConversation *conv;
-			gchar *from = slpcall->slplink->remote_user;
-			conv = purple_find_conversation_with_account(
-					PURPLE_CONV_TYPE_IM, from,
-					slpcall->slplink->session->account);
-			if (conv) {
-				char *buf;
-				buf = g_strdup_printf(
-						_("%s invited you to view his/her webcam, but "
-						"this is not yet supported."), from);
-				purple_conversation_write(conv, NULL, buf,
-						PURPLE_MESSAGE_SYSTEM |
-						PURPLE_MESSAGE_NOTIFY,
-						time(NULL));
-				g_free(buf);
-			}
-		}
-
-	} else
-		purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid);
-
-	if (!accepted) {
-		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
-		                                slpcall->session_id);
-		msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
-		g_free(content);
-	}
-}
-
-void
-send_bye(MsnSlpCall *slpcall, const char *type)
-{
-	MsnSlpLink *slplink;
-	PurpleAccount *account;
-	MsnSlpMessage *slpmsg;
-	char *header;
-
-	slplink = slpcall->slplink;
-
-	g_return_if_fail(slplink != NULL);
-
-	account = slplink->session->account;
-
-	header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0",
-							 purple_account_get_username(account));
-
-	slpmsg = msn_slpmsg_sip_new(slpcall, 0, header,
-								"A0D624A6-6C0C-4283-A9E0-BC97B4B46D32",
-								type,
-								"\r\n");
-	g_free(header);
-
-	slpmsg->info = "SLP BYE";
-	slpmsg->text_body = TRUE;
-
-	msn_slplink_queue_slpmsg(slplink, slpmsg);
-}
-
-static void
-got_invite(MsnSlpCall *slpcall,
-		   const char *branch, const char *type, const char *content)
-{
-	MsnSlpLink *slplink;
-
-	slplink = slpcall->slplink;
-
-	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
-	{
-		char *euf_guid, *context;
-		char *temp;
-
-		euf_guid = get_token(content, "EUF-GUID: {", "}\r\n");
-
-		temp = get_token(content, "SessionID: ", "\r\n");
-		if (temp != NULL)
-			slpcall->session_id = atoi(temp);
-		g_free(temp);
-
-		temp = get_token(content, "AppID: ", "\r\n");
-		if (temp != NULL)
-			slpcall->app_id = atoi(temp);
-		g_free(temp);
-
-		context = get_token(content, "Context: ", "\r\n");
-
-		if (context != NULL)
-			got_sessionreq(slpcall, branch, euf_guid, context);
-
-		g_free(context);
-		g_free(euf_guid);
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
-	{
-		/* A direct connection negotiation request */
-		char *bridges;
-		char *nonce;
-		MsnDirectConnNonceType ntype;
-
-		purple_debug_info("msn", "got_invite: transreqbody received\n");
-
-		/* Direct connections may be disabled. */
-		if (!purple_account_get_bool(slplink->session->account, "direct_connect", TRUE)) {
-			msn_slp_send_ok(slpcall, branch,
-				"application/x-msnmsgr-transrespbody",
-				"Bridge: TCPv1\r\n"
-				"Listening: false\r\n"
-				"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
-				"\r\n");
-			msn_slpcall_session_init(slpcall);
-
-			return;
-		}
-
-		/* Don't do anything if we already have a direct connection */
-		if (slplink->dc != NULL)
-			return;
-
-		bridges = get_token(content, "Bridges: ", "\r\n");
-		nonce = parse_dc_nonce(content, &ntype);
-		if (bridges && strstr(bridges, "TCPv1") != NULL) {
-			/*
-			 * Ok, the client supports direct TCP connection
-			 * Try to create a listening port
-			 */
-			MsnDirectConn *dc;
-
-			dc = msn_dc_new(slpcall);
-			if (ntype == DC_NONCE_PLAIN) {
-				/* There is only one nonce for plain auth. */
-				dc->nonce_type = ntype;
-				memcpy(dc->nonce, nonce, 16);
-			} else if (ntype == DC_NONCE_SHA1) {
-				/* Each side has a nonce in SHA1 auth. */
-				dc->nonce_type = ntype;
-				strncpy(dc->remote_nonce, nonce, 36);
-				dc->remote_nonce[36] = '\0';
-			}
-
-			dc->listen_data = purple_network_listen_range(
-				0, 0,
-				SOCK_STREAM,
-				msn_dc_listen_socket_created_cb,
-				dc
-			);
-
-			if (dc->listen_data == NULL) {
-				/* Listen socket creation failed */
-
-				purple_debug_info("msn", "got_invite: listening failed\n");
-
-				if (dc->nonce_type != DC_NONCE_PLAIN)
-					msn_slp_send_ok(slpcall, branch,
-						"application/x-msnmsgr-transrespbody",
-						"Bridge: TCPv1\r\n"
-						"Listening: false\r\n"
-						"Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
-						"\r\n");
-				else
-					msn_slp_send_ok(slpcall, branch,
-						"application/x-msnmsgr-transrespbody",
-						"Bridge: TCPv1\r\n"
-						"Listening: false\r\n"
-						"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
-						"\r\n");
-
-			} else {
-				/*
-				 * Listen socket created successfully.
-				 * Don't send anything here because we don't know the parameters
-				 * of the created socket yet. msn_dc_send_ok will be called from
-				 * the callback function: dc_listen_socket_created_cb
-				 */
-				purple_debug_info("msn", "got_invite: listening socket created\n");
-
-				dc->send_connection_info_msg_cb = msn_dc_send_ok;
-				slpcall->wait_for_socket = TRUE;
-			}
-
-		} else {
-			/*
-			 * Invalid direct connect invitation or
-			 * TCP connection is not supported.
-			 */
-		}
-
-		g_free(nonce);
-		g_free(bridges);
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
-	{
-		/* A direct connection negotiation response */
-		msn_slp_process_transresp(slpcall, content);
-	}
-}
-
-static void
-got_ok(MsnSlpCall *slpcall,
-	   const char *type, const char *content)
-{
-	g_return_if_fail(slpcall != NULL);
-	g_return_if_fail(type    != NULL);
-
-	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
-	{
-		char *content;
-		char *header;
-		char *nonce = NULL;
-		MsnSession *session = slpcall->slplink->session;
-		MsnSlpMessage *msg;
-		MsnDirectConn *dc;
-		MsnUser *user;
-
-		if (!purple_account_get_bool(session->account, "direct_connect", TRUE)) {
-			/* Don't attempt a direct connection if disabled. */
-			msn_slpcall_session_init(slpcall);
-			return;
-		}
-
-		if (slpcall->slplink->dc != NULL) {
-			/* If we already have an established direct connection
-			 * then just start the transfer.
-			 */
-			msn_slpcall_session_init(slpcall);
-			return;
-		}
-
-		user = msn_userlist_find_user(session->userlist,
-		                              slpcall->slplink->remote_user);
-		if (!user || !(user->clientid & 0xF0000000))	{
-			/* Just start a normal SB transfer. */
-			msn_slpcall_session_init(slpcall);
-			return;
-		}
-
-		/* Try direct file transfer by sending a second INVITE */
-		dc = msn_dc_new(slpcall);
-		slpcall->branch = rand_guid();
-
-		dc->listen_data = purple_network_listen_range(
-			0, 0,
-			SOCK_STREAM,
-			msn_dc_listen_socket_created_cb,
-			dc
-		);
-
-		header = g_strdup_printf(
-			"INVITE MSNMSGR:%s MSNSLP/1.0",
-			slpcall->slplink->remote_user
-		);
-
-		if (dc->nonce_type == DC_NONCE_SHA1)
-			nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash);
-
-		if (dc->listen_data == NULL) {
-			/* Listen socket creation failed */
-			purple_debug_info("msn", "got_ok: listening failed\n");
-
-			content = g_strdup_printf(
-				"Bridges: TCPv1\r\n"
-				"NetID: %u\r\n"
-				"Conn-Type: IP-Restrict-NAT\r\n"
-				"UPnPNat: false\r\n"
-				"ICF: false\r\n"
-				"%s"
-				"\r\n",
-
-				rand() % G_MAXUINT32,
-				nonce ? nonce : ""
-			);
-
-		} else {
-			/* Listen socket created successfully. */
-			purple_debug_info("msn", "got_ok: listening socket created\n");
-
-			content = g_strdup_printf(
-				"Bridges: TCPv1\r\n"
-				"NetID: 0\r\n"
-				"Conn-Type: Direct-Connect\r\n"
-				"UPnPNat: false\r\n"
-				"ICF: false\r\n"
-				"%s"
-				"\r\n",
-
-				nonce ? nonce : ""
-			);
-		}
-
-		msg = msn_slpmsg_sip_new(
-			slpcall,
-			0,
-			header,
-			slpcall->branch,
-			"application/x-msnmsgr-transreqbody",
-			content
-		);
-		msg->info = "DC INVITE";
-		msg->text_body = TRUE;
-		g_free(nonce);
-		g_free(header);
-		g_free(content);
-
-		msn_slplink_queue_slpmsg(slpcall->slplink, msg);
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
-	{
-		/* Do we get this? */
-		purple_debug_info("msn", "OK with transreqbody\n");
-	}
-	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
-	{
-		msn_slp_process_transresp(slpcall, content);
-	}
-}
-
-static void
-got_error(MsnSlpCall *slpcall,
-          const char *error, const char *type, const char *content)
-{
-	/* It's not valid. Kill this off. */
-	purple_debug_error("msn", "Received non-OK result: %s\n",
-	                   error ? error : "Unknown");
-
-	if (type && (!strcmp(type, "application/x-msnmsgr-transreqbody")
-	          || !strcmp(type, "application/x-msnmsgr-transrespbody"))) {
-		MsnDirectConn *dc = slpcall->slplink->dc;
-		if (dc) {
-			msn_dc_fallback_to_sb(dc);
-			return;
-		}
-	}
-
-	slpcall->wasted = TRUE;
-}
-
-MsnSlpCall *
-msn_slp_sip_recv(MsnSlpLink *slplink, const char *body)
-{
-	MsnSlpCall *slpcall;
-
-	if (body == NULL)
-	{
-		purple_debug_warning("msn", "received bogus message\n");
-		return NULL;
-	}
-
-	if (!strncmp(body, "INVITE", strlen("INVITE")))
-	{
-		char *branch;
-		char *call_id;
-		char *content;
-		char *content_type;
-
-		/* From: <msnmsgr:buddy@hotmail.com> */
-#if 0
-		slpcall->remote_user = get_token(body, "From: <msnmsgr:", ">\r\n");
-#endif
-
-		branch = get_token(body, ";branch={", "}");
-
-		call_id = get_token(body, "Call-ID: {", "}");
-
-#if 0
-		long content_len = -1;
-
-		temp = get_token(body, "Content-Length: ", "\r\n");
-		if (temp != NULL)
-			content_len = atoi(temp);
-		g_free(temp);
-#endif
-		content_type = get_token(body, "Content-Type: ", "\r\n");
-
-		content = get_token(body, "\r\n\r\n", NULL);
-
-		slpcall = NULL;
-		if (branch && call_id)
-		{
-			slpcall = msn_slplink_find_slp_call(slplink, call_id);
-			if (slpcall)
-			{
-				g_free(slpcall->branch);
-				slpcall->branch = g_strdup(branch);
-				got_invite(slpcall, branch, content_type, content);
-			}
-			else if (content_type && content)
-			{
-				slpcall = msn_slpcall_new(slplink);
-				slpcall->id = g_strdup(call_id);
-				got_invite(slpcall, branch, content_type, content);
-			}
-		}
-
-		g_free(call_id);
-		g_free(branch);
-		g_free(content_type);
-		g_free(content);
-	}
-	else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 ")))
-	{
-		char *content;
-		char *content_type;
-		/* Make sure this is "OK" */
-		const char *status = body + strlen("MSNSLP/1.0 ");
-		char *call_id;
-
-		call_id = get_token(body, "Call-ID: {", "}");
-		slpcall = msn_slplink_find_slp_call(slplink, call_id);
-		g_free(call_id);
-
-		g_return_val_if_fail(slpcall != NULL, NULL);
-
-		content_type = get_token(body, "Content-Type: ", "\r\n");
-
-		content = get_token(body, "\r\n\r\n", NULL);
-
-		if (strncmp(status, "200 OK", 6))
-		{
-			char *error = NULL;
-			const char *c;
-
-			/* Eww */
-			if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) ||
-				(c = strchr(status, '\0')))
-			{
-				size_t len = c - status;
-				error = g_strndup(status, len);
-			}
-
-			got_error(slpcall, error, content_type, content);
-			g_free(error);
-
-		} else {
-			/* Everything's just dandy */
-			got_ok(slpcall, content_type, content);
-		}
-
-		g_free(content_type);
-		g_free(content);
-	}
-	else if (!strncmp(body, "BYE", strlen("BYE")))
-	{
-		char *call_id;
-
-		call_id = get_token(body, "Call-ID: {", "}");
-		slpcall = msn_slplink_find_slp_call(slplink, call_id);
-		g_free(call_id);
-
-		if (slpcall != NULL)
-			slpcall->wasted = TRUE;
-
-		/* msn_slpcall_destroy(slpcall); */
-	}
-	else
-		slpcall = NULL;
-
-	return slpcall;
-}
-
 /**************************************************************************
  * Msg Callbacks
  **************************************************************************/
 
-void
-msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	MsnSession *session;
-	MsnSlpLink *slplink;
-	const char *data;
-	gsize len;
-
-	session = cmdproc->servconn->session;
-	slplink = msn_session_get_slplink(session, msg->remote_user);
-
-	if (slplink->swboard == NULL)
-	{
-		/*
-		 * We will need swboard in order to change its flags.  If its
-		 * NULL, something has probably gone wrong earlier on.  I
-		 * didn't want to do this, but MSN 7 is somehow causing us
-		 * to crash here, I couldn't reproduce it to debug more,
-		 * and people are reporting bugs. Hopefully this doesn't
-		 * cause more crashes. Stu.
-		 */
-		if (cmdproc->data == NULL)
-			g_warning("msn_p2p_msg cmdproc->data was NULL\n");
-		else {
-			slplink->swboard = (MsnSwitchBoard *)cmdproc->data;
-			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
-		}
-	}
-
-	data = msn_message_get_bin_data(msg, &len);
-
-	msn_slplink_process_msg(slplink, &msg->msnslp_header, data, len);
-}
-
-static void
-got_emoticon(MsnSlpCall *slpcall,
-			 const guchar *data, gsize size)
-{
-	PurpleConversation *conv;
-	MsnSwitchBoard *swboard;
-
-	swboard = slpcall->slplink->swboard;
-	conv = swboard->conv;
-
-	if (conv) {
-		/* FIXME: it would be better if we wrote the data as we received it
-		   instead of all at once, calling write multiple times and
-		   close once at the very end
-		 */
-		purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size);
-		purple_conv_custom_smiley_close(conv, slpcall->data_info );
-	}
-	if (purple_debug_is_verbose())
-		purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info);
-}
-
-void
-msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
-{
-	MsnSession *session;
-	MsnSlpLink *slplink;
-	MsnSwitchBoard *swboard;
-	MsnObject *obj;
-	char **tokens;
-	char *smile, *body_str;
-	const char *body, *who, *sha1;
-	guint tok;
-	size_t body_len;
-
-	PurpleConversation *conv;
-
-	session = cmdproc->servconn->session;
-
-	if (!purple_account_get_bool(session->account, "custom_smileys", TRUE))
-		return;
-
-	swboard = cmdproc->data;
-	conv = swboard->conv;
-
-	body = msn_message_get_bin_data(msg, &body_len);
-	if (!body || !body_len)
-		return;
-	body_str = g_strndup(body, body_len);
-
-	/* MSN Messenger 7 may send more than one MSNObject in a single message...
-	 * Maybe 10 tokens is a reasonable max value. */
-	tokens = g_strsplit(body_str, "\t", 10);
-
-	g_free(body_str);
-
-	for (tok = 0; tok < 9; tok += 2) {
-		if (tokens[tok] == NULL || tokens[tok + 1] == NULL) {
-			break;
-		}
-
-		smile = tokens[tok];
-		obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1]));
-
-		if (obj == NULL)
-			break;
-
-		who = msn_object_get_creator(obj);
-		sha1 = msn_object_get_sha1(obj);
-
-		slplink = msn_session_get_slplink(session, who);
-		if (slplink->swboard != swboard) {
-			if (slplink->swboard != NULL)
-				/*
-				 * Apparently we're using a different switchboard now or
-				 * something?  I don't know if this is normal, but it
-				 * definitely happens.  So make sure the old switchboard
-				 * doesn't still have a reference to us.
-				 */
-				slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink);
-			slplink->swboard = swboard;
-			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
-		}
-
-		/* If the conversation doesn't exist then this is a custom smiley
-		 * used in the first message in a MSN conversation: we need to create
-		 * the conversation now, otherwise the custom smiley won't be shown.
-		 * This happens because every GtkIMHtml has its own smiley tree: if
-		 * the conversation doesn't exist then we cannot associate the new
-		 * smiley with its GtkIMHtml widget. */
-		if (!conv) {
-			conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who);
-		}
-
-		if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) {
-			msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj);
-		}
-
-		msn_object_destroy(obj);
-		obj =   NULL;
-		who =   NULL;
-		sha1 = NULL;
-	}
-	g_strfreev(tokens);
-}
-
-static gboolean
-buddy_icon_cached(PurpleConnection *gc, MsnObject *obj)
-{
-	PurpleAccount *account;
-	PurpleBuddy *buddy;
-	const char *old;
-	const char *new;
-
-	g_return_val_if_fail(obj != NULL, FALSE);
-
-	account = purple_connection_get_account(gc);
-
-	buddy = purple_find_buddy(account, msn_object_get_creator(obj));
-	if (buddy == NULL)
-		return FALSE;
-
-	old = purple_buddy_icons_get_checksum_for_user(buddy);
-	new = msn_object_get_sha1(obj);
-
-	if (new == NULL)
-		return FALSE;
-
-	/* If the old and new checksums are the same, and the file actually exists,
-	 * then return TRUE */
-	if (old != NULL && !strcmp(old, new))
-		return TRUE;
-
-	return FALSE;
-}
-
-static void
-msn_release_buddy_icon_request(MsnUserList *userlist)
-{
-	MsnUser *user;
-
-	g_return_if_fail(userlist != NULL);
-
-	if (purple_debug_is_verbose())
-		purple_debug_info("msn", "Releasing buddy icon request\n");
-
-	if (userlist->buddy_icon_window > 0)
-	{
-		GQueue *queue;
-
-		queue = userlist->buddy_icon_requests;
-
-		if (g_queue_is_empty(userlist->buddy_icon_requests))
-			return;
-
-		user = g_queue_pop_head(queue);
-
-		userlist->buddy_icon_window--;
-		request_user_display(user);
-
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn",
-			                  "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n",
-			                  userlist->buddy_icon_window);
-	}
-}
-
 /*
  * Called on a timeout from end_user_display(). Frees a buddy icon window slow and dequeues the next
  * buddy icon request if there is one.
@@ -1301,61 +114,24 @@
 	return FALSE;
 }
 
-void
-msn_queue_buddy_icon_request(MsnUser *user)
-{
-	PurpleAccount *account;
-	MsnObject *obj;
-	GQueue *queue;
-
-	g_return_if_fail(user != NULL);
-
-	account = user->userlist->session->account;
-
-	obj = msn_user_get_object(user);
-
-	if (obj == NULL)
-	{
-		purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL);
-		return;
-	}
-
-	if (!buddy_icon_cached(account->gc, obj))
-	{
-		MsnUserList *userlist;
-
-		userlist = user->userlist;
-		queue = userlist->buddy_icon_requests;
-
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n",
-			                  user->passport, userlist->buddy_icon_window);
-
-		g_queue_push_tail(queue, user);
-
-		if (userlist->buddy_icon_window > 0)
-			msn_release_buddy_icon_request(userlist);
-	}
-}
-
 static void
 got_user_display(MsnSlpCall *slpcall,
 				 const guchar *data, gsize size)
 {
-	MsnSlpLink *slplink;
+	MsnUserList *userlist;
 	const char *info;
 	PurpleAccount *account;
 
 	g_return_if_fail(slpcall != NULL);
-	slplink = slpcall->slplink;
 
 	info = slpcall->data_info;
 	if (purple_debug_is_verbose())
-		purple_debug_info("msn", "Got User Display: %s\n", slplink->remote_user);
+		purple_debug_info("msn", "Got User Display: %s\n", slpcall->slplink->remote_user);
 
-	account = slplink->session->account;
+	userlist = slpcall->slplink->session->userlist;
+	account = slpcall->slplink->session->account;
 
-	purple_buddy_icons_set_for_user(account, slplink->remote_user,
+	purple_buddy_icons_set_for_user(account, slpcall->slplink->remote_user,
 								  g_memdup(data, size), size, info);
 }
 
@@ -1416,7 +192,43 @@
 }
 
 static void
-request_user_display(MsnUser *user)
+request_own_user_display(MsnUser *user)
+{
+	PurpleAccount *account;
+	MsnSession *session;
+	MsnObject *my_obj = NULL;
+	gconstpointer data = NULL;
+	const char *info;
+	size_t len = 0;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "Requesting our own user display\n");
+
+	session = user->userlist->session;
+	account = session->account;
+	my_obj = msn_user_get_object(user);
+
+	if (my_obj != NULL) {
+		PurpleStoredImage *img = msn_object_get_image(my_obj);
+		data = purple_imgstore_get_data(img);
+		len = purple_imgstore_get_size(img);
+		info = msn_object_get_sha1(my_obj);
+	}
+
+	purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info);
+
+	/* Free one window slot */
+	session->userlist->buddy_icon_window++;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n",
+				session->userlist->buddy_icon_window);
+
+	msn_release_buddy_icon_request(session->userlist);
+}
+
+void
+msn_request_user_display(MsnUser *user)
 {
 	PurpleAccount *account;
 	MsnSession *session;
@@ -1452,32 +264,134 @@
 		}
 	}
 	else
-	{
-		MsnObject *my_obj = NULL;
-		gconstpointer data = NULL;
-		size_t len = 0;
+		request_own_user_display(user);
+}
+
+static void
+send_file_cb(MsnSlpCall *slpcall)
+{
+	MsnSlpMessage *slpmsg;
+	PurpleXfer *xfer;
+
+	xfer = (PurpleXfer *)slpcall->xfer;
+	if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED)
+		return;
+
+	purple_xfer_ref(xfer);
+	purple_xfer_start(xfer, -1, NULL, 0);
+	if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) {
+		purple_xfer_unref(xfer);
+		return;
+	}
+	purple_xfer_unref(xfer);
+
+	slpmsg = msn_slpmsg_file_new(slpcall, purple_xfer_get_size(xfer));
+	msn_slpmsg_set_slplink(slpmsg, slpcall->slplink);
+
+	msn_slplink_send_slpmsg(slpcall->slplink, slpmsg);
+}
 
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn", "Requesting our own user display\n");
+static gchar *
+gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path)
+{
+	gsize size = 0;
+	MsnFileContext *header;
+	gchar *u8 = NULL;
+	gchar *ret;
+	gunichar2 *uni = NULL;
+	glong currentChar = 0;
+	glong len = 0;
+	const char *preview;
+	gsize preview_len;
+
+	size = purple_xfer_get_size(xfer);
+
+	purple_xfer_prepare_thumbnail(xfer, "png");
 
-		my_obj = msn_user_get_object(session->user);
+	if (!file_name) {
+		gchar *basename = g_path_get_basename(file_path);
+		u8 = purple_utf8_try_convert(basename);
+		g_free(basename);
+		file_name = u8;
+	}
+
+	uni = g_utf8_to_utf16(file_name, -1, NULL, &len, NULL);
+
+	if (u8) {
+		g_free(u8);
+		file_name = NULL;
+		u8 = NULL;
+	}
+
+	preview = purple_xfer_get_thumbnail(xfer, &preview_len);
+	header = g_malloc(sizeof(MsnFileContext) + preview_len);
 
-		if (my_obj != NULL)
-		{
-			PurpleStoredImage *img = msn_object_get_image(my_obj);
-			data = purple_imgstore_get_data(img);
-			len = purple_imgstore_get_size(img);
-		}
+	header->length = GUINT32_TO_LE(sizeof(MsnFileContext) - 1);
+	header->version = GUINT32_TO_LE(2); /* V.3 contains additional unnecessary data */
+	header->file_size = GUINT64_TO_LE(size);
+	if (preview)
+		header->type = GUINT32_TO_LE(0);
+	else
+		header->type = GUINT32_TO_LE(1);
 
-		purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info);
+	len = MIN(len, MAX_FILE_NAME_LEN);
+	for (currentChar = 0; currentChar < len; currentChar++) {
+		header->file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]);
+	}
+	memset(&header->file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2);
+
+	memset(&header->unknown1, 0, sizeof(header->unknown1));
+	header->unknown2 = GUINT32_TO_LE(0xffffffff);
+	if (preview) {
+		memcpy(&header->preview, preview, preview_len);
+	}
+	header->preview[preview_len] = '\0';
+
+	g_free(uni);
+	ret = purple_base64_encode((const guchar *)header, sizeof(MsnFileContext) + preview_len);
+	g_free(header);
+	return ret;
+}
 
-		/* Free one window slot */
-		session->userlist->buddy_icon_window++;
+void
+msn_request_ft(PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	MsnSlpLink *slplink;
+	char *context;
+	const char *fn;
+	const char *fp;
+
+	fn = purple_xfer_get_filename(xfer);
+	fp = purple_xfer_get_local_filename(xfer);
+
+	slplink = xfer->data;
+
+	g_return_if_fail(slplink != NULL);
+	g_return_if_fail(fp != NULL);
+
+	slpcall = msn_slpcall_new(slplink);
+	msn_slpcall_init(slpcall, MSN_SLPCALL_DC);
 
-		if (purple_debug_is_verbose())
-			purple_debug_info("msn", "request_user_display(): buddy_icon_window++ yields =%d\n",
-			                  session->userlist->buddy_icon_window);
+	slpcall->session_init_cb = send_file_cb;
+	slpcall->end_cb = msn_xfer_end_cb;
+	slpcall->cb = msn_xfer_completed_cb;
+	slpcall->xfer = xfer;
+	purple_xfer_ref(slpcall->xfer);
+
+	slpcall->pending = TRUE;
 
-		msn_release_buddy_icon_request(session->userlist);
-	}
+	purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel);
+	purple_xfer_set_read_fnc(xfer, msn_xfer_read);
+	purple_xfer_set_write_fnc(xfer, msn_xfer_write);
+
+	xfer->data = slpcall;
+
+	context = gen_context(xfer, fn, fp);
+
+	msn_slpcall_invite(slpcall, MSN_FT_GUID, P2P_APPID_FILE, context);
+	msn_slplink_unref(slplink);
+
+	g_free(context);
 }
+
--- a/libpurple/protocols/msn/slp.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slp.h	Mon Nov 01 07:12:08 2010 +0000
@@ -25,10 +25,12 @@
 #define MSN_SLP_H
 
 #include "internal.h"
+#include "ft.h"
 
-#include "ft.h"
 #include "session.h"
 #include "slpcall.h"
+#include "slplink.h"
+#include "user.h"
 
 #define MAX_FILE_NAME_LEN 260 /* MAX_PATH in Windows */
 
@@ -49,8 +51,6 @@
 } MsnFileContext;
 #pragma pack(pop)
 
-MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink,
-							  const char *body);
 void
 msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch,
 		const char *type, const char *content);
@@ -62,15 +62,9 @@
 
 void send_bye(MsnSlpCall *slpcall, const char *type);
 
-void msn_xfer_completed_cb(MsnSlpCall *slpcall,
-						   const guchar *body, gsize size);
 
-void msn_xfer_cancel(PurpleXfer *xfer);
-gssize msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer);
-gssize msn_xfer_read(guchar **data, PurpleXfer *xfer);
+void msn_request_user_display(MsnUser *user);
 
-void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session);
-
-void msn_queue_buddy_icon_request(MsnUser *user);
+void msn_request_ft(PurpleXfer *xfer);
 
 #endif /* MSN_SLP_H */
--- a/libpurple/protocols/msn/slpcall.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,11 +21,17 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+#include "smiley.h"
+
 #include "msnutils.h"
 #include "slpcall.h"
 
 #include "slp.h"
+#include "p2p.h"
+#include "xfer.h"
 
 /**************************************************************************
  * Main
@@ -140,7 +146,7 @@
 
 void
 msn_slpcall_invite(MsnSlpCall *slpcall, const char *euf_guid,
-					int app_id, const char *context)
+					MsnP2PAppId app_id, const char *context)
 {
 	MsnSlpLink *slplink;
 	MsnSlpMessage *slpmsg;
@@ -190,6 +196,861 @@
 	msn_slpcall_destroy(slpcall);
 }
 
+/*****************************************************************************
+ * Parse received SLP messages
+ ****************************************************************************/
+
+/**************************************************************************
+ *** Util
+ **************************************************************************/
+
+static char *
+get_token(const char *str, const char *start, const char *end)
+{
+	const char *c, *c2;
+
+	if ((c = strstr(str, start)) == NULL)
+		return NULL;
+
+	c += strlen(start);
+
+	if (end != NULL)
+	{
+		if ((c2 = strstr(c, end)) == NULL)
+			return NULL;
+
+		return g_strndup(c, c2 - c);
+	}
+	else
+	{
+		/* This has to be changed */
+		return g_strdup(c);
+	}
+
+}
+
+/* XXX: this could be improved if we tracked custom smileys
+ * per-protocol, per-account, per-session or (ideally) per-conversation
+ */
+static PurpleStoredImage *
+find_valid_emoticon(PurpleAccount *account, const char *path)
+{
+	GList *smileys;
+
+	if (!purple_account_get_bool(account, "custom_smileys", TRUE))
+		return NULL;
+
+	smileys = purple_smileys_get_all();
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		PurpleSmiley *smiley;
+		PurpleStoredImage *img;
+
+		smiley = smileys->data;
+		img = purple_smiley_get_stored_image(smiley);
+
+		if (purple_strequal(path, purple_imgstore_get_filename(img))) {
+			g_list_free(smileys);
+			return img;
+		}
+
+		purple_imgstore_unref(img);
+	}
+
+	purple_debug_error("msn", "Received illegal request for file %s\n", path);
+	return NULL;
+}
+
+static char *
+parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype)
+{
+	char *nonce;
+
+	*ntype = DC_NONCE_UNKNOWN;
+
+	nonce = get_token(content, "Hashed-Nonce: {", "}\r\n");
+	if (nonce) {
+		*ntype = DC_NONCE_SHA1;
+	} else {
+		guint32 n1, n6;
+		guint16 n2, n3, n4, n5;
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		if (nonce
+		 && sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%04hx%08x",
+		           &n1, &n2, &n3, &n4, &n5, &n6) == 6) {
+			*ntype = DC_NONCE_PLAIN;
+			g_free(nonce);
+			nonce = g_malloc(16);
+			*(guint32 *)(nonce +  0) = GUINT32_TO_LE(n1);
+			*(guint16 *)(nonce +  4) = GUINT16_TO_LE(n2);
+			*(guint16 *)(nonce +  6) = GUINT16_TO_LE(n3);
+			*(guint16 *)(nonce +  8) = GUINT16_TO_BE(n4);
+			*(guint16 *)(nonce + 10) = GUINT16_TO_BE(n5);
+			*(guint32 *)(nonce + 12) = GUINT32_TO_BE(n6);
+		} else {
+			/* Invalid nonce, so ignore request */
+			g_free(nonce);
+			nonce = NULL;
+		}
+	}
+
+	return nonce;
+}
+
+static void
+msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content)
+{
+	/* A direct connection negotiation response */
+	char *bridge;
+	char *nonce;
+	char *listening;
+	MsnDirectConn *dc = slpcall->slplink->dc;
+	MsnDirectConnNonceType ntype;
+
+	purple_debug_info("msn", "process_transresp\n");
+
+	/* Direct connections are disabled. */
+	if (!purple_account_get_bool(slpcall->slplink->session->account, "direct_connect", TRUE))
+		return;
+
+	g_return_if_fail(dc != NULL);
+	g_return_if_fail(dc->state == DC_STATE_CLOSED);
+
+	bridge = get_token(content, "Bridge: ", "\r\n");
+	nonce = parse_dc_nonce(content, &ntype);
+	listening = get_token(content, "Listening: ", "\r\n");
+	if (listening && bridge && !strcmp(bridge, "TCPv1")) {
+		/* Ok, the client supports direct TCP connection */
+
+		/* We always need this. */
+		if (ntype == DC_NONCE_SHA1) {
+			strncpy(dc->remote_nonce, nonce, 36);
+			dc->remote_nonce[36] = '\0';
+		}
+
+		if (!strcasecmp(listening, "false")) {
+			if (dc->listen_data != NULL) {
+				/*
+				 * We'll listen for incoming connections but
+				 * the listening socket isn't ready yet so we cannot
+				 * send the INVITE packet now. Put the slpcall into waiting mode
+				 * and let the callback send the invite.
+				 */
+				slpcall->wait_for_socket = TRUE;
+
+			} else if (dc->listenfd != -1) {
+				/* The listening socket is ready. Send the INVITE here. */
+				msn_dc_send_invite(dc);
+
+			} else {
+				/* We weren't able to create a listener either. Use SB. */
+				msn_dc_fallback_to_sb(dc);
+			}
+
+		} else {
+			/*
+			 * We should connect to the client so parse
+			 * IP/port from response.
+			 */
+			char *ip, *port_str;
+			int port = 0;
+
+			if (ntype == DC_NONCE_PLAIN) {
+				/* Only needed for listening side. */
+				memcpy(dc->nonce, nonce, 16);
+			}
+
+			/* Cancel any listen attempts because we don't need them. */
+			if (dc->listenfd_handle != 0) {
+				purple_input_remove(dc->listenfd_handle);
+				dc->listenfd_handle = 0;
+			}
+			if (dc->connect_timeout_handle != 0) {
+				purple_timeout_remove(dc->connect_timeout_handle);
+				dc->connect_timeout_handle = 0;
+			}
+			if (dc->listenfd != -1) {
+				purple_network_remove_port_mapping(dc->listenfd);
+				close(dc->listenfd);
+				dc->listenfd = -1;
+			}
+			if (dc->listen_data != NULL) {
+				purple_network_listen_cancel(dc->listen_data);
+				dc->listen_data = NULL;
+			}
+
+			/* Save external IP/port for later use. We'll try local connection first. */
+			dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4External-Port: ", "\r\n");
+			if (port_str) {
+				dc->ext_port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4Internal-Port: ", "\r\n");
+			if (port_str) {
+				port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			if (ip && port) {
+				/* Try internal address first */
+				dc->connect_data = purple_proxy_connect(
+					NULL,
+					slpcall->slplink->session->account,
+					ip,
+					port,
+					msn_dc_connected_to_peer_cb,
+					dc
+				);
+
+				if (dc->connect_data) {
+					/* Add connect timeout handle */
+					dc->connect_timeout_handle = purple_timeout_add_seconds(
+						DC_OUTGOING_TIMEOUT,
+						msn_dc_outgoing_connection_timeout_cb,
+						dc
+					);
+				} else {
+					/*
+					 * Connection failed
+					 * Try external IP/port (if specified)
+					 */
+					msn_dc_outgoing_connection_timeout_cb(dc);
+				}
+
+			} else {
+				/*
+				 * Omitted or invalid internal IP address / port
+				 * Try external IP/port (if specified)
+				 */
+				msn_dc_outgoing_connection_timeout_cb(dc);
+			}
+
+			g_free(ip);
+		}
+
+	} else {
+		/*
+		 * Invalid direct connect invitation or
+		 * TCP connection is not supported
+		 */
+	}
+
+	g_free(listening);
+	g_free(nonce);
+	g_free(bridge);
+
+	return;
+}
+
+static void
+got_sessionreq(MsnSlpCall *slpcall, const char *branch,
+			   const char *euf_guid, const char *context)
+{
+	gboolean accepted = FALSE;
+
+	if (!strcmp(euf_guid, MSN_OBJ_GUID))
+	{
+		/* Emoticon or UserDisplay */
+		char *content;
+		gsize len;
+		MsnSlpLink *slplink;
+		MsnSlpMessage *slpmsg;
+		MsnObject *obj;
+		char *msnobj_data;
+		PurpleStoredImage *img = NULL;
+		int type;
+
+		/* Send Ok */
+		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+								  slpcall->session_id);
+
+		msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
+				content);
+
+		g_free(content);
+
+		slplink = slpcall->slplink;
+
+		msnobj_data = (char *)purple_base64_decode(context, &len);
+		obj = msn_object_new_from_string(msnobj_data);
+		type = msn_object_get_type(obj);
+		g_free(msnobj_data);
+		if (type == MSN_OBJECT_EMOTICON) {
+			img = find_valid_emoticon(slplink->session->account, obj->location);
+		} else if (type == MSN_OBJECT_USERTILE) {
+			img = msn_object_get_image(obj);
+			if (img)
+				purple_imgstore_ref(img);
+		}
+		msn_object_destroy(obj);
+
+		if (img != NULL) {
+			/* DATA PREP */
+			slpmsg = msn_slpmsg_dataprep_new(slpcall);
+			msn_slpmsg_set_slplink(slpmsg, slplink);
+			msn_slplink_queue_slpmsg(slplink, slpmsg);
+
+			/* DATA */
+			slpmsg = msn_slpmsg_obj_new(slpcall, img);
+			msn_slpmsg_set_slplink(slpmsg, slplink);
+			msn_slplink_queue_slpmsg(slplink, slpmsg);
+			purple_imgstore_unref(img);
+
+			accepted = TRUE;
+
+		} else {
+			purple_debug_error("msn", "Wrong object.\n");
+		}
+	}
+
+	else if (!strcmp(euf_guid, MSN_FT_GUID))
+	{
+		/* File Transfer */
+		PurpleAccount *account;
+		PurpleXfer *xfer;
+		MsnFileContext *header;
+		gsize bin_len;
+		guint32 file_size;
+		char *file_name;
+
+		account = slpcall->slplink->session->account;
+
+		slpcall->end_cb = msn_xfer_end_cb;
+		slpcall->branch = g_strdup(branch);
+
+		slpcall->pending = TRUE;
+
+		xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE,
+							 slpcall->slplink->remote_user);
+
+		header = (MsnFileContext *)purple_base64_decode(context, &bin_len);
+		if (header != NULL && bin_len >= sizeof(MsnFileContext) - 1 &&
+			(header->version == 2 ||
+			 (header->version == 3 && header->length == sizeof(MsnFileContext) + 63))) {
+			file_size = GUINT64_FROM_LE(header->file_size);
+
+			file_name = g_convert((const gchar *)&header->file_name,
+			                      MAX_FILE_NAME_LEN * 2,
+			                      "UTF-8", "UTF-16LE",
+			                      NULL, NULL, NULL);
+
+			purple_xfer_set_filename(xfer, file_name ? file_name : "");
+			g_free(file_name);
+			purple_xfer_set_size(xfer, file_size);
+			purple_xfer_set_init_fnc(xfer, msn_xfer_init);
+			purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel);
+			purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel);
+			purple_xfer_set_read_fnc(xfer, msn_xfer_read);
+			purple_xfer_set_write_fnc(xfer, msn_xfer_write);
+
+			slpcall->u.incoming_data = g_byte_array_new();
+
+			slpcall->xfer = xfer;
+			purple_xfer_ref(slpcall->xfer);
+
+			xfer->data = slpcall;
+
+			if (header->type == 0 && bin_len >= sizeof(MsnFileContext)) {
+				purple_xfer_set_thumbnail(xfer, &header->preview,
+				                          bin_len - sizeof(MsnFileContext),
+				    					  "image/png");
+			}
+
+			purple_xfer_request(xfer);
+		}
+		g_free(header);
+
+		accepted = TRUE;
+
+	} else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) {
+		purple_debug_info("msn", "Cam request.\n");
+		if (slpcall && slpcall->slplink &&
+				slpcall->slplink->session) {
+			PurpleConversation *conv;
+			gchar *from = slpcall->slplink->remote_user;
+			conv = purple_find_conversation_with_account(
+					PURPLE_CONV_TYPE_IM, from,
+					slpcall->slplink->session->account);
+			if (conv) {
+				char *buf;
+				buf = g_strdup_printf(
+						_("%s requests to view your "
+						"webcam, but this request is "
+						"not yet supported."), from);
+				purple_conversation_write(conv, NULL, buf,
+						PURPLE_MESSAGE_SYSTEM |
+						PURPLE_MESSAGE_NOTIFY,
+						time(NULL));
+				g_free(buf);
+			}
+		}
+
+	} else if (!strcmp(euf_guid, MSN_CAM_GUID)) {
+		purple_debug_info("msn", "Cam invite.\n");
+		if (slpcall && slpcall->slplink &&
+				slpcall->slplink->session) {
+			PurpleConversation *conv;
+			gchar *from = slpcall->slplink->remote_user;
+			conv = purple_find_conversation_with_account(
+					PURPLE_CONV_TYPE_IM, from,
+					slpcall->slplink->session->account);
+			if (conv) {
+				char *buf;
+				buf = g_strdup_printf(
+						_("%s invited you to view his/her webcam, but "
+						"this is not yet supported."), from);
+				purple_conversation_write(conv, NULL, buf,
+						PURPLE_MESSAGE_SYSTEM |
+						PURPLE_MESSAGE_NOTIFY,
+						time(NULL));
+				g_free(buf);
+			}
+		}
+
+	} else
+		purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid);
+
+	if (!accepted) {
+		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+		                                slpcall->session_id);
+		msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
+		g_free(content);
+	}
+}
+
+void
+send_bye(MsnSlpCall *slpcall, const char *type)
+{
+	MsnSlpLink *slplink;
+	PurpleAccount *account;
+	MsnSlpMessage *slpmsg;
+	char *header;
+
+	slplink = slpcall->slplink;
+
+	g_return_if_fail(slplink != NULL);
+
+	account = slplink->session->account;
+
+	header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0",
+							 purple_account_get_username(account));
+
+	slpmsg = msn_slpmsg_sip_new(slpcall, 0, header,
+								"A0D624A6-6C0C-4283-A9E0-BC97B4B46D32",
+								type,
+								"\r\n");
+	g_free(header);
+
+	slpmsg->info = "SLP BYE";
+	slpmsg->text_body = TRUE;
+
+	msn_slplink_queue_slpmsg(slplink, slpmsg);
+}
+
+static void
+got_invite(MsnSlpCall *slpcall,
+		   const char *branch, const char *type, const char *content)
+{
+	MsnSlpLink *slplink;
+
+	slplink = slpcall->slplink;
+
+	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
+	{
+		char *euf_guid, *context;
+		char *temp;
+
+		euf_guid = get_token(content, "EUF-GUID: {", "}\r\n");
+
+		temp = get_token(content, "SessionID: ", "\r\n");
+		if (temp != NULL)
+			slpcall->session_id = atoi(temp);
+		g_free(temp);
+
+		temp = get_token(content, "AppID: ", "\r\n");
+		if (temp != NULL)
+			slpcall->app_id = atoi(temp);
+		g_free(temp);
+
+		context = get_token(content, "Context: ", "\r\n");
+
+		if (context != NULL)
+			got_sessionreq(slpcall, branch, euf_guid, context);
+
+		g_free(context);
+		g_free(euf_guid);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
+	{
+		/* A direct connection negotiation request */
+		char *bridges;
+		char *nonce;
+		MsnDirectConnNonceType ntype;
+
+		purple_debug_info("msn", "got_invite: transreqbody received\n");
+
+		/* Direct connections may be disabled. */
+		if (!purple_account_get_bool(slplink->session->account, "direct_connect", TRUE)) {
+			msn_slp_send_ok(slpcall, branch,
+				"application/x-msnmsgr-transrespbody",
+				"Bridge: TCPv1\r\n"
+				"Listening: false\r\n"
+				"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+				"\r\n");
+			msn_slpcall_session_init(slpcall);
+
+			return;
+		}
+
+		/* Don't do anything if we already have a direct connection */
+		if (slplink->dc != NULL)
+			return;
+
+		bridges = get_token(content, "Bridges: ", "\r\n");
+		nonce = parse_dc_nonce(content, &ntype);
+		if (bridges && strstr(bridges, "TCPv1") != NULL) {
+			/*
+			 * Ok, the client supports direct TCP connection
+			 * Try to create a listening port
+			 */
+			MsnDirectConn *dc;
+
+			dc = msn_dc_new(slpcall);
+			if (ntype == DC_NONCE_PLAIN) {
+				/* There is only one nonce for plain auth. */
+				dc->nonce_type = ntype;
+				memcpy(dc->nonce, nonce, 16);
+			} else if (ntype == DC_NONCE_SHA1) {
+				/* Each side has a nonce in SHA1 auth. */
+				dc->nonce_type = ntype;
+				strncpy(dc->remote_nonce, nonce, 36);
+				dc->remote_nonce[36] = '\0';
+			}
+
+			dc->listen_data = purple_network_listen_range(
+				0, 0,
+				SOCK_STREAM,
+				msn_dc_listen_socket_created_cb,
+				dc
+			);
+
+			if (dc->listen_data == NULL) {
+				/* Listen socket creation failed */
+
+				purple_debug_info("msn", "got_invite: listening failed\n");
+
+				if (dc->nonce_type != DC_NONCE_PLAIN)
+					msn_slp_send_ok(slpcall, branch,
+						"application/x-msnmsgr-transrespbody",
+						"Bridge: TCPv1\r\n"
+						"Listening: false\r\n"
+						"Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+						"\r\n");
+				else
+					msn_slp_send_ok(slpcall, branch,
+						"application/x-msnmsgr-transrespbody",
+						"Bridge: TCPv1\r\n"
+						"Listening: false\r\n"
+						"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+						"\r\n");
+
+			} else {
+				/*
+				 * Listen socket created successfully.
+				 * Don't send anything here because we don't know the parameters
+				 * of the created socket yet. msn_dc_send_ok will be called from
+				 * the callback function: dc_listen_socket_created_cb
+				 */
+				purple_debug_info("msn", "got_invite: listening socket created\n");
+
+				dc->send_connection_info_msg_cb = msn_dc_send_ok;
+				slpcall->wait_for_socket = TRUE;
+			}
+
+		} else {
+			/*
+			 * Invalid direct connect invitation or
+			 * TCP connection is not supported.
+			 */
+		}
+
+		g_free(nonce);
+		g_free(bridges);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
+	{
+		/* A direct connection negotiation response */
+		msn_slp_process_transresp(slpcall, content);
+	}
+}
+
+static void
+got_ok(MsnSlpCall *slpcall,
+	   const char *type, const char *content)
+{
+	g_return_if_fail(slpcall != NULL);
+	g_return_if_fail(type    != NULL);
+
+	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
+	{
+		char *content;
+		char *header;
+		char *nonce = NULL;
+		MsnSession *session = slpcall->slplink->session;
+		MsnSlpMessage *msg;
+		MsnDirectConn *dc;
+		MsnUser *user;
+
+		if (!purple_account_get_bool(session->account, "direct_connect", TRUE)) {
+			/* Don't attempt a direct connection if disabled. */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+
+		if (slpcall->slplink->dc != NULL) {
+			/* If we already have an established direct connection
+			 * then just start the transfer.
+			 */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+
+		user = msn_userlist_find_user(session->userlist,
+		                              slpcall->slplink->remote_user);
+		if (!user || !(user->clientid & 0xF0000000))	{
+			/* Just start a normal SB transfer. */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+
+		/* Try direct file transfer by sending a second INVITE */
+		dc = msn_dc_new(slpcall);
+		slpcall->branch = rand_guid();
+
+		dc->listen_data = purple_network_listen_range(
+			0, 0,
+			SOCK_STREAM,
+			msn_dc_listen_socket_created_cb,
+			dc
+		);
+
+		header = g_strdup_printf(
+			"INVITE MSNMSGR:%s MSNSLP/1.0",
+			slpcall->slplink->remote_user
+		);
+
+		if (dc->nonce_type == DC_NONCE_SHA1)
+			nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash);
+
+		if (dc->listen_data == NULL) {
+			/* Listen socket creation failed */
+			purple_debug_info("msn", "got_ok: listening failed\n");
+
+			content = g_strdup_printf(
+				"Bridges: TCPv1\r\n"
+				"NetID: %u\r\n"
+				"Conn-Type: IP-Restrict-NAT\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+				"%s"
+				"\r\n",
+
+				rand() % G_MAXUINT32,
+				nonce ? nonce : ""
+			);
+
+		} else {
+			/* Listen socket created successfully. */
+			purple_debug_info("msn", "got_ok: listening socket created\n");
+
+			content = g_strdup_printf(
+				"Bridges: TCPv1\r\n"
+				"NetID: 0\r\n"
+				"Conn-Type: Direct-Connect\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+				"%s"
+				"\r\n",
+
+				nonce ? nonce : ""
+			);
+		}
+
+		msg = msn_slpmsg_sip_new(
+			slpcall,
+			0,
+			header,
+			slpcall->branch,
+			"application/x-msnmsgr-transreqbody",
+			content
+		);
+		msg->info = "DC INVITE";
+		msg->text_body = TRUE;
+		g_free(nonce);
+		g_free(header);
+		g_free(content);
+
+		msn_slplink_queue_slpmsg(slpcall->slplink, msg);
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
+	{
+		/* Do we get this? */
+		purple_debug_info("msn", "OK with transreqbody\n");
+	}
+	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
+	{
+		msn_slp_process_transresp(slpcall, content);
+	}
+}
+
+static void
+got_error(MsnSlpCall *slpcall,
+          const char *error, const char *type, const char *content)
+{
+	/* It's not valid. Kill this off. */
+	purple_debug_error("msn", "Received non-OK result: %s\n",
+	                   error ? error : "Unknown");
+
+	if (type && !strcmp(type, "application/x-msnmsgr-transreqbody")) {
+		MsnDirectConn *dc = slpcall->slplink->dc;
+		if (dc) {
+			msn_dc_fallback_to_sb(dc);
+			return;
+		}
+	}
+
+	slpcall->wasted = TRUE;
+}
+
+static MsnSlpCall *
+msn_slp_sip_recv(MsnSlpLink *slplink, const char *body)
+{
+	MsnSlpCall *slpcall;
+
+	if (body == NULL)
+	{
+		purple_debug_warning("msn", "received bogus message\n");
+		return NULL;
+	}
+
+	if (!strncmp(body, "INVITE", strlen("INVITE")))
+	{
+		/* This is an INVITE request */
+		char *branch;
+		char *call_id;
+		char *content;
+		char *content_type;
+
+		/* From: <msnmsgr:buddy@hotmail.com> */
+#if 0
+		slpcall->remote_user = get_token(body, "From: <msnmsgr:", ">\r\n");
+#endif
+
+		branch = get_token(body, ";branch={", "}");
+
+		call_id = get_token(body, "Call-ID: {", "}");
+
+#if 0
+		long content_len = -1;
+
+		temp = get_token(body, "Content-Length: ", "\r\n");
+		if (temp != NULL)
+			content_len = atoi(temp);
+		g_free(temp);
+#endif
+		content_type = get_token(body, "Content-Type: ", "\r\n");
+
+		content = get_token(body, "\r\n\r\n", NULL);
+
+		slpcall = NULL;
+		if (branch && call_id)
+		{
+			slpcall = msn_slplink_find_slp_call(slplink, call_id);
+			if (slpcall)
+			{
+				g_free(slpcall->branch);
+				slpcall->branch = g_strdup(branch);
+				got_invite(slpcall, branch, content_type, content);
+			}
+			else if (content_type && content)
+			{
+				slpcall = msn_slpcall_new(slplink);
+				slpcall->id = g_strdup(call_id);
+				got_invite(slpcall, branch, content_type, content);
+			}
+		}
+
+		g_free(call_id);
+		g_free(branch);
+		g_free(content_type);
+		g_free(content);
+	}
+	else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 ")))
+	{
+		/* This is a response */
+		char *content;
+		char *content_type;
+		/* Make sure this is "OK" */
+		const char *status = body + strlen("MSNSLP/1.0 ");
+		char *call_id;
+
+		call_id = get_token(body, "Call-ID: {", "}");
+		slpcall = msn_slplink_find_slp_call(slplink, call_id);
+		g_free(call_id);
+
+		g_return_val_if_fail(slpcall != NULL, NULL);
+
+		content_type = get_token(body, "Content-Type: ", "\r\n");
+
+		content = get_token(body, "\r\n\r\n", NULL);
+
+		if (strncmp(status, "200 OK", 6))
+		{
+			char *error = NULL;
+			const char *c;
+
+			/* Eww */
+			if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) ||
+				(c = strchr(status, '\0')))
+			{
+				size_t len = c - status;
+				error = g_strndup(status, len);
+			}
+
+			got_error(slpcall, error, content_type, content);
+			g_free(error);
+
+		} else {
+			/* Everything's just dandy */
+			got_ok(slpcall, content_type, content);
+		}
+
+		g_free(content_type);
+		g_free(content);
+	}
+	else if (!strncmp(body, "BYE", strlen("BYE")))
+	{
+		/* This is a BYE request */
+		char *call_id;
+
+		call_id = get_token(body, "Call-ID: {", "}");
+		slpcall = msn_slplink_find_slp_call(slplink, call_id);
+		g_free(call_id);
+
+		if (slpcall != NULL)
+			slpcall->wasted = TRUE;
+
+		/* msn_slpcall_destroy(slpcall); */
+	}
+	else
+		slpcall = NULL;
+
+	return slpcall;
+}
+
 MsnSlpCall *
 msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
 {
@@ -199,13 +1060,13 @@
 
 	slpcall = NULL;
 	body = slpmsg->buffer;
-	body_len = slpmsg->offset;
+	body_len = slpmsg->header->offset;
 
-	if (slpmsg->flags == 0x0 || slpmsg->flags == 0x1000000)
+	if (slpmsg->header->flags == P2P_NO_FLAG || slpmsg->header->flags == P2P_WML2009_COMP)
 	{
 		char *body_str;
 
-		if (slpmsg->session_id == 64)
+		if (slpmsg->header->session_id == 64)
 		{
 			/* This is for handwritten messages (Ink) */
 			GError *error = NULL;
@@ -262,11 +1123,9 @@
 		}
 		g_free(body_str);
 	}
-	else if (slpmsg->flags == 0x20 ||
-	         slpmsg->flags == 0x1000020 ||
-	         slpmsg->flags == 0x1000030)
+	 else if (msn_p2p_msg_is_data(slpmsg->header->flags))
 	{
-		slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
+		slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->header->session_id);
 
 		if (slpcall != NULL)
 		{
@@ -282,7 +1141,7 @@
 		}
 	}
 #if 0
-	else if (slpmsg->flags == 0x100)
+	else if (slpmsg->header->flags == 0x100)
 	{
 		slpcall = slplink->directconn->initial_call;
 
@@ -290,13 +1149,13 @@
 			msn_slpcall_session_init(slpcall);
 	}
 #endif
-	else if (slpmsg->flags == 0x2)
+	else if (slpmsg->header->flags == P2P_ACK)
 	{
 		/* Acknowledgement of previous message. Don't do anything currently. */
 	}
 	else
-		purple_debug_warning("msn", "Unprocessed SLP message with flags 0x%08lx\n",
-		                     slpmsg->flags);
+		purple_debug_warning("msn", "Unprocessed SLP message with flags 0x%04x\n",
+		                     slpmsg->header->flags);
 
 	return slpcall;
 }
--- a/libpurple/protocols/msn/slpcall.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slpcall.h	Mon Nov 01 07:12:08 2010 +0000
@@ -34,8 +34,6 @@
 
 #include "internal.h"
 
-#include "ft.h"
-
 #include "slplink.h"
 
 /* The official client seems to timeout slp calls after 5 minutes */
@@ -94,7 +92,7 @@
 void msn_slpcall_session_init(MsnSlpCall *slpcall);
 void msn_slpcall_destroy(MsnSlpCall *slpcall);
 void msn_slpcall_invite(MsnSlpCall *slpcall, const char *euf_guid,
-						 int app_id, const char *context);
+						 MsnP2PAppId app_id, const char *context);
 void msn_slpcall_close(MsnSlpCall *slpcall);
 
 #endif /* MSN_SLPCALL_H */
--- a/libpurple/protocols/msn/slplink.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slplink.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,30 +21,37 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "internal.h"
+#include "debug.h"
+
 #include "msn.h"
 #include "slplink.h"
+#include "slpmsg_part.h"
 
+#include "sbconn.h"
 #include "switchboard.h"
 #include "slp.h"
+#include "p2p.h"
 
 #ifdef MSN_DEBUG_SLP_FILES
 static int m_sc = 0;
 static int m_rc = 0;
 
 static void
-debug_msg_to_file(MsnMessage *msg, gboolean send)
+debug_part_to_file(MsnSlpMessage *msg, gboolean send)
 {
 	char *tmp;
 	char *dir;
-	char *pload;
+	char *data;
 	int c;
-	gsize pload_size;
+	gsize data_size;
 
 	dir = send ? "send" : "recv";
 	c = send ? m_sc++ : m_rc++;
 	tmp = g_strdup_printf("%s/msntest/%s/%03d", g_get_home_dir(), dir, c);
-	pload = msn_message_gen_payload(msg, &pload_size);
-	if (!purple_util_write_data_to_file_absolute(tmp, pload, pload_size))
+	data = msn_slpmsg_serialize(msg, &data_size);
+	if (!purple_util_write_data_to_file_absolute(tmp, data, data_size))
 	{
 		purple_debug_error("msn", "could not save debug file\n");
 	}
@@ -81,7 +88,7 @@
 	return msn_slplink_ref(slplink);
 }
 
-void
+static void
 msn_slplink_destroy(MsnSlpLink *slplink)
 {
 	MsnSession *session;
@@ -264,78 +271,69 @@
 	return NULL;
 }
 
-void
-msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg)
+static void
+msn_slplink_send_part(MsnSlpLink *slplink, MsnSlpMessagePart *part)
 {
 	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
 	{
-		msn_dc_enqueue_msg(slplink->dc, msg);
+		msn_dc_enqueue_part(slplink->dc, part);
 	}
 	else
 	{
-		if (slplink->swboard == NULL)
-		{
-			slplink->swboard = msn_session_get_swboard(slplink->session,
-													   slplink->remote_user, MSN_SB_FLAG_FT);
-
-			g_return_if_fail(slplink->swboard != NULL);
-
-			/* If swboard is destroyed we will be too */
-			slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink);
-		}
-
-		msn_switchboard_send_msg(slplink->swboard, msg, TRUE);
+		msn_sbconn_send_part(slplink, part);
 	}
 }
 
 void
 msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
 {
-	MsnMessage *msg;
+	MsnSlpMessagePart *part;
 	long long real_size;
 	size_t len = 0;
 
 	/* Maybe we will want to create a new msg for this slpmsg instead of
 	 * reusing the same one all the time. */
-	msg = slpmsg->msg;
+	part = msn_slpmsgpart_new(slpmsg->header, slpmsg->footer);
+	part->ack_data = slpmsg;
 
-	real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size;
+	real_size = (slpmsg->header->flags == P2P_ACK) ? 0 : slpmsg->size;
 
-	if (slpmsg->offset < real_size)
+	if (slpmsg->header->offset < real_size)
 	{
 		if (slpmsg->slpcall && slpmsg->slpcall->xfer && purple_xfer_get_type(slpmsg->slpcall->xfer) == PURPLE_XFER_SEND &&
 				purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED)
 		{
-			len = MIN(1202, slpmsg->slpcall->u.outgoing.len);
-			msn_message_set_bin_data(msg, slpmsg->slpcall->u.outgoing.data, len);
+			len = MIN(MSN_SBCONN_MAX_SIZE, slpmsg->slpcall->u.outgoing.len);
+			msn_slpmsgpart_set_bin_data(part, slpmsg->slpcall->u.outgoing.data, len);
 		}
 		else
 		{
-			len = slpmsg->size - slpmsg->offset;
+			len = slpmsg->size - slpmsg->header->offset;
 
-			if (len > 1202)
-				len = 1202;
+			if (len > MSN_SBCONN_MAX_SIZE)
+				len = MSN_SBCONN_MAX_SIZE;
 
-			msn_message_set_bin_data(msg, slpmsg->buffer + slpmsg->offset, len);
+			msn_slpmsgpart_set_bin_data(part, slpmsg->buffer + slpmsg->header->offset, len);
 		}
 
-		msg->msnslp_header.offset = slpmsg->offset;
-		msg->msnslp_header.length = len;
+		slpmsg->header->length = len;
 	}
 
+#if 0
+	/* TODO: port this function to SlpMessageParts */
 	if (purple_debug_is_verbose())
 		msn_message_show_readable(msg, slpmsg->info, slpmsg->text_body);
-
-#ifdef MSN_DEBUG_SLP_FILES
-	debug_msg_to_file(msg, TRUE);
 #endif
 
-	slpmsg->msgs =
-		g_list_append(slpmsg->msgs, msn_message_ref(msg));
-	msn_slplink_send_msg(slplink, msg);
+#ifdef MSN_DEBUG_SLP_FILES
+	debug_part_to_file(slpmsg, TRUE);
+#endif
 
-	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||
-	     slpmsg->flags == 0x1000030) &&
+	slpmsg->parts = g_list_append(slpmsg->parts, part);
+	msn_slplink_send_part(slplink, part);
+
+
+	if (msn_p2p_msg_is_data(slpmsg->header->flags) &&
 		(slpmsg->slpcall != NULL))
 	{
 		slpmsg->slpcall->progress = TRUE;
@@ -343,124 +341,39 @@
 		if (slpmsg->slpcall->progress_cb != NULL)
 		{
 			slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size,
-										 len, slpmsg->offset);
+										 len, slpmsg->header->offset);
 		}
 	}
 
 	/* slpmsg->offset += len; */
 }
 
-/* We have received the message ack */
-static void
-msg_ack(MsnMessage *msg, void *data)
-{
-	MsnSlpMessage *slpmsg;
-	long long real_size;
-
-	slpmsg = data;
-
-	real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size;
-
-	slpmsg->offset += msg->msnslp_header.length;
-
-	slpmsg->msgs = g_list_remove(slpmsg->msgs, msg);
-
-	if (slpmsg->offset < real_size)
-	{
-		if (slpmsg->slpcall->xfer && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED)
-		{
-			slpmsg->slpcall->xfer_msg = slpmsg;
-			msn_message_ref(msg);
-			purple_xfer_prpl_ready(slpmsg->slpcall->xfer);
-		}
-		else
-			msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
-	}
-	else
-	{
-		/* The whole message has been sent */
-		if (slpmsg->flags == 0x20 ||
-		    slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030)
-		{
-			if (slpmsg->slpcall != NULL)
-			{
-				if (slpmsg->slpcall->cb)
-					slpmsg->slpcall->cb(slpmsg->slpcall,
-						NULL, 0);
-			}
-		}
-	}
-
-	msn_message_unref(msg);
-}
-
-/* We have received the message nak. */
-static void
-msg_nak(MsnMessage *msg, void *data)
-{
-	MsnSlpMessage *slpmsg;
-
-	slpmsg = data;
-
-	msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
-
-	slpmsg->msgs = g_list_remove(slpmsg->msgs, msg);
-	msn_message_unref(msg);
-}
-
 static void
 msn_slplink_release_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg)
 {
-	MsnMessage *msg;
-	const char *passport;
-
-	slpmsg->msg = msg = msn_message_new_msnslp();
+	slpmsg = slpmsg;
+	slpmsg->footer = g_new0(MsnP2PFooter, 1);
 
-	if (slpmsg->flags == 0x0)
+	if (slpmsg->header->flags == P2P_NO_FLAG)
 	{
-		msg->msnslp_header.session_id = slpmsg->session_id;
-		msg->msnslp_header.ack_id = rand() % 0xFFFFFF00;
+		slpmsg->header->ack_id = rand() % 0xFFFFFF00;
 	}
-	else if (slpmsg->flags == 0x2)
-	{
-		msg->msnslp_header.session_id = slpmsg->session_id;
-		msg->msnslp_header.ack_id = slpmsg->ack_id;
-		msg->msnslp_header.ack_size = slpmsg->ack_size;
-		msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id;
-	}
-	else if (slpmsg->flags == 0x20 ||
-	         slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030)
+	else if (msn_p2p_msg_is_data(slpmsg->header->flags))
 	{
 		MsnSlpCall *slpcall;
 		slpcall = slpmsg->slpcall;
 
 		g_return_if_fail(slpcall != NULL);
-		msg->msnslp_header.session_id = slpcall->session_id;
-		msg->msnslp_footer.value = slpcall->app_id;
-		msg->msnslp_header.ack_id = rand() % 0xFFFFFF00;
-	}
-	else if (slpmsg->flags == 0x100)
-	{
-		msg->msnslp_header.ack_id     = slpmsg->ack_id;
-		msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id;
-		msg->msnslp_header.ack_size   = slpmsg->ack_size;
+		slpmsg->header->session_id = slpcall->session_id;
+		slpmsg->footer->value = slpcall->app_id;
+		slpmsg->header->ack_id = rand() % 0xFFFFFF00;
 	}
 
-	msg->msnslp_header.id = slpmsg->id;
-	msg->msnslp_header.flags = slpmsg->flags;
-
-	msg->msnslp_header.total_size = slpmsg->size;
+	slpmsg->header->id = slpmsg->id;
 
-	passport = purple_normalize(slplink->session->account, slplink->remote_user);
-	msn_message_set_attr(msg, "P2P-Dest", passport);
-
-	msg->ack_cb = msg_ack;
-	msg->nak_cb = msg_nak;
-	msg->ack_data = slpmsg;
+	slpmsg->header->total_size = slpmsg->size;
 
 	msn_slplink_send_msgpart(slplink, slpmsg);
-
-	msn_message_destroy(msg);
 }
 
 void
@@ -494,25 +407,18 @@
 }
 
 static MsnSlpMessage *
-msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
+msn_slplink_create_ack(MsnSlpLink *slplink, MsnP2PHeader *header)
 {
 	MsnSlpMessage *slpmsg;
 
-	slpmsg = msn_slpmsg_new(slplink);
-
-	slpmsg->session_id = header->session_id;
-	slpmsg->size       = header->total_size;
-	slpmsg->flags      = 0x02;
-	slpmsg->ack_id     = header->id;
-	slpmsg->ack_sub_id = header->ack_id;
-	slpmsg->ack_size   = header->total_size;
-	slpmsg->info = "SLP ACK";
+	slpmsg = msn_slpmsg_ack_new(header);
+	msn_slpmsg_set_slplink(slpmsg, slplink);
 
 	return slpmsg;
 }
 
 static void
-msn_slplink_send_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
+msn_slplink_send_ack(MsnSlpLink *slplink, MsnP2PHeader *header)
 {
 	MsnSlpMessage *slpmsg = msn_slplink_create_ack(slplink, header);
 
@@ -520,33 +426,6 @@
 	msn_slpmsg_destroy(slpmsg);
 }
 
-static void
-send_file_cb(MsnSlpCall *slpcall)
-{
-	MsnSlpMessage *slpmsg;
-	PurpleXfer *xfer;
-
-	xfer = (PurpleXfer *)slpcall->xfer;
-	if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED)
-		return;
-
-	purple_xfer_ref(xfer);
-	purple_xfer_start(xfer, -1, NULL, 0);
-	if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) {
-		purple_xfer_unref(xfer);
-		return;
-	}
-	purple_xfer_unref(xfer);
-
-	slpmsg = msn_slpmsg_new(slpcall->slplink);
-	slpmsg->slpcall = slpcall;
-	slpmsg->flags = 0x1000030;
-	slpmsg->info = "SLP FILE";
-	slpmsg->size = purple_xfer_get_size(xfer);
-
-	msn_slplink_send_slpmsg(slpcall->slplink, slpmsg);
-}
-
 static MsnSlpMessage *
 msn_slplink_message_find(MsnSlpLink *slplink, long session_id, long id)
 {
@@ -556,79 +435,164 @@
 	{
 		MsnSlpMessage *slpmsg = e->data;
 
-		if ((slpmsg->session_id == session_id) && (slpmsg->id == id))
+		if ((slpmsg->header->session_id == session_id) && (slpmsg->id == id))
 			return slpmsg;
 	}
 
 	return NULL;
 }
 
-void
-msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len)
+static MsnSlpMessage *
+init_first_msg(MsnSlpLink *slplink, MsnP2PHeader *header)
 {
 	MsnSlpMessage *slpmsg;
+
+	slpmsg = msn_slpmsg_new(slplink);
+	slpmsg->id = header->id;
+	slpmsg->header->session_id = header->session_id;
+	slpmsg->size = header->total_size;
+	slpmsg->header->flags = header->flags;
+
+	if (slpmsg->header->session_id)
+	{
+		slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->header->session_id);
+		if (slpmsg->slpcall != NULL)
+		{
+			if (msn_p2p_msg_is_data(header->flags))
+			{
+				PurpleXfer *xfer = slpmsg->slpcall->xfer;
+				if (xfer != NULL)
+				{
+					slpmsg->ft = TRUE;
+					slpmsg->slpcall->xfer_msg = slpmsg;
+
+					purple_xfer_ref(xfer);
+					purple_xfer_start(xfer,	-1, NULL, 0);
+
+					if (xfer->data == NULL) {
+						purple_xfer_unref(xfer);
+						msn_slpmsg_destroy(slpmsg);
+						g_return_val_if_reached(NULL);
+					} else {
+						purple_xfer_unref(xfer);
+					}
+				}
+			}
+		}
+	}
+	if (!slpmsg->ft && slpmsg->size)
+	{
+		slpmsg->buffer = g_try_malloc(slpmsg->size);
+		if (slpmsg->buffer == NULL)
+		{
+			purple_debug_error("msn", "Failed to allocate buffer for slpmsg\n");
+			msn_slpmsg_destroy(slpmsg);
+			return NULL;
+		}
+	}
+
+	return slpmsg;
+}
+
+static void
+process_complete_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg, MsnP2PHeader *header)
+{
+	MsnSlpCall *slpcall;
+
+	slpcall = msn_slp_process_msg(slplink, slpmsg);
+
+	if (slpcall == NULL) {
+		msn_slpmsg_destroy(slpmsg);
+		return;
+	}
+
+	purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n");
+
+	if (/* !slpcall->wasted && */ slpmsg->header->flags == P2P_DC_HANDSHAKE)
+	{
+#if 0
+		MsnDirectConn *directconn;
+
+		directconn = slplink->directconn;
+		if (!directconn->acked)
+			msn_directconn_send_handshake(directconn);
+#endif
+	}
+	else if (slpmsg->header->flags == P2P_NO_FLAG || slpmsg->header->flags == P2P_WML2009_COMP ||
+			msn_p2p_msg_is_data(slpmsg->header->flags))
+	{
+		/* Release all the messages and send the ACK */
+
+		if (slpcall->wait_for_socket) {
+			/*
+			 * Save ack for later because we have to send
+			 * a 200 OK message to the previous direct connect
+			 * invitation before ACK but the listening socket isn't
+			 * created yet.
+			 */
+			purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n");
+
+			slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header);
+		} else if (!slpcall->wasted) {
+			purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n");
+
+			msn_slplink_send_ack(slplink, header);
+			msn_slplink_send_queued_slpmsgs(slplink);
+		}
+	}
+
+	msn_slpmsg_destroy(slpmsg);
+
+	if (!slpcall->wait_for_socket && slpcall->wasted)
+		msn_slpcall_destroy(slpcall);
+}
+
+static void
+slpmsg_add_part(MsnSlpMessage *slpmsg, MsnSlpMessagePart *part)
+{
+	if (slpmsg->ft) {
+		slpmsg->slpcall->u.incoming_data =
+				g_byte_array_append(slpmsg->slpcall->u.incoming_data, (const guchar *)part->buffer, part->size);
+		purple_xfer_prpl_ready(slpmsg->slpcall->xfer);
+	}
+	else if (slpmsg->size && slpmsg->buffer) {
+		if (G_MAXSIZE - part->size < part->header->offset
+				|| (part->header->offset + part->size) > slpmsg->size
+				|| slpmsg->header->offset != part->header->offset) {
+			purple_debug_error("msn",
+				"Oversized slpmsg - msgsize=%lld offset=%" G_GUINT64_FORMAT " len=%" G_GSIZE_FORMAT "\n",
+				slpmsg->size, part->header->offset, part->size);
+			g_return_if_reached();
+		} else {
+			memcpy(slpmsg->buffer + part->header->offset, part->buffer, part->size);
+			slpmsg->header->offset += part->size;
+		}
+	}
+}
+
+void
+msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpMessagePart *part)
+{
+	MsnSlpMessage *slpmsg;
+	MsnP2PHeader *header;
 	guint64 offset;
-	PurpleXfer *xfer = NULL;
+
+	header = part->header;
 
 	if (header->total_size < header->length)
 	{
-		purple_debug_error("msn", "This can't be good\n");
-		g_return_if_reached();
+		/* We seem to have received a bad header */
+		purple_debug_warning("msn", "Total size listed in SLP binary header "
+				"was less than length of this particular message.  This "
+				"should not happen.  Dropping message.\n");
+		return;
 	}
 
 	offset = header->offset;
 
 	if (offset == 0)
-	{
-		slpmsg = msn_slpmsg_new(slplink);
-		slpmsg->id = header->id;
-		slpmsg->session_id = header->session_id;
-		slpmsg->size = header->total_size;
-		slpmsg->flags = header->flags;
-
-		if (slpmsg->session_id)
-		{
-			if (slpmsg->slpcall == NULL)
-				slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
-
-			if (slpmsg->slpcall != NULL)
-			{
-				if (slpmsg->flags == 0x20 ||
-				    slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030)
-				{
-					xfer = slpmsg->slpcall->xfer;
-					if (xfer != NULL)
-					{
-						slpmsg->ft = TRUE;
-						slpmsg->slpcall->xfer_msg = slpmsg;
-
-						purple_xfer_ref(xfer);
-						purple_xfer_start(xfer,	-1, NULL, 0);
-
-						if (xfer->data == NULL) {
-							purple_xfer_unref(xfer);
-							msn_slpmsg_destroy(slpmsg);
-							g_return_if_reached();
-						} else {
-							purple_xfer_unref(xfer);
-						}
-					}
-				}
-			}
-		}
-		if (!slpmsg->ft && slpmsg->size)
-		{
-			slpmsg->buffer = g_try_malloc(slpmsg->size);
-			if (slpmsg->buffer == NULL)
-			{
-				purple_debug_error("msn", "Failed to allocate buffer for slpmsg\n");
-				msn_slpmsg_destroy(slpmsg);
-				return;
-			}
-		}
-	}
-	else
-	{
+		slpmsg = init_first_msg(slplink, header);
+	else {
 		slpmsg = msn_slplink_message_find(slplink, header->session_id, header->id);
 		if (slpmsg == NULL)
 		{
@@ -638,29 +602,10 @@
 		}
 	}
 
-	if (slpmsg->ft)
-	{
-		xfer = slpmsg->slpcall->xfer;
-		slpmsg->slpcall->u.incoming_data =
-				g_byte_array_append(slpmsg->slpcall->u.incoming_data, (const guchar *)data, len);
-		purple_xfer_prpl_ready(xfer);
-	}
-	else if (slpmsg->size && slpmsg->buffer)
-	{
-		if (G_MAXSIZE - len < offset || (offset + len) > slpmsg->size || slpmsg->offset != offset)
-		{
-			purple_debug_error("msn",
-				"Oversized slpmsg - msgsize=%lld offset=%" G_GUINT64_FORMAT " len=%" G_GSIZE_FORMAT "\n",
-				slpmsg->size, offset, len);
-			g_return_if_reached();
-		} else {
-			memcpy(slpmsg->buffer + offset, data, len);
-			slpmsg->offset += len;
-		}
-	}
+	slpmsg_add_part(slpmsg, part);
 
-	if ((slpmsg->flags == 0x20 ||
-	     slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) &&
+
+	if (msn_p2p_msg_is_data(slpmsg->header->flags) &&
 		(slpmsg->slpcall != NULL))
 	{
 		slpmsg->slpcall->progress = TRUE;
@@ -668,7 +613,7 @@
 		if (slpmsg->slpcall->progress_cb != NULL)
 		{
 			slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size,
-										 len, offset);
+										 part->size, offset);
 		}
 	}
 
@@ -677,159 +622,9 @@
 		return;
 #endif
 
+	/* All the pieces of the slpmsg have been received */
 	if (header->offset + header->length >= header->total_size)
-	{
-		/* All the pieces of the slpmsg have been received */
-		MsnSlpCall *slpcall;
-
-		slpcall = msn_slp_process_msg(slplink, slpmsg);
-
-		if (slpcall == NULL) {
-			msn_slpmsg_destroy(slpmsg);
-			return;
-		}
-
-		purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n");
-
-		if (/* !slpcall->wasted && */ slpmsg->flags == 0x100)
-		{
-#if 0
-			MsnDirectConn *directconn;
-
-			directconn = slplink->directconn;
-			if (!directconn->acked)
-				msn_directconn_send_handshake(directconn);
-#endif
-		}
-		else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||
-		         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||
-		         slpmsg->flags == 0x1000030)
-		{
-			/* Release all the messages and send the ACK */
-
-			if (slpcall->wait_for_socket) {
-				/*
-				 * Save ack for later because we have to send
-				 * a 200 OK message to the previous direct connect
-				 * invitation before ACK but the listening socket isn't
-				 * created yet.
-				 */
-				purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n");
-
-				slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header);
-			} else if (!slpcall->wasted) {
-				purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n");
-
-				msn_slplink_send_ack(slplink, header);
-				msn_slplink_send_queued_slpmsgs(slplink);
-			}
-		}
-
-		msn_slpmsg_destroy(slpmsg);
-
-		if (!slpcall->wait_for_socket && slpcall->wasted)
-			msn_slpcall_destroy(slpcall);
-	}
-}
-
-static gchar *
-gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path)
-{
-	gsize size = 0;
-	MsnFileContext *header;
-	gchar *u8 = NULL;
-	gchar *ret;
-	gunichar2 *uni = NULL;
-	glong currentChar = 0;
-	glong len = 0;
-	const char *preview;
-	gsize preview_len;
-
-	size = purple_xfer_get_size(xfer);
-
-	purple_xfer_prepare_thumbnail(xfer, "png");
-
-	if (!file_name) {
-		gchar *basename = g_path_get_basename(file_path);
-		u8 = purple_utf8_try_convert(basename);
-		g_free(basename);
-		file_name = u8;
-	}
-
-	uni = g_utf8_to_utf16(file_name, -1, NULL, &len, NULL);
-
-	if (u8) {
-		g_free(u8);
-		file_name = NULL;
-		u8 = NULL;
-	}
-
-	preview = purple_xfer_get_thumbnail(xfer, &preview_len);
-	header = g_malloc(sizeof(MsnFileContext) + preview_len);
-
-	header->length = GUINT32_TO_LE(sizeof(MsnFileContext) - 1);
-	header->version = GUINT32_TO_LE(2); /* V.3 contains additional unnecessary data */
-	header->file_size = GUINT64_TO_LE(size);
-	if (preview)
-		header->type = GUINT32_TO_LE(0);
-	else
-		header->type = GUINT32_TO_LE(1);
-
-	len = MIN(len, MAX_FILE_NAME_LEN);
-	for (currentChar = 0; currentChar < len; currentChar++) {
-		header->file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]);
-	}
-	memset(&header->file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2);
-
-	memset(&header->unknown1, 0, sizeof(header->unknown1));
-	header->unknown2 = GUINT32_TO_LE(0xffffffff);
-	if (preview) {
-		memcpy(&header->preview, preview, preview_len);
-	}
-	header->preview[preview_len] = '\0';
-
-	g_free(uni);
-	ret = purple_base64_encode((const guchar *)header, sizeof(MsnFileContext) + preview_len);
-	g_free(header);
-	return ret;
-}
-
-void
-msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer)
-{
-	MsnSlpCall *slpcall;
-	char *context;
-	const char *fn;
-	const char *fp;
-
-	fn = purple_xfer_get_filename(xfer);
-	fp = purple_xfer_get_local_filename(xfer);
-
-	g_return_if_fail(slplink != NULL);
-	g_return_if_fail(fp != NULL);
-
-	slpcall = msn_slpcall_new(slplink);
-	msn_slpcall_init(slpcall, MSN_SLPCALL_DC);
-
-	slpcall->session_init_cb = send_file_cb;
-	slpcall->end_cb = msn_xfer_end_cb;
-	slpcall->cb = msn_xfer_completed_cb;
-	slpcall->xfer = xfer;
-	purple_xfer_ref(slpcall->xfer);
-
-	slpcall->pending = TRUE;
-
-	purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel);
-	purple_xfer_set_read_fnc(xfer, msn_xfer_read);
-	purple_xfer_set_write_fnc(xfer, msn_xfer_write);
-
-	xfer->data = slpcall;
-
-	context = gen_context(xfer, fn, fp);
-
-	msn_slpcall_invite(slpcall, MSN_FT_GUID, 2, context);
-
-	g_free(context);
+		process_complete_msg(slplink, slpmsg, header);
 }
 
 void
@@ -857,7 +652,7 @@
 	slpcall->cb = cb;
 	slpcall->end_cb = end_cb;
 
-	msn_slpcall_invite(slpcall, MSN_OBJ_GUID, 1, msnobj_base64);
+	msn_slpcall_invite(slpcall, MSN_OBJ_GUID, P2P_APPID_OBJ, msnobj_base64);
 
 	g_free(msnobj_base64);
 }
--- a/libpurple/protocols/msn/slplink.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slplink.h	Mon Nov 01 07:12:08 2010 +0000
@@ -26,8 +26,6 @@
 
 typedef struct _MsnSlpLink MsnSlpLink;
 
-#include "ft.h"
-
 #include "directconn.h"
 #include "session.h"
 #include "slpcall.h"
@@ -59,8 +57,6 @@
 MsnSlpLink *msn_slplink_ref(MsnSlpLink *slplink);
 void msn_slplink_unref(MsnSlpLink *slplink);
 
-void msn_slplink_destroy(MsnSlpLink *slplink);
-
 /**
  * @return An MsnSlpLink for the given user, or NULL if there is no
  *         existing MsnSlpLink.
@@ -83,10 +79,8 @@
 void msn_slplink_send_slpmsg(MsnSlpLink *slplink,
 							 MsnSlpMessage *slpmsg);
 void msn_slplink_send_queued_slpmsgs(MsnSlpLink *slplink);
-void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len);
-void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer);
+void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpMessagePart *part);
 
-void msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg);
 /* Only exported for msn_xfer_write */
 void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg);
 
--- a/libpurple/protocols/msn/slpmsg.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slpmsg.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,8 +21,12 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+
 #include "slpmsg.h"
+#include "slpmsg_part.h"
 #include "slplink.h"
 
 /**************************************************************************
@@ -39,10 +43,48 @@
 	if (purple_debug_is_verbose())
 		purple_debug_info("msn", "slpmsg new (%p)\n", slpmsg);
 
-	slpmsg->slplink = slplink;
+	if (slplink) 
+		msn_slpmsg_set_slplink(slpmsg, slplink);
+	else
+		slpmsg->slplink = NULL;
+
+	slpmsg->header = g_new0(MsnP2PHeader, 1);
+	slpmsg->footer = NULL;
+
+	return slpmsg;
+}
+
+MsnSlpMessage *msn_slpmsg_new_from_data(const char *data, size_t data_len)
+{
+	MsnSlpMessage *slpmsg;
+	MsnP2PHeader *header;
+	const char *tmp;
+	int body_len;
+
+	tmp = data;
+	slpmsg = msn_slpmsg_new(NULL);
 
-	slplink->slp_msgs =
-		g_list_append(slplink->slp_msgs, slpmsg);
+	if (data_len < sizeof(*header)) {
+		return NULL;
+	}
+
+	/* Extract the binary SLP header */
+	slpmsg->header = msn_p2p_header_from_wire((MsnP2PHeader*)tmp);
+
+	/* Extract the body */
+	body_len = data_len - (tmp - data);
+	/* msg->body_len = msg->msnslp_header.length; */
+
+	if (body_len > 0) {
+		slpmsg->size = body_len;
+		slpmsg->buffer = g_malloc(body_len);
+		memcpy(slpmsg->buffer, tmp, body_len);
+		tmp += body_len;
+	}
+
+	/* Extract the footer */
+	if (body_len >= 0) 
+		slpmsg->footer = msn_p2p_footer_from_wire((MsnP2PFooter*)tmp);
 
 	return slpmsg;
 }
@@ -67,26 +109,41 @@
 	if (slpmsg->img == NULL)
 		g_free(slpmsg->buffer);
 
-	for (cur = slpmsg->msgs; cur != NULL; cur = g_list_delete_link(cur, cur))
+	for (cur = slpmsg->parts; cur != NULL; cur = g_list_delete_link(cur, cur))
 	{
 		/* Something is pointing to this slpmsg, so we should remove that
 		 * pointer to prevent a crash. */
 		/* Ex: a user goes offline and after that we receive an ACK */
 
-		MsnMessage *msg = cur->data;
+		MsnSlpMessagePart *part = cur->data;
 
-		msg->ack_cb = NULL;
-		msg->nak_cb = NULL;
-		msg->ack_data = NULL;
-		msn_message_unref(msg);
+		part->ack_cb = NULL;
+		part->nak_cb = NULL;
+		part->ack_data = NULL;
+		msn_slpmsgpart_destroy(part);
 	}
 
 	slplink->slp_msgs = g_list_remove(slplink->slp_msgs, slpmsg);
 
+	g_free(slpmsg->header);
+	g_free(slpmsg->footer);
+
 	g_free(slpmsg);
 }
 
 void
+msn_slpmsg_set_slplink(MsnSlpMessage *slpmsg, MsnSlpLink *slplink)
+{
+	g_return_if_fail(slplink != NULL);
+
+	slpmsg->slplink = slplink;
+
+	slplink->slp_msgs =
+		g_list_append(slplink->slp_msgs, slpmsg);
+
+}
+
+void
 msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body,
 						 long long size)
 {
@@ -116,34 +173,6 @@
 	slpmsg->size = purple_imgstore_get_size(img);
 }
 
-void
-msn_slpmsg_show(MsnMessage *msg)
-{
-	const char *info;
-	gboolean text;
-	guint32 flags;
-
-	text = FALSE;
-
-	flags = GUINT32_TO_LE(msg->msnslp_header.flags);
-
-	switch (flags)
-	{
-		case 0x0:
-			info = "SLP CONTROL";
-			text = TRUE;
-			break;
-		case 0x2:
-			info = "SLP ACK"; break;
-		case 0x20:
-		case 0x1000030:
-			info = "SLP DATA"; break;
-		default:
-			info = "SLP UNKNOWN"; break;
-	}
-
-	msn_message_show_readable(msg, info, text);
-}
 
 MsnSlpMessage *
 msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq,
@@ -206,3 +235,128 @@
 
 	return slpmsg;
 }
+
+MsnSlpMessage *msn_slpmsg_ack_new(MsnP2PHeader *header)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = msn_slpmsg_new(NULL);
+
+	slpmsg->header->session_id = header->session_id;
+	slpmsg->size       = header->total_size;
+	slpmsg->header->flags      = P2P_ACK;
+	slpmsg->header->ack_id     = header->id;
+	slpmsg->header->ack_sub_id = header->ack_id;
+	slpmsg->header->ack_size   = header->total_size;
+	slpmsg->info = "SLP ACK";
+
+	return slpmsg;
+}
+
+MsnSlpMessage *msn_slpmsg_obj_new(MsnSlpCall *slpcall, PurpleStoredImage *img)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = msn_slpmsg_new(NULL);
+	slpmsg->slpcall = slpcall;
+	slpmsg->header->flags = P2P_MSN_OBJ_DATA;
+	slpmsg->info = "SLP DATA";
+
+	msn_slpmsg_set_image(slpmsg, img);
+
+	return slpmsg;
+}
+
+MsnSlpMessage *msn_slpmsg_dataprep_new(MsnSlpCall *slpcall)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = msn_slpmsg_new(NULL);
+
+	slpmsg->slpcall = slpcall;
+	slpmsg->header->session_id = slpcall->session_id;
+	msn_slpmsg_set_body(slpmsg, NULL, 4);
+	slpmsg->info = "SLP DATA PREP";
+
+	return slpmsg;
+
+}
+
+MsnSlpMessage *msn_slpmsg_file_new(MsnSlpCall *slpcall, size_t size)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = msn_slpmsg_new(NULL);
+
+	slpmsg->slpcall = slpcall;
+	slpmsg->header->flags = P2P_FILE_DATA;
+	slpmsg->info = "SLP FILE";
+	slpmsg->size = size;
+
+	return slpmsg;
+}
+
+char *msn_slpmsg_serialize(MsnSlpMessage *slpmsg, size_t *ret_size)
+{
+	MsnP2PHeader *header;
+	MsnP2PFooter *footer;
+	char *base;
+	char *tmp;
+	size_t siz;
+
+	base = g_malloc(P2P_PACKET_HEADER_SIZE + slpmsg->size + sizeof(MsnP2PFooter));
+	tmp = base;
+
+	header = msn_p2p_header_to_wire(slpmsg->header);
+	footer = msn_p2p_footer_to_wire(slpmsg->footer);
+
+	siz = sizeof(MsnP2PHeader);
+	/* Copy header */
+	memcpy(tmp, (char*)header, siz);
+	tmp += siz;
+
+	/* Copy body */
+	memcpy(tmp, slpmsg->buffer, slpmsg->size);
+	tmp += slpmsg->size;
+
+	/* Copy footer */
+	siz = sizeof(MsnP2PFooter);
+	memcpy(tmp, (char*)footer, siz);
+	tmp += siz;
+
+	*ret_size = tmp - base;
+
+	return base;
+}
+
+void msn_slpmsg_show_readable(MsnSlpMessage *slpmsg)
+{
+	GString *str;
+
+	str = g_string_new(NULL);
+
+	g_string_append_printf(str, "Session ID: %u\r\n", slpmsg->header->session_id);
+	g_string_append_printf(str, "ID:         %u\r\n", slpmsg->header->id);
+	g_string_append_printf(str, "Offset:     %" G_GUINT64_FORMAT "\r\n", slpmsg->header->offset);
+	g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", slpmsg->header->total_size);
+	g_string_append_printf(str, "Length:     %u\r\n", slpmsg->header->length);
+	g_string_append_printf(str, "Flags:      0x%x\r\n", slpmsg->header->flags);
+	g_string_append_printf(str, "ACK ID:     %u\r\n", slpmsg->header->ack_id);
+	g_string_append_printf(str, "SUB ID:     %u\r\n", slpmsg->header->ack_sub_id);
+	g_string_append_printf(str, "ACK Size:   %" G_GUINT64_FORMAT "\r\n", slpmsg->header->ack_size);
+
+	if (purple_debug_is_verbose() && slpmsg->buffer != NULL) {
+		g_string_append_len(str, (gchar*)slpmsg->buffer, slpmsg->size);
+
+		if (slpmsg->buffer[slpmsg->size - 1] == '\0') {
+			str->len--;
+			g_string_append(str, " 0x00");
+		}
+		g_string_append(str, "\r\n");
+
+	}
+
+	g_string_append_printf(str, "Footer:     %u\r\n", slpmsg->footer->value);
+
+	purple_debug_info("msn", "SlpMessage %s:\n{%s}\n", slpmsg->info, str->str);
+}
--- a/libpurple/protocols/msn/slpmsg.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/slpmsg.h	Mon Nov 01 07:12:08 2010 +0000
@@ -31,7 +31,7 @@
 #include "slpcall.h"
 #include "slplink.h"
 #include "session.h"
-#include "msg.h"
+#include "p2p.h"
 
 #include "slp.h"
 
@@ -45,37 +45,24 @@
 	MsnSlpLink *slplink; /**< The slplink through which this slp message is being sent. */
 	MsnSession *session;
 
-	long session_id;
+	MsnP2PHeader *header;
+	MsnP2PFooter *footer;
+
 	long id;
-	long ack_id;
-	long ack_sub_id;
-	long long ack_size;
 
 	gboolean sip; /**< A flag that states if this is a SIP slp message. */
-	long flags;
 
 	gboolean ft;
 	PurpleStoredImage *img;
 	guchar *buffer;
 
 	/**
-	 * For outgoing messages this is the number of bytes from buffer that
-	 * have already been sent out.  For incoming messages this is the
-	 * number of bytes that have been written to buffer.
-	 */
-	long long offset;
-
-	/**
 	 * This is the size of buffer, unless this is an outgoing file transfer,
 	 * in which case this is the size of the file.
 	 */
 	long long size;
 
-	GList *msgs; /**< The real messages. */
-
-#if 1
-	MsnMessage *msg; /**< The temporary real message that will be sent. */
-#endif
+	GList *parts; /**< A list with the SlpMsgParts */
 
 	const char *info;
 	gboolean text_body;
@@ -85,17 +72,35 @@
  * Creates a new slp message
  *
  * @param slplink The slplink through which this slp message will be sent.
+ * If it's set to NULL, it is a temporary SlpMessage.
  * @return The created slp message.
  */
 MsnSlpMessage *msn_slpmsg_new(MsnSlpLink *slplink);
 
 /**
+ * Creates a MsnSlpMessage without a MsnSlpLink by parsing the raw data.
+ *
+ * @param data 		The raw data with the slp message.
+ * @param data_len 	The len of the data
+ *
+ * @return The createed slp message.
+ */
+MsnSlpMessage *msn_slpmsg_new_from_data(const char *data, size_t data_len);
+
+/**
  * Destroys a slp message
  *
  * @param slpmsg The slp message to destory.
  */
 void msn_slpmsg_destroy(MsnSlpMessage *slpmsg);
 
+/**
+ * Relate this SlpMessage with an existing SlpLink
+ *
+ * @param slplink 	The SlpLink that will send this message.
+ */
+void msn_slpmsg_set_slplink(MsnSlpMessage *slpmsg, MsnSlpLink *slplink);
+
 void msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body,
 						 long long size);
 void msn_slpmsg_set_image(MsnSlpMessage *slpmsg, PurpleStoredImage *img);
@@ -107,6 +112,54 @@
 								   const char *content_type,
 								   const char *content);
 
-void msn_slpmsg_show(MsnMessage *msg);
+/**
+ * Create a new SLP Ack message
+ *
+ * @param header the value of the header in this slpmsg.
+ *
+ * @return A new SlpMessage with ACK headers
+ */
+MsnSlpMessage *msn_slpmsg_ack_new(MsnP2PHeader *header);
+
+/**
+ * Create a new SLP message for MsnObject data.
+ *
+ * @param slpcall 	The slpcall that manages this message.
+ * @param img 		The image to be sent in this message.
+ *
+ * @return A new SlpMessage with MsnObject info.
+ */
+MsnSlpMessage *msn_slpmsg_obj_new(MsnSlpCall *slpcall, PurpleStoredImage *img);
+
+/**
+ * Create a new SLP message for data preparation.
+ *
+ * @param slpcall 	The slpcall that manages this message.
+ * 
+ * @return A new SlpMessage with data preparation info.
+ */
+MsnSlpMessage *msn_slpmsg_dataprep_new(MsnSlpCall *slpcall);
+
+/**
+ * Create a new SLP message for File transfer.
+ *
+ * @param slpcall 	The slpcall that manages this message.
+ * @param size 		The size of the file being transsmited.
+ *
+ * @return A new SlpMessage with the file transfer info.
+ */
+MsnSlpMessage *msn_slpmsg_file_new(MsnSlpCall *slpcall, size_t size);
+
+/**
+ * Serialize the MsnSlpMessage in a way it can be used to be transmited
+ *
+ * @param slpmsg 	The MsnSlpMessage.
+ * @param ret_size 	The size of the buffer cointaining the message.
+ *
+ * @return a buffer with the serialized data.
+ */
+char *msn_slpmsg_serialize(MsnSlpMessage *slpmsg, size_t *ret_size);
+
+void msn_slpmsg_show_readable(MsnSlpMessage *slpmsg);
 
 #endif /* _MSN_SLPMSG_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/slpmsg_part.c	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,210 @@
+#include "internal.h"
+#include "debug.h"
+
+#include "slpmsg.h"
+#include "slpmsg_part.h"
+
+MsnSlpMessagePart *msn_slpmsgpart_new(MsnP2PHeader *header, MsnP2PFooter *footer)
+{
+	MsnSlpMessagePart *part;
+
+	part = g_new0(MsnSlpMessagePart, 1);
+
+	if (header)
+		part->header = g_memdup(header, P2P_PACKET_HEADER_SIZE);
+	if (footer)
+		part->footer = g_memdup(footer, P2P_PACKET_FOOTER_SIZE);
+
+	part->ack_cb = msn_slpmsgpart_ack;
+	part->nak_cb = msn_slpmsgpart_nak;
+
+	return msn_slpmsgpart_ref(part);
+}
+
+MsnSlpMessagePart *msn_slpmsgpart_new_from_data(const char *data, size_t data_len)
+{
+	MsnSlpMessagePart *part;
+	MsnP2PHeader *header;
+	const char *tmp;
+	int body_len;
+
+	tmp = data;
+	part = msn_slpmsgpart_new(NULL, NULL);
+
+	if (data_len < sizeof(*header)) {
+		return NULL;
+	}
+
+	/* Extract the binary SLP header */
+	part->header = msn_p2p_header_from_wire((MsnP2PHeader*)tmp);
+	tmp += P2P_PACKET_HEADER_SIZE;
+
+	/* Extract the body */
+	body_len = data_len - P2P_PACKET_HEADER_SIZE - P2P_PACKET_FOOTER_SIZE;
+	/* msg->body_len = msg->msnslp_header.length; */
+
+	if (body_len > 0) {
+		part->size = body_len;
+		part->buffer = g_malloc(body_len);
+		memcpy(part->buffer, tmp, body_len);
+		tmp += body_len;
+	}
+
+	/* Extract the footer */
+	if (body_len >= 0) 
+		part->footer = msn_p2p_footer_from_wire((MsnP2PFooter*)tmp);
+
+	return part;
+}
+
+void msn_slpmsgpart_destroy(MsnSlpMessagePart *part)
+{
+	if (!part)
+		return;
+
+	if (part->ref_count > 0) {
+		msn_slpmsgpart_unref(part);
+		
+		return;
+	}
+
+	g_free(part->header);
+	g_free(part->footer);
+
+	g_free(part);
+
+}
+
+MsnSlpMessagePart *msn_slpmsgpart_ref(MsnSlpMessagePart *part)
+{
+	g_return_val_if_fail(part != NULL, NULL);
+	part->ref_count ++;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "part ref (%p)[%d]\n", part, part->ref_count);
+
+	return part;
+}
+
+MsnSlpMessagePart *msn_slpmsgpart_unref(MsnSlpMessagePart *part)
+{
+	g_return_val_if_fail(part != NULL, NULL);
+	g_return_val_if_fail(part->ref_count > 0, NULL);
+
+	part->ref_count--;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "part unref (%p)[%d]\n", part, part->ref_count);
+
+	if (part->ref_count == 0) {
+		msn_slpmsgpart_destroy(part);
+
+		 return NULL;
+	}
+
+	return part;
+}
+
+void msn_slpmsgpart_set_bin_data(MsnSlpMessagePart *part, const void *data, size_t len)
+{
+	g_return_if_fail(part != NULL);
+
+	if (part->buffer != NULL)
+		g_free(part->buffer);
+
+	if (data != NULL && len > 0) {
+		part->buffer = g_malloc(len + 1);
+		memcpy(part->buffer, data, len);
+		part->buffer[len] = '\0';
+		part->size = len;
+	} else {
+		part->buffer = NULL;
+		part->size = 0;
+	}
+
+}
+
+char *msn_slpmsgpart_serialize(MsnSlpMessagePart *part, size_t *ret_size)
+{
+	MsnP2PHeader *header;
+	MsnP2PFooter *footer;
+	char *base;
+	char *tmp;
+	size_t siz;
+
+	base = g_malloc(P2P_PACKET_HEADER_SIZE + part->size + sizeof(MsnP2PFooter));
+	tmp = base;
+
+	header = msn_p2p_header_to_wire(part->header);
+	footer = msn_p2p_footer_to_wire(part->footer);
+
+	siz = sizeof(MsnP2PHeader);
+	/* Copy header */
+	memcpy(tmp, (char*)header, siz);
+	tmp += siz;
+
+	/* Copy body */
+	memcpy(tmp, part->buffer, part->size);
+	tmp += part->size;
+
+	/* Copy footer */
+	siz = sizeof(MsnP2PFooter);
+	memcpy(tmp, (char*)footer, siz);
+	tmp += siz;
+
+	*ret_size = tmp - base;
+
+	return base;
+}
+/* We have received the message ack */
+void
+msn_slpmsgpart_ack(MsnSlpMessagePart *part, void *data)
+{
+	MsnSlpMessage *slpmsg;
+	long long real_size;
+
+	slpmsg = data;
+
+	real_size = (slpmsg->header->flags == P2P_ACK) ? 0 : slpmsg->size;
+
+	slpmsg->header->offset += part->header->length;
+
+	slpmsg->parts = g_list_remove(slpmsg->parts, part);
+
+	if (slpmsg->header->offset < real_size)
+	{
+		if (slpmsg->slpcall->xfer && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED)
+		{
+			slpmsg->slpcall->xfer_msg = slpmsg;
+			purple_xfer_prpl_ready(slpmsg->slpcall->xfer);
+		}
+		else
+			msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
+	}
+	else
+	{
+		/* The whole message has been sent */
+		if (msn_p2p_msg_is_data(slpmsg->header->flags))
+		{
+			if (slpmsg->slpcall != NULL)
+			{
+				if (slpmsg->slpcall->cb)
+					slpmsg->slpcall->cb(slpmsg->slpcall,
+						NULL, 0);
+			}
+		}
+	}
+}
+
+/* We have received the message nak. */
+void
+msn_slpmsgpart_nak(MsnSlpMessagePart *part, void *data)
+{
+	MsnSlpMessage *slpmsg;
+
+	slpmsg = data;
+
+	msn_slplink_send_msgpart(slpmsg->slplink, slpmsg);
+
+	slpmsg->parts = g_list_remove(slpmsg->parts, part);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/slpmsg_part.h	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,41 @@
+#ifndef MSN_SLPMSG_PART_H
+#define MSN_SLPMSG_PART_H
+
+#include "p2p.h"
+
+typedef struct _MsnSlpMessagePart MsnSlpMessagePart;
+typedef void (*MsnSlpPartCb)(MsnSlpMessagePart *part, void *data);
+
+struct _MsnSlpMessagePart
+{
+	int ref_count;
+
+	MsnP2PHeader *header;
+	MsnP2PFooter *footer;
+
+	MsnSlpPartCb ack_cb;
+	MsnSlpPartCb nak_cb;
+	void *ack_data;
+
+	guchar *buffer;
+	size_t size;
+};
+
+MsnSlpMessagePart *msn_slpmsgpart_new(MsnP2PHeader *header, MsnP2PFooter *footer);
+
+MsnSlpMessagePart *msn_slpmsgpart_new_from_data(const char *data, size_t data_len);
+
+void msn_slpmsgpart_destroy(MsnSlpMessagePart *part);
+
+MsnSlpMessagePart *msn_slpmsgpart_ref(MsnSlpMessagePart *part);
+
+MsnSlpMessagePart *msn_slpmsgpart_unref(MsnSlpMessagePart *part);
+
+void msn_slpmsgpart_set_bin_data(MsnSlpMessagePart *part, const void *data, size_t len);
+
+char *msn_slpmsgpart_serialize(MsnSlpMessagePart *part, size_t *ret_size);
+
+void msn_slpmsgpart_ack(MsnSlpMessagePart *part, void *data);
+
+void msn_slpmsgpart_nak(MsnSlpMessagePart *part, void *data);
+#endif /* MSN_SLPMSG_PART_H */
--- a/libpurple/protocols/msn/state.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/state.c	Mon Nov 01 07:12:08 2010 +0000
@@ -23,10 +23,11 @@
  */
 
 #include "internal.h"
+#include "debug.h"
 
 #include "core.h"
 
-#include "msn.h"
+#include "notification.h"
 #include "state.h"
 
 static const char *away_text[] =
@@ -43,10 +44,6 @@
 	N_("Available")
 };
 
-/* Local Function Prototype*/
-static char *msn_build_psm(const char *psmstr,const char *mediastr,
-						   const char *guidstr);
-
 /*
  * WLM media PSM info build prcedure
  *
@@ -56,7 +53,7 @@
  *	<CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia>"
  */
 static char *
-msn_build_psm(const char *psmstr,const char *mediastr, const char *guidstr)
+msn_build_psm(const char *psmstr,const char *mediastr, const char *guidstr, guint protocol_ver)
 {
 	xmlnode *dataNode,*psmNode,*mediaNode,*guidNode;
 	char *result;
@@ -82,60 +79,50 @@
 	}
 	xmlnode_insert_child(dataNode, guidNode);
 
+	if (protocol_ver >= 16) {
+		/* TODO: What is this for? */
+		xmlnode *ddpNode = xmlnode_new("DDP");
+		xmlnode_insert_child(dataNode, ddpNode);
+	}
+
 	result = xmlnode_to_str(dataNode, &length);
 	xmlnode_free(dataNode);
 	return result;
 }
 
-/* get the CurrentMedia info from the XML string */
+/* get the CurrentMedia info from the XML node */
 char *
-msn_get_currentmedia(char *xml_str, gsize len)
+msn_get_currentmedia(xmlnode *payloadNode)
 {
-	xmlnode *payloadNode, *currentmediaNode;
+	xmlnode *currentmediaNode;
 	char *currentmedia;
 
 	purple_debug_info("msn", "Get CurrentMedia\n");
-	payloadNode = xmlnode_from_str(xml_str, len);
-	if (!payloadNode) {
-		purple_debug_error("msn", "PSM XML parse Error!\n");
-		return NULL;
-	}
 	currentmediaNode = xmlnode_get_child(payloadNode, "CurrentMedia");
 	if (currentmediaNode == NULL) {
 		purple_debug_info("msn", "No CurrentMedia Node\n");
-		xmlnode_free(payloadNode);
 		return NULL;
 	}
 	currentmedia = xmlnode_get_data(currentmediaNode);
 
-	xmlnode_free(payloadNode);
-
 	return currentmedia;
 }
 
-/*get the PSM info from the XML string*/
+/* Get the PSM info from the XML node */
 char *
-msn_get_psm(char *xml_str, gsize len)
+msn_get_psm(xmlnode *payloadNode)
 {
-	xmlnode *payloadNode, *psmNode;
+	xmlnode *psmNode;
 	char *psm;
 
 	purple_debug_info("msn", "msn get PSM\n");
-	payloadNode = xmlnode_from_str(xml_str, len);
-	if (!payloadNode) {
-		purple_debug_error("msn", "PSM XML parse Error!\n");
-		return NULL;
-	}
 	psmNode = xmlnode_get_child(payloadNode, "PSM");
 	if (psmNode == NULL) {
 		purple_debug_info("msn", "No PSM status Node\n");
-		xmlnode_free(payloadNode);
 		return NULL;
 	}
 	psm = xmlnode_get_data(psmNode);
 
-	xmlnode_free(payloadNode);
-
 	return psm;
 }
 
@@ -175,14 +162,12 @@
 /* set the MSN's PSM info,Currently Read from the status Line
  * Thanks for Cris Code
  */
-void
+static void
 msn_set_psm(MsnSession *session)
 {
 	PurpleAccount *account;
 	PurplePresence *presence;
 	PurpleStatus *status;
-	MsnCmdProc *cmdproc;
-	MsnTransaction *trans;
 	char *payload;
 	const char *statusline;
 	gchar *statusline_stripped, *media = NULL;
@@ -191,7 +176,6 @@
 	g_return_if_fail(session->notification != NULL);
 
 	account = session->account;
-	cmdproc = session->notification->cmdproc;
 
 	/* Get the PSM string from Purple's Status Line */
 	presence = purple_account_get_presence(account);
@@ -202,13 +186,11 @@
 	statusline_stripped = purple_markup_strip_html(statusline);
 	media = create_media_string(presence);
 	g_free(session->psm);
-	session->psm = msn_build_psm(statusline_stripped, media, NULL);
+	session->psm = msn_build_psm(statusline_stripped, media, session->protocol_ver >= 16 ? session->guid : NULL, session->protocol_ver);
 
 	payload = session->psm;
-	purple_debug_misc("msn", "Sending UUX command with payload: %s\n", payload);
-	trans = msn_transaction_new(cmdproc, "UUX", "%" G_GSIZE_FORMAT, strlen(payload));
-	msn_transaction_set_payload(trans, payload, strlen(payload));
-	msn_cmdproc_send_trans(cmdproc, trans);
+
+	msn_notification_send_uux(session, payload);
 
 	g_free(statusline_stripped);
 	g_free(media);
@@ -219,6 +201,7 @@
 {
 	PurpleAccount *account;
 	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
 	MsnUser *user;
 	MsnObject *msnobj;
 	const char *state_text;
@@ -256,11 +239,16 @@
 	if (!session->logged_in)
 		return;
 
+	msn_set_psm(session);
+
 	msnobj = msn_user_get_object(user);
 
 	if (msnobj == NULL)
 	{
-		msn_cmdproc_send(cmdproc, "CHG", "%s %u", state_text, caps);
+		if (session->protocol_ver >= 16)
+			trans = msn_transaction_new(cmdproc, "CHG", "%s %u:%02u 0", state_text, caps, MSN_CLIENT_ID_EXT_CAPS);
+		else
+			trans = msn_transaction_new(cmdproc, "CHG", "%s %u", state_text, caps);
 	}
 	else
 	{
@@ -268,12 +256,17 @@
 
 		msnobj_str = msn_object_to_string(msnobj);
 
-		msn_cmdproc_send(cmdproc, "CHG", "%s %u %s", state_text,
-						 caps, purple_url_encode(msnobj_str));
+		if (session->protocol_ver >= 16)
+			trans = msn_transaction_new(cmdproc, "CHG", "%s %u:%02u %s", state_text,
+							 caps, MSN_CLIENT_ID_EXT_CAPS, purple_url_encode(msnobj_str));
+		else
+			trans = msn_transaction_new(cmdproc, "CHG", "%s %u %s", state_text,
+							 caps, purple_url_encode(msnobj_str));
 
 		g_free(msnobj_str);
 	}
-	msn_set_psm(session);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 const char *
--- a/libpurple/protocols/msn/state.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/state.h	Mon Nov 01 07:12:08 2010 +0000
@@ -58,13 +58,11 @@
 
 const char *msn_state_get_text(MsnAwayType state);
 
-void msn_set_psm(MsnSession *session);
+/* Get the CurrentMedia info from the XML node */
+char *msn_get_currentmedia(xmlnode *payloadNode);
 
-/* Get the CurrentMedia info from the XML string */
-char *msn_get_currentmedia(char *xml_str, gsize len);
-
-/*get the PSM info from the XML string*/
-char *msn_get_psm(char *xml_str, gsize len);
+/* Get the PSM info from the XML node */
+char *msn_get_psm(xmlnode *payloadNode);
 
 MsnAwayType msn_state_from_account(PurpleAccount *account);
 
--- a/libpurple/protocols/msn/switchboard.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,19 +21,19 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
-#include "prefs.h"
+
+#include "internal.h"
+#include "debug.h"
+
+#include "msnutils.h"
 #include "switchboard.h"
-#include "notification.h"
-#include "msnutils.h"
-
-#include "error.h"
+#include "sbconn.h"
+#include "slplink.h"
+#include "user.h"
+#include "userlist.h"
 
 static MsnTable *cbs_table;
 
-static void msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg,
-							 MsnMsgErrorType error);
-
 /**************************************************************************
  * Main
  **************************************************************************/
@@ -90,9 +90,11 @@
 	while (swboard->slplinks != NULL) {
 		MsnSlpLink *slplink = swboard->slplinks->data;
 
+		swboard->slplinks = g_list_remove(swboard->slplinks, slplink);
+
 		/* Destroy only those slplinks which use the switchboard */
 		if (slplink->dc == NULL)
-			msn_slplink_destroy(slplink);
+			msn_slplink_unref(slplink);
 		else {
 			swboard->slplinks = g_list_remove(swboard->slplinks, slplink);
 			slplink->swboard = NULL;
@@ -123,7 +125,7 @@
 	g_free(swboard->session_id);
 
 	for (; swboard->users; swboard->users = g_list_delete_link(swboard->users, swboard->users))
-		g_free(swboard->users->data);
+		msn_user_unref(swboard->users->data);
 
 	session = swboard->session;
 	session->switches = g_list_remove(session->switches, swboard);
@@ -231,6 +233,8 @@
 {
 	MsnCmdProc *cmdproc;
 	PurpleAccount *account;
+	MsnUserList *userlist;
+	MsnUser *msnuser;
 	char *semicolon;
 	char *passport;
 
@@ -246,13 +250,36 @@
 	else
 		passport = g_strdup(user);
 
+	userlist = swboard->session->userlist;
+	msnuser = msn_userlist_find_user(userlist, passport);
+
 	/* Don't add multiple endpoints to the conversation. */
-	if (g_list_find_custom(swboard->users, passport, (GCompareFunc)strcmp)) {
+	if (g_list_find_custom(swboard->users, passport, (GCompareFunc)msn_user_passport_cmp)) {
+		g_free(passport);
+		return;
+	}
+
+	/* Don't add ourselves either... */
+	if (g_str_equal(passport, purple_account_get_username(account))) {
 		g_free(passport);
 		return;
 	}
 
-	swboard->users = g_list_prepend(swboard->users, passport);
+	/* Don't add ourselves either... */
+	if (g_str_equal(passport, purple_account_get_username(account))) {
+		g_free(passport);
+		return;
+	}
+	
+	if (!msnuser) {
+		purple_debug_info("msn","User %s is not on our list.\n", passport);
+		msnuser = msn_user_new(userlist, passport, NULL);
+	} else
+		msn_user_ref(msnuser);
+
+	g_free(passport);
+
+	swboard->users = g_list_prepend(swboard->users, msnuser);
 	swboard->current_users++;
 	swboard->empty = FALSE;
 
@@ -270,11 +297,11 @@
 	if ((swboard->conv != NULL) &&
 		(purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT))
 	{
-		purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL,
+		purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), msnuser->passport, NULL,
 								PURPLE_CBFLAGS_NONE, TRUE);
 		msn_servconn_set_idle_timeout(swboard->servconn, 0);
 	}
-	else if (swboard->current_users > 1 || swboard->total_users > 1)
+	else if (swboard->current_users > 1)
 	{
 		msn_servconn_set_idle_timeout(swboard->servconn, 0);
 		if (swboard->conv == NULL ||
@@ -298,8 +325,9 @@
 			for (l = swboard->users; l != NULL; l = l->next)
 			{
 				const char *tmp_user;
+				user = l->data;
 
-				tmp_user = l->data;
+				tmp_user = msnuser->passport;
 
 				purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv),
 										tmp_user, NULL, PURPLE_CBFLAGS_NONE, TRUE);
@@ -316,7 +344,7 @@
 	else if (swboard->conv == NULL)
 	{
 		swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-															user, account);
+															msnuser->passport, account);
 	}
 	else
 	{
@@ -406,7 +434,7 @@
 	return FALSE;
 }
 
-static void
+void
 msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error)
 {
 	MsnSwitchBoard *swboard;
@@ -514,7 +542,7 @@
 		body_enc = g_markup_escape_text(body_str, -1);
 		g_free(body_str);
 
-		format = msn_message_get_attr(msg, "X-MMS-IM-Format");
+		format = msn_message_get_header_value(msg, "X-MMS-IM-Format");
 		msn_parse_format(format, &pre, &post);
 		body_str = g_strdup_printf("%s%s%s", pre ? pre : "",
 								   body_enc ? body_enc : "", post ? post : "");
@@ -543,17 +571,6 @@
  * Message Stuff
  **************************************************************************/
 
-/** Called when a message times out. */
-static void
-msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans)
-{
-	MsnMessage *msg;
-
-	msg = trans->data;
-
-	msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT);
-}
-
 /** Called when we receive an error of a message. */
 static void
 msg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error)
@@ -588,96 +605,6 @@
 }
 #endif
 
-static void
-release_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
-{
-	MsnCmdProc *cmdproc;
-	MsnTransaction *trans;
-	char *payload;
-	gsize payload_len;
-	char flag;
-
-	g_return_if_fail(swboard != NULL);
-	g_return_if_fail(msg     != NULL);
-
-	cmdproc = swboard->cmdproc;
-
-	payload = msn_message_gen_payload(msg, &payload_len);
-
-	if (purple_debug_is_verbose()) {
-		purple_debug_info("msn", "SB length:{%" G_GSIZE_FORMAT "}\n", payload_len);
-		msn_message_show_readable(msg, "SB SEND", FALSE);
-	}
-
-	flag = msn_message_get_flag(msg);
-	trans = msn_transaction_new(cmdproc, "MSG", "%c %" G_GSIZE_FORMAT,
-								flag, payload_len);
-
-	/* Data for callbacks */
-	msn_transaction_set_data(trans, msg);
-
-	if (flag != 'U') {
-		if (msg->type == MSN_MSG_TEXT)
-		{
-			msg->ack_ref = TRUE;
-			msn_message_ref(msg);
-			swboard->ack_list = g_list_append(swboard->ack_list, msg);
-			msn_transaction_set_timeout_cb(trans, msg_timeout);
-		}
-		else if (msg->type == MSN_MSG_SLP)
-		{
-			msg->ack_ref = TRUE;
-			msn_message_ref(msg);
-			swboard->ack_list = g_list_append(swboard->ack_list, msg);
-			msn_transaction_set_timeout_cb(trans, msg_timeout);
-#if 0
-			if (msg->ack_cb != NULL)
-			{
-				msn_transaction_add_cb(trans, "ACK", msg_ack);
-				msn_transaction_add_cb(trans, "NAK", msg_nak);
-			}
-#endif
-		}
-	}
-
-	trans->payload = payload;
-	trans->payload_len = payload_len;
-
-	msg->trans = trans;
-
-	msn_cmdproc_send_trans(cmdproc, trans);
-}
-
-static void
-queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg)
-{
-	g_return_if_fail(swboard != NULL);
-	g_return_if_fail(msg     != NULL);
-
-	purple_debug_info("msn", "Appending message to queue.\n");
-
-	g_queue_push_tail(swboard->msg_queue, msg);
-
-	msn_message_ref(msg);
-}
-
-static void
-process_queue(MsnSwitchBoard *swboard)
-{
-	MsnMessage *msg;
-
-	g_return_if_fail(swboard != NULL);
-
-	purple_debug_info("msn", "Processing queue\n");
-
-	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)
-	{
-		purple_debug_info("msn", "Sending message\n");
-		release_msg(swboard, msg);
-		msn_message_unref(msg);
-	}
-}
-
 gboolean
 msn_switchboard_can_send(MsnSwitchBoard *swboard)
 {
@@ -689,20 +616,6 @@
 	return TRUE;
 }
 
-void
-msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg,
-						 gboolean queue)
-{
-	g_return_if_fail(swboard != NULL);
-	g_return_if_fail(msg     != NULL);
-
-	purple_debug_info("msn", "switchboard send msg..\n");
-	if (msn_switchboard_can_send(swboard))
-		release_msg(swboard, msg);
-	else if (queue)
-		queue_msg(swboard, msg);
-}
-
 /**************************************************************************
  * Switchboard Commands
  **************************************************************************/
@@ -745,7 +658,10 @@
 		purple_conv_chat_remove_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL);
 
 		passport = g_list_find_custom(swboard->users, user, (GCompareFunc)strcmp);
-		g_free(passport->data);
+		if (passport)
+			g_free(passport->data);
+		else
+			purple_debug_warning("msn", "Can't find user %s in the switchboard\n", user);
 		swboard->users = g_list_delete_link(swboard->users, passport);
 		swboard->current_users--;
 		if (swboard->current_users == 0)
@@ -761,8 +677,12 @@
 static void
 iro_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
+	PurpleAccount *account;
+	PurpleConnection *gc;
 	MsnSwitchBoard *swboard;
 
+	account = cmdproc->session->account;
+	gc = account->gc;
 	swboard = cmdproc->data;
 
 	swboard->total_users = atoi(cmd->params[2]);
@@ -774,17 +694,21 @@
 joi_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	MsnSession *session;
+	PurpleAccount *account;
+	PurpleConnection *gc;
 	MsnSwitchBoard *swboard;
 	const char *passport;
 
 	passport = cmd->params[0];
 
 	session = cmdproc->session;
+	account = session->account;
+	gc = account->gc;
 	swboard = cmdproc->data;
 
 	msn_switchboard_add_user(swboard, passport);
 
-	process_queue(swboard);
+	msn_sbconn_process_queue(swboard);
 
 	if (!session->http_method)
 		send_clientcaps(swboard);
@@ -847,8 +771,8 @@
 
 	msg = cmd->trans->data;
 
-	if (msg->ack_cb != NULL)
-		msg->ack_cb(msg, msg->ack_data);
+	if (msg->part && msg->part->ack_cb != NULL)
+		msg->part->ack_cb(msg->part, msg->part->ack_data);
 
 	swboard = cmdproc->data;
 	if (swboard)
@@ -970,6 +894,7 @@
 	MsnTransaction *trans;
 	MsnCmdProc *cmdproc;
 	PurpleAccount *account;
+	char *username;
 
 	cmdproc = servconn->cmdproc;
 	g_return_if_fail(cmdproc != NULL);
@@ -978,24 +903,33 @@
 	swboard = cmdproc->data;
 	g_return_if_fail(swboard != NULL);
 
+	if (servconn->session->protocol_ver >= 16)
+		username = g_strdup_printf("%s;{%s}",
+		                           purple_account_get_username(account),
+		                           servconn->session->guid);
+	else
+		username = g_strdup(purple_account_get_username(account));
+
 	if (msn_switchboard_is_invited(swboard))
 	{
 		swboard->empty = FALSE;
 
 		trans = msn_transaction_new(cmdproc, "ANS", "%s %s %s",
-									purple_account_get_username(account),
+									username,
 									swboard->auth_key, swboard->session_id);
 	}
 	else
 	{
 		trans = msn_transaction_new(cmdproc, "USR", "%s %s",
-									purple_account_get_username(account),
+									username,
 									swboard->auth_key);
 	}
 
 	msn_transaction_set_error_cb(trans, ans_usr_error);
 	msn_transaction_set_data(trans, swboard);
 	msn_cmdproc_send_trans(cmdproc, trans);
+
+	g_free(username);
 }
 
 static void
@@ -1213,8 +1147,11 @@
 			 !swboard->session->connected)
 	{
 		MsnCmdProc *cmdproc;
+		MsnTransaction *trans;
 		cmdproc = swboard->cmdproc;
-		msn_cmdproc_send_quick(cmdproc, "OUT", NULL, NULL);
+		trans = msn_transaction_new(cmdproc, "OUT", NULL);
+		msn_transaction_set_saveable(trans, FALSE);
+		msn_cmdproc_send_trans(cmdproc, trans);
 
 		msn_switchboard_destroy(swboard);
 	}
--- a/libpurple/protocols/msn/switchboard.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.h	Mon Nov 01 07:12:08 2010 +0000
@@ -50,12 +50,10 @@
 	MSN_SB_FLAG_FT = 0x02  /**< This switchboard is being used for file transfer. */
 } MsnSBFlag;
 
-#include "conversation.h"
-
+#include "cmdproc.h"
 #include "msg.h"
 #include "servconn.h"
-#include "slplink.h"
-#include "user.h"
+#include "session.h"
 
 /**
  * A switchboard.
@@ -246,6 +244,9 @@
 void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg,
 							  gboolean queue);
 
+void
+msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error);
+
 gboolean msn_switchboard_chat_leave(MsnSwitchBoard *swboard);
 gboolean msn_switchboard_chat_invite(MsnSwitchBoard *swboard, const char *who);
 
@@ -253,30 +254,6 @@
 void msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user);
 
 /**
- * Processes peer to peer messages.
- *
- * @param cmdproc The command processor.
- * @param msg     The message.
- */
-void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
-
-/**
- * Processes emoticon messages.
- *
- * @param cmdproc The command processor.
- * @param msg     The message.
- */
-void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
-
-/**
- * Processes INVITE messages.
- *
- * @param cmdproc The command processor.
- * @param msg     The message.
- */
-void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg);
-
-/**
  * Shows an ink message from this switchboard.
  *
  * @param swboard  The switchboard.
--- a/libpurple/protocols/msn/sync.c	Mon Nov 01 07:10:36 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,253 +0,0 @@
-/**
- * @file sync.c MSN list synchronization functions
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-#include "msn.h"
-#include "sync.h"
-#include "state.h"
-
-static MsnTable *cbs_table;
-
-static void
-blp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	PurpleConnection *gc = cmdproc->session->account->gc;
-	const char *list_name;
-
-	list_name = cmd->params[0];
-
-	if (!g_ascii_strcasecmp(list_name, "AL"))
-	{
-		/*
-		 * If the current setting is AL, messages from users who
-		 * are not in BL will be delivered.
-		 *
-		 * In other words, deny some.
-		 */
-		gc->account->perm_deny = PURPLE_PRIVACY_DENY_USERS;
-	}
-	else
-	{
-		/* If the current setting is BL, only messages from people
-		 * who are in the AL will be delivered.
-		 *
-		 * In other words, permit some.
-		 */
-		gc->account->perm_deny = PURPLE_PRIVACY_ALLOW_USERS;
-	}
-}
-
-static void
-prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session = cmdproc->session;
-	const char *type, *value;
-
-	type  = cmd->params[0];
-	value = cmd->params[1];
-
-	if (cmd->param_count == 2)
-	{
-		if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(session->user, purple_url_decode(value));
-		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(session->user, purple_url_decode(value));
-		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(session->user, purple_url_decode(value));
-	}
-	else
-	{
-		if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(session->user, NULL);
-		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(session->user, NULL);
-		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(session->user, NULL);
-	}
-}
-
-static void
-lsg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session = cmdproc->session;
-	const char *name;
-	const char *group_id;
-
-	group_id = cmd->params[0];
-	name = purple_url_decode(cmd->params[1]);
-
-	msn_group_new(session->userlist, group_id, name);
-
-	/* HACK */
-	if (group_id == 0)
-	{
-		/* Group of ungroupped buddies */
-		if (session->sync->total_users == 0)
-		{
-			cmdproc->cbs_table = session->sync->old_cbs_table;
-
-			msn_session_finish_login(session);
-
-			msn_sync_destroy(session->sync);
-			session->sync = NULL;
-		}
-		return;
-	}
-
-	if ((purple_find_group(name)) == NULL)
-	{
-		PurpleGroup *g = purple_group_new(name);
-		purple_blist_add_group(g, NULL);
-	}
-}
-
-static void
-lst_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSession *session = cmdproc->session;
-	char *passport = NULL;
-	const char *friend = NULL;
-	int list_op;
-	MsnUser *user;
-
-	passport = cmd->params[0];
-	friend   = purple_url_decode(cmd->params[1]);
-	list_op  = atoi(cmd->params[2]);
-
-	user = msn_user_new(session->userlist, passport, friend);
-
-	msn_userlist_add_user(session->userlist, user);
-
-	session->sync->last_user = user;
-
-	/* TODO: This can be improved */
-
-	if (list_op & MSN_LIST_FL_OP)
-	{
-		char **c;
-		char **tokens;
-		const char *group_nums;
-		GSList *group_ids;
-
-		group_nums = cmd->params[3];
-
-		group_ids = NULL;
-
-		tokens = g_strsplit(group_nums, ",", -1);
-
-		for (c = tokens; *c != NULL; c++)
-		{
-			group_ids = g_slist_append(group_ids, *c);
-		}
-
-
-		msn_got_lst_user(session, user, list_op, group_ids);
-
-		g_strfreev(tokens);
-		g_slist_free(group_ids);
-	}
-	else
-	{
-		msn_got_lst_user(session, user, list_op, NULL);
-	}
-
-	session->sync->num_users++;
-
-	if (session->sync->num_users == session->sync->total_users)
-	{
-		cmdproc->cbs_table = session->sync->old_cbs_table;
-
-		msn_session_finish_login(session);
-
-		msn_sync_destroy(session->sync);
-		session->sync = NULL;
-	}
-}
-
-static void
-bpr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
-{
-	MsnSync *sync = cmdproc->session->sync;
-	const char *type, *value;
-	MsnUser *user;
-
-	user = sync->last_user;
-
-	g_return_if_fail(user != NULL);
-
-	type     = cmd->params[0];
-	value    = cmd->params[1];
-
-	if (value)
-	{
-		if (!strcmp(type, "MOB"))
-		{
-			if (!strcmp(value, "Y"))
-				user->mobile = TRUE;
-		}
-		else if (!strcmp(type, "PHH"))
-			msn_user_set_home_phone(user, purple_url_decode(value));
-		else if (!strcmp(type, "PHW"))
-			msn_user_set_work_phone(user, purple_url_decode(value));
-		else if (!strcmp(type, "PHM"))
-			msn_user_set_mobile_phone(user, purple_url_decode(value));
-	}
-}
-
-void
-msn_sync_init(void)
-{
-	cbs_table = msn_table_new();
-
-	/* Syncing */
-	msn_table_add_cmd(cbs_table, NULL, "GTC", NULL);
-	msn_table_add_cmd(cbs_table, NULL, "BLP", blp_cmd);
-	msn_table_add_cmd(cbs_table, NULL, "PRP", prp_cmd);
-	msn_table_add_cmd(cbs_table, NULL, "LSG", lsg_cmd);
-	msn_table_add_cmd(cbs_table, NULL, "LST", lst_cmd);
-	msn_table_add_cmd(cbs_table, NULL, "BPR", bpr_cmd);
-}
-
-void
-msn_sync_end(void)
-{
-	msn_table_destroy(cbs_table);
-}
-
-MsnSync *
-msn_sync_new(MsnSession *session)
-{
-	MsnSync *sync;
-
-	sync = g_new0(MsnSync, 1);
-
-	sync->session = session;
-	sync->cbs_table = cbs_table;
-
-	return sync;
-}
-
-void
-msn_sync_destroy(MsnSync *sync)
-{
-	g_free(sync);
-}
--- a/libpurple/protocols/msn/sync.h	Mon Nov 01 07:10:36 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * @file sync.h MSN list synchronization functions
- *
- * purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-#ifndef MSN_SYNC_H
-#define MSN_SYNC_H
-
-typedef struct _MsnSync MsnSync;
-
-#include "session.h"
-#include "table.h"
-#include "user.h"
-
-struct _MsnSync
-{
-	MsnSession *session;
-	MsnTable *cbs_table;
-
-	/*
-	 * TODO: What is the intended purpose of old_cbs_table?  Nothing
-	 *       sets it and it is only read in two places.
-	 */
-	MsnTable *old_cbs_table;
-
-	int num_users;
-	int total_users;
-	int num_groups;
-	int total_groups;
-	MsnUser *last_user;
-};
-
-void msn_sync_init(void);
-void msn_sync_end(void);
-
-MsnSync *msn_sync_new(MsnSession *session);
-void msn_sync_destroy(MsnSync *sync);
-
-#endif /* MSN_SYNC_H */
--- a/libpurple/protocols/msn/table.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/table.h	Mon Nov 01 07:12:08 2010 +0000
@@ -34,20 +34,60 @@
 
 struct _MsnTable
 {
-	GHashTable *cmds;
-	GHashTable *msgs;
-	GHashTable *errors;
+	GHashTable *cmds; 		/**< Callbacks that manage command response. */
+	GHashTable *msgs; 		/**< Callbacks that manage incoming messages. */
+	GHashTable *errors; 	/**< Callbacks that manage command errors. */
 
-	GHashTable *async;
-	GHashTable *fallback;
+	GHashTable *async; 		/**< Callbacks that manage incoming asyncronous messages. */
+	/* TODO: Does this one is really needed? */
+	GHashTable *fallback; 	/**< Fallback callback. */
 };
 
+/**
+ * Create a new instance of a MsnTable which map commands, errors and messages
+ * with callbacks that will handle it.
+ *
+ * @return A new MsnTable.
+ */
 MsnTable *msn_table_new(void);
+
+/**
+ * Destroy a MsnTable.
+ *
+ * @param table The MsnTable to be destroyed.
+ */
 void msn_table_destroy(MsnTable *table);
 
+/**
+ * Relate an incomming command from server with a callback able to handle
+ * the event.
+ *
+ * @param table 	The MsnTable.
+ * @param command 	If NULL this add an incoming asyncronous command set in answer.
+ * 					Else, the command sent.
+ * @param answer 	The server answer to 'command'. If 'command' is NULL,
+ * 					the asyncronous command sent by the server.
+ * @param cb 		Callback to handle this event.
+ */
 void msn_table_add_cmd(MsnTable *table, char *command, char *answer,
 					   MsnTransCb cb);
+
+/**
+ * Set a callback to handle incoming command errors.
+ *
+ * @param table 	The MsnTable.
+ * @param answer 	Incoming command with error.
+ * @param cb 		Callback to handle this error.
+ */
 void msn_table_add_error(MsnTable *table, char *answer, MsnErrorCb cb);
+
+/**
+ * Relate a message Content-type with a callback able to handle it.
+ *
+ * @param table 	The MsnTable.
+ * @param type 		The Message Content-Type.
+ * @param cb 		Callback to handle this Content-type.
+ */
 void msn_table_add_msg_type(MsnTable *table, char *type, MsnMsgTypeCb cb);
 
 #endif /* MSN_TABLE_H */
--- a/libpurple/protocols/msn/transaction.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/transaction.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,6 +21,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "internal.h"
+#include "debug.h"
+
 #include "msn.h"
 #include "transaction.h"
 
@@ -37,6 +41,7 @@
 
 	trans->cmdproc = cmdproc;
 	trans->command = g_strdup(command);
+	trans->saveable = TRUE;
 
 	if (format != NULL)
 	{
@@ -96,8 +101,10 @@
 
 	if (trans->params != NULL)
 		str = g_strdup_printf("%s %u %s\r\n", trans->command, trans->trId, trans->params);
+	else if (trans->saveable)
+		str = g_strdup_printf("%s %u\r\n", trans->command, trans->trId);
 	else
-		str = g_strdup_printf("%s %u\r\n", trans->command, trans->trId);
+		str = g_strdup_printf("%s\r\n", trans->command);
 
 	return str;
 }
@@ -175,6 +182,14 @@
 }
 
 void
+msn_transaction_set_saveable(MsnTransaction  *trans, gboolean saveable)
+{
+	g_return_if_fail(trans != NULL);
+
+	trans->saveable = saveable;
+}
+
+void
 msn_transaction_add_cb(MsnTransaction *trans, char *answer,
 					   MsnTransCb cb)
 {
--- a/libpurple/protocols/msn/transaction.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/transaction.h	Mon Nov 01 07:12:08 2010 +0000
@@ -24,6 +24,8 @@
 #ifndef MSN_TRANSACTION_H
 #define MSN_TRANSACTION_H
 
+#include "internal.h"
+
 typedef struct _MsnTransaction MsnTransaction;
 
 #include "cmdproc.h"
@@ -40,7 +42,9 @@
 struct _MsnTransaction
 {
 	MsnCmdProc *cmdproc;
-	unsigned int trId;
+
+	gboolean saveable;	/**< Whether to save this transaction in the history */
+	unsigned int trId;	/**< The ID of this transaction, if it's being saved */
 
 	char *command;
 	char *params;
@@ -74,6 +78,7 @@
 								 const char *payload, int payload_len);
 void msn_transaction_set_data(MsnTransaction *trans, void *data);
 void msn_transaction_set_data_free(MsnTransaction *trans, GDestroyNotify fn);
+void msn_transaction_set_saveable(MsnTransaction  *trans, gboolean saveable);
 void msn_transaction_add_cb(MsnTransaction *trans, char *answer,
 							MsnTransCb cb);
 void msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb);
--- a/libpurple/protocols/msn/user.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/user.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,10 +21,21 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "msn.h"
+
+#include "internal.h"
+#include "debug.h"
+#include "util.h"
+
 #include "user.h"
 #include "slp.h"
 
+static void free_user_endpoint(MsnUserEndpoint *data)
+{
+	g_free(data->id);
+	g_free(data->name);
+	g_free(data);
+}
+
 /*new a user object*/
 MsnUser *
 msn_user_new(MsnUserList *userlist, const char *passport,
@@ -39,15 +50,25 @@
 	msn_user_set_passport(user, passport);
 	msn_user_set_friendly_name(user, friendly_name);
 
-	return user;
+	return msn_user_ref(user);
 }
 
 /*destroy a user object*/
-void
+static void
 msn_user_destroy(MsnUser *user)
 {
 	g_return_if_fail(user != NULL);
 
+	if (user->refcount > 1) {
+		msn_user_unref(user);
+		return;
+	}
+
+	while (user->endpoints != NULL) {
+		free_user_endpoint(user->endpoints->data);
+		user->endpoints = g_slist_delete_link(user->endpoints, user->endpoints);
+	}
+
 	if (user->clientcaps != NULL)
 		g_hash_table_destroy(user->clientcaps);
 
@@ -82,6 +103,27 @@
 	g_free(user);
 }
 
+MsnUser *
+msn_user_ref(MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, NULL);
+
+	user->refcount++;
+
+	return user;
+}
+
+void
+msn_user_unref(MsnUser *user)
+{
+	g_return_if_fail(user != NULL);
+
+	user->refcount--;
+
+	if(user->refcount == 0)
+		msn_user_destroy(user);
+}
+
 void
 msn_user_update(MsnUser *user)
 {
@@ -186,6 +228,9 @@
 {
 	g_return_val_if_fail(user != NULL, FALSE);
 
+	if (user == user->userlist->session->user)
+		return FALSE;
+
 	if (user->friendly_name && name && (!strcmp(user->friendly_name, name) ||
 				!strcmp(user->passport, name)))
 		return FALSE;
@@ -217,6 +262,47 @@
 }
 
 void
+msn_user_set_endpoint_data(MsnUser *user, const char *input, MsnUserEndpoint *newep)
+{
+	MsnUserEndpoint *ep;
+	char *endpoint;
+	GSList *l;
+
+	g_return_if_fail(user != NULL);
+	g_return_if_fail(input != NULL);
+
+	endpoint = g_ascii_strdown(input, -1);
+
+	for (l = user->endpoints; l; l = l->next) {
+		ep = l->data;
+		if (g_str_equal(ep->id, endpoint)) {
+			/* We have info about this endpoint! */
+
+			g_free(endpoint);
+
+			if (newep == NULL) {
+				/* Delete it and exit */
+				user->endpoints = g_slist_delete_link(user->endpoints, l);
+				free_user_endpoint(ep);
+				return;
+			}
+
+			/* Break out of our loop and update it */
+			break;
+		}
+	}
+	if (l == NULL) {
+		/* Need to add a new endpoint */
+		ep = g_new0(MsnUserEndpoint, 1);
+		ep->id = endpoint;
+		user->endpoints = g_slist_prepend(user->endpoints, ep);
+	}
+
+	ep->clientid = newep->clientid;
+	ep->extcaps = newep->extcaps;
+}
+
+void
 msn_user_set_op(MsnUser *user, MsnListOp list_op)
 {
 	g_return_if_fail(user != NULL);
@@ -406,6 +492,14 @@
 }
 
 void
+msn_user_set_extcaps(MsnUser *user, guint extcaps)
+{
+	g_return_if_fail(user != NULL);
+
+	user->extcaps = extcaps;
+}
+
+void
 msn_user_set_network(MsnUser *user, MsnNetwork network)
 {
 	g_return_if_fail(user != NULL);
@@ -413,18 +507,83 @@
 	user->networkid = network;
 }
 
+static gboolean
+buddy_icon_cached(PurpleConnection *gc, MsnObject *obj)
+{
+	PurpleAccount *account;
+	PurpleBuddy *buddy;
+	const char *old;
+	const char *new;
+
+	g_return_val_if_fail(obj != NULL, FALSE);
+
+	account = purple_connection_get_account(gc);
+
+	buddy = purple_find_buddy(account, msn_object_get_creator(obj));
+	if (buddy == NULL)
+		return FALSE;
+
+	old = purple_buddy_icons_get_checksum_for_user(buddy);
+	new = msn_object_get_sha1(obj);
+
+	if (new == NULL)
+		return FALSE;
+
+	/* If the old and new checksums are the same, and the file actually exists,
+	 * then return TRUE */
+	if (old != NULL && !strcmp(old, new))
+		return TRUE;
+
+	return FALSE;
+}
+
+static void
+queue_buddy_icon_request(MsnUser *user)
+{
+	PurpleAccount *account;
+	MsnObject *obj;
+	GQueue *queue;
+
+	g_return_if_fail(user != NULL);
+
+	account = user->userlist->session->account;
+
+	obj = msn_user_get_object(user);
+
+	if (obj == NULL) {
+		purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL);
+		return;
+	}
+
+	if (!buddy_icon_cached(account->gc, obj)) {
+		MsnUserList *userlist;
+
+		userlist = user->userlist;
+		queue = userlist->buddy_icon_requests;
+
+		if (purple_debug_is_verbose())
+			purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n",
+			                  user->passport, userlist->buddy_icon_window);
+
+		g_queue_push_tail(queue, user);
+
+		if (userlist->buddy_icon_window > 0)
+			msn_release_buddy_icon_request(userlist);
+	}
+}
+
 void
 msn_user_set_object(MsnUser *user, MsnObject *obj)
 {
 	g_return_if_fail(user != NULL);
 
-	if (user->msnobj != NULL)
+	if (user->msnobj != NULL && !msn_object_find_local(msn_object_get_sha1(obj)))
 		msn_object_destroy(user->msnobj);
 
 	user->msnobj = obj;
 
-	if (user->list_op & MSN_LIST_FL_OP)
-		msn_queue_buddy_icon_request(user);
+	if (user != user->userlist->session->user && user->list_op & MSN_LIST_FL_OP)
+		queue_buddy_icon_request(user);
 }
 
 void
@@ -496,6 +655,39 @@
 	return user->clientid;
 }
 
+guint
+msn_user_get_extcaps(const MsnUser *user)
+{
+	g_return_val_if_fail(user != NULL, 0);
+
+	return user->extcaps;
+}
+
+MsnUserEndpoint *
+msn_user_get_endpoint_data(MsnUser *user, const char *input)
+{
+	char *endpoint;
+	GSList *l;
+	MsnUserEndpoint *ep;
+
+	g_return_val_if_fail(user != NULL, NULL);
+	g_return_val_if_fail(input != NULL, NULL);
+
+	endpoint = g_ascii_strdown(input, -1);
+
+	for (l = user->endpoints; l; l = l->next) {
+		ep = l->data;
+		if (g_str_equal(ep->id, endpoint)) {
+			g_free(endpoint);
+			return ep;
+		}
+	}
+
+	g_free(endpoint);
+
+	return NULL;
+}
+
 MsnObject *
 msn_user_get_object(const MsnUser *user)
 {
@@ -520,3 +712,60 @@
 	return user->invite_message;
 }
 
+gboolean
+msn_user_is_capable(MsnUser *user, char *endpoint, guint capability, guint extcap)
+{
+	g_return_val_if_fail(user != NULL, FALSE);
+
+	if (endpoint != NULL) {
+		MsnUserEndpoint *ep = msn_user_get_endpoint_data(user, endpoint);
+		if (ep != NULL)
+			return (ep->clientid & capability) && (ep->extcaps & extcap);
+		else
+			return FALSE;
+	}
+
+	return (user->clientid & capability) && (user->extcaps & extcap);
+}
+
+/**************************************************************************
+ * Utility functions
+ **************************************************************************/
+
+int
+msn_user_passport_cmp(MsnUser *user, const char *passport)
+{
+	const char *str;
+	char *pass;
+	int result;
+
+	str = purple_normalize_nocase(NULL, msn_user_get_passport(user));
+	pass = g_strdup(str);
+
+	result = g_strcmp0(pass, purple_normalize_nocase(NULL, passport));
+	g_free(pass);
+
+	return result;
+}
+
+gboolean
+msn_user_is_in_group(MsnUser *user, const char * group_id)
+{
+	if (user == NULL)
+		return FALSE;
+
+	if (group_id == NULL)
+		return FALSE;
+
+	return (g_list_find_custom(user->group_ids, group_id, (GCompareFunc)strcmp)) != NULL;
+}
+
+gboolean
+msn_user_is_in_list(MsnUser *user, MsnListId list_id)
+{
+	if (user == NULL)
+		return FALSE;
+
+	return (user->list_op & (1 << list_id));
+}
+
--- a/libpurple/protocols/msn/user.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/user.h	Mon Nov 01 07:12:08 2010 +0000
@@ -79,10 +79,13 @@
 {
 	MsnUserList *userlist;
 
+	guint8 refcount;        /**< The reference count of this object */
+
 	char *passport;         /**< The passport account.          */
 	char *friendly_name;    /**< The friendly name.             */
 
 	char *uid;              /*< User ID                         */
+	GSList *endpoints;      /*< Endpoint-specific data          */
 
 	const char *status;     /**< The state of the user.         */
 	char *statusline;       /**< The state of the user.         */
@@ -102,6 +105,7 @@
 	GHashTable *clientcaps; /**< The client's capabilities.     */
 
 	guint clientid;         /**< The client's ID                */
+	guint extcaps;			/**< The client's extended capabilities */
 
 	MsnNetwork networkid;   /**< The user's network             */
 
@@ -116,6 +120,18 @@
 	char *invite_message;   /**< Invite message of user request */
 };
 
+/**
+ * A specific user endpoint.
+ */
+typedef struct MsnUserEndpoint {
+	char *id;               /**< The client's endpoint ID          */
+	char *name;             /**< The client's endpoint's name      */
+	int type;               /**< The client's endpoint type        */
+	guint clientid;         /**< The client's ID                   */
+	guint extcaps;          /**< The client's extended capabilites */
+
+} MsnUserEndpoint;
+
 /**************************************************************************
  ** @name User API                                                        *
  **************************************************************************/
@@ -134,12 +150,20 @@
 					  const char *friendly_name);
 
 /**
- * Destroys a user structure.
+ * Increment the reference count.
+ *
+ * @param user 	The user.
  *
- * @param user The user to destroy.
+ * @return 		user.
  */
-void msn_user_destroy(MsnUser *user);
+MsnUser *msn_user_ref(MsnUser *user);
 
+/**
+ * Decrement the reference count.
+ *
+ * @param user 	The user
+ */
+void msn_user_unref(MsnUser *user);
 
 /**
  * Updates the user.
@@ -252,6 +276,16 @@
 void msn_user_set_uid(MsnUser *user, const char *uid);
 
 /**
+ * Sets endpoint data for a user.
+ *
+ * @param user     The user.
+ * @param endpoint The endpoint.
+ * @param data     The endpoint data.
+ */
+void
+msn_user_set_endpoint_data(MsnUser *user, const char *endpoint, MsnUserEndpoint *data);
+
+/**
  * Sets the client id for a user.
  *
  * @param user     The user.
@@ -260,6 +294,14 @@
 void msn_user_set_clientid(MsnUser *user, guint clientid);
 
 /**
+ * Sets the client id for a user.
+ *
+ * @param user     The user.
+ * @param extcaps  The client's extended capabilities.
+ */
+void msn_user_set_extcaps(MsnUser *user, guint extcaps);
+
+/**
  * Sets the network id for a user.
  *
  * @param user    The user.
@@ -346,6 +388,17 @@
 const char *msn_user_get_mobile_phone(const MsnUser *user);
 
 /**
+ * Gets endpoint data for a user.
+ *
+ * @param user     The user.
+ * @param endpoint The endpoint.
+ *
+ * @return The user's endpoint data.
+ */
+MsnUserEndpoint *
+msn_user_get_endpoint_data(MsnUser *user, const char *endpoint);
+
+/**
  * Returns the client id for a user.
  *
  * @param user    The user.
@@ -355,6 +408,39 @@
 guint msn_user_get_clientid(const MsnUser *user);
 
 /**
+ * Returns the extended capabilities for a user.
+ *
+ * @param user    The user.
+ *
+ * @return The user's extended capabilities.
+ */
+guint msn_user_get_extcaps(const MsnUser *user);
+
+/**************************************************************************
+ * Utility functions
+ **************************************************************************/
+
+
+/**
+ * Check if the user is part of the group.
+ *
+ * @param user 		The user we are asking group membership.
+ * @param group_id 	The group where the user may be in.
+ * 
+ * @return TRUE if user is part of the group. Otherwise, FALSE.
+ */
+gboolean msn_user_is_in_group(MsnUser *user, const char * group_id);
+
+/**
+ * Check if user is on list.
+ *
+ * @param user 		The user we are asking list membership.
+ * @param list_id 	The list where the user may be in.
+ *
+ * @return TRUE if the user is on the list, else FALSE.
+ */
+gboolean msn_user_is_in_list(MsnUser *user, MsnListId list_id);
+/**
  * Returns the network id for a user.
  *
  * @param user    The user.
@@ -398,11 +484,36 @@
 /**
  * check to see if user is Yahoo User
  */
-gboolean msn_user_is_yahoo(PurpleAccount *account ,const char *name);
+gboolean msn_user_is_yahoo(PurpleAccount *account, const char *name);
 
 void msn_user_set_op(MsnUser *user, MsnListOp list_op);
 void msn_user_unset_op(MsnUser *user, MsnListOp list_op);
 
+/**
+ * Compare the given passport with the one of the user
+ *
+ * @param user 	User to compare.
+ * @oaran passport 	Passport to compare.
+ *
+ * @return Zero if the passport match with the one of the user, otherwise
+ * a positive integer if the user passport is greather than the one given
+ * and a negative integer if it is less.
+ */
+int msn_user_passport_cmp(MsnUser *user, const char *passport);
+
+/**
+ * Checks whether a user is capable of some task.
+ *
+ * @param user       The user.
+ * @param endpoint   The endpoint. Can be @NULL to check overall capabilities.
+ * @param capability The capability (including client version).
+ * @param extcap     The extended capability.
+ *
+ * @return Whether the user supports the capability.
+ */
+gboolean
+msn_user_is_capable(MsnUser *user, char *endpoint, guint capability, guint extcap);
+
 /*@}*/
 
 #endif /* MSN_USER_H */
--- a/libpurple/protocols/msn/userlist.c	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/userlist.c	Mon Nov 01 07:12:08 2010 +0000
@@ -21,7 +21,13 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+
+#include "internal.h"
+#include "debug.h"
+#include "request.h"
+
 #include "msn.h"
+#include "msnutils.h"
 #include "userlist.h"
 
 #include "contact.h"
@@ -107,31 +113,6 @@
 }
 
 /**************************************************************************
- * Utility functions
- **************************************************************************/
-
-gboolean
-msn_userlist_user_is_in_group(MsnUser *user, const char * group_id)
-{
-	if (user == NULL)
-		return FALSE;
-
-	if (group_id == NULL)
-		return FALSE;
-
-	return (g_list_find_custom(user->group_ids, group_id, (GCompareFunc)strcmp)) != NULL;
-}
-
-gboolean
-msn_userlist_user_is_in_list(MsnUser *user, MsnListId list_id)
-{
-	if (user == NULL)
-		return FALSE;
-
-	return (user->list_op & (1 << list_id));
-}
-
-/**************************************************************************
  * Server functions
  **************************************************************************/
 
@@ -235,7 +216,7 @@
 	/*destroy userlist*/
 	for (l = userlist->users; l != NULL; l = l->next)
 	{
-		msn_user_destroy(l->data);
+		msn_user_unref(l->data);
 	}
 	g_list_free(userlist->users);
 
@@ -503,7 +484,7 @@
 
 	g_return_if_fail(user != NULL);
 
-	if ( !msn_userlist_user_is_in_list(user, list_id)) {
+	if ( !msn_user_is_in_list(user, list_id)) {
 		list = lists[list_id];
 		purple_debug_info("msn", "User %s is not in list %s, not removing.\n", who, list);
 		return;
@@ -569,13 +550,13 @@
 
 	user = msn_userlist_find_add_user(userlist, who, who);
 
-	if ( msn_userlist_user_is_in_list(user, MSN_LIST_FL) ) {
+	if ( msn_user_is_in_list(user, MSN_LIST_FL) ) {
 
 		purple_debug_info("msn", "User %s already exists\n", who);
 
 		msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_BL);
 
-		if (msn_userlist_user_is_in_group(user, group_id)) {
+		if (msn_user_is_in_group(user, group_id)) {
 			purple_debug_info("msn", "User %s is already in group %s, returning\n", who, new_group_name);
 			msn_callback_state_free(state);
 			return;
@@ -604,7 +585,7 @@
 	user = msn_userlist_find_add_user(userlist, who, who);
 
 	/* First we're going to check if it's already there. */
-	if (msn_userlist_user_is_in_list(user, list_id))
+	if (msn_user_is_in_list(user, list_id))
 	{
 		list = lists[list_id];
 		purple_debug_info("msn", "User '%s' is already in list: %s\n", who, list);
@@ -706,6 +687,42 @@
 	msn_add_contact_to_group(userlist->session, state, who, new_group_id);
 }
 
+void
+msn_release_buddy_icon_request(MsnUserList *userlist)
+{
+	MsnUser *user;
+
+	g_return_if_fail(userlist != NULL);
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "Releasing buddy icon request\n");
+
+	if (userlist->buddy_icon_window > 0) {
+		GQueue *queue;
+		PurpleAccount *account;
+		const char *username;
+
+		queue = userlist->buddy_icon_requests;
+
+		if (g_queue_is_empty(userlist->buddy_icon_requests))
+			return;
+
+		user = g_queue_pop_head(queue);
+
+		account  = userlist->session->account;
+		username = user->passport;
+
+		userlist->buddy_icon_window--;
+
+		msn_request_user_display(user);
+
+		if (purple_debug_is_verbose())
+			purple_debug_info("msn",
+			                  "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n",
+			                  userlist->buddy_icon_window);
+	}
+}
+
 /*load userlist from the Blist file cache*/
 void
 msn_userlist_load(MsnSession *session)
--- a/libpurple/protocols/msn/userlist.h	Mon Nov 01 07:10:36 2010 +0000
+++ b/libpurple/protocols/msn/userlist.h	Mon Nov 01 07:12:08 2010 +0000
@@ -35,6 +35,16 @@
 	MSN_LIST_PL  /**< Pending list */
 } MsnListId;
 
+typedef enum
+{
+	MSN_LIST_FL_OP = 0x01,
+	MSN_LIST_AL_OP = 0x02,
+	MSN_LIST_BL_OP = 0x04,
+	MSN_LIST_RL_OP = 0x08,
+	MSN_LIST_PL_OP = 0x10
+} MsnListOp;
+#define MSN_LIST_OP_MASK	0x07
+
 #include "group.h"
 #include "msn.h"
 #include "user.h"
@@ -52,9 +62,6 @@
 
 };
 
-gboolean msn_userlist_user_is_in_group(MsnUser *user, const char * group_id);
-gboolean msn_userlist_user_is_in_list(MsnUser *user, MsnListId list_id);
-
 void msn_got_lst_user(MsnSession *session, MsnUser *user,
 					  MsnListOp list_op, GSList *group_ids);
 
@@ -98,6 +105,7 @@
 				    MsnListId list_id);
 void msn_userlist_rem_buddy_from_list(MsnUserList *userlist, const char *who,
 				      MsnListId list_id);
+void msn_release_buddy_icon_request(MsnUserList *userlist);
 
 void msn_userlist_load(MsnSession *session);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/xfer.c	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,138 @@
+#include "internal.h"
+#include "debug.h"
+
+#include "sbconn.h"
+#include "xfer.h"
+
+/**************************************************************************
+ * Xfer
+ **************************************************************************/
+
+void
+msn_xfer_init(PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	/* MsnSlpLink *slplink; */
+	char *content;
+
+	purple_debug_info("msn", "xfer_init\n");
+
+	slpcall = xfer->data;
+
+	/* Send Ok */
+	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+							  slpcall->session_id);
+
+	msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+			content);
+
+	g_free(content);
+	msn_slplink_send_queued_slpmsgs(slpcall->slplink);
+}
+
+void
+msn_xfer_cancel(PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	char *content;
+
+	g_return_if_fail(xfer != NULL);
+	g_return_if_fail(xfer->data != NULL);
+
+	slpcall = xfer->data;
+
+	if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
+	{
+		if (slpcall->started)
+		{
+			msn_slpcall_close(slpcall);
+		}
+		else
+		{
+			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
+									slpcall->session_id);
+
+			msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+						content);
+
+			g_free(content);
+			msn_slplink_send_queued_slpmsgs(slpcall->slplink);
+
+			if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND)
+				slpcall->wasted = TRUE;
+			else
+				msn_slpcall_destroy(slpcall);
+		}
+	}
+}
+
+gssize
+msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+
+	g_return_val_if_fail(xfer != NULL, -1);
+	g_return_val_if_fail(data != NULL, -1);
+	g_return_val_if_fail(len > 0, -1);
+
+	g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1);
+
+	slpcall = xfer->data;
+	/* Not sure I trust it'll be there */
+	g_return_val_if_fail(slpcall != NULL, -1);
+
+	g_return_val_if_fail(slpcall->xfer_msg != NULL, -1);
+
+	slpcall->u.outgoing.len = len;
+	slpcall->u.outgoing.data = data;
+	msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg);
+
+	return MIN(MSN_SBCONN_MAX_SIZE, len);
+}
+
+gssize
+msn_xfer_read(guchar **data, PurpleXfer *xfer)
+{
+	MsnSlpCall *slpcall;
+	gsize len;
+
+	g_return_val_if_fail(xfer != NULL, -1);
+	g_return_val_if_fail(data != NULL, -1);
+
+	g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1);
+
+	slpcall = xfer->data;
+	/* Not sure I trust it'll be there */
+	g_return_val_if_fail(slpcall != NULL, -1);
+
+	/* Just pass up the whole GByteArray. We'll make another. */
+	*data = slpcall->u.incoming_data->data;
+	len = slpcall->u.incoming_data->len;
+
+	g_byte_array_free(slpcall->u.incoming_data, FALSE);
+	slpcall->u.incoming_data = g_byte_array_new();
+
+	return len;
+}
+
+void
+msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session)
+{
+	if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) &&
+		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) &&
+		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL))
+	{
+		purple_xfer_cancel_remote(slpcall->xfer);
+	}
+}
+
+void
+msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body,
+					  gsize size)
+{
+	PurpleXfer *xfer = slpcall->xfer;
+
+	purple_xfer_set_completed(xfer, TRUE);
+	purple_xfer_end(xfer);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/msn/xfer.h	Mon Nov 01 07:12:08 2010 +0000
@@ -0,0 +1,12 @@
+
+#include "slpcall.h"
+
+void msn_xfer_init(PurpleXfer *xfer);
+void msn_xfer_cancel(PurpleXfer *xfer);
+
+gssize msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer);
+gssize msn_xfer_read(guchar **data, PurpleXfer *xfer);
+
+void msn_xfer_completed_cb(MsnSlpCall *slpcall,
+						   const guchar *body, gsize size);
+void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session);
--- a/po/POTFILES.in	Mon Nov 01 07:10:36 2010 +0000
+++ b/po/POTFILES.in	Mon Nov 01 07:12:08 2010 +0000
@@ -106,7 +106,6 @@
 libpurple/protocols/jabber/usernick.c
 libpurple/protocols/jabber/xdata.c
 libpurple/protocols/msn/contact.c
-libpurple/protocols/msn/dialog.c
 libpurple/protocols/msn/error.c
 libpurple/protocols/msn/group.h
 libpurple/protocols/msn/msg.c