changeset 30478:0b22c840f4f4

merge of '5378b2067023c1be51d87906196d87f03c9992e3' and 'da604206b18f52b79c03f5cdd11dc0438e46038b'
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Fri, 07 May 2010 20:04:42 +0000
parents 7fbb7612e3dd (current diff) 7a82bc59b63f (diff)
children a6642358155b
files COPYRIGHT ChangeLog libpurple/protocols/msn/msg.c libpurple/protocols/msn/slp.c libpurple/protocols/msn/slplink.c
diffstat 14 files changed, 1553 insertions(+), 693 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu May 06 21:28:32 2010 +0000
+++ b/COPYRIGHT	Fri May 07 20:04:42 2010 +0000
@@ -477,6 +477,7 @@
 Marcus Sundberg
 Mårten Svantesson (fursten)
 Amir Szekely (kichik)
+Gábor Szuromi (kukkerman)
 Robert T.
 Greg Taeger
 Rob Taft
--- a/ChangeLog	Thu May 06 21:28:32 2010 +0000
+++ b/ChangeLog	Fri May 07 20:04:42 2010 +0000
@@ -81,6 +81,8 @@
 	* Support for version 9 of the MSN protocol has been removed.  This
 	  version is no longer supported on the servers.
 	* Support file transfer thumbnails (previews) for images.
+	* 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	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.am	Fri May 07 20:04:42 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	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/Makefile.mingw	Fri May 07 20:04:42 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	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/directconn.c	Fri May 07 20:04:42 2010 +0000
@@ -27,479 +27,962 @@
 #include "slp.h"
 #include "slpmsg.h"
 
-/**************************************************************************
- * Directconn Specific
- **************************************************************************/
-
-void
-msn_directconn_send_handshake(MsnDirectConn *directconn)
-{
-	MsnSlpLink *slplink;
-	MsnSlpMessage *slpmsg;
-
-	g_return_if_fail(directconn != NULL);
-
-	slplink = directconn->slplink;
-
-	slpmsg = msn_slpmsg_new(slplink);
-	slpmsg->flags = 0x100;
-
-	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);
-
-		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);
-
-		slpmsg->ack_id     = t1;
-		slpmsg->ack_sub_id = t2 | (t3 << 16);
-		slpmsg->ack_size   = t4 | t5;
-	}
-
-	g_free(directconn->nonce);
-
-	msn_slplink_send_slpmsg(slplink, slpmsg);
-
-	directconn->acked =TRUE;
-}
+#pragma pack(push,1)
+typedef struct {
+	guint32 session_id;
+	guint32 seq_id;
+	guint64 offset;
+	guint64 total_size;
+	guint32 length;
+	guint32 flags;
+	guint32 ack_id;
+	guint32 ack_uid;
+	guint64 ack_size;
+/*	guint8  body[1]; */
+} MsnDcContext;
+#pragma pack(pop)
 
-/**************************************************************************
- * Connection Functions
- **************************************************************************/
-
-static int
-create_listener(int port)
-{
-	int fd;
-	int flags;
-	const int on = 1;
-
-#if 0
-	struct addrinfo hints;
-	struct addrinfo *c, *res;
-	char port_str[5];
-
-	snprintf(port_str, sizeof(port_str), "%d", port);
-
-	memset(&hints, 0, sizeof(hints));
-
-	hints.ai_flags = AI_PASSIVE;
-	hints.ai_family = AF_UNSPEC;
-	hints.ai_socktype = SOCK_STREAM;
+#define DC_PACKET_HEADER_SIZE sizeof(MsnDcContext)
+#define DC_MAX_BODY_SIZE      8*1024
+#define DC_MAX_PACKET_SIZE    (DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE)
 
-	if (getaddrinfo(NULL, port_str, &hints, &res) != 0)
-	{
-		purple_debug_error("msn", "Could not get address info: %s.\n",
-						 port_str);
-		return -1;
-	}
-
-	for (c = res; c != NULL; c = c->ai_next)
-	{
-		fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol);
+static void
+msn_dc_calculate_nonce_hash(MsnDirectConnNonceType type,
+                            const guchar nonce[16], gchar nonce_hash[37])
+{
+	guchar digest[20];
 
-		if (fd < 0)
-			continue;
-
-		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
-
-		if (bind(fd, c->ai_addr, c->ai_addrlen) == 0)
-			break;
-
-		close(fd);
-	}
-
-	if (c == NULL)
-	{
-		purple_debug_error("msn", "Could not find socket: %s.\n", port_str);
-		return -1;
+	if (type == DC_NONCE_SHA1) {
+		PurpleCipher *cipher = purple_ciphers_find_cipher("sha1");
+		PurpleCipherContext *context = purple_cipher_context_new(cipher, NULL);
+		purple_cipher_context_append(context, nonce, sizeof(nonce));
+		purple_cipher_context_digest(context, sizeof(digest), digest, NULL);
+		purple_cipher_context_destroy(context);
+	} else if (type == DC_NONCE_PLAIN) {
+		memcpy(digest, nonce, 16);
 	}
 
-	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;
-	}
-
-	memset(&sockin, 0, sizeof(struct sockaddr_in));
-	sockin.sin_family = AF_INET;
-	sockin.sin_port = htons(port);
-
-	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;
-	}
-
-	flags = fcntl(fd, F_GETFL);
-	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-#ifndef _WIN32
-	fcntl(fd, F_SETFD, FD_CLOEXEC);
-#endif
-
-	return fd;
+	g_sprintf(nonce_hash,
+	          "%08X-%04X-%04X-%04X-%08X%04X",
+	          GUINT32_FROM_LE(*((guint32 *)(digest + 0))),
+	          GUINT16_FROM_LE(*((guint16 *)(digest + 4))),
+	          GUINT16_FROM_LE(*((guint16 *)(digest + 6))),
+	          GUINT16_FROM_BE(*((guint16 *)(digest + 8))),
+	          GUINT32_FROM_BE(*((guint32 *)(digest + 10))),
+	          GUINT16_FROM_BE(*((guint16 *)(digest + 14)))
+	);
 }
 
-static gssize
-msn_directconn_write(MsnDirectConn *directconn,
-					 const char *data, size_t len)
+static void
+msn_dc_generate_nonce(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);
+	guint32 *nonce;
+	int i;
 
-	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);
+	nonce = (guint32 *)&dc->nonce;
+	for (i = 0; i < 4; i++)
+		nonce[i] = rand();
 
-	FILE *tf = g_fopen(str, "w");
-	fwrite(buffer, 1, buf_size, tf);
-	fclose(tf);
+	msn_dc_calculate_nonce_hash(dc->nonce_type, dc->nonce, dc->nonce_hash);
 
-	g_free(str);
-#endif
-
-	g_free(buffer);
-
-	directconn->c++;
-
-	return ret;
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "DC %p generated nonce %s\n", dc, dc->nonce_hash);
 }
 
-#if 0
-void
-msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce)
+static MsnDirectConnPacket *
+msn_dc_new_packet(guint32 length)
 {
-	guint32 t1;
-	guint16 t2;
-	guint16 t3;
-	guint16 t4;
-	guint64 t5;
-
-	g_return_if_fail(directconn != NULL);
-	g_return_if_fail(nonce      != NULL);
-
-	sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &t1, &t2, &t3, &t4, &t5);
+	MsnDirectConnPacket	*p;
 
-	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);
+	p = g_new0(MsnDirectConnPacket, 1);
+	p->length = length;
+	p->data = g_malloc(length);
 
-	directconn->slpheader->ack_id     = t1;
-	directconn->slpheader->ack_sub_id = t2 | (t3 << 16);
-	directconn->slpheader->ack_size   = t4 | t5;
-}
-#endif
-
-void
-msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg)
-{
-	char *body;
-	size_t body_len;
-
-	body = msn_message_gen_slp_body(msg, &body_len);
-
-	msn_directconn_write(directconn, body, body_len);
+	return p;
 }
 
 static void
-read_cb(gpointer data, gint source, PurpleInputCondition cond)
+msn_dc_destroy_packet(MsnDirectConnPacket *p)
+{
+	g_free(p->data);
+
+	if (p->msg)
+		msn_message_unref(p->msg);
+
+	g_free(p);
+}
+
+MsnDirectConn *
+msn_dc_new(MsnSlpCall *slpcall)
 {
-	MsnDirectConn* directconn;
-	char *body;
-	size_t body_len;
-	gssize len;
+	MsnDirectConn *dc;
+
+	g_return_val_if_fail(slpcall != NULL, NULL);
+
+	dc = g_new0(MsnDirectConn, 1);
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_new %p\n", dc);
+
+	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;
 
-	purple_debug_info("msn", "read_cb: %d, %d\n", source, cond);
+	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 = -1;
+	dc->send_connection_info_msg_cb = NULL;
+	dc->ext_ip = NULL;
+	dc->timeout_handle = 0;
+	dc->progress = FALSE;
+	//dc->num_calls = 1;
 
-	directconn = data;
+	/* TODO: Probably should set this based on buddy caps */
+	dc->nonce_type = DC_NONCE_PLAIN;
+	msn_dc_generate_nonce(dc);
+
+	return dc;
+}
+
+void
+msn_dc_destroy(MsnDirectConn *dc)
+{
+	MsnSlpLink *slplink;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_destroy %p\n", dc);
 
-	/* 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));
+	g_return_if_fail(dc != NULL);
+
+	slplink = dc->slplink;
+
+	if (dc->slpcall != NULL)
+		dc->slpcall->wait_for_socket = FALSE;
+
+	slplink->dc = NULL;
+
+	if (slplink->swboard == NULL)
+		msn_slplink_destroy(slplink);
+
+	g_free(dc->msg_body);
+
+	if (dc->prev_ack) {
+		msn_slpmsg_destroy(dc->prev_ack);
+	}
+
+	if (dc->listen_data != NULL) {
+		purple_network_listen_cancel(dc->listen_data);
+	}
+
+	if (dc->connect_data != NULL) {
+		purple_proxy_connect_cancel(dc->connect_data);
+	}
+
+	if (dc->listenfd != -1) {
+		purple_network_remove_port_mapping(dc->listenfd);
+		close(dc->listenfd);
+	}
 
-	if (len <= 0)
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
+	if (dc->listenfd_handle != 0) {
+		purple_timeout_remove(dc->listenfd_handle);
+	}
+
+	if (dc->connect_timeout_handle != 0) {
+		purple_timeout_remove(dc->connect_timeout_handle);
+	}
+
+	if (dc->fd != -1) {
+		close(dc->fd);
+	}
+
+	if (dc->send_handle != 0) {
+		purple_input_remove(dc->send_handle);
+	}
 
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
+	if (dc->recv_handle != 0) {
+		purple_input_remove(dc->recv_handle);
+	}
+
+	g_free(dc->in_buffer);
 
-		close(directconn->fd);
+	if (dc->out_queue != NULL) {
+		while (!g_queue_is_empty(dc->out_queue))
+			msn_dc_destroy_packet( g_queue_pop_head(dc->out_queue) );
 
-		msn_directconn_destroy(directconn);
+		g_queue_free(dc->out_queue);
+	}
 
-		return;
+	g_free(dc->ext_ip);
+
+	if (dc->timeout_handle != 0) {
+		purple_timeout_remove(dc->timeout_handle);
 	}
 
-	body_len = GUINT32_FROM_LE(body_len);
+	g_free(dc);
+}
+
+/*
+void
+msn_dc_ref(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+
+	dc->num_calls++;
+}
+
+void
+msn_dc_unref(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
+
+
+	if (dc->num_calls > 0) {
+		dc->num_calls--;
+	}
+}
+*/
+
+void
+msn_dc_send_invite(MsnDirectConn *dc)
+{
+	MsnSlpCall    *slpcall;
+	MsnSlpMessage *msg;
+	gchar *header;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_send_invite %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
 
-	purple_debug_info("msn", "body_len=%" G_GSIZE_FORMAT "\n", body_len);
+	slpcall = dc->slpcall;
+	g_return_if_fail(slpcall != NULL);
+
+	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
+	);
+	msg->info = "DC INVITE";
+	msg->text_body = TRUE;
+	g_free(header);
+	g_free(dc->msg_body);
+	dc->msg_body = NULL;
+
+	msn_slplink_queue_slpmsg(slpcall->slplink, msg);
+}
+
+void
+msn_dc_send_ok(MsnDirectConn *dc)
+{
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_send_ok %p\n", dc);
+
+	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;
 
-	if (body_len <= 0)
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
+	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);
+}
+
+void
+msn_dc_fallback_to_p2p(MsnDirectConn *dc)
+{
+	MsnSlpCall *slpcall;
+
+	purple_debug_info("msn", "msn_dc_try_fallback_to_p2p %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	slpcall = dc->slpcall;
+	g_return_if_fail(slpcall != NULL);
+
+	msn_dc_destroy(dc);
+
+	msn_slpcall_session_init(slpcall);
+}
+
+static void
+msn_dc_parse_binary_header(MsnDirectConn *dc)
+{
+	MsnSlpHeader *h;
+	MsnDcContext *context;
+
+	g_return_if_fail(dc != NULL);
+
+	h = &dc->header;
+	/* Skip packet size */
+	context = (MsnDcContext *)(dc->in_buffer + 4);
 
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
+	h->session_id = GUINT32_FROM_LE(context->session_id);
+	h->id = GUINT32_FROM_LE(context->seq_id);
+	h->offset = GUINT64_FROM_LE(context->offset);
+	h->total_size = GUINT64_FROM_LE(context->total_size);
+	h->length = GUINT32_FROM_LE(context->length);
+	h->flags = GUINT32_FROM_LE(context->flags);
+	h->ack_id = GUINT32_FROM_LE(context->ack_id);
+	h->ack_sub_id = GUINT32_FROM_LE(context->ack_uid);
+	h->ack_size = GUINT64_FROM_LE(context->ack_size);
+}
+
+static const gchar *
+msn_dc_serialize_binary_header(MsnDirectConn *dc) {
+	MsnSlpHeader *h;
+	static MsnDcContext bin_header;
+
+	g_return_val_if_fail(dc != NULL, NULL);
+
+	h = &dc->header;
 
-		close(directconn->fd);
+	bin_header.session_id = GUINT32_TO_LE(h->session_id);
+	bin_header.seq_id = GUINT32_TO_LE(h->id);
+	bin_header.offset = GUINT64_TO_LE(h->offset);
+	bin_header.total_size = GUINT64_TO_LE(h->total_size);
+	bin_header.length = GUINT32_TO_LE(h->length);
+	bin_header.flags = GUINT32_TO_LE(h->flags);
+	bin_header.ack_id = GUINT32_TO_LE(h->ack_id);
+	bin_header.ack_uid = GUINT32_TO_LE(h->ack_sub_id);
+	bin_header.ack_size = GUINT64_TO_LE(h->ack_size);
+
+	return (const gchar *)&bin_header;
+}
 
-		msn_directconn_destroy(directconn);
+static void
+msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond)
+{
+	MsnDirectConn *dc = data;
+	MsnDirectConnPacket *p;
+	int bytes_to_send;
+	int bytes_sent;
 
+	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);
 
-		purple_debug_info("msn", "len=%" G_GSIZE_FORMAT "\n", len);
-	}
-	else
-	{
-		purple_debug_error("msn", "Failed to allocate memory for read\n");
-		len = 0;
+	if (dc->msg_pos < 0) {
+		/* First we send the length of the packet */
+		guint32 len = GUINT32_TO_LE(p->length);
+		bytes_sent = send(fd, &len, 4, 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;
+		}
+		dc->msg_pos = 0;
 	}
 
-	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++;
+	bytes_to_send = p->length - dc->msg_pos;
+	bytes_sent = send(fd, p->data + dc->msg_pos, bytes_to_send, 0);
+	if (bytes_sent < 0) {
+		if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
+			return;
 
-		msg = msn_message_new_msnslp();
-		msn_message_parse_slp_body(msg, body, body_len);
-
-		purple_debug_info("msn", "directconn: process_msg\n");
-		msn_slplink_process_msg(directconn->slplink, msg);
-	}
-	else
-	{
-		/* ERROR */
-		purple_debug_error("msn", "error reading\n");
-
-		if (directconn->inpa)
-			purple_input_remove(directconn->inpa);
-
-		close(directconn->fd);
-
-		msn_directconn_destroy(directconn);
+		purple_debug_warning("msn", "msn_dc_send_cb: send error\n");
+		msn_dc_destroy(dc);
+		return;
 	}
 
-	g_free(body);
+	dc->progress = TRUE;
+
+	dc->msg_pos += bytes_sent;
+	if (dc->msg_pos == p->length) {
+		if (p->sent_cb != NULL)
+			p->sent_cb(p);
+
+		g_queue_pop_head(dc->out_queue);
+		msn_dc_destroy_packet(p);
+
+		dc->msg_pos = -1;
+	}
 }
 
 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;
 
-	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);
+	was_empty = g_queue_is_empty(dc->out_queue);
+	g_queue_push_tail(dc->out_queue, p);
 
-			/* 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)
+{
+	MsnDirectConnPacket	*p;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_send_foo %p\n", dc);
+
+	p = msn_dc_new_packet(4);
+
+	memcpy(p->data, "foo\0", 4);
+
+	msn_dc_enqueue_packet(dc, p);
+}
+
+static void
+msn_dc_send_handshake_with_nonce(MsnDirectConn *dc, MsnDirectConnPacket *p)
 {
-	if (error_message)
-		purple_debug_error("msn", "Error making direct connection: %s\n", error_message);
+	const gchar *h;
+
+	h = msn_dc_serialize_binary_header(dc);
+	memcpy(p->data, h, DC_PACKET_HEADER_SIZE);
+
+	memcpy(p->data + offsetof(MsnDcContext, ack_id), dc->nonce, 16);
+
+	msn_dc_enqueue_packet(dc, p);
+}
 
-	connect_cb(data, source, PURPLE_INPUT_READ);
+static void
+msn_dc_send_handshake(MsnDirectConn *dc)
+{
+	MsnDirectConnPacket *p;
+
+	p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE);
+
+	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;
+
+	msn_dc_send_handshake_with_nonce(dc, p);
 }
 
-gboolean
-msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port)
+static void
+msn_dc_send_handshake_reply(MsnDirectConn *dc)
 {
-	MsnSession *session;
+	MsnDirectConnPacket *p;
+
+	p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE);
+
+	dc->header.id = dc->slpcall->slplink->slp_seq_id++;
+	dc->header.length = 0;
 
-	g_return_val_if_fail(directconn != NULL, FALSE);
-	g_return_val_if_fail(host       != NULL, TRUE);
-	g_return_val_if_fail(port        > 0,    FALSE);
+	msn_dc_send_handshake_with_nonce(dc, p);
+}
 
-	session = directconn->slplink->session;
+static gboolean
+msn_dc_verify_handshake(MsnDirectConn *dc, guint32 packet_length)
+{
+	guchar nonce[16];
+	gchar  nonce_hash[37];
+
+	if (packet_length != DC_PACKET_HEADER_SIZE)
+		return FALSE;
+
+	memcpy(nonce, dc->in_buffer + 4 + offsetof(MsnDcContext, ack_id), 16);
 
-#if 0
-	if (session->http_method)
-	{
-		servconn->http_data->gateway_host = g_strdup(host);
-	}
-#endif
+	if (dc->nonce_type == DC_NONCE_PLAIN) {
+		if (memcmp(dc->nonce, nonce, 16) == 0) {
+			purple_debug_info("msn",
+					"Nonce from buddy request and nonce from DC attempt match, "
+					"allowing direct connection\n");
+			return TRUE;
+		} else {
+			purple_debug_warning("msn",
+					"Nonce from buddy request and nonce from DC attempt "
+					"don't match, ignoring direct connection\n");
+			return FALSE;
+		}
+
+	} else if (dc->nonce_type == DC_NONCE_SHA1) {
+		msn_dc_calculate_nonce_hash(dc->nonce_type, nonce, nonce_hash);
 
-	directconn->connect_data = purple_proxy_connect(NULL, session->account,
-			host, port, directconn_connect_cb, directconn);
+		if (g_str_equal(dc->remote_nonce, nonce_hash)) {
+			purple_debug_info("msn",
+					"Received nonce %s from buddy request "
+					"and calculated nonce %s from DC attempt. "
+					"Nonces match, allowing direct connection\n",
+					dc->remote_nonce, nonce_hash);
+			return TRUE;
+		} else {
+			purple_debug_warning("msn",
+					"Received nonce %s from buddy request "
+					"and calculated nonce %s from DC attempt. "
+					"Nonces don't match, ignoring direct connection\n",
+					dc->remote_nonce, nonce_hash);
+			return FALSE;
+		}
+	} else
+		return FALSE;
+}
 
-	return (directconn->connect_data != NULL);
+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)
 {
-	int port;
-	int fd;
+	MsnDirectConnPacket *p;
+	guint32 length;
+
+	length = msg->body_len + DC_PACKET_HEADER_SIZE;
+	p = msn_dc_new_packet(length);
+
+	memcpy(p->data, &msg->msnslp_header, DC_PACKET_HEADER_SIZE);
+	memcpy(p->data + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len);
+
+	p->sent_cb = msn_dc_send_packet_cb;
+	p->msg = msn_message_ref(msg);
+
+	msn_dc_enqueue_packet(dc, p);
+}
 
-	port = 7000;
+static int
+msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length)
+{
+	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;
 
-	for (fd = -1; fd < 0;)
-		fd = create_listener(++port);
+	case DC_STATE_HANDSHAKE:
+		if (!msn_dc_verify_handshake(dc, packet_length))
+			return DC_PROCESS_FALLBACK;
+
+		msn_dc_send_handshake_reply(dc);
+		dc->state = DC_STATE_ESTABLISHED;
 
-	directconn->fd = fd;
+		msn_slpcall_session_init(dc->slpcall);
+		dc->slpcall = NULL;
+		break;
+
+	case DC_STATE_HANDSHAKE_REPLY:
+		if (!msn_dc_verify_handshake(dc, packet_length))
+			return DC_PROCESS_FALLBACK;
+
+		dc->state = DC_STATE_ESTABLISHED;
 
-	directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb,
-		directconn);
+		msn_slpcall_session_init(dc->slpcall);
+		dc->slpcall = NULL;
+		break;
+
+	case DC_STATE_ESTABLISHED:
+		msn_slplink_process_msg(
+			dc->slplink,
+			&dc->header,
+			dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE,
+			dc->header.length
+		);
 
-	directconn->port = port;
-	directconn->c = 0;
+		/*
+		if (dc->num_calls == 0) {
+			msn_dc_destroy(dc);
+
+			return DC_PROCESS_CLOSE;
+		}
+		*/
+		break;
+	}
+
+	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_ESTABLISHED)
+			msn_dc_fallback_to_p2p(dc);
+		else
+			msn_dc_destroy(dc);
+		return;
 
-	directconn = g_new0(MsnDirectConn, 1);
+	} 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_ESTABLISHED)
+			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_FROM_LE(*((guint32*)dc->in_buffer));
+
+		if (packet_length > DC_MAX_PACKET_SIZE) {
+			/* Oversized packet */
+			purple_debug_warning("msn", "msn_dc_recv_cb: oversized packet received\n");
+			return;
+		}
 
-	directconn->slplink = slplink;
+		/* 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) {
+			g_memmove(dc->in_buffer, dc->in_buffer + 4 + packet_length, dc->in_pos - packet_length - 4);
+		}
+
+		dc->in_pos -= packet_length + 4;
+	}
+}
 
-	if (slplink->directconn != NULL)
-		purple_debug_info("msn", "got_transresp: LEAK\n");
+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);
 
-	slplink->directconn = directconn;
+	return TRUE;
+}
+
+static void
+msn_dc_init(MsnDirectConn *dc)
+{
+	g_return_if_fail(dc != NULL);
 
-	return directconn;
+	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 = data;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_connected_to_peer_cb %p\n", dc);
+
+	g_return_if_fail(dc != NULL);
+
+	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;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_incoming_connection_timeout_cb %p\n", dc);
+
+	g_return_val_if_fail(dc != NULL, FALSE);
+
+	slpcall = dc->slpcall;
+
+	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;
+	}
+
+	dc->connect_timeout_handle = 0;
+	msn_dc_destroy(dc);
 
-	if (directconn->inpa != 0)
-		purple_input_remove(directconn->inpa);
+	/* Start p2p file transfer, if possible */
+	if (slpcall)
+		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 %p\n", dc);
+
+	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;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_incoming_connection_cb %p\n", dc);
+
+	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;
+
+	if (purple_debug_is_verbose())
+		purple_debug_info("msn", "msn_dc_listen_socket_created_cb %p\n", dc);
+
+	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"
+				"%sNonce: {%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_type != DC_NONCE_PLAIN ? "Hashed-" : "",
+				dc->nonce_hash,
+				ext_ip,
+				port,
+				int_ip,
+				port
+			);
+
+		} else {
+			dc->msg_body = g_strdup_printf(
+				"Bridge: TCPv1\r\n"
+				"Listening: true\r\n"
+				"%sNonce: {%s}\r\n"
+				"IPv4External-Addrs: %s\r\n"
+				"IPv4External-Port: %d\r\n"
+				"\r\n",
 
-	g_free(directconn);
+				dc->nonce_type != DC_NONCE_PLAIN ? "Hashed-" : "",
+				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	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/directconn.h	Fri May 07 20:04:42 2010 +0000
@@ -26,36 +26,170 @@
 
 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_ESTABLISHED        /*< Handshake complete */
+} MsnDirectConnState;
+
+typedef enum
+{
+	DC_PROCESS_OK = 0,
+	DC_PROCESS_ERROR,
+	DC_PROCESS_FALLBACK,
+	DC_PROCESS_CLOSE
+
+} MsnDirectConnProcessResult;
+
+typedef enum
+{
+	DC_NONCE_UNKNOWN,	/**< Invalid scheme */
+	DC_NONCE_PLAIN,     /**< No hashing */
+	DC_NONCE_SHA1       /**< First 16 bytes of SHA1 of nonce */
+
+} MsnDirectConnNonceType;
+
+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 */
 
-	PurpleProxyConnectData *connect_data;
+	MsnDirectConnNonceType nonce_type;         /**< The type of nonce hashing */
+	guchar                 nonce[16];          /**< The nonce used for handshake */
+	gchar                  nonce_hash[37];     /**< The hash of nonce */
+	gchar                  remote_nonce[37];   /**< The remote side's nonce */
 
-	gboolean acked;
+	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 */
 
-	char *nonce;
+	int     fd;             /**< The direct connection socket */
+	guint   recv_handle;    /**< The incoming data callback handle */
+	guint   send_handle;    /**< The outgoing data callback handle */
 
-	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 socket */
+	void (*send_connection_info_msg_cb)(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);
+
+/*
+ * Fallback to switchboard connection. Used when neither side is able to
+ * create a listening socket.
+ */
+void
+msn_dc_fallback_to_p2p(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 its 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	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/msg.c	Fri May 07 20:04:42 2010 +0000
@@ -1102,7 +1102,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);
@@ -1115,59 +1116,71 @@
 				"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 invalid.\n"
+		);
+		return;
 
-		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");
+	} else if (!strcmp(command, "INVITE")) {
+		const gchar	*guid = g_hash_table_lookup(body, "Application-GUID");
+	
+		if (guid == NULL) {
+			if (!strcmp(command, "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");
 
-		accepted = TRUE;
-
-	} else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
-		purple_debug_info("msn", "Computer call\n");
+			accepted = TRUE;
 
-		if (cmdproc->session) {
-			PurpleConversation *conv = NULL;
-			gchar *from = msg->remote_user;
-			gchar *buf = NULL;
+		} else if (!strcmp(guid, MSN_FT_GUID)) {
 
-			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, "{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;
@@ -1187,6 +1200,12 @@
 			msn_switchboard_send_msg(swboard, cancel, TRUE);
 			msn_message_destroy(cancel);
 		}
+
+	} else {
+		/*
+		 * Some other already established invitation session.
+		 * Can be retrieved by Invitation-Cookie.
+		 */
 	}
 
 	g_hash_table_destroy(body);
--- a/libpurple/protocols/msn/slp.c	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/slp.c	Fri May 07 20:04:42 2010 +0000
@@ -25,23 +25,20 @@
 #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,
-					const char *type, const char *content);
+static void request_user_display(MsnUser *user);
 
-static void send_decline(MsnSlpCall *slpcall, const char *branch,
-						 const char *type, const char *content);
-
-static void request_user_display(MsnUser *user);
 
 /**************************************************************************
  * Util
@@ -91,7 +88,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);
@@ -120,7 +117,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);
@@ -198,6 +195,7 @@
 					  gsize size)
 {
 	PurpleXfer *xfer = slpcall->xfer;
+
 	purple_xfer_set_completed(xfer, TRUE);
 	purple_xfer_end(xfer);
 }
@@ -206,36 +204,8 @@
  * SLP Control
  **************************************************************************/
 
-#if 0
-static void
-got_transresp(MsnSlpCall *slpcall, const char *nonce,
-			  const char *ips_str, int port)
-{
-	MsnDirectConn *directconn;
-	char **ip_addrs, **c;
-
-	directconn = msn_directconn_new(slpcall->slplink);
-
-	directconn->initial_call = slpcall;
-
-	/* msn_directconn_parse_nonce(directconn, nonce); */
-	directconn->nonce = g_strdup(nonce);
-
-	ip_addrs = g_strsplit(ips_str, " ", -1);
-
-	for (c = ip_addrs; *c != NULL; c++)
-	{
-		purple_debug_info("msn", "ip_addr = %s\n", *c);
-		if (msn_directconn_connect(directconn, *c, port))
-			break;
-	}
-
-	g_strfreev(ip_addrs);
-}
-#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;
@@ -252,12 +222,10 @@
 	slpmsg->text_body = TRUE;
 
 	msn_slplink_queue_slpmsg(slplink, slpmsg);
-
-	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 +276,188 @@
 	return NULL;
 }
 
+static char *
+parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype)
+{
+	char *nonce;
+
+	*ntype = DC_NONCE_UNKNOWN;
+
+	nonce = get_token(content, "Hashed-Nonce: {", "}\r\n");
+	if (nonce) {
+		*ntype = DC_NONCE_SHA1;
+	} else {
+		guint32 n1, n5;
+		guint16 n2, n3, n4, n6;
+		nonce = get_token(content, "Nonce: {", "}\r\n");
+		*ntype = DC_NONCE_PLAIN;
+		if (sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+		           &n1, &n2, &n3, &n4, &n5, &n6) == 6) {
+			g_free(nonce);
+			nonce = g_malloc(16);
+			*(guint32 *)(nonce +  0) = GUINT32_TO_LE(n1);
+			*(guint16 *)(nonce +  4) = GUINT16_TO_LE(n2);
+			*(guint16 *)(nonce +  6) = GUINT16_TO_LE(n3);
+			*(guint16 *)(nonce +  8) = GUINT16_TO_BE(n4);
+			*(guint32 *)(nonce + 10) = GUINT32_TO_BE(n5);
+			*(guint16 *)(nonce + 14) = GUINT16_TO_BE(n6);
+		} else {
+			/* Invalid nonce, so ignore request */
+			g_free(nonce);
+			nonce = NULL;
+		}
+	}
+
+	return nonce;
+}
+
+static gboolean
+msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content)
+{
+	/* A direct connection negotiation response */
+	char *bridge;
+	char *nonce;
+	char *listening;
+	MsnDirectConn *dc = slpcall->slplink->dc;
+	MsnDirectConnNonceType ntype;
+	gboolean result = FALSE;
+
+	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");
+	nonce = parse_dc_nonce(content, &ntype);
+	listening = get_token(content, "Listening: ", "\r\n");
+	if (listening && bridge && !strcmp(bridge, "TCPv1")) {
+		/* Ok, the client supports direct TCP connection */
+
+		/* We always need this. */
+		if (ntype == DC_NONCE_SHA1) {
+			strncpy(dc->remote_nonce, nonce, 36);
+			dc->remote_nonce[36] = '\0';
+		}
+
+		if (!strcasecmp(listening, "false")) {
+			if (dc->listen_data != NULL) {
+				/*
+				 * We'll listen for incoming connections but
+				 * the listening socket isn't ready yet so we cannot
+				 * send the INVITE packet now. Put the slpcall into waiting mode
+				 * and let the callback send the invite.
+				 */
+				slpcall->wait_for_socket = TRUE;
+
+			} else if (dc->listenfd != -1) {
+				/* The listening socket is ready. Send the INVITE here. */
+				msn_dc_send_invite(dc);
+
+			} else {
+				/* We weren't able to create a listener either. Use SB. */
+				msn_dc_fallback_to_p2p(dc);
+			}
+
+		} else {
+			/*
+			 * We should connect to the client so parse
+			 * IP/port from response.
+			 */
+			char *ip, *port_str;
+			int port = 0;
+
+			if (ntype == DC_NONCE_PLAIN) {
+				/* Only needed for listening side. */
+				memcpy(dc->nonce, nonce, 16);
+			}
+
+			/* Cancel any listen attempts because we don't need them. */
+			if (dc->listenfd_handle != 0) {
+				purple_input_remove(dc->listenfd_handle);
+				dc->listenfd_handle = 0;
+			}
+			if (dc->connect_timeout_handle != 0) {
+				purple_timeout_remove(dc->connect_timeout_handle);
+				dc->connect_timeout_handle = 0;
+			}
+			if (dc->listenfd != -1) {
+				purple_network_remove_port_mapping(dc->listenfd);
+				close(dc->listenfd);
+				dc->listenfd = -1;
+			}
+			if (dc->listen_data != NULL) {
+				purple_network_listen_cancel(dc->listen_data);
+				dc->listen_data = NULL;
+			}
+
+			/* Save external IP/port for later use. We'll try local connection first. */
+			dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4External-Port: ", "\r\n");
+			if (port_str) {
+				dc->ext_port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
+			port_str = get_token(content, "IPv4Internal-Port: ", "\r\n");
+			if (port_str) {
+				port = atoi(port_str);
+				g_free(port_str);
+			}
+
+			if (ip && port) {
+				/* Try internal address first */
+				dc->connect_data = purple_proxy_connect(
+					NULL,
+					slpcall->slplink->session->account,
+					ip,
+					port,
+					msn_dc_connected_to_peer_cb,
+					dc
+				);
+
+				if (dc->connect_data) {
+					/* Add connect timeout handle */
+					dc->connect_timeout_handle = purple_timeout_add_seconds(
+						DC_CONNECT_TIMEOUT,
+						msn_dc_outgoing_connection_timeout_cb,
+						dc
+					);
+				} else {
+					/*
+					 * Connection failed
+					 * Try external IP/port (if specified)
+					 */
+					msn_dc_outgoing_connection_timeout_cb(dc);
+				}
+
+			} else {
+				/*
+				 * Omitted or invalid internal IP address / port
+				 * Try external IP/port (if specified)
+				 */
+				msn_dc_outgoing_connection_timeout_cb(dc);
+			}
+
+			g_free(ip);
+		}
+
+		result = TRUE;
+
+	} else {
+		/*
+		 * Invalid direct connect invitation or
+		 * TCP connection is not supported
+		 */
+	}
+
+	g_free(listening);
+	g_free(nonce);
+	g_free(bridge);
+
+	return result;
+}
+
 static void
 got_sessionreq(MsnSlpCall *slpcall, const char *branch,
 			   const char *euf_guid, const char *context)
@@ -330,7 +480,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);
@@ -485,7 +635,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);
 	}
 }
@@ -554,92 +704,92 @@
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
 	{
-		/* A direct connection? */
+		/* A direct connection negotiation request */
+		char *bridges;
+		char *nonce;
+		MsnDirectConnNonceType ntype;
 
-		char *listening, *nonce;
-		char *content;
+		purple_debug_info("msn", "got_invite: transreqbody received\n");
+
+		/* Don't do anything if we already have a direct connection */
+		if (slpcall->slplink->dc != NULL)
+			return;
 
-		if (FALSE)
-		{
-#if 0
-			MsnDirectConn *directconn;
-			/* const char *ip_addr; */
-			char *ip_port;
-			int port;
+		bridges = get_token(content, "Bridges: ", "\r\n");
+		nonce = parse_dc_nonce(content, &ntype);
+		if (nonce && bridges && strstr(bridges, "TCPv1") != NULL) {
+			/*
+			 * Ok, the client supports direct TCP connection
+			 * Try to create a listening port
+			 */
+			MsnDirectConn *dc;
 
-			/* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */
-			ip_port = "5190";
-			listening = "true";
-			nonce = rand_guid();
+			dc = msn_dc_new(slpcall);
+			if (ntype == DC_NONCE_PLAIN) {
+				/* There is only one nonce for plain auth. */
+				dc->nonce_type = ntype;
+				memcpy(dc->nonce, nonce, 16);
+			} else if (ntype == DC_NONCE_SHA1) {
+				/* Each side has a nonce in SHA1 auth. */
+				dc->nonce_type = ntype;
+				strncpy(dc->remote_nonce, nonce, 36);
+				dc->remote_nonce[36] = '\0';
+			}
 
-			directconn = msn_directconn_new(slplink);
-
-			/* msn_directconn_parse_nonce(directconn, nonce); */
-			directconn->nonce = g_strdup(nonce);
-
-			msn_directconn_listen(directconn);
+			dc->listen_data = purple_network_listen_range(
+				0, 0,
+				SOCK_STREAM,
+				msn_dc_listen_socket_created_cb,
+				dc
+			);
 
-			port = directconn->port;
+			if (dc->listen_data == NULL) {
+				/* Listen socket creation failed */
+
+				purple_debug_info("msn", "got_invite: listening failed\n");
 
-			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
-		}
-		else
-		{
-			listening = "false";
-			nonce = g_strdup("00000000-0000-0000-0000-000000000000");
+				if (dc->nonce_type != DC_NONCE_PLAIN)
+					msn_slp_send_ok(slpcall, branch,
+						"application/x-msnmsgr-transrespbody",
+						"Bridge: TCPv1\r\n"
+						"Listening: false\r\n"
+						"Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+						"\r\n");
+				else
+					msn_slp_send_ok(slpcall, branch,
+						"application/x-msnmsgr-transrespbody",
+						"Bridge: TCPv1\r\n"
+						"Listening: false\r\n"
+						"Nonce: {00000000-0000-0000-0000-000000000000}\r\n"
+						"\r\n");
 
-			content = g_strdup_printf(
-				"Bridge: TCPv1\r\n"
-				"Listening: %s\r\n"
-				"Nonce: {%s}\r\n"
-				"\r\n",
-				listening,
-				nonce);
+			} 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.
+			 */
 		}
 
-		send_ok(slpcall, branch,
-				"application/x-msnmsgr-transrespbody", content);
-
-		g_free(content);
 		g_free(nonce);
+		g_free(bridges);
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
 	{
-#if 0
-		char *ip_addrs;
-		char *temp;
-		char *nonce;
-		int port;
-
-		nonce = get_token(content, "Nonce: {", "}\r\n");
-		if (ip_addrs == NULL)
-			return;
-
-		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
-
-		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
-		if (temp != NULL)
-			port = atoi(temp);
-		else
-			port = -1;
-		g_free(temp);
-
-		if (port > 0)
-			got_transresp(slpcall, nonce, ip_addrs, port);
-
-		g_free(nonce);
-		g_free(ip_addrs);
-#endif
+		/* A direct connection negotiation response */
+		msn_slp_process_transresp(slpcall, content);
 	}
 }
 
@@ -652,52 +802,97 @@
 
 	if (!strcmp(type, "application/x-msnmsgr-sessionreqbody"))
 	{
-#if 0
-		if (slpcall->type == MSN_SLPCALL_DC)
-		{
-			/* First let's try a DirectConnection. */
+		char *content;
+		char *header;
+		char *nonce = NULL;
+		MsnSlpMessage *msg;
+		MsnDirectConn *dc;
+		MsnUser *user;
+
+		if (slpcall->slplink->dc != NULL) {
+			/* If we already have an established direct connection
+			 * then just start the transfer.
+			 */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
+
+		user = msn_userlist_find_user(slpcall->slplink->session->userlist,
+		                              slpcall->slplink->remote_user);
+		if (!user || !(user->clientid & 0xF0000000))	{
+			/* Just start a normal SB transfer. */
+			msn_slpcall_session_init(slpcall);
+			return;
+		}
 
-			MsnSlpLink *slplink;
-			MsnSlpMessage *slpmsg;
-			char *header;
-			char *content;
-			char *branch;
+		/* 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
+		);
 
-			slplink = slpcall->slplink;
+		header = g_strdup_printf(
+			"INVITE MSNMSGR:%s MSNSLP/1.0",
+			slpcall->slplink->remote_user
+		);
 
-			branch = rand_guid();
+		if (dc->nonce_type == DC_NONCE_SHA1)
+			nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash);
+
+		if (dc->listen_data == NULL) {
+			/* Listen socket creation failed */
+			purple_debug_info("msn", "got_ok: listening failed\n");
 
 			content = g_strdup_printf(
-				"Bridges: TRUDPv1 TCPv1\r\n"
+				"Bridges: TCPv1\r\n"
+				"NetID: %u\r\n"
+				"Conn-Type: IP-Restrict-NAT\r\n"
+				"UPnPNat: false\r\n"
+				"ICF: false\r\n"
+				"%s"
+				"\r\n",
+
+				rand() % G_MAXUINT32,
+				nonce ? nonce : ""
+			);
+
+		} else {
+			/* Listen socket created successfully. */
+			purple_debug_info("msn", "got_ok: listening socket created\n");
+
+			content = g_strdup_printf(
+				"Bridges: TCPv1\r\n"
 				"NetID: 0\r\n"
 				"Conn-Type: Direct-Connect\r\n"
 				"UPnPNat: false\r\n"
 				"ICF: false\r\n"
-			);
-
-			header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0",
-									 slplink->remote_user);
+				"%s"
+				"\r\n",
 
-			slpmsg = msn_slp_sipmsg_new(slpcall, 0, header, branch,
-										"application/x-msnmsgr-transreqbody",
-										content);
-
-			slpmsg->info = "SLP INVITE";
-			slpmsg->text_body = TRUE;
-			msn_slplink_send_slpmsg(slplink, slpmsg);
+				nonce ? nonce : ""
+			);
+		}
 
-			g_free(header);
-			g_free(content);
+		msg = msn_slpmsg_sip_new(
+			slpcall,
+			0,
+			header,
+			slpcall->branch,
+			"application/x-msnmsgr-transreqbody",
+			content
+		);
+		msg->info = "DC INVITE";
+		msg->text_body = TRUE;
+		g_free(nonce);
+		g_free(header);
+		g_free(content);
 
-			g_free(branch);
-		}
-		else
-		{
-			msn_slpcall_session_init(slpcall);
-		}
-#else
-		msn_slpcall_session_init(slpcall);
-#endif
+		msn_slplink_queue_slpmsg(slpcall->slplink, msg);
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transreqbody"))
 	{
@@ -706,31 +901,7 @@
 	}
 	else if (!strcmp(type, "application/x-msnmsgr-transrespbody"))
 	{
-#if 0
-		char *ip_addrs;
-		char *temp;
-		char *nonce;
-		int port;
-
-		nonce = get_token(content, "Nonce: {", "}\r\n");
-		if (ip_addrs == NULL)
-			return;
-
-		ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n");
-
-		temp = get_token(content, "IPv4Internal-Port: ", "\r\n");
-		if (temp != NULL)
-			port = atoi(temp);
-		else
-			port = -1;
-		g_free(temp);
-
-		if (port > 0)
-			got_transresp(slpcall, nonce, ip_addrs, port);
-
-		g_free(nonce);
-		g_free(ip_addrs);
-#endif
+		msn_slp_process_transresp(slpcall, content);
 	}
 }
 
@@ -773,18 +944,24 @@
 
 		content = get_token(body, "\r\n\r\n", NULL);
 
-		if (branch && call_id && content_type && content)
+		slpcall = NULL;
+		if (branch && call_id)
 		{
-			slpcall = msn_slpcall_new(slplink);
-			slpcall->id = call_id;
-			got_invite(slpcall, branch, content_type, content);
-		}
-		else
-		{
-			g_free(call_id);
-			slpcall = NULL;
+			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 = g_strdup(call_id);
+				got_invite(slpcall, branch, content_type, content);
+			}
 		}
 
+		g_free(call_id);
 		g_free(branch);
 		g_free(content_type);
 		g_free(content);
@@ -866,6 +1043,8 @@
 {
 	MsnSession *session;
 	MsnSlpLink *slplink;
+	const char *data;
+	gsize len;
 
 	session = cmdproc->servconn->session;
 	slplink = msn_session_get_slplink(session, msg->remote_user);
@@ -888,7 +1067,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	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/slp.h	Fri May 07 20:04:42 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.h	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/slpcall.h	Fri May 07 20:04:42 2010 +0000
@@ -64,6 +64,8 @@
 	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);
--- a/libpurple/protocols/msn/slplink.c	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/slplink.c	Fri May 07 20:04:42 2010 +0000
@@ -101,10 +101,8 @@
 
 	session = slplink->session;
 
-#if 0
-	if (slplink->directconn != NULL)
-		msn_directconn_destroy(slplink->directconn);
-#endif
+	if (slplink->dc != NULL)
+		msn_dc_destroy(slplink->dc);
 
 	while (slplink->slp_calls != NULL)
 		msn_slpcall_destroy(slplink->slp_calls->data);
@@ -185,11 +183,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_ESTABLISHED)
+		msn_dc_ref(slplink->dc);
+	*/
 }
 
 void
 msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall)
 {
+	/*
+	if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
+		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.
@@ -197,6 +205,10 @@
 	 * destroyed. */
 	if (slplink->slp_calls == NULL && slplink->swboard != NULL)
 		msn_switchboard_release(slplink->swboard, MSN_SB_FLAG_FT);
+
+	/* The slplink has no slpcalls in it, release it from the DC. */
+	if (slplink->slp_calls == NULL && slplink->dc != NULL)
+		msn_dc_destroy(slplink->dc);
 }
 
 MsnSlpCall *
@@ -239,13 +251,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_ESTABLISHED)
 	{
-		msn_directconn_send_msg(slplink->directconn, msg);
+		msn_dc_enqueue_msg(slplink->dc, msg);
 	}
 	else
-#endif
 	{
 		if (slplink->swboard == NULL)
 		{
@@ -461,21 +471,29 @@
 	}
 }
 
-static void
-msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg)
+static MsnSlpMessage *
+msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header)
 {
 	MsnSlpMessage *slpmsg;
 
 	slpmsg = msn_slpmsg_new(slplink);
 
-	slpmsg->session_id = msg->msnslp_header.session_id;
-	slpmsg->size       = msg->msnslp_header.total_size;
+	slpmsg->session_id = header->session_id;
+	slpmsg->size       = header->total_size;
 	slpmsg->flags      = 0x02;
-	slpmsg->ack_id     = msg->msnslp_header.id;
-	slpmsg->ack_sub_id = msg->msnslp_header.ack_id;
-	slpmsg->ack_size   = msg->msnslp_header.total_size;
+	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);
+
 	msn_slplink_send_slpmsg(slplink, slpmsg);
 	msn_slpmsg_destroy(slpmsg);
 }
@@ -521,38 +539,27 @@
 }
 
 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);
-
-	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)
 		{
@@ -597,7 +604,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 */
@@ -645,8 +652,7 @@
 		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;
@@ -658,32 +664,45 @@
 			return;
 		}
 
-		if (!slpcall->wasted) {
-			if (slpmsg->flags == 0x100)
-			{
-				MsnDirectConn *directconn;
+		purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n");
 
-				directconn = slplink->directconn;
+		if (/* !slpcall->wasted && */ slpmsg->flags == 0x100)
+		{
 #if 0
-				if (!directconn->acked)
-					msn_directconn_send_handshake(directconn);
+			MsnDirectConn *directconn;
+
+			directconn = slplink->directconn;
+			if (!directconn->acked)
+				msn_directconn_send_handshake(directconn);
 #endif
-			}
-			else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||  
-			         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||  
-			         slpmsg->flags == 0x1000030)
-			{
-				/* Release all the messages and send the ACK */
+		}
+		else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||
+		         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||
+		         slpmsg->flags == 0x1000030)
+		{
+			/* Release all the messages and send the ACK */
 
-				msn_slplink_send_ack(slplink, msg);
+			if (slpcall->wait_for_socket) {
+				/*
+				 * Save ack for later because we have to send
+				 * a 200 OK message to the previous direct connect
+				 * invitation before ACK but the listening socket isn't
+				 * created yet.
+				 */
+				purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n");
+
+				slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header);
+			} else if (!slpcall->wasted) {
+				purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n");
+
+				msn_slplink_send_ack(slplink, header);
 				msn_slplink_send_queued_slpmsgs(slplink);
 			}
-
 		}
 
 		msn_slpmsg_destroy(slpmsg);
 
-		if (slpcall->wasted)
+		if (!slpcall->wait_for_socket && slpcall->wasted)
 			msn_slpcall_destroy(slpcall);
 	}
 }
--- a/libpurple/protocols/msn/slplink.h	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/slplink.h	Fri May 07 20:04:42 2010 +0000
@@ -42,6 +42,7 @@
 {
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
+	MsnDirectConn *dc;
 
 	int refs;
 
@@ -49,8 +50,6 @@
 
 	int slp_seq_id;
 
-	MsnDirectConn *directconn;
-
 	GList *slp_calls;
 	GList *slp_msgs;
 
@@ -84,7 +83,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/state.c	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/state.c	Fri May 07 20:04:42 2010 +0000
@@ -260,7 +260,7 @@
 
 	if (msnobj == NULL)
 	{
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d", state_text, caps);
+		msn_cmdproc_send(cmdproc, "CHG", "%s %u", state_text, caps);
 	}
 	else
 	{
@@ -268,7 +268,7 @@
 
 		msnobj_str = msn_object_to_string(msnobj);
 
-		msn_cmdproc_send(cmdproc, "CHG", "%s %d %s", state_text,
+		msn_cmdproc_send(cmdproc, "CHG", "%s %u %s", state_text,
 						 caps, purple_url_encode(msnobj_str));
 
 		g_free(msnobj_str);
--- a/libpurple/protocols/msn/switchboard.c	Thu May 06 21:28:32 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Fri May 07 20:04:42 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) {
+		MsnSlpLink *slplink = swboard->slplinks->data;
+
+		/* Destroy only those slplinks which use the switchboard */
+		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)