changeset 30722:7a26ff6c0044

propagate from branch 'im.pidgin.pidgin' (head 73717266fa7d3a35ac09e09781fc5b48e2954fab) to branch 'im.pidgin.cpw.qulogic.msnp16' (head 0e28179a771a31a9afec296749d9f6c1e7f7587c)
author Mark Doliner <mark@kingant.net>
date Thu, 22 Apr 2010 17:15:26 +0000
parents 77b2d015bbf5 (diff) 2140074fa387 (current diff)
children b1bc99c468ee
files libpurple/protocols/msn/dialog.c libpurple/protocols/msn/dialog.h libpurple/protocols/msn/msn.c libpurple/protocols/msn/notification.c libpurple/protocols/msn/slplink.c libpurple/protocols/msn/switchboard.c libpurple/protocols/msn/sync.c libpurple/protocols/msn/sync.h pidgin/eggtrayicon.c pidgin/eggtrayicon.h
diffstat 30 files changed, 1114 insertions(+), 782 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/msn/Makefile.am	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Thu Apr 22 17:15:26 2010 +0000
@@ -12,8 +12,6 @@
 	command.h \
 	contact.c\
 	contact.h\
-	dialog.c \
-	dialog.h \
 	error.c \
 	error.h \
 	group.c \
@@ -54,8 +52,6 @@
 	state.h \
 	switchboard.c \
 	switchboard.h \
-	sync.c \
-	sync.h \
 	table.c \
 	table.h \
 	transaction.c \
--- a/libpurple/protocols/msn/cmdproc.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Thu Apr 22 17:15:26 2010 +0000
@@ -122,7 +122,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 +156,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 +175,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 +259,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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/cmdproc.h	Thu Apr 22 17:15:26 2010 +0000
@@ -51,18 +51,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.h	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/command.h	Thu Apr 22 17:15:26 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/dialog.c	Thu Apr 22 05:18:52 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	Thu Apr 22 05:18:52 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/error.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/error.c	Thu Apr 22 17:15:26 2010 +0000
@@ -24,6 +24,15 @@
 #include "msn.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 +273,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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/error.h	Thu Apr 22 17:15:26 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/msg.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/msg.c	Thu Apr 22 17:15:26 2010 +0000
@@ -36,7 +36,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 +64,8 @@
 	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);
 
 	g_free(msg);
 }
@@ -112,11 +112,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 +133,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;
 
@@ -169,7 +169,7 @@
 		g_return_if_reached();
 	}
 
-	/* Import the header. */
+	/* Extract the binary SLP header */
 	memcpy(&header, tmp, sizeof(header));
 	tmp += sizeof(header);
 
@@ -183,7 +183,7 @@
 	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. */
+	/* Extract the body */
 	body_len = len - (tmp - body);
 	/* msg->body_len = msg->msnslp_header.length; */
 
@@ -211,7 +211,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 +222,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 +240,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 +278,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" */
@@ -306,7 +305,7 @@
 
 		msg->msnslp_message = TRUE;
 
-		/* Import the header. */
+		/* Extract the binary SLP header */
 		memcpy(&header, tmp, sizeof(header));
 		tmp += sizeof(header);
 
@@ -322,7 +321,7 @@
 
 		body_len = payload_len - (tmp - tmp_base) - sizeof(footer);
 
-		/* Import the body. */
+		/* Extract the body */
 		if (body_len > 0) {
 			msg->body_len = body_len;
 			g_free(msg->body);
@@ -332,7 +331,7 @@
 			tmp += body_len;
 		}
 
-		/* Import the footer. */
+		/* Extract the footer */
 		if (body_len >= 0) {
 			memcpy(&footer, tmp, sizeof(footer));
 			tmp += sizeof(footer);
@@ -348,7 +347,7 @@
 			memcpy(msg->body, tmp, msg->body_len);
 			msg->body[msg->body_len] = '\0';
 		}
-		
+
 		if ((!content_type || !strcmp(content_type, "text/plain"))
 			&& msg->charset == NULL) {
 			char *body = g_convert(msg->body, msg->body_len, "UTF-8",
@@ -454,13 +453,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);
@@ -610,15 +609,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 +625,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 +740,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);
 	}
@@ -817,7 +816,6 @@
 {
 	PurpleConnection *gc;
 	const char *body;
-	char *body_str;
 	char *body_enc;
 	char *body_final;
 	size_t body_len;
@@ -827,9 +825,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 +836,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;
 
@@ -914,7 +910,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) {
--- a/libpurple/protocols/msn/msg.h	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/msg.h	Thu Apr 22 17:15:26 2010 +0000
@@ -109,8 +109,8 @@
 	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 +295,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.
--- a/libpurple/protocols/msn/msn.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/msn.c	Thu Apr 22 17:15:26 2010 +0000
@@ -43,7 +43,6 @@
 #include "msg.h"
 #include "switchboard.h"
 #include "notification.h"
-#include "sync.h"
 #include "slplink.h"
 
 #if PHOTO_SUPPORT
@@ -200,6 +199,7 @@
 {
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
+	MsnTransaction *trans;
 	PurpleAccount *account;
 	const char *alias;
 
@@ -227,7 +227,9 @@
 		alias = purple_url_encode(purple_account_get_username(account));
 	}
 
-	msn_cmdproc_send(cmdproc, "PRP", "MFN %s", alias);
+	trans = msn_transaction_new(cmdproc, "PRP", "MFN %s", alias);
+
+	msn_cmdproc_send_trans(cmdproc, trans);
 }
 
 static void
@@ -235,19 +237,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
@@ -364,6 +368,115 @@
 	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
+create_endpoint_fields(gpointer key, gpointer value, gpointer user_data)
+{
+	const char *id = key;
+	MsnUserEndpoint *ep = value;
+	MsnLocationData *data = user_data;
+	PurpleRequestField *field;
+
+	if (g_str_equal(id, data->session->guid))
+		return;
+
+	field = purple_request_field_bool_new(id, ep->name, FALSE);
+	purple_request_field_group_add_field(data->group, field);
+}
+
+static void
+msn_show_locations(PurplePluginAction *action)
+{
+	PurpleConnection *pc;
+	PurpleAccount *account;
+	MsnSession *session;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	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);
+	
+	data = g_new0(MsnLocationData, 1);
+	data->account = account;
+	data->session = session;
+	data->group = group;
+	g_hash_table_foreach(session->user->endpoints, create_endpoint_fields, data);
+
+	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)
 {
@@ -534,6 +647,7 @@
 	PurpleAccount *account;
 	MsnSession *session;
 	MsnCmdProc *cmdproc;
+	MsnTransaction *trans;
 
 	account = purple_connection_get_account(gc);
 	session = gc->proto_data;
@@ -541,9 +655,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
@@ -946,6 +1062,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);
@@ -1080,6 +1201,10 @@
 	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) {
+		purple_account_set_string(account, "endpoint-name", "Pidgin");
+	}
+
 	if (!msn_session_connect(session, host, port, http_method))
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -1290,7 +1415,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);
@@ -1323,7 +1448,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 : "");
@@ -1398,7 +1523,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);
 
@@ -1805,7 +1930,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) {
@@ -1845,6 +1970,7 @@
 msn_keepalive(PurpleConnection *gc)
 {
 	MsnSession *session;
+	MsnTransaction *trans;
 
 	session = gc->proto_data;
 
@@ -1854,7 +1980,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);
 	}
 }
 
@@ -2592,7 +2720,6 @@
 {
 	msn_notification_init();
 	msn_switchboard_init();
-	msn_sync_init();
 
 	return TRUE;
 }
@@ -2601,7 +2728,6 @@
 {
 	msn_notification_end();
 	msn_switchboard_end();
-	msn_sync_end();
 
 	return TRUE;
 }
--- a/libpurple/protocols/msn/msn.h	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/msn.h	Thu Apr 22 17:15:26 2010 +0000
@@ -109,9 +109,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,15 +134,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_act_id(PurpleConnection *gc, const char *entry);
 void msn_send_privacy(PurpleConnection *gc);
--- a/libpurple/protocols/msn/msnutils.h	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/msnutils.h	Thu Apr 22 17:15:26 2010 +0000
@@ -54,7 +54,23 @@
  */
 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);
+
+/**
+ * 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/notification.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/notification.c	Thu Apr 22 17:15:26 2010 +0000
@@ -22,6 +22,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
+#include "core.h"
 #include "notification.h"
 #include "contact.h"
 #include "state.h"
@@ -30,7 +31,6 @@
 #include "page.h"
 
 #include "userlist.h"
-#include "sync.h"
 #include "slplink.h"
 
 static MsnTable *cbs_table;
@@ -92,6 +92,7 @@
 {
 	MsnCmdProc *cmdproc;
 	MsnSession *session;
+	MsnTransaction *trans;
 	PurpleAccount *account;
 	GString *vers;
 	const char *ver_str;
@@ -117,7 +118,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);
 }
@@ -156,22 +158,30 @@
 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
@@ -231,22 +241,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;
+			}
 		}
 	}
 
@@ -257,15 +270,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);
 }
 
 /**************************************************************************
@@ -287,12 +303,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);
 }
@@ -999,7 +1019,8 @@
 	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;
@@ -1020,7 +1041,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
@@ -1030,7 +1055,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,
@@ -1039,17 +1068,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;
@@ -1064,6 +1105,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);
@@ -1202,7 +1244,8 @@
 	PurpleConnection *gc;
 	MsnUser *user;
 	MsnObject *msnobj;
-	unsigned long clientid;
+	unsigned long clientid, extcaps;
+	char *extcap_str;
 	int networkid;
 	const char *state, *passport, *friendly;
 
@@ -1215,7 +1258,10 @@
 	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))
@@ -1233,10 +1279,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);
@@ -1517,6 +1569,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;
@@ -1583,13 +1706,17 @@
 	PurpleAccount *account;
 	MsnUser *user;
 	const char *passport;
+	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) {
 		char *str = g_strndup(payload, len);
 		purple_debug_info("msn", "unknown user %s, payload is %s\n",
@@ -1609,13 +1736,28 @@
 	}
 
 	if (len != 0) {
-		psm_str = msn_get_psm(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(cmd->payload, len);
+		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);
 	}
@@ -1648,6 +1790,158 @@
 	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");
+	caps = g_strdup_printf("%d:%02d", MSN_CLIENT_ID_CAPABILITIES, MSN_CLIENT_ID_EXT_CAPS);
+	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
  **************************************************************************/
@@ -1665,39 +1959,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);
 
 	/*starting retrieve the contact list*/
@@ -2104,6 +2398,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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/notification.h	Thu Apr 22 17:15:26 2010 +0000
@@ -62,6 +62,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);
@@ -83,6 +92,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/oim.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/oim.c	Thu Apr 22 17:15:26 2010 +0000
@@ -618,7 +618,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 +656,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 +671,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 +690,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);
--- a/libpurple/protocols/msn/session.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/session.c	Thu Apr 22 17:15:26 2010 +0000
@@ -21,13 +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 "error.h"
 #include "msn.h"
+#include "msnutils.h"
 #include "session.h"
 #include "notification.h"
 #include "oim.h"
 
-#include "dialog.h"
-
 MsnSession *
 msn_session_new(PurpleAccount *account)
 {
@@ -45,7 +45,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,9 +74,6 @@
 	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);
 
@@ -90,6 +89,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);
@@ -297,7 +297,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);
@@ -448,6 +448,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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/session.h	Thu Apr 22 17:15:26 2010 +0000
@@ -67,7 +67,6 @@
 #include "slpcall.h"
 #include "sslconn.h"
 #include "switchboard.h"
-#include "sync.h"
 #include "user.h"
 #include "userlist.h"
 
@@ -89,7 +88,6 @@
 	MsnNotification *notification;
 	MsnNexus        *nexus;
 	MsnOim          *oim;
-	MsnSync         *sync;
 	MsnUserList     *userlist;
 	char            *abch_cachekey;
 
@@ -119,6 +117,7 @@
 
 	GHashTable *soap_table;
 	guint soap_cleanup_handle;
+	char *guid;
 };
 
 /**
--- a/libpurple/protocols/msn/slp.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/slp.c	Thu Apr 22 17:15:26 2010 +0000
@@ -741,6 +741,7 @@
 
 	if (!strncmp(body, "INVITE", strlen("INVITE")))
 	{
+		/* This is an INVITE request */
 		char *branch;
 		char *call_id;
 		char *content;
@@ -785,6 +786,7 @@
 	}
 	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" */
@@ -834,6 +836,7 @@
 	}
 	else if (!strncmp(body, "BYE", strlen("BYE")))
 	{
+		/* This is a BYE request */
 		char *call_id;
 
 		call_id = get_token(body, "Call-ID: {", "}");
--- a/libpurple/protocols/msn/slplink.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/slplink.c	Thu Apr 22 17:15:26 2010 +0000
@@ -420,7 +420,7 @@
 
 	msg->msnslp_header.total_size = slpmsg->size;
 
-	msn_message_set_attr(msg, "P2P-Dest", slplink->remote_user);
+	msn_message_set_header(msg, "P2P-Dest", slplink->remote_user);
 
 	msg->ack_cb = msg_ack;
 	msg->nak_cb = msg_nak;
@@ -527,7 +527,6 @@
 	const char *data;
 	guint64 offset;
 	gsize len;
-	PurpleXfer *xfer = NULL;
 
 	if (purple_debug_is_verbose())
 		msn_slpmsg_show(msg);
@@ -538,8 +537,11 @@
 
 	if (msg->msnslp_header.total_size < msg->msnslp_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;
 	}
 
 	data = msn_message_get_bin_data(msg, &len);
@@ -556,15 +558,13 @@
 
 		if (slpmsg->session_id)
 		{
-			if (slpmsg->slpcall == NULL)
-				slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
-
+			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;
+					PurpleXfer *xfer = slpmsg->slpcall->xfer;
 					if (xfer != NULL)
 					{
 						slpmsg->ft = TRUE;
@@ -608,10 +608,9 @@
 
 	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);
+		purple_xfer_prpl_ready(slpmsg->slpcall->xfer);
 	}
 	else if (slpmsg->size && slpmsg->buffer)
 	{
--- a/libpurple/protocols/msn/state.c	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/state.c	Thu Apr 22 17:15:26 2010 +0000
@@ -27,6 +27,7 @@
 #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 %d", 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 %d", state_text, caps);
 	}
 	else
 	{
@@ -268,12 +256,17 @@
 
 		msnobj_str = msn_object_to_string(msnobj);
 
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d %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 %d %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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/state.h	Thu Apr 22 17:15:26 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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Thu Apr 22 17:15:26 2010 +0000
@@ -243,6 +243,12 @@
 		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);
 	swboard->current_users++;
 	swboard->empty = FALSE;
@@ -265,7 +271,7 @@
 								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 ||
@@ -505,7 +511,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 : "");
@@ -964,6 +970,7 @@
 	MsnTransaction *trans;
 	MsnCmdProc *cmdproc;
 	PurpleAccount *account;
+	char *username;
 
 	cmdproc = servconn->cmdproc;
 	g_return_if_fail(cmdproc != NULL);
@@ -972,24 +979,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
@@ -1207,8 +1223,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/sync.c	Thu Apr 22 05:18:52 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	Thu Apr 22 05:18:52 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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/table.h	Thu Apr 22 17:15:26 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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/transaction.c	Thu Apr 22 17:15:26 2010 +0000
@@ -37,6 +37,7 @@
 
 	trans->cmdproc = cmdproc;
 	trans->command = g_strdup(command);
+	trans->saveable = TRUE;
 
 	if (format != NULL)
 	{
@@ -96,8 +97,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 +178,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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/transaction.h	Thu Apr 22 17:15:26 2010 +0000
@@ -40,7 +40,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 +76,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	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/user.c	Thu Apr 22 17:15:26 2010 +0000
@@ -25,6 +25,8 @@
 #include "user.h"
 #include "slp.h"
 
+static void free_user_endpoint(MsnUserEndpoint *data);
+
 /*new a user object*/
 MsnUser *
 msn_user_new(MsnUserList *userlist, const char *passport,
@@ -39,6 +41,9 @@
 	msn_user_set_passport(user, passport);
 	msn_user_set_friendly_name(user, friendly_name);
 
+	user->endpoints = g_hash_table_new_full(g_str_hash, g_str_equal,
+	                                        g_free, (GDestroyNotify)free_user_endpoint);
+
 	return user;
 }
 
@@ -48,6 +53,9 @@
 {
 	g_return_if_fail(user != NULL);
 
+	if (user->endpoints != NULL)
+		g_hash_table_destroy(user->endpoints);
+
 	if (user->clientcaps != NULL)
 		g_hash_table_destroy(user->clientcaps);
 
@@ -186,6 +194,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;
@@ -216,6 +227,44 @@
 	user->uid = g_strdup(uid);
 }
 
+static void
+free_user_endpoint(MsnUserEndpoint *data)
+{
+	g_free(data->id);
+	g_free(data->name);
+	g_free(data);
+}
+
+void
+msn_user_set_endpoint_data(MsnUser *user, const char *input, MsnUserEndpoint *data)
+{
+	MsnUserEndpoint *new;
+	char *endpoint;
+
+	g_return_if_fail(user != NULL);
+	g_return_if_fail(input != NULL);
+
+	endpoint = g_ascii_strdown(input, -1);
+
+	if (data == NULL) {
+		g_hash_table_remove(user->endpoints, endpoint);
+		g_free(endpoint);
+		return;
+	}
+
+	new = g_hash_table_lookup(user->endpoints, endpoint);
+	if (!new) {
+		new = g_new0(MsnUserEndpoint, 1);
+		new->id = g_strdup(endpoint);
+		g_hash_table_insert(user->endpoints, g_strdup(endpoint), new);
+	}
+
+	new->clientid = data->clientid;
+	new->extcaps = data->extcaps;
+
+	g_free(endpoint);
+}
+
 void
 msn_user_set_op(MsnUser *user, MsnListOp list_op)
 {
@@ -406,6 +455,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);
@@ -423,7 +480,7 @@
 
 	user->msnobj = obj;
 
-	if (user->list_op & MSN_LIST_FL_OP)
+	if (user != user->userlist->session->user && user->list_op & MSN_LIST_FL_OP)
 		msn_queue_buddy_icon_request(user);
 }
 
@@ -496,6 +553,30 @@
 	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;
+	MsnUserEndpoint *data;
+
+	g_return_val_if_fail(user != NULL, NULL);
+	g_return_val_if_fail(input != NULL, NULL);
+
+	endpoint = g_ascii_strdown(input, -1);
+	data = g_hash_table_lookup(user->endpoints, endpoint);
+	g_free(endpoint);
+
+	return data;
+}
+
 MsnObject *
 msn_user_get_object(const MsnUser *user)
 {
@@ -520,3 +601,22 @@
 	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 = g_hash_table_lookup(user->endpoints, endpoint);
+		if (ep != NULL)
+			return (ep->clientid & capability) == capability
+			    && (ep->extcaps & extcap) == extcap;
+		else
+			return FALSE;
+	}
+
+	return (user->clientid & capability) == capability
+	    && (user->extcaps & extcap) == extcap;
+}
+
+
--- a/libpurple/protocols/msn/user.h	Thu Apr 22 05:18:52 2010 +0000
+++ b/libpurple/protocols/msn/user.h	Thu Apr 22 17:15:26 2010 +0000
@@ -83,6 +83,7 @@
 	char *friendly_name;    /**< The friendly name.             */
 
 	char *uid;              /*< User ID                         */
+	GHashTable *endpoints;  /*< Endpoint-specific data          */
 
 	const char *status;     /**< The state of the user.         */
 	char *statusline;       /**< The state of the user.         */
@@ -102,6 +103,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 +118,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                                                        *
  **************************************************************************/
@@ -252,6 +266,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 +284,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 +378,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 +398,15 @@
 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);
+
+/**
  * Returns the network id for a user.
  *
  * @param user    The user.
@@ -398,11 +450,24 @@
 /**
  * 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);
 
+/**
+ * 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 */