changeset 30013:119bd7b072eb

Initial support for direct connections. Preliminary patch from ticket #247 by GŹ«ˇbor Szuromi. Still needs lots of testing and fixes. References #247. committer: Elliott Sales de Andrade <qulogic@pidgin.im>
author kukkerman@gmail.com
date Wed, 17 Mar 2010 03:45:07 +0000
parents bfaf039aed87
children b1cda3f8fdc9
files COPYRIGHT ChangeLog libpurple/protocols/msn/Makefile.am libpurple/protocols/msn/Makefile.mingw libpurple/protocols/msn/directconn.c libpurple/protocols/msn/directconn.h libpurple/protocols/msn/msg.c libpurple/protocols/msn/slp.c libpurple/protocols/msn/slp.h libpurple/protocols/msn/slpcall.c libpurple/protocols/msn/slpcall.h libpurple/protocols/msn/slplink.c libpurple/protocols/msn/slplink.h libpurple/protocols/msn/switchboard.c
diffstat 14 files changed, 1887 insertions(+), 538 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Tue Mar 16 06:20:05 2010 +0000
+++ b/COPYRIGHT	Wed Mar 17 03:45:07 2010 +0000
@@ -468,6 +468,7 @@
 Marcus Sundberg
 MĂĄrten Svantesson (fursten)
 Amir Szekely (kichik)
+Gábor Szuromi (kukkerman)
 Robert T.
 Greg Taeger
 Rob Taft
--- a/ChangeLog	Tue Mar 16 06:20:05 2010 +0000
+++ b/ChangeLog	Wed Mar 17 03:45:07 2010 +0000
@@ -42,6 +42,8 @@
 	MSN:
 	* Support for version 9 of the MSN protocol has been removed.  This
 	  version is no longer supported on the servers.
+	* Support for direct connections, enabling faster file transfers,
+	  smiley and buddy icon loading.  (Gábor Szuromi)
 
 	XMPP:
 	* Direct messages to a specific resource only upon receipt of a message
--- a/libpurple/protocols/msn/Makefile.am	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Wed Mar 17 03:45:07 2010 +0000
@@ -14,6 +14,8 @@
 	contact.h\
 	dialog.c \
 	dialog.h \
+	directconn.c \
+	directconn.h \
 	error.c \
 	error.h \
 	group.c \
--- a/libpurple/protocols/msn/Makefile.mingw	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.mingw	Wed Mar 17 03:45:07 2010 +0000
@@ -41,6 +41,7 @@
 			command.c \
 			contact.c\
 			dialog.c \
+			directconn.c \
 			error.c \
 			group.c \
 			history.c \
--- a/libpurple/protocols/msn/directconn.c	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/directconn.c	Wed Mar 17 03:45:07 2010 +0000
@@ -27,479 +27,1379 @@
 #include "slp.h"
 #include "slpmsg.h"
 
-/**************************************************************************
- * Directconn Specific
- **************************************************************************/
+#define DC_SESSION_ID_OFFS	0
+#define DC_SEQ_ID_OFFS		4
+#define DC_DATA_OFFSET_OFFS	8
+#define DC_TOTAL_DATA_SIZE_OFFS	16
+#define DC_MESSAGE_LENGTH_OFFS	24
+#define DC_FLAGS_OFFS		28
+#define DC_ACK_ID_OFFS		32
+#define DC_ACK_UID_OFFS		36
+#define DC_ACK_DATA_SIZE_OFFS	40
+#define DC_MESSAGE_BODY_OFFS	48
 
-void
-msn_directconn_send_handshake(MsnDirectConn *directconn)
+#define DC_PACKET_HEADER_SIZE	48
+#define DC_MAX_BODY_SIZE	1352
+#define DC_MAX_PACKET_SIZE 	(DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE)
+
+static void
+msn_dc_generate_nonce(MsnDirectConn *dc)
 {
-	MsnSlpLink *slplink;
-	MsnSlpMessage *slpmsg;
-
-	g_return_if_fail(directconn != NULL);
+	PurpleCipher		*cipher = NULL;
+	PurpleCipherContext	*context = NULL;
+	static guchar		digest[20];
+	int			i;
+	
+	guint32			g1;
+	guint16			g2;
+	guint16			g3;
+	guint64			g4;
 
-	slplink = directconn->slplink;
-
-	slpmsg = msn_slpmsg_new(slplink);
-	slpmsg->flags = 0x100;
+	cipher = purple_ciphers_find_cipher("sha1");
+	g_return_if_fail(cipher != NULL);
+	
+	for (i = 0; i < 16; i++)
+		dc->nonce[i] = rand() & 0xff;
 
-	if (directconn->nonce != NULL)
-	{
-		guint32 t1;
-		guint16 t2;
-		guint16 t3;
-		guint16 t4;
-		guint64 t5;
-
-		sscanf (directconn->nonce, "%08X-%04hX-%04hX-%04hX-%012" G_GINT64_MODIFIER "X", &t1, &t2, &t3, &t4, &t5);
+	context = purple_cipher_context_new(cipher, NULL);
+	purple_cipher_context_append(context, dc->nonce, 16);
+	purple_cipher_context_digest(context, 20, digest, NULL);
+	purple_cipher_context_destroy(context);
 
-		t1 = GUINT32_TO_LE(t1);
-		t2 = GUINT16_TO_LE(t2);
-		t3 = GUINT16_TO_LE(t3);
-		t4 = GUINT16_TO_BE(t4);
-		t5 = GUINT64_TO_BE(t5);
+	g1 = *((guint32*)(digest + 0));
+	g1 = GUINT32_FROM_LE(g1);
+	
+	g2 = *((guint16*)(digest + 4));
+	g2 = GUINT16_FROM_LE(g2);
+	
+	g3 = *((guint16*)(digest + 6));
+	g3 = GUINT32_FROM_LE(g3);
+	
+	g4 = *((guint64*)(digest + 8));
+	g4 = GUINT64_FROM_BE(g4);
+	
+	g_sprintf(
+		dc->nonce_hash,
+		"%08X-%04X-%04X-%04X-%08X%04X",
+		g1,
+		g2,
+		g3,
+		(guint16)(g4 >> 48),
+		(guint32)((g4 >> 16) & 0xffffffff),
+		(guint16)(g4 & 0xffff)
+	);
+}
 
-		slpmsg->ack_id     = t1;
-		slpmsg->ack_sub_id = t2 | (t3 << 16);
-		slpmsg->ack_size   = t4 | t5;
-	}
+static MsnDirectConnPacket*
+msn_dc_new_packet()
+{
+	MsnDirectConnPacket	*p;
 
-	g_free(directconn->nonce);
+	p = g_new0(MsnDirectConnPacket, 1);
+	p->data = NULL;
+	p->sent_cb = NULL;
+	p->msg = NULL;
 
-	msn_slplink_send_slpmsg(slplink, slpmsg);
-
-	directconn->acked =TRUE;
+	return p;
 }
 
-/**************************************************************************
- * Connection Functions
- **************************************************************************/
+static void
+msn_dc_destroy_packet(MsnDirectConnPacket *p)
+{
+	if (p->data)
+		g_free(p->data);
+	
+	if (p->msg)
+		msn_message_unref(p->msg);
 
-static int
-create_listener(int port)
-{
-	int fd;
-	int flags;
-	const int on = 1;
+	g_free(p);
+}
 
-#if 0
-	struct addrinfo hints;
-	struct addrinfo *c, *res;
-	char port_str[5];
+MsnDirectConn*
+msn_dc_new(MsnSlpCall *slpcall)
+{
+	MsnDirectConn	*dc;
+
+	purple_debug_info("msn", "msn_dc_new\n");
 
-	snprintf(port_str, sizeof(port_str), "%d", port);
-
-	memset(&hints, 0, sizeof(hints));
+	g_return_val_if_fail(slpcall != NULL, NULL);
+	
+	dc = g_new0(MsnDirectConn, 1);
 
-	hints.ai_flags = AI_PASSIVE;
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
+	dc->slplink = slpcall->slplink;
+	dc->slpcall = slpcall;
+
+	if (dc->slplink->dc != NULL)
+		purple_debug_warning("msn", "msn_dc_new: slplink already has an allocated DC!\n");
+
+	dc->slplink->dc = dc;
 
-	if (getaddrinfo(NULL, port_str, &hints, &res) != 0)
-	{
-		purple_debug_error("msn", "Could not get address info: %s.\n",
-						 port_str);
-		return -1;
-	}
+	dc->msg_body = NULL;
+	dc->prev_ack = NULL;
+	dc->listen_data = NULL;
+	dc->connect_data = NULL;
+	dc->listenfd = -1;
+	dc->listenfd_handle = 0;
+	dc->connect_timeout_handle = 0;
+	dc->fd = -1;
+	dc->recv_handle = 0;
+	dc->send_handle = 0;
+	dc->state = DC_STATE_CLOSED;
+	dc->in_buffer = NULL;
+	dc->out_queue = g_queue_new();
+	dc->msg_pos = 0;
+	dc->send_connection_info_msg_cb = NULL;
+	dc->ext_ip = NULL;
+	dc->timeout_handle = 0;
+	dc->progress = FALSE;
+	//dc->num_calls = 1;
 
-	for (c = res; c != NULL; c = c->ai_next)
-	{
-		fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol);
+	msn_dc_generate_nonce(dc);
+
+	return dc;
+}
 
-		if (fd < 0)
-			continue;
+void
+msn_dc_destroy(MsnDirectConn *dc)
+{
+	MsnSlpLink	*slplink;
+	
+	purple_debug_info("msn", "msn_dc_destroy\n");
+	
+	g_return_if_fail(dc != NULL);
 
-		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
-
-		if (bind(fd, c->ai_addr, c->ai_addrlen) == 0)
-			break;
+	slplink = dc->slplink;
 
-		close(fd);
-	}
+	if (dc->slpcall != NULL)
+		dc->slpcall->wait_for_socket = FALSE;
+	
+	slplink->dc = NULL;
+
+	if (slplink->swboard == NULL)
+		msn_slplink_destroy(slplink);
 
-	if (c == NULL)
-	{
-		purple_debug_error("msn", "Could not find socket: %s.\n", port_str);
-		return -1;
+	if (dc->msg_body != NULL) {
+		g_free(dc->msg_body);
+		dc->msg_body = NULL;
+	}
+	
+	if (dc->prev_ack) {
+		msn_slpmsg_destroy(dc->prev_ack);
+		dc->prev_ack = NULL;
 	}
 
-	freeaddrinfo(res);
-#else
-	struct sockaddr_in sockin;
-
-	fd = socket(AF_INET, SOCK_STREAM, 0);
-
-	if (fd < 0)
-		return -1;
-
-	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0)
-	{
-		close(fd);
-		return -1;
+	if (dc->listen_data != NULL) {
+		purple_network_listen_cancel(dc->listen_data);
+		dc->listen_data = NULL;
+	}
+	
+	if (dc->connect_data != NULL) {
+		purple_proxy_connect_cancel(dc->connect_data);
+		dc->connect_data = NULL;
+	}
+	
+	if (dc->listenfd != -1) {
+		purple_network_remove_port_mapping(dc->listenfd);
+		close(dc->listenfd);
+		dc->listenfd = -1;
+	}
+	
+	if (dc->listenfd_handle != 0) {
+		purple_timeout_remove(dc->listenfd_handle);
+		dc->listenfd_handle = 0;
+	}
+	
+	if (dc->connect_timeout_handle != 0) {
+		purple_timeout_remove(dc->connect_timeout_handle);
+		dc->connect_timeout_handle = 0;
+	}
+	
+	if (dc->fd != -1) {
+		close(dc->fd);
+		dc->fd = -1;
+	}
+	
+	if (dc->send_handle != 0) {
+		purple_input_remove(dc->send_handle);
+		dc->send_handle = 0;
+	}
+	
+	if (dc->recv_handle != 0) {
+		purple_input_remove(dc->recv_handle);
+		dc->recv_handle = 0;
 	}
 
-	memset(&sockin, 0, sizeof(struct sockaddr_in));
-	sockin.sin_family = AF_INET;
-	sockin.sin_port = htons(port);
+	if (dc->in_buffer != NULL) {
+		g_free(dc->in_buffer);
+		dc->in_buffer = NULL;
+	}
+	
+	if (dc->out_queue != NULL) {
+		while (!g_queue_is_empty(dc->out_queue))
+			msn_dc_destroy_packet( g_queue_pop_head(dc->out_queue) );
 
-	if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0)
-	{
-		close(fd);
-		return -1;
-	}
-#endif
-
-	if (listen (fd, 4) != 0)
-	{
-		close (fd);
-		return -1;
+		g_queue_free(dc->out_queue);
 	}
 
-	flags = fcntl(fd, F_GETFL);
-	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-#ifndef _WIN32
-	fcntl(fd, F_SETFD, FD_CLOEXEC);
-#endif
+	if (dc->ext_ip != NULL) {
+		g_free(dc->ext_ip);
+		dc->ext_ip = NULL;
+	}
 
-	return fd;
+	if (dc->timeout_handle != 0) {
+		purple_timeout_remove(dc->timeout_handle);
+		dc->timeout_handle = 0;
+	}
+
+	g_free(dc);
 }
 
-static gssize
-msn_directconn_write(MsnDirectConn *directconn,
-					 const char *data, size_t len)
+/*
+void
+msn_dc_ref(MsnDirectConn *dc)
 {
-	char *buffer, *tmp;
-	size_t buf_size;
-	gssize ret;
-	guint32 sent_len;
-
-	g_return_val_if_fail(directconn != NULL, 0);
-
-	buf_size = len + 4;
-	buffer = tmp = g_malloc(buf_size);
-
-	sent_len = GUINT32_TO_LE(len);
+	g_return_if_fail(dc != NULL);
 
-	memcpy(tmp, &sent_len, 4);
-	tmp += 4;
-	memcpy(tmp, data, len);
-	tmp += len;
-
-	ret = write(directconn->fd, buffer, buf_size);
-
-#ifdef DEBUG_DC
-	char *str;
-	str = g_strdup_printf("%s/msntest/w%.4d.bin", g_get_home_dir(), directconn->c);
-
-	FILE *tf = g_fopen(str, "w");
-	fwrite(buffer, 1, buf_size, tf);
-	fclose(tf);
-
-	g_free(str);
-#endif
-
-	g_free(buffer);
-
-	directconn->c++;
-
-	return ret;
+	dc->num_calls++;
 }
 
-#if 0
 void
-msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce)
+msn_dc_unref(MsnDirectConn *dc)
 {
-	guint32 t1;
-	guint16 t2;
-	guint16 t3;
-	guint16 t4;
-	guint64 t5;
-
-	g_return_if_fail(directconn != NULL);
-	g_return_if_fail(nonce      != NULL);
+	g_return_if_fail(dc != NULL);
 
-	sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &t1, &t2, &t3, &t4, &t5);
-
-	t1 = GUINT32_TO_LE(t1);
-	t2 = GUINT16_TO_LE(t2);
-	t3 = GUINT16_TO_LE(t3);
-	t4 = GUINT16_TO_BE(t4);
-	t5 = GUINT64_TO_BE(t5);
-
-	directconn->slpheader = g_new0(MsnSlpHeader, 1);
-
-	directconn->slpheader->ack_id     = t1;
-	directconn->slpheader->ack_sub_id = t2 | (t3 << 16);
-	directconn->slpheader->ack_size   = t4 | t5;
+	
+	if (dc->num_calls > 0) {
+		dc->num_calls--;
+	}
 }
-#endif
+*/
 
 void
-msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg)
+msn_dc_send_invite(MsnDirectConn *dc)
 {
-	char *body;
-	size_t body_len;
+	MsnSlpCall	*slpcall;
+	MsnSlpMessage	*msg;
+	gchar		*header;
+	
+	purple_debug_info("msn", "msn_dc_send_invite\n");
+
+	g_return_if_fail(dc != NULL);
+
+	slpcall = dc->slpcall;
+	g_return_if_fail(slpcall != NULL);
 
-	body = msn_message_gen_slp_body(msg, &body_len);
+	header = g_strdup_printf(
+		"INVITE MSNMSGR:%s MSNSLP/1.0",
+		slpcall->slplink->remote_user
+	);
+	
+	msg = msn_slpmsg_sip_new(
+		slpcall,
+		0,
+		header,
+		slpcall->branch,
+		"application/x-msnmsgr-transrespbody",
+		dc->msg_body
+	);
+	g_free(header);
+	g_free(dc->msg_body);
+	dc->msg_body = NULL;
 
-	msn_directconn_write(directconn, body, body_len);
+	msn_slplink_queue_slpmsg(slpcall->slplink, msg);
+}
+
+void
+msn_dc_send_ok(MsnDirectConn *dc)
+{
+	purple_debug_info("msn", "msn_dc_send_ok\n");
+	
+	g_return_if_fail(dc != NULL);
+	
+	msn_slp_send_ok(dc->slpcall, dc->slpcall->branch,
+		"application/x-msnmsgr-transrespbody", dc->msg_body);
+	g_free(dc->msg_body);
+	dc->msg_body = NULL;
+
+	msn_slplink_send_slpmsg(dc->slpcall->slplink, dc->prev_ack);
+	msn_slpmsg_destroy(dc->prev_ack);
+	dc->prev_ack = NULL;
+	msn_slplink_send_queued_slpmsgs(dc->slpcall->slplink);
 }
 
 static void
-read_cb(gpointer data, gint source, PurpleInputCondition cond)
+msn_dc_fallback_to_p2p(MsnDirectConn *dc)
 {
-	MsnDirectConn* directconn;
-	char *body;
-	size_t body_len;
-	gssize len;
+	MsnSlpCall	*slpcall;
+	PurpleXfer	*xfer;
+	
+	purple_debug_info("msn", "msn_dc_try_fallback_to_p2p\n");
+	
+	g_return_if_fail(dc != NULL);
+
+	slpcall = dc->slpcall;
+	g_return_if_fail(slpcall != NULL);
+	
+	xfer = slpcall->xfer;
+	g_return_if_fail(xfer != NULL);
+		
+	msn_dc_destroy(dc);
+
+	msn_slpcall_session_init(slpcall);
+
+	/*
+	switch (purple_xfer_get_status(xfer)) {
+	case PURPLE_XFER_STATUS_NOT_STARTED:
+	case PURPLE_XFER_STATUS_ACCEPTED:
+		msn_slpcall_session_init(slpcall);
+		break;
 
-	purple_debug_info("msn", "read_cb: %d, %d\n", source, cond);
+	case PURPLE_XFER_STATUS_STARTED:
+		slpcall->session_init_cb = NULL;
+		slpcall->end_cb = NULL;
+		slpcall->progress_cb = NULL;
+		slpcall->cb = NULL;
+	
+		if (fail_local)
+			purple_xfer_cancel_local(xfer);
+		else
+			purple_xfer_cancel_remote(xfer);
+		break;
 
-	directconn = data;
+	default:
+		slpcall->session_init_cb = NULL;
+		slpcall->end_cb = NULL;
+		slpcall->progress_cb = NULL;
+		slpcall->cb = NULL;
+		
+		if (fail_local)
+			purple_xfer_cancel_local(xfer);
+		else
+			purple_xfer_cancel_remote(xfer);
+
+		break;
+	}
+	*/
+}
+
+static void
+msn_dc_parse_binary_header(MsnDirectConn *dc)
+{
+	MsnSlpHeader		*h;
+	gchar			*buffer;
+
+	g_return_if_fail(dc != NULL);
+
+	h = &dc->header;
+	/* Skip packet size */
+	buffer = dc->in_buffer + 4;
 
-	/* Let's read the length of the data. */
-#error This code is broken.  See the note below.
-	/*
-	 * TODO: This has problems!  First of all, sizeof(body_len) will be
-	 *       different on 32bit systems and on 64bit systems (4 bytes
-	 *       vs. 8 bytes).
-	 *       Secondly, we're reading from a TCP stream.  There is no
-	 *       guarantee that we have received the number of bytes we're
-	 *       trying to read.  We need to read into a buffer.  If read
-	 *       returns <0 then we need to check errno.  If errno is EAGAIN
-	 *       then don't destroy anything, just exit and wait for more
-	 *       data.  See every other function in libpurple that does this
-	 *       correctly for an example.
-	 */
-	len = read(directconn->fd, &body_len, sizeof(body_len));
+	memcpy(&h->session_id, buffer + DC_SESSION_ID_OFFS, sizeof(h->session_id));
+	h->session_id = GUINT32_FROM_LE(h->session_id);
+	
+	memcpy(&h->id, buffer + DC_SEQ_ID_OFFS, sizeof(h->id));
+	h->id = GUINT32_FROM_LE(h->id);
+
+	memcpy(&h->offset, buffer + DC_DATA_OFFSET_OFFS, sizeof(h->offset));
+	h->offset = GUINT64_FROM_LE(h->offset);
+
+	memcpy(&h->total_size, buffer + DC_TOTAL_DATA_SIZE_OFFS, sizeof(h->total_size));
+	h->total_size = GUINT64_FROM_LE(h->total_size);
+
+	memcpy(&h->length, buffer + DC_MESSAGE_LENGTH_OFFS, sizeof(h->length));
+	h->length = GUINT32_FROM_LE(h->length);
+
+	memcpy(&h->flags, buffer + DC_FLAGS_OFFS, sizeof(h->flags));
+	h->flags = GUINT32_FROM_LE(h->flags);
+
+	memcpy(&h->ack_id, buffer + DC_ACK_ID_OFFS, sizeof(h->ack_id));
+	h->ack_id = GUINT32_FROM_LE(h->ack_id);
+
+	memcpy(&h->ack_sub_id, buffer + DC_ACK_UID_OFFS, sizeof(h->ack_sub_id));
+	h->ack_sub_id = GUINT32_FROM_LE(h->ack_sub_id);
+
+	memcpy(&h->ack_size, buffer + DC_ACK_DATA_SIZE_OFFS, sizeof(h->ack_size));
+	h->ack_size = GUINT64_FROM_LE(h->ack_size);
+}
+
+static gchar*
+msn_dc_serialize_binary_header(MsnDirectConn *dc) {
+	static MsnSlpHeader		h;
+	static gchar			bin_header[DC_PACKET_HEADER_SIZE];
+	
+	g_return_val_if_fail(dc != NULL, NULL);
+
+	memcpy(&h, &dc->header, sizeof(h));
+
+	h.session_id = GUINT32_TO_LE(h.session_id);
+	memcpy(bin_header + DC_SESSION_ID_OFFS, &h.session_id, sizeof(h.session_id));
+	
+	h.id = GUINT32_TO_LE(h.id);
+	memcpy(bin_header + DC_SEQ_ID_OFFS, &h.id, sizeof(h.id));
+
+	h.offset = GUINT64_TO_LE(h.offset);
+	memcpy(bin_header + DC_DATA_OFFSET_OFFS, &h.offset, sizeof(h.offset));
+
+	h.total_size = GUINT64_TO_LE(h.total_size);
+	memcpy(bin_header + DC_TOTAL_DATA_SIZE_OFFS, &h.total_size, sizeof(h.total_size));
+
+	h.length = GUINT32_TO_LE(h.length);
+	memcpy(bin_header + DC_MESSAGE_LENGTH_OFFS, &h.length, sizeof(h.length));
+
+	h.flags = GUINT32_TO_LE(h.flags);
+	memcpy(bin_header + DC_FLAGS_OFFS, &h.flags, sizeof(h.flags));
+
+	h.ack_id = GUINT32_TO_LE(h.ack_id);
+	memcpy(bin_header + DC_ACK_ID_OFFS, &h.ack_id, sizeof(h.ack_id));
+
+	h.ack_sub_id = GUINT32_TO_LE(h.ack_sub_id);
+	memcpy(bin_header + DC_ACK_UID_OFFS, &h.ack_sub_id, sizeof(h.ack_sub_id));
+
+	h.ack_size = GUINT64_TO_LE(h.ack_size);
+	memcpy(bin_header + DC_ACK_DATA_SIZE_OFFS, &h.ack_size, sizeof(h.ack_size));
+
+	return bin_header;
+}
 
-	if (len <= 0)
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
+/*
+static void
+msn_dc_send_bye(MsnDirectConn *dc)
+{
+	MsnSlpLink	*slplink;
+	PurpleAccount	*account;
+	char		*body;
+	int		body_len;
+	
+	purple_debug_info("msn", "msn_dc_send_bye\n");
+	
+	g_return_if_fail(dc != NULL);
+	g_return_if_fail(dc->slpcall != NULL);
+
+	slplink = dc->slpcall->slplink;
+	account = slplink->session->account;
+	
+	dc->header.session_id = 0;
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.offset = 0; 
+	
+	body = g_strdup_printf(
+		"BYE MSNMSGR:%s MSNSLP/1.0\r\n"
+		"To: <msnmsgr:%s>\r\n"
+		"From: <msnmsgr:%s>\r\n"
+		"Via: MSNSLP/1.0/TLP ;branch={%s}\r\n"
+		"CSeq: 0\r\n"
+		"Call-ID: {%s}\r\n"
+		"Max-Forwards: 0\r\n"
+		"Content-Type: application/x-msnmsgr-sessionclosebody\r\n"
+		"Content-Length: 3\r\n"
+		"\r\n\r\n",
 
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
+		slplink->remote_user,
+		slplink->remote_user,
+		purple_account_get_username(account),
+		dc->slpcall->branch,
+		dc->slpcall->id
+	);
+	body_len = strlen(body) + 1;
+	memcpy(dc->buffer, body, body_len);
+	g_free(body);
 
-		close(directconn->fd);
+	dc->header.total_size = body_len;
+	dc->header.length = body_len;
+	dc->header.flags = 0;
+	dc->header.ack_sub_id = 0;
+	dc->header.ack_size = 0;
 
-		msn_directconn_destroy(directconn);
+	msn_dc_send_packet(dc);
+}
 
-		return;
-	}
+static void
+msn_dc_send_ack(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+	
+	dc->header.session_id = 0;
+	dc->header.ack_sub_id = dc->header.ack_id;
+	dc->header.ack_id = dc->header.id;
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.offset = 0;
+	dc->header.length = 0;
+	dc->header.flags = 0x02;
+	dc->header.ack_size = dc->header.total_size;
+
+	msn_dc_send_packet(dc);
+}
 
-	body_len = GUINT32_FROM_LE(body_len);
+static void
+msn_dc_send_data_ack(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+	
+	dc->header.session_id = dc->slpcall->session_id;
+	dc->header.ack_sub_id = dc->header.ack_id;
+	dc->header.ack_id = dc->header.id;
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.offset = 0;
+	dc->header.length = 0;
+	dc->header.flags = 0x02;
+	dc->header.ack_size = dc->header.total_size;
+
+	msn_dc_send_packet(dc);
+}
 
-	purple_debug_info("msn", "body_len=%" G_GSIZE_FORMAT "\n", body_len);
+static void
+msn_dc_xfer_send_cancel(PurpleXfer *xfer)
+{
+	MsnSlpCall	*slpcall;
+	MsnDirectConn	*dc;
+	
+	purple_debug_info("msn", "msn_dc_xfer_send_cancel\n");
+	
+	g_return_if_fail(xfer != NULL);
+
+	slpcall = xfer->data;
+	g_return_if_fail(slpcall != NULL);
+
+	dc = slpcall->dc;
+	g_return_if_fail(dc != NULL);
+	
+	switch (dc->state) {
+	case DC_STATE_TRANSFER:
+		msn_dc_send_bye(dc);
+		dc->state = DC_STATE_CANCELLED;
+		break;
+
+	default:
+		msn_dc_destroy(dc);
+		break;
+	}
+}
 
-	if (body_len <= 0)
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
+static void
+msn_dc_xfer_recv_cancel(PurpleXfer *xfer)
+{
+	MsnSlpCall	*slpcall;
+	MsnDirectConn	*dc;
+	
+	purple_debug_info("msn", "msn_dc_xfer_recv_cancel\n");
+
+	g_return_if_fail(xfer != NULL);
+
+	slpcall = xfer->data;
+	g_return_if_fail(slpcall != NULL);
+
+	dc = slpcall->dc;
+	g_return_if_fail(dc != NULL);
+
+	switch (dc->state) {
+	case DC_STATE_TRANSFER:
+		msn_dc_send_bye(dc);
+		dc->state = DC_STATE_CANCELLED;
+		break;
 
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
+	default:
+		msn_dc_destroy(dc);
+		break;
+	}
+}
+*/
 
-		close(directconn->fd);
+static void
+msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond)
+{
+	MsnDirectConn		*dc = data;
+	MsnDirectConnPacket	*p;
+	int			bytes_to_send;
+	int			bytes_sent;
 
-		msn_directconn_destroy(directconn);
-
+	g_return_if_fail(dc != NULL);
+	g_return_if_fail(fd != -1);
+	
+	if(g_queue_is_empty(dc->out_queue)) {
+		if (dc->send_handle != 0) {
+			purple_input_remove(dc->send_handle);
+			dc->send_handle = 0;
+		}
 		return;
 	}
 
-	body = g_try_malloc(body_len);
-
-	if (body != NULL)
-	{
-		/* Let's read the data. */
-		len = read(directconn->fd, body, body_len);
+	p = g_queue_peek_head(dc->out_queue);
+	bytes_to_send = p->length - dc->msg_pos;
 
-		purple_debug_info("msn", "len=%" G_GSIZE_FORMAT "\n", len);
-	}
-	else
-	{
-		purple_debug_error("msn", "Failed to allocate memory for read\n");
-		len = 0;
+	bytes_sent = send(fd, p->data, bytes_to_send, 0);
+	if (bytes_sent < 0) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return;
+
+		purple_debug_warning("msn", "msn_dc_send_cb: send error\n");
+		msn_dc_destroy(dc);
+		return;
 	}
 
-	if (len > 0)
-	{
-		MsnMessage *msg;
-
-#ifdef DEBUG_DC
-		str = g_strdup_printf("%s/msntest/r%.4d.bin", g_get_home_dir(), directconn->c);
-
-		FILE *tf = g_fopen(str, "w");
-		fwrite(body, 1, len, tf);
-		fclose(tf);
-
-		g_free(str);
-#endif
-
-		directconn->c++;
+	dc->progress = TRUE;
 
-		msg = msn_message_new_msnslp();
-		msn_message_parse_slp_body(msg, body, body_len);
+	dc->msg_pos += bytes_sent;
+	if (dc->msg_pos == p->length) {
+		if (p->sent_cb != NULL)
+			p->sent_cb(p);
 
-		purple_debug_info("msn", "directconn: process_msg\n");
-		msn_slplink_process_msg(directconn->slplink, msg);
+		g_queue_pop_head(dc->out_queue);
+		msn_dc_destroy_packet(p);
+		
+		dc->msg_pos = 0;
 	}
-	else
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
-
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
-
-		close(directconn->fd);
-
-		msn_directconn_destroy(directconn);
-	}
-
-	g_free(body);
 }
 
 static void
-connect_cb(gpointer data, gint source, PurpleInputCondition cond)
+msn_dc_enqueue_packet(MsnDirectConn *dc, MsnDirectConnPacket *p)
 {
-	MsnDirectConn* directconn;
-	int fd;
-
-	purple_debug_misc("msn", "directconn: connect_cb: %d, %d.\n", source, cond);
-
-	directconn = data;
-	directconn->connect_data = NULL;
-
-	if (TRUE)
-	{
-		fd = source;
-	}
-	else
-	{
-		struct sockaddr_in client_addr;
-		socklen_t client;
-		fd = accept (source, (struct sockaddr *)&client_addr, &client);
-	}
-
-	directconn->fd = fd;
+	gboolean			was_empty;
+	
+	was_empty = g_queue_is_empty(dc->out_queue);
+	g_queue_push_tail(dc->out_queue, p);
 
-	if (fd > 0)
-	{
-		directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, read_cb,
-										  directconn);
-
-		if (TRUE)
-		{
-			/* Send foo. */
-			msn_directconn_write(directconn, "foo", strlen("foo") + 1);
-
-			/* Send Handshake */
-			msn_directconn_send_handshake(directconn);
-		}
-		else
-		{
-		}
-	}
-	else
-	{
-		/* ERROR */
-		purple_debug_error("msn", "could not add input\n");
-
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
-
-		close(directconn->fd);
+	if (was_empty && dc->send_handle == 0) {
+		dc->send_handle = purple_input_add(dc->fd, PURPLE_INPUT_WRITE, msn_dc_send_cb, dc);
+		msn_dc_send_cb(dc, dc->fd, PURPLE_INPUT_WRITE);
 	}
 }
 
 static void
-directconn_connect_cb(gpointer data, gint source, const gchar *error_message)
+msn_dc_send_foo(MsnDirectConn *dc)
 {
-	if (error_message)
-		purple_debug_error("msn", "Error making direct connection: %s\n", error_message);
+	MsnDirectConnPacket	*p;
+	
+	purple_debug_info("msn", "msn_dc_send_foo\n");
+	
+	g_return_if_fail(dc != NULL);
 
-	connect_cb(data, source, PURPLE_INPUT_READ);
+	p = msn_dc_new_packet();
+
+	p->length = 8;
+	p->data = (guchar*)g_strdup("\4\0\0\0foo");
+	p->sent_cb = NULL;
+
+	msn_dc_enqueue_packet(dc, p);
 }
 
-gboolean
-msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port)
+static void
+msn_dc_send_handshake(MsnDirectConn *dc)
 {
-	MsnSession *session;
+	MsnDirectConnPacket	*p;
+	gchar			*h;
+	guint32			l;
+
+	g_return_if_fail(dc != NULL);
+
+	p = msn_dc_new_packet();
+
+	p->length = 4 + DC_PACKET_HEADER_SIZE;
+	p->data = g_malloc(p->length);
 
-	g_return_val_if_fail(directconn != NULL, FALSE);
-	g_return_val_if_fail(host       != NULL, TRUE);
-	g_return_val_if_fail(port        > 0,    FALSE);
+	l = DC_PACKET_HEADER_SIZE;
+	l = GUINT32_TO_LE(l);
+	memcpy(p->data, &l, 4);
+	
+	dc->header.session_id = 0;
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.offset = 0;
+	dc->header.total_size = 0;
+	dc->header.length = 0;
+	dc->header.flags = 0x100;
 
-	session = directconn->slplink->session;
+	h = msn_dc_serialize_binary_header(dc);
+	memcpy(p->data + 4, h, DC_PACKET_HEADER_SIZE);
+	memcpy(p->data + 4 + DC_ACK_ID_OFFS, dc->nonce, 16);
+
+	msn_dc_enqueue_packet(dc, p);
+}
 
-#if 0
-	if (session->http_method)
-	{
-		servconn->http_data->gateway_host = g_strdup(host);
-	}
-#endif
+static void
+msn_dc_send_handshake_reply(MsnDirectConn *dc)
+{	
+	MsnDirectConnPacket	*p;
+	gchar			*h;
+	guint32			l;
+
+	g_return_if_fail(dc != NULL);
+
+	p = msn_dc_new_packet();
+
+	p->length = 4 + DC_PACKET_HEADER_SIZE;
+	p->data = g_malloc(p->length);
 
-	directconn->connect_data = purple_proxy_connect(NULL, session->account,
-			host, port, directconn_connect_cb, directconn);
+	l = DC_PACKET_HEADER_SIZE;
+	l = GUINT32_TO_LE(l);
+	memcpy(p->data, &l, 4);
+	
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.length = 0;
 
-	return (directconn->connect_data != NULL);
+	h = msn_dc_serialize_binary_header(dc);
+	memcpy(p->data + 4, h, DC_PACKET_HEADER_SIZE);
+	memcpy(p->data + 4 + DC_ACK_ID_OFFS, dc->nonce, 16);
+
+	msn_dc_enqueue_packet(dc, p);
+}
+
+static void
+msn_dc_send_packet_cb(MsnDirectConnPacket *p)
+{
+	if (p->msg != NULL && p->msg->ack_cb != NULL)
+		p->msg->ack_cb(p->msg, p->msg->ack_data);
 }
 
 void
-msn_directconn_listen(MsnDirectConn *directconn)
+msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg)
+{
+	MsnDirectConnPacket		*p = msn_dc_new_packet();
+	guint32				length = msg->body_len + DC_PACKET_HEADER_SIZE;
+
+	p->length = 4 + length;
+	p->data = g_malloc(p->length);
+
+	length = GUINT32_TO_LE(length);
+	memcpy(p->data, &length, 4);
+	memcpy(p->data + 4, &msg->msnslp_header, DC_PACKET_HEADER_SIZE);
+	memcpy(p->data + 4 + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len);
+
+	p->sent_cb = msn_dc_send_packet_cb;
+	p->msg = msg;
+	msn_message_ref(msg);
+
+	msn_dc_enqueue_packet(dc, p);
+}
+
+static int
+msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length)
 {
-	int port;
-	int fd;
+	g_return_val_if_fail(dc != NULL, DC_PROCESS_ERROR);
+
+	switch (dc->state) {
+	case DC_STATE_CLOSED:
+		break;
+
+	case DC_STATE_FOO: {
+		/* FOO message is always 4 bytes long */
+		if (packet_length != 4 || memcmp(dc->in_buffer, "\4\0\0\0foo", 8) != 0)
+			return DC_PROCESS_FALLBACK;
+
+		dc->state = DC_STATE_HANDSHAKE;
+		break;
+		}
+
+	case DC_STATE_HANDSHAKE: {
+		if (packet_length != DC_PACKET_HEADER_SIZE)
+			return DC_PROCESS_FALLBACK;
+
+		/* TODO: Check! */
+		msn_dc_send_handshake_reply(dc);
+		dc->state = DC_STATE_ESTABILISHED;
 
-	port = 7000;
+		msn_slpcall_session_init(dc->slpcall);
+		dc->slpcall = NULL;
+		break;
+		}
+	
+	case DC_STATE_HANDSHAKE_REPLY:
+		/* TODO: Check! */
+		dc->state = DC_STATE_ESTABILISHED;
+		
+		msn_slpcall_session_init(dc->slpcall);
+		dc->slpcall = NULL;
+		break;
+
+	case DC_STATE_ESTABILISHED: 
+		msn_slplink_process_msg(
+			dc->slplink,
+			&dc->header,
+			dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE,
+			dc->header.length
+		);
+
+		/*
+		if (dc->num_calls == 0) {
+			msn_dc_destroy(dc);
+
+			return DC_PROCESS_CLOSE;
+		}
+		*/
+		break;
+#if 0
+		{
+		guint64			file_size;
+		int			bytes_written;
+		PurpleXfer		*xfer;
+		MsnSlpHeader		*h = &dc->header;
+
+		if (packet_length < DC_PACKET_HEADER_SIZE)
+			return DC_TRANSFER_FALLBACK;
 
-	for (fd = -1; fd < 0;)
-		fd = create_listener(++port);
+		/* 
+		 * TODO: MSN Messenger 7.0 sends BYE with flags 0x0000000 so we'll get rid of
+		 * 0x1000000 bit but file data is always sent with flags 0x1000030 in both
+		 * MSN Messenger and Live.*/
+		switch (h->flags) {
+		case 0x0000000:
+		case 0x1000000:
+			msn_dc_send_ack(dc);
+			if (strncmp(dc->buffer, "BYE", 3) == 0) {
+				/* Remote side cancelled the transfer. */
+				purple_xfer_cancel_remote(dc->slpcall->xfer);
+				return DC_TRANSFER_CANCELLED;
+			}
+			break;
+
+		case 0x1000030:
+			/* File data */
+			xfer = dc->slpcall->xfer;
+			file_size = purple_xfer_get_size(xfer);
 
-	directconn->fd = fd;
+			/* Packet sanity checks */
+			if (	h->session_id != dc->slpcall->session_id || 
+				h->offset >= file_size ||
+				h->total_size != file_size ||
+				h->length != packet_length - DC_PACKET_HEADER_SIZE ||
+				h->offset + h->length > file_size) {
+
+				purple_debug_warning("msn", "msn_dc_recv_process_packet_cb: packet range check error!\n");
+				purple_xfer_cancel_local(dc->slpcall->xfer);
+				return DC_TRANSFER_CANCELLED;
+			}
+
+			bytes_written = fwrite(dc->buffer, 1, h->length, xfer->dest_fp);
+			if (bytes_written != h->length) {
+				purple_debug_warning("msn", "msn_dc_recv_process_packet_cb: cannot write whole packet to file!\n");
+				purple_xfer_cancel_local(dc->slpcall->xfer);
+				return DC_TRANSFER_CANCELLED;
+			}
+
+			xfer->bytes_sent = (h->offset + h->length);
+			xfer->bytes_remaining = h->total_size - xfer->bytes_sent;
+		
+			purple_xfer_update_progress(xfer);
 
-	directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb,
-		directconn);
+			if (xfer->bytes_remaining == 0) {
+				/* ACK only the last data packet */
+				msn_dc_send_data_ack(dc);
+				purple_xfer_set_completed(xfer, TRUE);
+				dc->state = DC_STATE_BYE;
+			}
+			break;
+		default:
+			/* 
+			 * TODO: Packet with unknown flags. Should we ACK these?
+			 */
+			msn_dc_send_ack(dc);
+			
+			purple_debug_warning(
+				"msn",
+				"msn_dc_recv_process_packet_cb: received packet with unknown flags: 0x%08x\n",
+				dc->header.flags
+			);
+		}
+		break;
+		}
 
-	directconn->port = port;
-	directconn->c = 0;
+	case DC_STATE_BYE:
+		/* TODO: Check! */
+		switch (dc->header.flags) {
+		case 0x0000000:
+		case 0x1000000:
+			msn_dc_send_ack(dc);
+			if (strncmp(dc->buffer, "BYE", 3) == 0) {
+				dc->state = DC_STATE_COMPLETED;
+				return DC_TRANSFER_COMPLETED;
+			}
+			break;
+
+		default:
+			/* 
+			 * TODO: Packet with unknown flags. Should we ACK these?
+			 */
+			msn_dc_send_ack(dc);
+			purple_debug_warning(
+				"msn",
+				"msn_dc_recv_process_packet_cb: received packet with unknown flags: 0x%08x\n",
+				dc->header.flags
+			);
+		}
+		break;
+#endif
+	}
+
+	return DC_PROCESS_OK;
 }
 
-MsnDirectConn*
-msn_directconn_new(MsnSlpLink *slplink)
+static void
+msn_dc_recv_cb(gpointer data, gint fd, PurpleInputCondition cond)
 {
-	MsnDirectConn *directconn;
+	MsnDirectConn	*dc;
+	int		free_buf_space;
+	int		bytes_received;
+	guint32		packet_length;
+	
+	g_return_if_fail(data != NULL);
+	g_return_if_fail(fd != -1);
+
+	dc = data;
+	free_buf_space = dc->in_size - dc->in_pos;
+		
+	bytes_received = recv(fd, dc->in_buffer + dc->in_pos, free_buf_space, 0);
+	if (bytes_received < 0) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return;
+
+		purple_debug_warning("msn", "msn_dc_recv_cb: recv error\n");
+
+		if(dc->state != DC_STATE_ESTABILISHED)
+			msn_dc_fallback_to_p2p(dc);
+		else
+			msn_dc_destroy(dc);
+		return;
+
+	} else if (bytes_received == 0) {
+		/* EOF. Remote side closed connection. */
+		purple_debug_info("msn", "msn_dc_recv_cb: recv EOF\n");
+		
+		if(dc->state != DC_STATE_ESTABILISHED)
+			msn_dc_fallback_to_p2p(dc);
+		else
+			msn_dc_destroy(dc);
+		return;
+	}
+	
+	dc->progress = TRUE;
+
+	dc->in_pos += bytes_received;
+
+	/* Wait for packet length */
+	while (dc->in_pos >= 4) {
+		packet_length = *((guint32*)dc->in_buffer);
+		packet_length = GUINT32_FROM_LE(packet_length);
+		
+		if (packet_length > DC_MAX_PACKET_SIZE) {
+			/* Oversized packet */
+			purple_debug_warning("msn", "msn_dc_recv_cb: oversized packet received\n");
+			return;
+		}
 
-	directconn = g_new0(MsnDirectConn, 1);
+		/* Wait for the whole packet to arrive */
+		if (dc->in_pos < 4 + packet_length)
+			return;
+	
+		if (dc->state != DC_STATE_FOO) {
+			msn_dc_parse_binary_header(dc);
+		}
+
+		switch (msn_dc_process_packet(dc, packet_length)) {
+		case DC_PROCESS_CLOSE:
+			return;
+
+		case DC_PROCESS_FALLBACK:
+			purple_debug_warning("msn", "msn_dc_recv_cb: packet processing error, fall back to p2p\n");
+			msn_dc_fallback_to_p2p(dc);
+			return;
+		
+		}
+
+		if (dc->in_pos > packet_length + 4) {
+			memcpy(dc->in_buffer, dc->in_buffer + 4 + packet_length, dc->in_pos - packet_length - 4);
+		}
+
+		dc->in_pos -= packet_length + 4;
+	}
+}
+
+#if 0
+static gboolean
+msn_dc_send_next_packet(MsnDirectConn *dc)
+{
+	MsnSlpMessage	*msg;
+
+	if(g_queue_is_empty(dc->out_queue))
+		return TRUE;
+
+	msg = g_queue_peek_head(dc->out_queue);
+	msn_slplink_send_msgpart(dc->slplink, msg);
+
+
+
+	PurpleXfer	*xfer;
+	int		bytes_read;
+	
+	g_return_val_if_fail(dc != NULL, FALSE);
+	g_return_val_if_fail(dc->slpcall != NULL, FALSE);
+
+	xfer = dc->slpcall->xfer;
+	
+	bytes_read = fread(dc->buffer, 1, DC_MAX_BODY_SIZE, xfer->dest_fp);
+
+	if (bytes_read > 0) {
+		dc->header.session_id = dc->slpcall->session_id;
+		/* Only increment seq. ID before sending BYE */
+		dc->header.id = dc->slpcall->slplink->slp_seq_id;
+		dc->header.offset = xfer->bytes_sent;
+		dc->header.total_size = xfer->size;
+		dc->header.length = bytes_read;
+		dc->header.flags = 0x1000030;
+		dc->header.ack_id = rand() % G_MAXUINT32;
+		dc->header.ack_sub_id = 0;
+		dc->header.ack_size = 0;
+
+		msn_dc_send_packet(dc);
 
-	directconn->slplink = slplink;
+		xfer->bytes_sent += bytes_read;
+		xfer->bytes_remaining -= bytes_read;
+		purple_xfer_update_progress(xfer);
+		
+		if (xfer->bytes_remaining == 0) {
+			purple_xfer_set_completed(xfer, TRUE);
+			
+			/* Increment seq. ID for the next BYE message */
+			dc->slpcall->slplink->slp_seq_id++;
+			dc->state = DC_STATE_DATA_ACK;
+		}
+
+	} else {
+		/* File read error */
+		purple_xfer_cancel_local(xfer);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int
+msn_dc_send_process_packet_cb(MsnDirectConn *dc, guint32 packet_length)
+{
+	g_return_val_if_fail(dc != NULL, DC_TRANSFER_CANCELLED);
+	
+	switch (dc->state) {
+	case DC_STATE_FOO: {
+		if (packet_length != 4)
+			return DC_TRANSFER_FALLBACK;
+
+		if (memcmp(dc->in_buffer, "\4\0\0\0foo", 8) != 0)
+			return DC_TRANSFER_FALLBACK;
+
+		dc->state = DC_STATE_HANDSHAKE;
+		break;
+		}
+
+	case DC_STATE_HANDSHAKE: {
+		if (packet_length != DC_PACKET_HEADER_SIZE)
+			return DC_TRANSFER_FALLBACK;
+
+		/* TODO: Check! */
+		msn_dc_send_handshake_reply(dc);
+		dc->state = DC_STATE_TRANSFER;
+		
+		purple_xfer_set_request_denied_fnc(dc->slpcall->xfer, msn_dc_xfer_send_cancel);
+		purple_xfer_set_cancel_send_fnc(dc->slpcall->xfer, msn_dc_xfer_send_cancel);
+		purple_xfer_set_end_fnc(dc->slpcall->xfer, msn_dc_xfer_end);
+		purple_xfer_start(dc->slpcall->xfer, -1, NULL, 0);
+		break;
+		}
 
-	if (slplink->directconn != NULL)
-		purple_debug_info("msn", "got_transresp: LEAK\n");
+	case DC_STATE_HANDSHAKE_REPLY:
+		/* TODO: Check! */
+		dc->state = DC_STATE_TRANSFER;
+		break;
+		
+	case DC_STATE_TRANSFER: {
+		switch (dc->header.flags) {
+		case 0x0000000:
+		case 0x1000000:
+			msn_dc_send_ack(dc);
+			if (strncmp(dc->buffer, "BYE", 3) == 0) {
+				/* Remote side cancelled the transfer. */
+				purple_xfer_cancel_remote(dc->slpcall->xfer);
+				return DC_TRANSFER_CANCELLED;
+			}
+			break;
+		}
+		break;
+		}
+
+	case DC_STATE_DATA_ACK: {
+		/* TODO: Check! */
+		msn_dc_send_bye(dc);
+		dc->state = DC_STATE_BYE_ACK;
+		break;
+		}
+
+	case DC_STATE_BYE_ACK:
+		/* TODO: Check! */
+		dc->state = DC_STATE_COMPLETED;
+		return DC_TRANSFER_COMPLETED;
+	}
 
-	slplink->directconn = directconn;
+	return DC_TRANSFER_OK;
+}
+#endif
+
+static gboolean
+msn_dc_timeout(gpointer data)
+{
+	MsnDirectConn	*dc = data;
+	
+	g_return_val_if_fail(dc != NULL, FALSE);
+
+	if (dc->progress)
+		dc->progress = FALSE;
+	else
+		msn_dc_destroy(dc);
 
-	return directconn;
+	return TRUE;
+}
+
+static void
+msn_dc_init(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+	
+	dc->in_size = DC_MAX_PACKET_SIZE + 4;
+	dc->in_pos = 0;
+	dc->in_buffer = g_malloc(dc->in_size);
+
+	dc->recv_handle = purple_input_add(dc->fd, PURPLE_INPUT_READ, msn_dc_recv_cb, dc);
+	dc->send_handle = purple_input_add(dc->fd, PURPLE_INPUT_WRITE, msn_dc_send_cb, dc);
+
+	dc->timeout_handle = purple_timeout_add_seconds(DC_TIMEOUT, msn_dc_timeout, dc);
 }
 
 void
-msn_directconn_destroy(MsnDirectConn *directconn)
+msn_dc_connected_to_peer_cb(gpointer data, gint fd, const gchar *error_msg)
 {
-	if (directconn->connect_data != NULL)
-		purple_proxy_connect_cancel(directconn->connect_data);
+	MsnDirectConn	*dc;
+
+	purple_debug_info("msn", "msn_dc_connected_to_peer_cb\n");
+	
+	g_return_if_fail(data != NULL);
+
+	dc = data;
+
+	dc->connect_data = NULL;
+	purple_timeout_remove(dc->connect_timeout_handle);
+	dc->connect_timeout_handle = 0;
+	
+	dc->fd = fd;
+	if (dc->fd != -1) {
+		msn_dc_init(dc);
+		msn_dc_send_foo(dc);
+		msn_dc_send_handshake(dc);
+		dc->state = DC_STATE_HANDSHAKE_REPLY;
+	}
+}
+
+/* 
+ * This callback will be called when we're the server
+ * and nobody has connected us in DC_CONNECT_TIMEOUT seconds
+ */
+static gboolean
+msn_dc_incoming_connection_timeout_cb(gpointer data) {
+	MsnDirectConn	*dc = data;
+	MsnSlpCall	*slpcall = dc->slpcall;
+	
+	purple_debug_info("msn", "msn_dc_incoming_connection_timeout_cb\n");
+	
+	dc = data;
+	g_return_val_if_fail(dc != NULL, FALSE);
+
+	slpcall = dc->slpcall;
+	g_return_val_if_fail(slpcall != NULL, FALSE);
+	
+	if (dc->listen_data != NULL) {
+		purple_network_listen_cancel(dc->listen_data);
+		dc->listen_data = NULL;
+	}
+
+	if (dc->listenfd_handle != 0) {
+		purple_timeout_remove(dc->listenfd_handle);
+		dc->listenfd_handle = 0;
+	}
+
+	if (dc->listenfd != -1) {
+		purple_network_remove_port_mapping(dc->listenfd);
+		close(dc->listenfd);
+		dc->listenfd = -1;
+	}
 
-	if (directconn->inpa != 0)
-		purple_input_remove(directconn->inpa);
+	msn_dc_destroy(dc);
+	/* Start p2p file transfer */
+	msn_slpcall_session_init(slpcall);
+
+	return FALSE;
+}
+
+/*
+ * This callback will be called when we're unable to connect to
+ * the remote host in DC_CONNECT_TIMEOUT seconds.
+ */
+gboolean
+msn_dc_outgoing_connection_timeout_cb(gpointer data)
+{
+	MsnDirectConn	*dc = data;
+	
+	purple_debug_info("msn", "msn_dc_outgoing_connection_timeout_cb\n");
+
+	g_return_val_if_fail(dc != NULL, FALSE);
+	
+	if (dc->connect_timeout_handle != 0) {
+		purple_timeout_remove(dc->connect_timeout_handle);
+		dc->connect_timeout_handle = 0;
+	}
+
+	if (dc->connect_data != NULL) {
+		purple_proxy_connect_cancel(dc->connect_data);
+		dc->connect_data = NULL;
+	}
+
+	if (dc->ext_ip && dc->ext_port) {
+		/* Try external IP/port if available. */
+		dc->connect_data = purple_proxy_connect(
+			NULL,
+			dc->slpcall->slplink->session->account,
+			dc->ext_ip,
+			dc->ext_port,
+			msn_dc_connected_to_peer_cb,
+			dc
+		);
+
+		g_free(dc->ext_ip);
+		dc->ext_ip = NULL;
+
+		if (dc->connect_data) {
+			dc->connect_timeout_handle = purple_timeout_add_seconds(
+				DC_CONNECT_TIMEOUT,
+				msn_dc_outgoing_connection_timeout_cb,
+				dc
+			);
+		}
+
+	} else {
+		/*
+		 * Both internal and external connection attempts are failed.
+		 * Fall back to p2p transfer.
+		 */
+		MsnSlpCall	*slpcall = dc->slpcall;
 
-	if (directconn->fd >= 0)
-		close(directconn->fd);
+		msn_dc_destroy(dc);
+		/* Start p2p file transfer */
+		msn_slpcall_session_init(slpcall);
+	}
+
+	return FALSE;
+}
+
+/* 
+ * This callback will be called when we're the server
+ * and somebody has connected to us in DC_CONNECT_TIMEOUT seconds.
+ */
+static void
+msn_dc_incoming_connection_cb(gpointer data, gint listenfd, PurpleInputCondition cond)
+{
+	MsnDirectConn	*dc = data;
+	
+	purple_debug_info("msn", "msn_dc_incoming_connection_cb\n");
+
+	g_return_if_fail(dc != NULL);
+	
+	if (dc->connect_timeout_handle != 0) {
+		purple_timeout_remove(dc->connect_timeout_handle);
+		dc->connect_timeout_handle = 0;
+	}
+
+	if (dc->listenfd_handle != 0) {
+		purple_input_remove(dc->listenfd_handle);
+		dc->listenfd_handle = 0;
+	}
+	
+	dc->fd = accept(listenfd, NULL, 0);
+
+	purple_network_remove_port_mapping(dc->listenfd);
+	close(dc->listenfd);
+	dc->listenfd = -1;
+
+	if (dc->fd != -1) {
+		msn_dc_init(dc);
+		dc->state = DC_STATE_FOO;
+	}
+}
+
+void
+msn_dc_listen_socket_created_cb(int listenfd, gpointer data)
+{
+	MsnDirectConn		*dc = data;
+
+	purple_debug_info("msn", "msn_dc_listen_socket_created_cb\n");
+	
+	g_return_if_fail(dc != NULL);
+	
+	dc->listen_data = NULL;
+
+	if (listenfd != -1) {
+		const char	*ext_ip;
+		const char	*int_ip;
+		int		port;
 
-	if (directconn->nonce != NULL)
-		g_free(directconn->nonce);
+		ext_ip = purple_network_get_my_ip(listenfd);
+		int_ip = purple_network_get_local_system_ip(listenfd);
+		port = purple_network_get_port_from_fd(listenfd);
+
+		dc->listenfd = listenfd;
+		dc->listenfd_handle = purple_input_add(
+			listenfd,
+			PURPLE_INPUT_READ,
+			msn_dc_incoming_connection_cb,
+			dc
+		);
+		dc->connect_timeout_handle = purple_timeout_add_seconds(
+			DC_CONNECT_TIMEOUT * 2, /* Internal + external connection attempts */
+			msn_dc_incoming_connection_timeout_cb,
+			dc
+		);
+
+		if (strcmp(int_ip, ext_ip) != 0) {
+			dc->msg_body = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: true\r\n"
+				"Hashed-Nonce: {%s}\r\n"
+				"IPv4External-Addrs: %s\r\n"
+				"IPv4External-Port: %d\r\n"
+				"IPv4Internal-Addrs: %s\r\n"
+				"IPv4Internal-Port: %d\r\n"
+				"\r\n",
 
-	directconn->slplink->directconn = NULL;
+				dc->nonce_hash,
+				ext_ip,
+				port,
+				int_ip,
+				port
+			);
+
+		} else {
+			dc->msg_body = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: true\r\n"
+				"Hashed-Nonce: {%s}\r\n"
+				"IPv4External-Addrs: %s\r\n"
+				"IPv4External-Port: %d\r\n"
+				"\r\n",
 
-	g_free(directconn);
+				dc->nonce_hash,
+				ext_ip,
+				port
+			);
+		}
+
+		if (dc->slpcall->wait_for_socket) {
+			if (dc->send_connection_info_msg_cb != NULL)
+				dc->send_connection_info_msg_cb(dc);
+
+			dc->slpcall->wait_for_socket = FALSE;
+		}
+	}
 }
+
--- a/libpurple/protocols/msn/directconn.h	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/directconn.h	Wed Mar 17 03:45:07 2010 +0000
@@ -26,36 +26,153 @@
 
 typedef struct _MsnDirectConn MsnDirectConn;
 
+#include "network.h"
+#include "proxy.h"
+#include "circbuffer.h"
+
 #include "msg.h"
 #include "slp.h"
 #include "slplink.h"
+#include "slpmsg.h"
+
+typedef enum
+{
+	DC_STATE_CLOSED,		/*< No socket opened yet */
+	DC_STATE_FOO,			/*< Waiting for FOO message */
+	DC_STATE_HANDSHAKE,		/*< Waiting for handshake message */
+	DC_STATE_HANDSHAKE_REPLY,	/*< Waiting for handshake reply message */
+	DC_STATE_ESTABILISHED		/*< Handshake complete */
+} MsnDirectConnState;
+
+typedef enum
+{
+	DC_PROCESS_OK = 0,
+	DC_PROCESS_ERROR,
+	DC_PROCESS_FALLBACK,
+	DC_PROCESS_CLOSE
+
+} MsnDirectConnProcessResult;
+
+typedef struct _MsnDirectConnPacket MsnDirectConnPacket;
+
+struct _MsnDirectConnPacket {
+	guint32		length;
+	guchar		*data;
+
+	void		(*sent_cb)(struct _MsnDirectConnPacket*);
+	MsnMessage	*msg;
+};
 
 struct _MsnDirectConn
 {
-	MsnSlpLink *slplink;
-	MsnSlpCall *initial_call;
+	MsnDirectConnState	state;			/**< Direct connection status */
+	MsnSlpLink 		*slplink;		/**< The slplink using this direct connection */
+	MsnSlpCall		*slpcall;		/**< The slpcall which initiated the direct connection */
+	char			*msg_body;		/**< The body of message sent by send_connection_info_msg_cb */
+	MsnSlpMessage		*prev_ack;		/**< The saved SLP ACK message */
+
+	guchar			nonce[16];		/**< The nonce used for direct connection handshake */
+	gchar			nonce_hash[37];		/**< The hash of nonce */
 
-	PurpleProxyConnectData *connect_data;
+	PurpleNetworkListenData	*listen_data;		/**< The pending socket creation request */
+	PurpleProxyConnectData	*connect_data;		/**< The pending connection attempt */
+	int			listenfd;		/**< The socket we're listening for incoming connections */
+	guint			listenfd_handle;	/**< The timeout handle for incoming connection */
+	guint			connect_timeout_handle;	/**< The timeout handle for outgoing connection */
 
-	gboolean acked;
+	int			fd;			/**< The direct connection socket */
+	guint			recv_handle;		/**< The incoming data callback handle */
+	guint			send_handle;		/**< The outgoing data callback handle */
 
-	char *nonce;
-
-	int fd;
+	gchar			*in_buffer;		/**< The receive buffer */
+	int			in_size;		/**< The receive buffer size */
+	int			in_pos;			/**< The first free position in receive buffer */
+	GQueue			*out_queue;		/**< The outgoing packet queue */
+	int			msg_pos;		/**< The position of next byte to be sent in the actual packet */
 
-	int port;
-	int inpa;
+	MsnSlpHeader		header;			/**< SLP header for parsing / serializing */
+	
+							/**< The callback used for sending information to the peer about the opened scoket */
+	void			(*send_connection_info_msg_cb)(struct _MsnDirectConn*);
 
-	int c;
+	gchar			*ext_ip;		/**< Our external IP address */
+	int			ext_port;		/**< Our external port */
+
+	guint			timeout_handle;
+	gboolean		progress;
+
+	//int			num_calls;		/**< The number of slpcalls using this direct connection */
 };
 
-MsnDirectConn *msn_directconn_new(MsnSlpLink *slplink);
-gboolean msn_directconn_connect(MsnDirectConn *directconn,
-								const char *host, int port);
-void msn_directconn_listen(MsnDirectConn *directconn);
-void msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg);
-void msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce);
-void msn_directconn_destroy(MsnDirectConn *directconn);
-void msn_directconn_send_handshake(MsnDirectConn *directconn);
+#define DC_CONNECT_TIMEOUT 	5
+#define DC_TIMEOUT 		60
+
+/*
+ * Queues an MSN message to be sent via direct connection.
+ */
+void
+msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg);
+
+/*
+ * Creates initializes and returns a new MsnDirectConn structure.
+ */
+MsnDirectConn*
+msn_dc_new(MsnSlpCall *slplink);
+
+/*
+ * Destroys an MsnDirectConn structure. Frees every buffer allocated earlier
+ * restores saved callbacks, etc.
+ */
+void
+msn_dc_destroy(MsnDirectConn *dc);
+
+/*
+ * Increases the slpcall counter in DC. The direct connection remains open
+ * until all slpcalls using it are destroyed.
+ */
+void
+msn_dc_ref(MsnDirectConn *dc);
+
+/*
+ * Decrease the slpcall counter in DC. The direct connection remains open
+ * until all slpcalls using it are destroyed.
+ */
+void
+msn_dc_unref(MsnDirectConn *dc);
+
+/*
+ * Sends a direct connect INVITE message on the associated slplink
+ * with the corresponding connection type and information.
+ */
+void
+msn_dc_send_invite(MsnDirectConn *dc);
+
+/*
+ * Sends a direct connect OK message as a response to an INVITE received earliaer
+ * on the corresponding slplink.
+ */
+void
+msn_dc_send_ok(MsnDirectConn *dc);
+
+/*
+ * This callback will be called when we're successfully connected to
+ * the remote host.
+ */
+void
+msn_dc_connected_to_peer_cb(gpointer data, gint fd, const gchar *error_msg);
+
+/*
+ * This callback will be called when we're unable to connect to
+ * the remote host in DC_CONNECT_TIMEOUT seconds.
+ */
+gboolean
+msn_dc_outgoing_connection_timeout_cb(gpointer data);
+
+/*
+ * This callback will be called when the listening socket is successfully
+ * created and it's parameters (IP/port) are available.
+ */
+void
+msn_dc_listen_socket_created_cb(int listenfd, gpointer data);
 
 #endif /* MSN_DIRECTCONN_H */
--- a/libpurple/protocols/msn/msg.c	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/msg.c	Wed Mar 17 03:45:07 2010 +0000
@@ -1100,7 +1100,8 @@
 msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	GHashTable *body;
-	const gchar *guid;
+	const gchar *command;
+	const gchar *cookie;
 	gboolean accepted = FALSE;
 
 	g_return_if_fail(cmdproc != NULL);
@@ -1113,59 +1114,75 @@
 				"Unable to parse invite msg body.\n");
 		return;
 	}
-
-	guid = g_hash_table_lookup(body, "Application-GUID");
+	
+	/*
+	 * GUID is NOT always present but Invitation-Command and Invitation-Cookie
+	 * are mandatory.
+	 */
+	command = g_hash_table_lookup(body, "Invitation-Command");
+	cookie = g_hash_table_lookup(body, "Invitation-Cookie");
 
-	if (guid == NULL) {
-		const gchar *cmd = g_hash_table_lookup(
-				body, "Invitation-Command");
+	if (command == NULL || cookie == NULL) {
+		purple_debug_warning("msn",
+			"Invalid invitation message: "
+			"either Invitation-Command or Invitation-Cookie is missing or invaild"
+		);
+		return;
+
+	} else if (!strcmp(command, "INVITE")) {
 
-		if (cmd && !strcmp(cmd, "CANCEL")) {
-			const gchar *code = g_hash_table_lookup(
-					body, "Cancel-Code");
-			purple_debug_info("msn",
-					"MSMSGS invitation cancelled: %s.\n",
-					code ? code : "no reason given");
-		} else
-			purple_debug_warning("msn", "Invite msg missing "
-					"Application-GUID.\n");
+		const gchar	*guid = g_hash_table_lookup(body, "Application-GUID");
+	
+		if (guid == NULL) {
+			const gchar *cmd = g_hash_table_lookup(
+					body, "Invitation-Command");
 
-		accepted = TRUE;
-
-	} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
-		purple_debug_info("msn", "Computer call\n");
+			if (cmd && !strcmp(cmd, "CANCEL")) {
+				const gchar *code = g_hash_table_lookup(
+						body, "Cancel-Code");
+				purple_debug_info("msn",
+						"MSMSGS invitation cancelled: %s.\n",
+						code ? code : "no reason given");
+			} else
+				purple_debug_warning("msn", "Invite msg missing "
+						"Application-GUID.\n");
 
-		if (cmdproc->session) {
-			PurpleConversation *conv = NULL;
-			gchar *from = msg->remote_user;
-			gchar *buf = NULL;
+			accepted = TRUE;
 
-			if (from)
-				conv = purple_find_conversation_with_account(
-						PURPLE_CONV_TYPE_IM, from,
-						cmdproc->session->account);
-			if (conv)
-				buf = g_strdup_printf(
-						_("%s sent you a voice chat "
-						"invite, which is not yet "
-						"supported."), from);
-			if (buf) {
-				purple_conversation_write(conv, NULL, buf,
-						PURPLE_MESSAGE_SYSTEM |
-						PURPLE_MESSAGE_NOTIFY,
-						time(NULL));
-				g_free(buf);
+		} else if (!strcmp(guid, MSN_FT_GUID)) {
+
+		} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
+			purple_debug_info("msn", "Computer call\n");
+
+			if (cmdproc->session) {
+				PurpleConversation *conv = NULL;
+				gchar *from = msg->remote_user;
+				gchar *buf = NULL;
+
+				if (from)
+					conv = purple_find_conversation_with_account(
+							PURPLE_CONV_TYPE_IM, from,
+							cmdproc->session->account);
+				if (conv)
+					buf = g_strdup_printf(
+							_("%s sent you a voice chat "
+							"invite, which is not yet "
+							"supported."), from);
+				if (buf) {
+					purple_conversation_write(conv, NULL, buf,
+							PURPLE_MESSAGE_SYSTEM |
+							PURPLE_MESSAGE_NOTIFY,
+							time(NULL));
+					g_free(buf);
+				}
 			}
+		} else {
+			const gchar *application = g_hash_table_lookup(body, "Application-Name");
+			purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
+					     guid, application ? application : "(null)");
 		}
-	} else {
-		const gchar *application = g_hash_table_lookup(body, "Application-Name");
-		purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n",
-		                     guid, application ? application : "(null)");
-	}
-
-	if (!accepted) {
-		const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie");
-		if (cookie) {
+		
+		if (!accepted) {
 			MsnSwitchBoard *swboard = cmdproc->data;
 			char *text;
 			MsnMessage *cancel;
@@ -1176,15 +1193,21 @@
 			msn_message_set_flag(cancel, 'U');
 
 			text = g_strdup_printf("Invitation-Command: CANCEL\r\n"
-			                       "Invitation-Cookie: %s\r\n"
-			                       "Cancel-Code: REJECT_NOT_INSTALLED\r\n",
-			                       cookie);
+					       "Invitation-Cookie: %s\r\n"
+					       "Cancel-Code: REJECT_NOT_INSTALLED\r\n",
+					       cookie);
 			msn_message_set_bin_data(cancel, text, strlen(text));
 			g_free(text);
 
 			msn_switchboard_send_msg(swboard, cancel, TRUE);
 			msn_message_destroy(cancel);
 		}
+
+	} else {
+		/*
+		 * Some other already estabilished invitation session.
+		 * Can be retrieved by Invitation-Cookie.
+		 */
 	}
 
 	g_hash_table_destroy(body);
--- a/libpurple/protocols/msn/slp.c	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/slp.c	Wed Mar 17 03:45:07 2010 +0000
@@ -25,23 +25,27 @@
 #include "slp.h"
 #include "slpcall.h"
 #include "slpmsg.h"
+#include "msnutils.h"
 
 #include "object.h"
 #include "user.h"
 #include "switchboard.h"
+#include "directconn.h"
 
 #include "smiley.h"
 
 /* ms to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20
 
-static void send_ok(MsnSlpCall *slpcall, const char *branch,
+/*
+static void msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch,
 					const char *type, const char *content);
 
-static void send_decline(MsnSlpCall *slpcall, const char *branch,
+static void msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch,
 						 const char *type, const char *content);
+*/
+static void request_user_display(MsnUser *user);
 
-static void request_user_display(MsnUser *user);
 
 /**************************************************************************
  * Util
@@ -91,7 +95,7 @@
 	content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 							  slpcall->session_id);
 
-	send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+	msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
 			content);
 
 	g_free(content);
@@ -109,6 +113,7 @@
 
 	slpcall = xfer->data;
 
+	
 	if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL)
 	{
 		if (slpcall->started)
@@ -120,7 +125,7 @@
 			content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 									slpcall->session_id);
 
-			send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
+			msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody",
 						content);
 
 			g_free(content);
@@ -185,6 +190,8 @@
 void
 msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session)
 {
+	purple_debug_info("msn", "msn_xfer_end_cb\n");
+
 	if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) &&
 		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) &&
 		(purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL))
@@ -198,6 +205,7 @@
 					  gsize size)
 {
 	PurpleXfer *xfer = slpcall->xfer;
+
 	purple_xfer_set_completed(xfer, TRUE);
 	purple_xfer_end(xfer);
 }
@@ -234,8 +242,8 @@
 }
 #endif
 
-static void
-send_ok(MsnSlpCall *slpcall, const char *branch,
+void
+msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch,
 		const char *type, const char *content)
 {
 	MsnSlpLink *slplink;
@@ -253,11 +261,18 @@
 
 	msn_slplink_queue_slpmsg(slplink, slpmsg);
 
-	msn_slpcall_session_init(slpcall);
+	/*
+	 * TODO: Removed because it interferes with
+	 * direct file transfer. If we're sending some file
+	 * then this call initiates a p2p file transfer which is
+	 * undesirable.
+	 */
+
+	/* msn_slpcall_session_init(slpcall); */
 }
 
-static void
-send_decline(MsnSlpCall *slpcall, const char *branch,
+void
+msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch,
 			 const char *type, const char *content)
 {
 	MsnSlpLink *slplink;
@@ -308,6 +323,119 @@
 	return NULL;
 }
 
+static gboolean
+msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content)
+{
+	/* A direct connection negotiation response */
+	char		*bridge;
+	MsnDirectConn	*dc = slpcall->slplink->dc;
+	
+	purple_debug_info("msn", "process_transresp\n");
+	
+	g_return_val_if_fail(dc != NULL, FALSE);
+	g_return_val_if_fail(dc->state == DC_STATE_CLOSED, FALSE); 
+
+	bridge = get_token(content, "Bridge: ", "\r\n");
+	if(bridge && strcmp(bridge, "TCPv1") == 0) {
+		/* Ok, the client supports direct TCP connection */
+		
+		if (dc->listen_data != NULL || dc->listenfd != -1) {
+			if (dc->listen_data != NULL) {
+				/* 
+				 * We'll listen for incoming connections but
+				 * the listening socket isn't ready yet so we cannot
+				 * send the INVITE packet now. Put the slpcall into waiting mode
+				 * and let the callback send the invite.
+				 */
+				slpcall->wait_for_socket = TRUE;
+
+			} else {
+				/* The listening socket is ready. Send the INVITE here. */
+				msn_dc_send_invite(dc);
+			}
+
+			return TRUE;
+
+		} else {
+			/*
+			 * We should connect to the client so parse
+			 * IP/port from response.
+			 */
+			char		*ip, *port_str;
+			int		port = 0;
+
+			/* Save external IP/port for later use. We'll try local connection first. */
+			dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4External-Port: ", "\r\n");
+			if (port_str) {
+				dc->ext_port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4Internal-Port: ", "\r\n");
+			if (port_str) {
+				port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			if (ip && port) {
+				/* Try internal address first */
+				dc->connect_data = purple_proxy_connect(
+					NULL,
+					slpcall->slplink->session->account,
+					ip,
+					port,
+					msn_dc_connected_to_peer_cb,
+					dc
+				);
+
+				if (dc->connect_data) {
+					/* Add connect timeout handle */
+					dc->connect_timeout_handle = purple_timeout_add_seconds(
+						DC_CONNECT_TIMEOUT,
+						msn_dc_outgoing_connection_timeout_cb,
+						dc
+					);
+					return TRUE;
+
+				} else {
+					/*
+					 * Connection failed
+					 * Try external IP/port (if specified)
+					 */
+					msn_dc_outgoing_connection_timeout_cb(dc);
+					return TRUE;
+				}
+
+				g_free(ip);
+
+			} else {
+				/*
+				 * Omitted or invalid internal IP address / port
+				 * Try external IP/port (if specified)
+				 */
+				msn_dc_outgoing_connection_timeout_cb(dc);
+				return TRUE;
+			}
+
+			if (ip)
+				g_free(ip);
+		}
+
+	} else {
+		/*
+		 * Invalid direct connect invitation or
+		 * TCP connection is not supported
+		 */
+	}
+
+	if (bridge)
+		g_free(bridge);
+
+	return FALSE;
+}
+
 static void
 got_sessionreq(MsnSlpCall *slpcall, const char *branch,
 			   const char *euf_guid, const char *context)
@@ -330,7 +458,7 @@
 		content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 								  slpcall->session_id);
 
-		send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
+		msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody",
 				content);
 
 		g_free(content);
@@ -479,7 +607,7 @@
 	if (!accepted) {
 		char *content = g_strdup_printf("SessionID: %lu\r\n\r\n",
 		                                slpcall->session_id);
-		send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
+		msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content);
 		g_free(content);
 	}
 }
@@ -548,67 +676,75 @@
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
 	{
-		/* A direct connection? */
-
-		char *listening, *nonce;
-		char *content;
-
-		if (FALSE)
-		{
-#if 0
-			MsnDirectConn *directconn;
-			/* const char *ip_addr; */
-			char *ip_port;
-			int port;
+		/* A direct connection negotiation request */
+		char		*bridges;
+			
+		purple_debug_info("msn", "got_invite: transreqbody received\n");
+		
+		g_return_if_fail(slpcall->xfer != NULL);
 
-			/* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */
-			ip_port = "5190";
-			listening = "true";
-			nonce = rand_guid();
-
-			directconn = msn_directconn_new(slplink);
+		/* Don't do anything if we already have a direct connection */
+		g_return_if_fail(slpcall->slplink->dc == NULL);
+		
+		bridges = get_token(content, "Bridges: ", "\r\n");
+		if(bridges && strstr(bridges, "TCPv1") != NULL) {
+			/*
+			 * Ok, the client supports direct TCP connection
+			 * Try to create a listening port
+			 */
+			char		*content;
+			MsnDirectConn	*dc;
 
-			/* msn_directconn_parse_nonce(directconn, nonce); */
-			directconn->nonce = g_strdup(nonce);
+			dc = msn_dc_new(slpcall);
 
-			msn_directconn_listen(directconn);
-
-			port = directconn->port;
+			dc->listen_data = purple_network_listen_range(
+				0, 0,
+				SOCK_STREAM,
+				msn_dc_listen_socket_created_cb,
+				dc
+			);
 
-			content = g_strdup_printf(
-				"Bridge: TCPv1\r\n"
-				"Listening: %s\r\n"
-				"Nonce: {%s}\r\n"
-				"Ipv4Internal-Addrs: 192.168.0.82\r\n"
-				"Ipv4Internal-Port: %d\r\n"
-				"\r\n",
-				listening,
-				nonce,
-				port);
-#endif
+			if (dc->listen_data == NULL) {
+				/* Listen socket creation failed */
+				
+				purple_debug_info("msn", "got_invite: listening failed\n");
+				
+				content = g_strdup(
+					"Bridge: TCPv1\r\n"
+					"Listening: false\r\n"
+					"Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+					"\r\n"
+				);
+				msn_slp_send_ok(slpcall, branch,
+					"application/x-msnmsgr-transrespbody", content);
+				g_free(content);
+
+			} else {
+				/* 
+				 * Listen socket created successfully.
+				 * Don't send anything here because we don't know the parameters
+				 * of the created socket yet. msn_dc_send_ok will be called from
+				 * the callback function: dc_listen_socket_created_cb
+				 */
+				purple_debug_info("msn", "got_invite: listening socket created\n");
+			
+				dc->send_connection_info_msg_cb = msn_dc_send_ok;
+				slpcall->wait_for_socket = TRUE;
+			}
+
+		} else {
+			/* 
+			 * Invalid direct connect invitation or
+			 * TCP connection is not supported.
+			 */
 		}
-		else
-		{
-			listening = "false";
-			nonce = g_strdup("00000000-0000-0000-0000-000000000000");
-
-			content = g_strdup_printf(
-				"Bridge: TCPv1\r\n"
-				"Listening: %s\r\n"
-				"Nonce: {%s}\r\n"
-				"\r\n",
-				listening,
-				nonce);
-		}
-
-		send_ok(slpcall, branch,
-				"application/x-msnmsgr-transrespbody", content);
-
-		g_free(content);
-		g_free(nonce);
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
 	{
+		/* A direct connection negotiation response */
+		g_return_if_fail(slpcall->xfer != NULL);
+		
+		msn_slp_process_transresp(slpcall, content);
 #if 0
 		char *ip_addrs;
 		char *temp;
@@ -646,6 +782,86 @@
 
 	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
 	{
+		char		*content;
+		char		*header;
+		MsnSlpMessage	*msg;
+		MsnDirectConn	*dc;
+
+		g_return_if_fail(slpcall->xfer != NULL);
+
+		if(slpcall->slplink->dc != NULL) {
+			/* 
+			 * If we already have an estabilished direct connection
+			 * then just start the transfer.
+			 */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+		
+		/* Try direct file transfer by sending a second INVITE */
+		
+		dc = msn_dc_new(slpcall);
+		slpcall->branch = rand_guid();
+
+		dc->listen_data = purple_network_listen_range(
+			0, 0,
+			SOCK_STREAM,
+			msn_dc_listen_socket_created_cb,
+			dc
+		);
+			
+		header = g_strdup_printf(
+			"INVITE MSNMSGR:%s MSNSLP/1.0",
+			slpcall->slplink->remote_user
+		);
+
+		if (dc->listen_data == NULL) {
+			/* Listen socket creation failed */
+			purple_debug_info("msn", "got_ok: listening failed\n");
+
+			content = g_strdup_printf(
+				"Bridges: TCPv1\r\n"
+				"NetID: %u\r\n"
+				"Conn-Type: IP-Restrict-NAT\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+				"Hashed-Nonce: {%s}\r\n"
+				"\r\n",
+
+				rand() % G_MAXUINT32,
+				dc->nonce_hash
+			);
+
+		} else {
+			/* Listen socket created successfully. */
+			
+			purple_debug_info("msn", "got_ok: listening socket created\n");
+			
+			content = g_strdup_printf(
+				"Bridges: TCPv1\r\n"
+				"NetID: 0\r\n"
+				"Conn-Type: Direct-Connect\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+				"Hashed-Nonce: {%s}\r\n"
+				"\r\n",
+
+				dc->nonce_hash
+			);
+		}
+		
+		msg = msn_slpmsg_sip_new(
+			slpcall,
+			0,
+			header,
+			slpcall->branch,
+			"application/x-msnmsgr-transreqbody",
+			content
+		);
+		g_free(header);
+		g_free(content);
+
+		msn_slplink_queue_slpmsg(slpcall->slplink, msg);
 #if 0
 		if (slpcall->type == MSN_SLPCALL_DC)
 		{
@@ -690,7 +906,12 @@
 			msn_slpcall_session_init(slpcall);
 		}
 #else
-		msn_slpcall_session_init(slpcall);
+		/* 
+		 * Removed because it messes up direct connection by
+		 * starting p2p transfer
+		 */
+
+		/* msn_slpcall_session_init(slpcall); */
 #endif
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
@@ -700,6 +921,7 @@
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
 	{
+		msn_slp_process_transresp(slpcall, content);
 #if 0
 		char *ip_addrs;
 		char *temp;
@@ -767,11 +989,25 @@
 
 		content = get_token(body, "\r\n\r\n", NULL);
 
-		if (branch && call_id && content_type && content)
+		if (branch && call_id)
 		{
-			slpcall = msn_slpcall_new(slplink);
-			slpcall->id = call_id;
-			got_invite(slpcall, branch, content_type, content);
+			slpcall = msn_slplink_find_slp_call(slplink, call_id);
+			if (slpcall)
+			{
+				g_free(slpcall->branch);
+				slpcall->branch = g_strdup(branch);
+			}
+			else if (content_type && content)
+			{
+				slpcall = msn_slpcall_new(slplink);
+				slpcall->id = call_id;
+				got_invite(slpcall, branch, content_type, content);
+			}
+			else
+			{
+				g_free(call_id);
+				slpcall = NULL;
+			}
 		}
 		else
 		{
@@ -860,6 +1096,8 @@
 {
 	MsnSession *session;
 	MsnSlpLink *slplink;
+	const char *data;
+	gsize len;
 
 	session = cmdproc->servconn->session;
 	slplink = msn_session_get_slplink(session, msg->remote_user);
@@ -882,7 +1120,9 @@
 		}
 	}
 
-	msn_slplink_process_msg(slplink, msg);
+	data = msn_message_get_bin_data(msg, &len);
+
+	msn_slplink_process_msg(slplink, &msg->msnslp_header, data, len);
 }
 
 static void
--- a/libpurple/protocols/msn/slp.h	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/slp.h	Wed Mar 17 03:45:07 2010 +0000
@@ -51,6 +51,14 @@
 
 MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink,
 							  const char *body);
+void
+msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch,
+		const char *type, const char *content);
+		
+void
+msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch,
+			 const char *type, const char *content);
+
 
 void send_bye(MsnSlpCall *slpcall, const char *type);
 
--- a/libpurple/protocols/msn/slpcall.c	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Wed Mar 17 03:45:07 2010 +0000
@@ -66,6 +66,10 @@
 
 	slpcall->slplink = slplink;
 
+	slpcall->wait_for_socket = FALSE;
+	slpcall->xfer = NULL;
+	slpcall->branch = NULL;
+
 	msn_slplink_add_slpcall(slplink, slpcall);
 
 	slpcall->timer = purple_timeout_add_seconds(MSN_SLPCALL_TIMEOUT, msn_slpcall_timeout, slpcall);
--- a/libpurple/protocols/msn/slpcall.h	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/slpcall.h	Wed Mar 17 03:45:07 2010 +0000
@@ -64,13 +64,15 @@
 	gboolean started; /**< A flag that states if this slpcall's session has
 						been initiated. */
 
+	gboolean wait_for_socket;
+
 	void (*progress_cb)(MsnSlpCall *slpcall,
 						gsize total_length, gsize len, gsize offset);
 	void (*session_init_cb)(MsnSlpCall *slpcall);
 
 	/* Can be checksum, or smile */
 	char *data_info;
-
+	
 	PurpleXfer *xfer;
 	union {
 		GByteArray *incoming_data;
--- a/libpurple/protocols/msn/slplink.c	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/slplink.c	Wed Mar 17 03:45:07 2010 +0000
@@ -155,11 +155,21 @@
 		slplink->swboard->flag |= MSN_SB_FLAG_FT;
 
 	slplink->slp_calls = g_list_append(slplink->slp_calls, slpcall);
+
+	/*
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABILISHED)
+		msn_dc_ref(slplink->dc);
+	*/
 }
 
 void
 msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall)
 {
+	/*
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABILISHED)
+		msn_dc_unref(slplink->dc);
+	*/
+	
 	slplink->slp_calls = g_list_remove(slplink->slp_calls, slpcall);
 
 	/* The slplink has no slpcalls in it, release it from MSN_SB_FLAG_FT.
@@ -209,13 +219,11 @@
 static void
 msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg)
 {
-#if 0
-	if (slplink->directconn != NULL)
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABILISHED)
 	{
-		msn_directconn_send_msg(slplink->directconn, msg);
+		msn_dc_enqueue_msg(slplink->dc, msg);
 	}
 	else
-#endif
 	{
 		if (slplink->swboard == NULL)
 		{
@@ -431,11 +439,30 @@
 	}
 }
 
-static void
-msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg)
+static MsnSlpMessage*
+msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
 {
-	MsnSlpMessage *slpmsg;
+	MsnSlpMessage	*slpmsg;
+
+	slpmsg = msn_slpmsg_new(slplink);
 
+	slpmsg->session_id = header->session_id;
+	slpmsg->size       = header->total_size;
+	slpmsg->flags      = 0x02;
+	slpmsg->ack_id     = header->id;
+	slpmsg->ack_sub_id = header->ack_id;
+	slpmsg->ack_size   = header->total_size;
+	slpmsg->info = "SLP ACK";
+
+	return slpmsg;
+}
+
+static void
+msn_slplink_send_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
+{
+	MsnSlpMessage *slpmsg = msn_slplink_create_ack(slplink, header);
+
+	/*
 	slpmsg = msn_slpmsg_new(slplink);
 
 	slpmsg->session_id = msg->msnslp_header.session_id;
@@ -445,6 +472,7 @@
 	slpmsg->ack_sub_id = msg->msnslp_header.ack_id;
 	slpmsg->ack_size   = msg->msnslp_header.total_size;
 	slpmsg->info = "SLP ACK";
+	*/
 
 	msn_slplink_send_slpmsg(slplink, slpmsg);
 	msn_slpmsg_destroy(slpmsg);
@@ -491,38 +519,36 @@
 }
 
 void
-msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg)
+msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len)
 {
 	MsnSlpMessage *slpmsg;
-	const char *data;
 	guint64 offset;
-	gsize len;
 	PurpleXfer *xfer = NULL;
 
+	/*
 	if (purple_debug_is_verbose())
 		msn_slpmsg_show(msg);
+	*/
 
 #ifdef MSN_DEBUG_SLP_FILES
-	debug_msg_to_file(msg, FALSE);
 #endif
-
-	if (msg->msnslp_header.total_size < msg->msnslp_header.length)
+	if (header->total_size < header->length)
 	{
 		purple_debug_error("msn", "This can't be good\n");
 		g_return_if_reached();
 	}
 
-	data = msn_message_get_bin_data(msg, &len);
+	/* data = msn_message_get_bin_data(msg, &len); */
 
-	offset = msg->msnslp_header.offset;
+	offset = header->offset;
 
 	if (offset == 0)
 	{
 		slpmsg = msn_slpmsg_new(slplink);
-		slpmsg->id = msg->msnslp_header.id;
-		slpmsg->session_id = msg->msnslp_header.session_id;
-		slpmsg->size = msg->msnslp_header.total_size;
-		slpmsg->flags = msg->msnslp_header.flags;
+		slpmsg->id = header->id;
+		slpmsg->session_id = header->session_id;
+		slpmsg->size = header->total_size;
+		slpmsg->flags = header->flags;
 
 		if (slpmsg->session_id)
 		{
@@ -567,7 +593,7 @@
 	}
 	else
 	{
-		slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id);
+		slpmsg = msn_slplink_message_find(slplink, header->session_id, header->id);
 		if (slpmsg == NULL)
 		{
 			/* Probably the transfer was canceled */
@@ -615,45 +641,60 @@
 		return;
 #endif
 
-	if (msg->msnslp_header.offset + msg->msnslp_header.length
-		>= msg->msnslp_header.total_size)
+	if (header->offset + header->length >= header->total_size)
 	{
 		/* All the pieces of the slpmsg have been received */
 		MsnSlpCall *slpcall;
-
+	
 		slpcall = msn_slp_process_msg(slplink, slpmsg);
 
 		if (slpcall == NULL) {
 			msn_slpmsg_destroy(slpmsg);
 			return;
 		}
+		
+		purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n");
 
-		if (!slpcall->wasted) {
+		/*if (!slpcall->wasted) {*/
 			if (slpmsg->flags == 0x100)
 			{
+#if 0
 				MsnDirectConn *directconn;
 
 				directconn = slplink->directconn;
-#if 0
 				if (!directconn->acked)
 					msn_directconn_send_handshake(directconn);
 #endif
 			}
 			else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||  
-			         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||  
-			         slpmsg->flags == 0x1000030)
+				 slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||  
+				 slpmsg->flags == 0x1000030)
 			{
 				/* Release all the messages and send the ACK */
-
-				msn_slplink_send_ack(slplink, msg);
-				msn_slplink_send_queued_slpmsgs(slplink);
+			
+				if (slpcall != NULL && slpcall->wait_for_socket) {
+					/*
+					 * Save ack for later because we have to send
+					 * a 200 OK message to the previous direct connect
+					 * invitation before ACK but the listening socket isn't
+					 * created yet.
+					 */
+					
+					purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n");
+					
+					slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header);
+				} else {
+					purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n");
+					
+					msn_slplink_send_ack(slplink, header);
+					msn_slplink_send_queued_slpmsgs(slplink);
+				}
 			}
-
-		}
+		/*}*/
 
 		msn_slpmsg_destroy(slpmsg);
 
-		if (slpcall->wasted)
+		if (slpcall != NULL && !slpcall->wait_for_socket && slpcall->wasted)
 			msn_slpcall_destroy(slpcall);
 	}
 }
--- a/libpurple/protocols/msn/slplink.h	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/slplink.h	Wed Mar 17 03:45:07 2010 +0000
@@ -42,13 +42,12 @@
 {
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
+	MsnDirectConn *dc;
 
 	char *remote_user;
 
 	int slp_seq_id;
 
-	MsnDirectConn *directconn;
-
 	GList *slp_calls;
 	GList *slp_msgs;
 
@@ -79,7 +78,7 @@
 void msn_slplink_send_slpmsg(MsnSlpLink *slplink,
 							 MsnSlpMessage *slpmsg);
 void msn_slplink_send_queued_slpmsgs(MsnSlpLink *slplink);
-void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg);
+void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len);
 void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer);
 
 /* Only exported for msn_xfer_write */
--- a/libpurple/protocols/msn/switchboard.c	Tue Mar 16 06:20:05 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Wed Mar 17 03:45:07 2010 +0000
@@ -87,8 +87,17 @@
 		purple_timeout_remove(swboard->reconn_timeout_h);
 
 	/* If it linked us is because its looking for trouble */
-	while (swboard->slplinks != NULL)
-		msn_slplink_destroy(swboard->slplinks->data);
+	while (swboard->slplinks != NULL) {
+		/* Destroy only those slplinks which use the switchboard */
+		MsnSlpLink	*slplink = swboard->slplinks->data;
+
+		if (slplink->dc == NULL)
+			msn_slplink_destroy(slplink);
+		else {
+			swboard->slplinks = g_list_remove(swboard->slplinks, slplink);
+			slplink->swboard = NULL;
+		}
+	}
 
 	/* Destroy the message queue */
 	while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)