diff libpurple/protocols/msn/directconn.c @ 30013:119bd7b072eb

Initial support for direct connections. Preliminary patch from ticket #247 by Gbor 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 3157a8ea0012
children b1cda3f8fdc9
line wrap: on
line diff
--- 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;
+		}
+	}
 }
+