diff libpurple/protocols/mxit/protocol.c @ 28526:69aa4660401a

Initial addition of the MXit protocol plugin, provided by the MXit folks themselves.
author John Bailey <rekkanoryo@rekkanoryo.org>
date Sun, 08 Nov 2009 23:55:56 +0000
parents
children 7d0b473f2295
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/mxit/protocol.c	Sun Nov 08 23:55:56 2009 +0000
@@ -0,0 +1,2442 @@
+/*
+ *					MXit Protocol libPurple Plugin
+ *
+ *			-- MXit client protocol implementation --
+ *
+ *				Pieter Loubser	<libpurple@mxit.com>
+ *
+ *			(C) Copyright 2009	MXit Lifestyle (Pty) Ltd.
+ *				<http://www.mxitlifestyle.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include	<stdio.h>
+#include	<unistd.h>
+#include	<string.h>
+#include	<errno.h>
+
+#include	"purple.h"
+
+#include	"protocol.h"
+#include	"mxit.h"
+#include	"roster.h"
+#include	"chunk.h"
+#include	"filexfer.h"
+#include	"markup.h"
+#include	"multimx.h"
+#include	"splashscreen.h"
+#include	"login.h"
+#include	"formcmds.h"
+#include	"http.h"
+
+
+#define		MXIT_MS_OFFSET		3
+
+/* configure the right record terminator char to use */
+#define		CP_REC_TERM			( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM )
+
+
+
+/*------------------------------------------------------------------------
+ * Display a notification popup message to the user.
+ *
+ *  @param type			The type of notification:
+ *		- info:		PURPLE_NOTIFY_MSG_INFO
+ *		- warning:	PURPLE_NOTIFY_MSG_WARNING
+ *		- error:	PURPLE_NOTIFY_MSG_ERROR
+ *  @param heading		Heading text
+ *  @param message		Message text
+ */
+void mxit_popup( int type, const char* heading, const char* message )
+{
+	/* (reference: "libpurple/notify.h") */
+	purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL );
+}
+
+
+/*------------------------------------------------------------------------
+ * For compatibility with legacy clients, all usernames are sent from MXit with a domain
+ *  appended.  For MXit contacts, this domain is set to "@m".  This function strips
+ *  those fake domains.
+ *
+ *  @param username		The username of the contact
+ */
+void mxit_strip_domain( char* username )
+{
+	if ( g_str_has_suffix( username, "@m" ) )
+		username[ strlen(username) - 2 ] = '\0';
+}
+
+
+/*------------------------------------------------------------------------
+ * Dump a byte buffer to the console for debugging purposes.
+ *
+ *  @param buf			The data
+ *  @param len			The data length
+ */
+void dump_bytes( struct MXitSession* session, const char* buf, int len )
+{
+	char		msg[( len * 3 ) + 1];
+	int			i;
+
+	memset( msg, 0x00, sizeof( msg ) );
+
+	for ( i = 0; i < len; i++ ) {
+		if ( buf[i] == CP_REC_TERM )		/* record terminator */
+			msg[i] = '!';
+		else if ( buf[i] == CP_FLD_TERM )	/* field terminator */
+			msg[i] = '^';
+		else if ( buf[i] == CP_PKT_TERM )	/* packet terminator */
+			msg[i] = '@';
+		else if ( buf[i] < 0x20 )
+			msg[i] = '_';
+		else
+			msg[i] = buf[i];
+
+	}
+
+	purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg );
+}
+
+
+/*------------------------------------------------------------------------
+ * Determine if we have an active chat with a specific contact
+ *
+ *  @param session		The MXit session object
+ *  @param who			The contact name
+ *  @return				Return true if we have an active chat with the contact
+ */
+gboolean find_active_chat( const GList* chats, const char* who )
+{
+	const GList*	list	= chats;
+	const char*		chat	= NULL;
+
+	while ( list ) {
+		chat = (const char*) list->data;
+
+		if ( strcmp( chat, who ) == 0 )
+			return TRUE;
+
+		list = g_list_next( list );
+	}
+
+	return FALSE;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet transmission
+ */
+
+/*------------------------------------------------------------------------
+ * Remove next packet from transmission queue.
+ *
+ *  @param session		The MXit session object
+ *  @return				The next packet for transmission (or NULL)
+ */
+static struct tx_packet* pop_tx_packet( struct MXitSession* session )
+{
+	struct tx_packet*	packet	= NULL;
+
+	if ( session->queue.count > 0 ) {
+		/* dequeue the next packet */
+		packet = session->queue.packets[session->queue.rd_i];
+		session->queue.packets[session->queue.rd_i] = NULL;
+		session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE;
+		session->queue.count--;
+	}
+
+	return packet;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add packet to transmission queue.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The packet to transmit
+ *  @return				Return TRUE if packet was enqueue, or FALSE if queue is full.
+ */
+static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+	if ( session->queue.count < MAX_QUEUE_SIZE ) {
+		/* enqueue packet */
+		session->queue.packets[session->queue.wr_i] = packet;
+		session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE;
+		session->queue.count++;
+		return TRUE;
+	}
+	else
+		return FALSE;		/* queue is full */
+}
+
+
+/*------------------------------------------------------------------------
+ * Deallocate transmission packet.
+ *
+ *  @param packet		The packet to deallocate.
+ */
+static void free_tx_packet( struct tx_packet* packet )
+{
+	g_free( packet->data );
+	g_free( packet );
+	packet = NULL;
+}
+
+
+/*------------------------------------------------------------------------
+ * Flush all the packets from the tx queue and release the resources.
+ *
+ *  @param session		The MXit session object
+ */
+static void flush_queue( struct MXitSession* session )
+{
+	struct tx_packet*	packet;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" );
+
+	while ( (packet = pop_tx_packet( session ) ) != NULL )
+		free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the TCP connection.
+ *
+ *  @param fd			The file descriptor
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen )
+{
+	int		written;
+	int		res;
+
+	written = 0;
+	while ( written < pktlen ) {
+		res = write( fd, &pktdata[written], pktlen - written );
+		if ( res <= 0 ) {
+			/* error on socket */
+			if ( errno == EAGAIN )
+				continue;
+
+			purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res );
+			return -1;
+		}
+		written += res;
+	}
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback called for handling a HTTP GET response
+ *
+ *  @param url_data			libPurple internal object (see purple_util_fetch_url_request)
+ *  @param user_data		The MXit session object
+ *  @param url_text			The data returned (could be NULL if error)
+ *  @param len				The length of the data returned (0 if error)
+ *  @param error_message	Descriptive error message
+ */
+static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
+{
+	struct MXitSession*		session		= (struct MXitSession*) user_data;
+
+	/* clear outstanding request */
+	session->http_out_req = NULL;
+
+	if ( ( !url_text ) || ( len == 0 ) ) {
+		/* error with request */
+		purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message );
+		return;
+	}
+
+	/* convert the HTTP result */
+	memcpy( session->rx_dbuf, url_text, len );
+	session->rx_i = len;
+
+	mxit_parse_packet( session );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (GET style).
+ *
+ *  @param session		The MXit session object
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet )
+{
+	char*		part	= NULL;
+	char*		url		= NULL;
+
+	if ( packet->datalen > 0 ) {
+		char*	tmp		= NULL;
+
+		tmp = g_strndup( packet->data, packet->datalen );
+		part = g_strdup( purple_url_encode( tmp ) );
+		g_free( tmp );
+	}
+
+	url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url );
+#endif
+
+	/* send the HTTP request */
+	session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session );
+
+	g_free( url );
+	if ( part )
+		g_free( part );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 3: Write the packet data to the HTTP connection (POST style).
+ *
+ *  @param session		The MXit session object
+ *  @param pktdata		The packet data
+ *  @param pktlen		The length of the packet data
+ *  @return				Return -1 on error, otherwise 0
+ */
+static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet )
+{
+	char		request[256 + packet->datalen];
+	int			reqlen;
+	char*		host_name;
+	int			host_port;
+	gboolean	ok;
+
+	/* extract the HTTP host name and host port number to connect to */
+	ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL );
+	if ( !ok ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server );
+	}
+
+	/* strip off the last '&' from the header */
+	packet->header[packet->headerlen - 1] = '\0';
+	packet->headerlen--;
+
+	/* build the HTTP request packet */
+	reqlen = g_snprintf( request, 256,
+					"POST %s?%s HTTP/1.1\r\n"
+			  		"User-Agent: " MXIT_HTTP_USERAGENT "\r\n"
+					"Content-Type: application/octet-stream\r\n"
+			 		"Host: %s\r\n"
+					"Content-Length: %" G_GSIZE_FORMAT "\r\n"
+					"\r\n",
+					session->http_server,
+					purple_url_encode( packet->header ),
+					host_name,
+					packet->datalen - MXIT_MS_OFFSET
+	);
+
+	/* copy over the packet body data (could be binary) */
+	memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET );
+	reqlen += packet->datalen;
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" );
+	dump_bytes( session, request, reqlen );
+#endif
+
+	/* send the request to the HTTP server */
+	mxit_http_send_request( session, host_name, host_port, request, reqlen );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 2: Handle the transmission of the packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The packet to transmit
+ */
+static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet )
+{
+	int		res;
+
+	if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+		/* we are not connected so ignore all packets to be send */
+		purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" );
+		return;
+	}
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen );
+#ifdef	DEBUG_PROTOCOL
+	dump_bytes( session, packet->header, packet->headerlen );
+	dump_bytes( session, packet->data, packet->datalen );
+#endif
+
+	if ( !session->http ) {
+		/* socket connection */
+		char		data[packet->datalen + packet->headerlen];
+		int			datalen;
+
+		/* create raw data buffer */
+		memcpy( data, packet->header, packet->headerlen );
+		memcpy( data + packet->headerlen, packet->data, packet->datalen );
+		datalen = packet->headerlen + packet->datalen;
+
+		res = mxit_write_sock_packet( session->fd, data, datalen );
+		if ( res < 0 ) {
+			/* we must have lost the connection, so terminate it so that we can reconnect */
+			purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) );
+		}
+	}
+	else {
+		/* http connection */
+
+		if ( packet->cmd == CP_CMD_MEDIA ) {
+			/* multimedia packets must be send with a HTTP POST */
+			mxit_write_http_post( session, packet );
+		}
+		else {
+			mxit_write_http_get( session, packet );
+		}
+	}
+
+	/* update the timestamp of the last-transmitted packet */
+	session->last_tx = time( NULL );
+
+	/*
+	 * we need to remember that we are still waiting for the ACK from
+	 * the server on this request
+	 */
+	session->outack = packet->cmd;
+
+	/* free up the packet resources */
+	free_tx_packet( packet );
+}
+
+
+/*------------------------------------------------------------------------
+ * TX Step 1: Create a new Tx packet and queue it for sending.
+ *
+ *  @param session		The MXit session object
+ *  @param data			The packet data (payload)
+ *  @param datalen		The length of the packet data
+ *  @param cmd			The MXit command for this packet
+ */
+static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd )
+{
+	struct tx_packet*	packet;
+	char				header[256];
+	int					hlen;
+
+	/* create a packet for sending */
+	packet = g_new0( struct tx_packet, 1 );
+	packet->data = g_malloc0( datalen );
+	packet->cmd = cmd;
+	packet->headerlen = 0;
+
+	/* create generic packet header */
+	hlen = sprintf( header,	"id=%s%c", session->acc->username, CP_REC_TERM );			/* client msisdn */
+
+	if ( session->http ) {
+		/* http connection only */
+		hlen += sprintf( header + hlen,	"s=" );
+		if ( session->http_sesid > 0 ) {
+			hlen += sprintf( header + hlen,	"%u%c", session->http_sesid, CP_FLD_TERM );	/* http session id */
+		}
+		session->http_seqno++;
+		hlen += sprintf( header + hlen,	"%u%c", session->http_seqno, CP_REC_TERM );		/* http request sequence id */
+	}
+
+	hlen += sprintf( header + hlen,	"cm=%i%c", cmd, CP_REC_TERM ); 						/* packet command */
+
+	if ( !session->http ) {
+		/* socket connection only */
+		packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM );		/* packet length */
+	}
+
+	/* copy the header to packet */
+	memcpy( packet->header + packet->headerlen, header, hlen );
+	packet->headerlen += hlen;
+
+	/* copy payload to packet */
+	if ( datalen > 0 )
+		memcpy( packet->data, data, datalen );
+	packet->datalen = datalen;
+
+
+	/*
+	 * shortcut: first check if there are any commands still outstanding.
+	 * if not, then we might as well just write this packet directly and
+	 * skip the whole queueing thing
+	 */
+	if ( session->outack == 0 ) {
+		/* no outstanding ACKs, so we might as well write it directly */
+		mxit_send_packet( session, packet );
+	}
+	else {
+		/* ACK still outstanding, so we need to queue this request until we have the ACK */
+
+		if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) {
+			/* we do NOT queue HTTP poll nor socket ping packets */
+			free_tx_packet( packet );
+			return;
+		}
+
+		purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd );
+		if ( !push_tx_packet( session, packet ) ) {
+			/* packet could not be queued for transmission */
+			mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) );
+			free_tx_packet( packet );
+		}
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage the packet send queue (send next packet, timeout's, etc).
+ *
+ *  @param session		The MXit session object
+ */
+gboolean mxit_manage_queue( gpointer user_data )
+{
+	struct MXitSession* session		= (struct MXitSession*) user_data;
+	struct tx_packet*	packet		= NULL;
+
+	if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+		/* we are not connected, so ignore the queue */
+		return TRUE;
+	}
+	else if ( session->outack > 0 ) {
+		/* we are still waiting for an outstanding ACK from the MXit server */
+		if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) {
+			/* ack timeout! so we close the connection here */
+			purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack );
+			purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) );
+		}
+		return TRUE;
+	}
+
+	packet = pop_tx_packet( session );
+	if ( packet != NULL ) {
+		/* there was a packet waiting to be sent to the server, now is the time to do something about it */
+
+		/* send the packet to MXit server */
+		mxit_send_packet( session, packet );
+	}
+
+	return TRUE;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback to manage HTTP server polling (HTTP connections ONLY)
+ *
+ *  @param session		The MXit session object
+ */
+gboolean mxit_manage_polling( gpointer user_data )
+{
+	struct MXitSession* session		= (struct MXitSession*) user_data;
+	gboolean			poll		= FALSE;
+	time_t				now			= time( NULL );
+	int					polldiff;
+	int					rxdiff;
+
+	if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+		/* we only poll if we are actually logged in */
+		return TRUE;
+	}
+
+	/* calculate the time differences */
+	rxdiff = now - session->last_rx;
+	polldiff = now - session->http_last_poll;
+
+	if ( rxdiff < MXIT_HTTP_POLL_MIN ) {
+		/* we received some reply a few moments ago, so reset the poll interval */
+		session->http_interval = MXIT_HTTP_POLL_MIN;
+	}
+	else if ( session->http_last_poll < ( now - session->http_interval ) ) {
+		/* time to poll again */
+		poll = TRUE;
+
+		/* back-off some more with the polling */
+		session->http_interval = session->http_interval + ( session->http_interval / 2 );
+		if ( session->http_interval > MXIT_HTTP_POLL_MAX )
+			session->http_interval = MXIT_HTTP_POLL_MAX;
+	}
+
+	/* debugging */
+	//purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff );
+
+	if ( poll ) {
+		/* send poll request */
+		session->http_last_poll = time( NULL );
+		mxit_send_poll( session );
+	}
+
+	return TRUE;
+}
+
+
+/*========================================================================================================================
+ * Send MXit operations.
+ */
+
+/*------------------------------------------------------------------------
+ * Send a ping/keepalive packet to MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_ping( struct MXitSession* session )
+{
+	/* queue packet for transmission */
+	mxit_queue_packet( session, NULL, 0, CP_CMD_PING );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a poll request to the HTTP server (HTTP connections ONLY).
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_poll( struct MXitSession* session )
+{
+	/* queue packet for transmission */
+	mxit_queue_packet( session, NULL, 0, CP_CMD_POLL );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a logout packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_logout( struct MXitSession* session )
+{
+	/* queue packet for transmission */
+	mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a register packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_register( struct MXitSession* session )
+{
+	struct MXitProfile*	profile		= session->profile;
+	const char*			locale;
+	char				data[CP_MAX_PACKET];
+	int					datalen;
+
+	locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%i%c%s%c"		/* "ms"=password\1version\1maxreplyLen\1name\1 */
+								"%s%c%i%c%s%c%s%c"			/* dateOfBirth\1gender\1location\1capabilities\1 */
+								"%s%c%i%c%s%c%s",			/* dc\1features\1dialingcode\1locale */
+								session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM,
+								profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, 
+								session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a login packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_send_login( struct MXitSession* session )
+{
+	const char*	splashId;
+	const char*	locale;
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%i%c"			/* "ms"=password\1version\1getContacts\1 */
+								"%s%c%s%c%i%c"				/* capabilities\1dc\1features\1 */
+								"%s%c%s",					/* dialingcode\1locale */
+								session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM,
+								MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM,
+								session->dialcode, CP_FLD_TERM, locale
+	);
+
+	/* include "custom resource" information */
+	splashId = splash_current( session );
+	if ( splashId != NULL )
+		datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a chat message packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param to			The username of the recipient
+ *  @param msg			The message text
+ */
+void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup )
+{
+	char		data[CP_MAX_PACKET];
+	char*		markuped_msg;
+	int			datalen;
+	int			msgtype = CP_MSGTYPE_NORMAL;
+
+	/* first we need to convert the markup from libPurple to MXit format */
+	if ( parse_markup )
+		markuped_msg = mxit_convert_markup_tx( msg, &msgtype );
+	else
+		markuped_msg = g_strdup( msg );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%i%c%i",		/* "ms"=jid\1msg\1type\1flags */
+								to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON
+	);
+
+	/* free the resources */
+	g_free( markuped_msg );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a extended profile request packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		Username who's profile is being requested (NULL = our own)
+ *  @param nr_attribs	Number of attributes being requested
+ *  @param attributes	The names of the attributes
+ */
+void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] )
+{
+	char			data[CP_MAX_PACKET];
+	int				datalen;
+	unsigned int	i;
+
+	datalen = sprintf( data,	"ms=%s%c%i",		/* "ms="mxitid\1nr_attributes */
+								(username ? username : ""), CP_FLD_TERM, nr_attrib);
+
+	/* add attributes */
+	for ( i = 0; i < nr_attrib; i++ )
+		datalen += sprintf(	data + datalen, "%c%s", CP_FLD_TERM, attribute[i] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update profile packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param password		The new password to be used for logging in (optional)
+ *	@param nr_attrib	The number of attributes
+ *	@param attributes	String containing the attributes and settings seperated by '0x01'
+ */
+void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes )
+{
+	char			data[CP_MAX_PACKET];
+	gchar**			parts;
+	int				datalen;
+	unsigned int	i;
+
+	parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%i",	/* "ms"=password\1nr_attibutes  */
+								( password ) ? password : "", CP_FLD_TERM, nr_attrib
+	);
+
+	/* add attributes */
+	for ( i = 1; i < nr_attrib * 3; i+=3 )
+		datalen += sprintf(	data + datalen, "%c%s%c%s%c%s",		/* \1name\1type\1value  */
+								CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET );
+
+	/* freeup the memory */
+	g_strfreev( parts );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a presence update packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param presence		The presence (as per MXit types)
+ *  @param statusmsg	The status message (can be NULL)
+ */
+void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%i%c",					/* "ms"=show\1status */
+								presence, CP_FLD_TERM
+	);
+
+	/* append status message (if one is set) */
+	if ( statusmsg )
+		datalen += sprintf( data + datalen, "%s", statusmsg );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_STATUS );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a mood update packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param mood			The mood (as per MXit types)
+ */
+void mxit_send_mood( struct MXitSession* session, int mood )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%i",	/* "ms"=mood */
+								mood
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MOOD );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an invite contact packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being invited
+ *  @param alias		Our alias for the contact
+ *  @param groupname	Group in which contact should be stored.
+ */
+void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%s%c%i%c%s",	/* "ms"=group\1username\1alias\1type\1msg */
+								groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias,
+								CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, ""
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a remove contact packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being removed
+ */
+void mxit_send_remove( struct MXitSession* session, const char* username )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s",	/* "ms"=username */
+								username
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an accept subscription (invite) packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being accepted
+ *  @param alias		Our alias for the contact
+ */
+void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%s",	/* "ms"=username\1group\1alias */
+								username, CP_FLD_TERM, "", CP_FLD_TERM, alias
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an deny subscription (invite) packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being denied
+ */
+void mxit_send_deny_sub( struct MXitSession* session, const char* username )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s",	/* "ms"=username */
+								username
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_DENY );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send an update contact packet to the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the contact being denied
+ *  @param alias		Our alias for the contact
+ *  @param groupname	Group in which contact should be stored.
+ */
+void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%s%c%s",	/* "ms"=groupname\1username\1alias */
+								groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a splash-screen click event packet.
+ *
+ *  @param session		The MXit session object
+ *  @param splashid		The identifier of the splash-screen
+ */
+void mxit_send_splashclick( struct MXitSession* session, const char* splashid )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s",	/* "ms"=splashId */
+								splashid
+	);
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to create a MultiMX room.
+ *
+ *  @param session		The MXit session object
+ *  @param groupname	Name of the room to create
+ *  @param nr_usernames	Number of users in initial invite
+ *  @param usernames	The usernames of the users in the initial invite
+ */
+void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+	int			i;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%i",	/* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */
+								groupname, CP_FLD_TERM, nr_usernames
+	);
+
+	/* add usernames */
+	for ( i = 0; i < nr_usernames; i++ )
+		datalen += sprintf(	data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send packet to invite users to existing MultiMX room.
+ *
+ *  @param session		The MXit session object
+ *  @param roomid		The unique RoomID for the MultiMx room.
+ *  @param nr_usernames	Number of users being invited
+ *  @param usernames	The usernames of the users being invited
+ */
+
+void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] )
+{
+	char		data[CP_MAX_PACKET];
+	int			datalen;
+	int			i;
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data,	"ms=%s%c%i",	/* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */
+								roomid, CP_FLD_TERM, nr_usernames
+	);
+
+	/* add usernames */
+	for ( i = 0; i < nr_usernames; i++ )
+		datalen += sprintf(	data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
+
+	/* queue packet for transmission */
+	mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "send file direct" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param username		The username of the recipient
+ *  @param filename		The name of the file being sent
+ *  @param buf			The content of the file
+ *  @param buflen		The length of the file contents
+ */
+void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_DIRECT_SND;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "reject file" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param fileid		A unique ID that identifies this file
+ */
+void mxit_send_file_reject( struct MXitSession* session, const char* fileid )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_reject( chunk->data, fileid );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_REJECT;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get file" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param fileid		A unique ID that identifies this file
+ *  @param filesize		The number of bytes to retrieve
+ *  @param offset		Offset in file at which to start retrieving
+ */
+void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_GET;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "received file" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param status		The status of the file-transfer
+ */
+void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_received( chunk->data, fileid, status );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_RECIEVED;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "set avatar" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param data			The avatar data
+ *  @param buflen		The length of the avatar data
+ */
+void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_SET_AVATAR;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Send a "get avatar" multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param mxitId		The username who's avatar to request
+ *  @param avatarId		The id of the avatar image (as string)
+ *  @param data			The avatar data
+ *  @param buflen		The length of the avatar data
+ */
+void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId )
+{
+	char				data[CP_MAX_PACKET];
+	int					datalen		= 0;
+	struct raw_chunk*	chunk;
+	int					size;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId );
+
+	/* convert the packet to a byte stream */
+	datalen = sprintf( data, "ms=" );
+
+	/* map chunk header over data buffer */
+	chunk = (struct raw_chunk *) &data[datalen];
+
+	size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE );
+	if ( size < 0 ) {
+		purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size );
+		return;
+	}
+
+	chunk->type = CP_CHUNK_GET_AVATAR;
+	chunk->length = htonl( size );
+	datalen += sizeof( struct raw_chunk ) + size;
+
+	/* send the byte stream to the mxit server */
+	mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a login message packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount )
+{
+	PurpleStatus*	status;
+	int				presence;
+	const char*		profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
+									CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL,
+									CP_PROFILE_MOBILENR };
+
+	purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
+
+	/* we were not yet logged in so we need to complete the login sequence here */
+	session->flags |= MXIT_FLAG_LOGGEDIN;
+	purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 );
+	purple_connection_set_state( session->con, PURPLE_CONNECTED );
+
+	/* display the current splash-screen */
+	if ( splash_popup_enabled( session ) )
+		splash_display( session );
+
+	/* update presence status */
+	status = purple_account_get_active_status( session->acc );
+	presence = mxit_convert_presence( purple_status_get_id( status ) );
+	if ( presence != MXIT_PRESENCE_ONLINE ) {
+		/* when logging into MXit, your default presence is online. but with the UI, one can change
+		 * the presence to whatever. in the case where its changed to a different presence setting
+		 * we need to send an update to the server, otherwise the user's presence will be out of
+		 * sync between the UI and MXit.
+		 */
+		mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) );
+	}
+
+	/* save extra info if this is a HTTP connection */
+	if ( session->http ) {
+		/* save the http server to use for this session */
+		g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) );
+
+		/* save the session id */
+		session->http_sesid = atoi( records[0]->fields[0]->data );
+	}
+
+	/* retrieve our MXit profile */
+	mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received message packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct RXMsgData*	mx			= NULL;
+	char*				message		= NULL;
+	int					msglen		= 0;
+	int					msgflags	= 0;
+	int					msgtype		= 0;
+
+	if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) {
+		/* packet contains no message or an empty message */
+		return;
+	}
+
+	message = records[1]->fields[0]->data;
+	msglen = strlen( message );
+
+	/* strip off dummy domain */
+	mxit_strip_domain( records[0]->fields[0]->data );
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data );
+#endif
+
+	/* decode message flags (if any) */
+	if ( records[0]->fcount >= 5 )
+		msgflags = atoi( records[0]->fields[4]->data );
+	msgtype = atoi( records[0]->fields[2]->data );
+
+	if ( msgflags & CP_MSG_ENCRYPTED ) {
+		/* this is an encrypted message. we do not currently support those so ignore it */
+		PurpleBuddy*	buddy;
+		const char*		name;
+		char			msg[128];
+
+		buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data );
+		if ( buddy )
+			name = purple_buddy_get_alias( buddy );
+		else
+			name = records[0]->fields[0]->data;
+		g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name );
+		mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) );
+		return;
+	}
+
+	/* create and initialise new markup struct */
+	mx = g_new0( struct RXMsgData, 1 );
+	mx->msg = g_string_sized_new( msglen );
+	mx->session = session;
+	mx->from = g_strdup( records[0]->fields[0]->data );
+	mx->timestamp = atoi( records[0]->fields[1]->data );
+	mx->got_img = FALSE;
+	mx->chatid = -1;
+	mx->img_count = 0;
+
+	/* update list of active chats */
+	if ( !find_active_chat( session->active_chats, mx->from ) ) {
+		session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) );
+	}
+
+	if ( is_multimx_contact( session, mx->from ) ) {
+		/* this is a MultiMx chatroom message */
+		multimx_message_received( mx, message, msglen, msgtype, msgflags );
+	}
+	else {
+		mxit_parse_markup( mx, message, msglen, msgtype, msgflags );
+	}
+
+	/* we are now done parsing the message */
+	mx->converted = TRUE;
+	if ( mx->img_count == 0 ) {
+		/* we have all the data we need for this message to be displayed now. */
+		mxit_show_message( mx );
+	}
+	else {
+		/* this means there are still images outstanding for this message and
+		 * still need to wait for them before we can display the message.
+		 * so the image received callback function will eventually display
+		 * the message. */
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received subscription request packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct contact*		contact;
+	struct record*		rec;
+	int					i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount );
+
+	for ( i = 0; i < rcount; i++ ) {
+		rec = records[i];
+
+		if ( rec->fcount < 4 ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount );
+			break;
+		}
+
+		/* build up a new contact info struct */
+		contact = g_new0( struct contact, 1 );
+
+		strcpy( contact->username, rec->fields[0]->data );
+		mxit_strip_domain( contact->username );				/* remove dummy domain */
+		strcpy( contact->alias, rec->fields[1]->data );
+		contact->type = atoi( rec->fields[2]->data );
+
+		if ( rec->fcount >= 5 ) {
+			/* there is a personal invite message attached */
+			contact->msg = strdup( rec->fields[4]->data );
+		}
+		else
+			contact->msg = NULL;
+
+		/* handle the subscription */
+		if ( contact-> type == MXIT_TYPE_MULTIMX ) {		/* subscription to a MultiMX room */
+			char* creator = NULL;
+
+			if ( rec->fcount >= 6 )
+				creator = rec->fields[5]->data;
+
+			multimx_invite( session, contact, creator );
+		}
+		else
+			mxit_new_subscription( session, contact );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received contact update packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct contact*		contact	= NULL;
+	struct record*		rec;
+	int					i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount );
+
+	for ( i = 0; i < rcount; i++ ) {
+		rec = records[i];
+
+		if ( rec->fcount < 6 ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount );
+			break;
+		}
+
+		/* build up a new contact info struct */
+		contact = g_new0( struct contact, 1 );
+
+		strcpy( contact->groupname, rec->fields[0]->data );
+		strcpy( contact->username, rec->fields[1]->data );
+		mxit_strip_domain( contact->username );				/* remove dummy domain */
+		strcpy( contact->alias, rec->fields[2]->data );
+
+		contact->presence = atoi( rec->fields[3]->data );
+		contact->type = atoi( rec->fields[4]->data );
+		contact->mood = atoi( rec->fields[5]->data );
+
+		if ( rec->fcount > 6 ) {
+			/* added in protocol 5.9.0 - flags & subtype */
+			contact->flags = atoi( rec->fields[6]->data );
+			contact->subtype = rec->fields[7]->data[0];
+		}
+
+		/* add the contact to the buddy list */
+		if ( contact-> type == MXIT_TYPE_MULTIMX )			/* contact is a MultiMX room */
+			multimx_created( session, contact );
+		else
+			mxit_update_contact( session, contact );
+	}
+
+	if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) {
+		session->flags |= MXIT_FLAG_FIRSTROSTER;
+		mxit_update_blist( session );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received presence update packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount )
+{
+	struct record*		rec;
+	int					i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount );
+
+	for ( i = 0; i < rcount; i++ ) {
+		rec = records[i];
+
+		if ( rec->fcount < 6 ) {
+			purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount );
+			break;
+		}
+
+		/*
+		 * The format of the record is:
+		 * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN
+		 */
+		mxit_strip_domain( rec->fields[0]->data );		/* contactAddress */
+
+		mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ),
+				rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received extended profile packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount )
+{
+	const char*				mxitId		= records[0]->fields[0]->data;
+	struct MXitProfile*		profile		= NULL;
+	int						count;
+	int						i;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId );
+
+	profile = g_new0( struct MXitProfile, 1 );
+
+	/* set the count for attributes */
+	count = atoi( records[0]->fields[1]->data );
+
+	for ( i = 0; i < count; i++ ) {
+		char* fname;
+		char* fvalue;
+		char* fstatus;
+		int f = ( i * 3 ) + 2;
+
+		fname = records[0]->fields[f]->data;		/* field name */
+		fvalue = records[0]->fields[f + 1]->data;	/* field value */
+		fstatus = records[0]->fields[f + 2]->data;	/* field status */
+
+		/* first check the status on the returned attribute */
+		if ( fstatus[0] != '0' ) {
+			/* error: attribute requested was NOT found */
+			purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname );
+			continue;
+		}
+
+		if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) {
+			/* birthdate */
+			if ( records[0]->fields[f + 1]->len > 10 ) {
+				fvalue[10] = '\0';
+				records[0]->fields[f + 1]->len = 10;
+			}
+			memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len );
+		}
+		else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) {
+			/* gender */
+			profile->male = ( fvalue[0] == '1' );
+		}
+		else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) {
+			/* hide number */
+			profile->hidden = ( fvalue[0] == '1' );
+		}
+		else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) {
+			/* nickname */
+			g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) );
+		}
+		else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) {
+			/* avatar id, we just ingore it cause we dont need it */
+		}
+		else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) {
+			/* title */
+			g_strlcpy( profile->title, fvalue, sizeof( profile->title ) );
+		}
+		else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) {
+			/* first name */
+			g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) );
+		}
+		else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) {
+			/* last name */
+			g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) );
+		}
+		else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) {
+			/* email address */
+			g_strlcpy( profile->email, fvalue, sizeof( profile->email ) );
+		}
+		else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) {
+			/* mobile number */
+			g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) );
+		}
+		else {
+			/* invalid profile attribute */
+			purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname );
+		}
+	}
+
+	if ( records[0]->fields[0]->len == 0 ) {
+		/* no MXit id provided, so this must be our own profile information */
+		if ( session->profile )
+			g_free( session->profile );
+		session->profile = profile;
+	}
+	else {
+		/* display other user's profile */
+		mxit_show_profile( session, mxitId, profile );
+
+		/* cleanup */
+		g_free( profile );
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Return the length of a multimedia chunk
+ *
+ * @return		The actual chunk data length in bytes
+ */
+static int get_chunk_len( const char* chunkdata )
+{
+	int*	sizeptr;
+
+	sizeptr = (int*) &chunkdata[1];		/* we skip the first byte (type field) */
+
+	return ntohl( *sizeptr );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a received multimedia packet.
+ *
+ *  @param session		The MXit session object
+ *  @param records		The packet's data records
+ *  @param rcount		The number of data records
+ */
+static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount )
+{
+	char	type;
+	int		size;
+
+	type = records[0]->fields[0]->data[0];
+	size = get_chunk_len( records[0]->fields[0]->data );
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size );
+
+	/* supported chunked data types */
+	switch ( type ) {
+		case CP_CHUNK_CUSTOM :				/* custom resource */
+			{
+				struct cr_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof( struct cr_chunk ) );
+				mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation );
+
+				/* this is a splash-screen operation */
+				if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) {
+					if ( chunk.operation == CR_OP_UPDATE ) {		/* update the splash-screen */
+						struct splash_chunk *splash = chunk.resources->data;			// TODO: Fix - assuming 1st resource is splash
+						gboolean clickable = ( g_list_length( chunk.resources ) > 1 );	// TODO: Fix - if 2 resources, then is clickable
+
+						if ( splash != NULL )
+							splash_update( session, chunk.id, splash->data, splash->datalen, clickable );
+					}
+					else if ( chunk.operation == CR_OP_REMOVE )		/* remove the splash-screen */
+						splash_remove( session );
+				}
+
+				/* cleanup custom resources */
+				g_list_foreach( chunk.resources, (GFunc)g_free, NULL );
+
+			}
+			break;
+
+		case CP_CHUNK_OFFER :				/* file offer */
+			{
+				struct offerfile_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof( struct offerfile_chunk ) );
+				mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				/* process the offer */
+				mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid );
+			}
+			break;
+
+		case CP_CHUNK_GET :					/* get file response */
+			{
+				struct getfile_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof( struct getfile_chunk ) );
+				mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				/* process the getfile */
+				mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length );
+			}
+			break;
+
+		case CP_CHUNK_GET_AVATAR :			/* get avatars */
+			{
+				struct getavatar_chunk chunk;
+
+				/* decode the chunked data */
+				memset( &chunk, 0, sizeof ( struct getavatar_chunk ) );
+				mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
+
+				/* update avatar image */
+				if ( chunk.data ) {
+					purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid );
+					purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid );
+				}
+
+			}
+			break;
+
+		case CP_CHUNK_SET_AVATAR :
+			/* this is a reply packet to a set avatar request. no action is required */
+			break;
+
+		case CP_CHUNK_DIRECT_SND :
+			/* this is a ack for a file send. no action is required */
+			break;
+
+		case CP_CHUNK_RECIEVED :
+			/* this is a ack for a file received. no action is required */
+			break;
+
+		default :
+			purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type );
+			break;
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Handle a redirect sent from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param url			The redirect information
+ */
+static void mxit_perform_redirect( struct MXitSession* session, const char* url )
+{
+	gchar**		parts;
+	gchar**		host;
+	int			type;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url );
+
+	/* tokenize the URL string */
+	parts = g_strsplit( url, ";", 0 );
+
+	/* Part 1: protocol://host:port */
+	host = g_strsplit( parts[0], ":", 4 );
+	if ( strcmp( host[0], "socket" ) == 0 ) {
+		/* redirect to a MXit socket proxy */
+		g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
+		session->port = atoi( host[2] );
+	}
+	else {
+		purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) );
+		goto redirect_fail;
+	}
+
+	/* Part 2: type of redirect */
+	type = atoi( parts[1] );
+	if ( type == CP_REDIRECT_PERMANENT ) {
+		/* permanent redirect, so save new MXit server and port */
+		purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
+		purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
+	}
+
+	/* Part 3: message (optional) */
+	if ( parts[2] != NULL )
+		purple_connection_notice( session->con, parts[2] );
+
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n",
+			( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port );
+
+	/* perform the re-connect to the new MXit server */
+	mxit_reconnect( session );
+
+redirect_fail:
+	g_strfreev( parts );
+	g_strfreev( host );
+}
+
+
+/*------------------------------------------------------------------------
+ * Process a success response received from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The received packet
+ */
+static int process_success_response( struct MXitSession* session, struct rx_packet* packet )
+{
+	/* ignore ping/poll packets */
+	if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) )
+		session->last_rx = time( NULL );
+
+	/*
+	 * when we pass the packet records to the next level for parsing
+	 * we minus 3 records because 1) the first record is the packet
+	 * type 2) packet reply status 3) the last record is bogus
+	 */
+
+	/* packet command */
+	switch ( packet->cmd ) {
+
+		case CP_CMD_REGISTER :
+				/* fall through, when registeration successful, MXit will auto login */
+		case CP_CMD_LOGIN :
+				/* login response */
+				if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
+					mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 );
+				}
+				break;
+
+		case CP_CMD_LOGOUT :
+				/* logout response */
+				session->flags &= ~MXIT_FLAG_LOGGEDIN;
+				purple_account_disconnect( session->acc );
+
+				/* note:
+				 * we do not prompt the user here for a reconnect, because this could be the user
+				 * logging in with his phone. so we just disconnect the account otherwise
+				 * mxit will start to bounce between the phone and pidgin. also could be a valid
+				 * disconnect selected by the user.
+				 */
+				return -1;
+
+		case CP_CMD_CONTACT :
+				/* contact update */
+				mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 );
+				break;
+
+		case CP_CMD_PRESENCE :
+				/* presence update */
+				mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 );
+				break;
+
+		case CP_CMD_RX_MSG :
+				/* incoming message (no bogus record) */
+				mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 );
+				break;
+
+		case CP_CMD_NEW_SUB :
+				/* new subscription request */
+				mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 );
+				break;
+
+		case CP_CMD_MEDIA :
+				/* multi-media message */
+				mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 );
+				break;
+
+		case CP_CMD_EXTPROFILE_GET :
+				/* profile update */
+				mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 );
+				break;
+
+		case CP_CMD_MOOD :
+				/* mood update */
+		case CP_CMD_UPDATE :
+				/* update contact information */
+		case CP_CMD_ALLOW :
+				/* allow subscription ack */
+		case CP_CMD_DENY :
+				/* deny subscription ack */
+		case CP_CMD_INVITE :
+				/* invite contact ack */
+		case CP_CMD_REMOVE :
+				/* remove contact ack */
+		case CP_CMD_TX_MSG :
+				/* outgoing message ack */
+		case CP_CMD_STATUS :
+				/* presence update ack */
+		case CP_CMD_GRPCHAT_CREATE :
+				/* create groupchat */
+		case CP_CMD_GRPCHAT_INVITE :
+				/* groupchat invite */
+		case CP_CMD_PING :
+				/* ping reply */
+		case CP_CMD_POLL :
+				/* HTTP poll reply */
+		case CP_CMD_EXTPROFILE_SET :
+				/* profile update */
+		case CP_CMD_SPLASHCLICK :
+				/* splash-screen clickthrough */
+				break;
+
+		default :
+			/* unknown packet */
+			purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd );
+	}
+
+	return 0;
+}
+
+
+/*------------------------------------------------------------------------
+ * Process an error response received from the MXit server.
+ *
+ *  @param session		The MXit session object
+ *  @param packet		The received packet
+ */
+static int process_error_response( struct MXitSession* session, struct rx_packet* packet )
+{
+	char			errmsg[256];
+	const char*		errdesc;
+
+	/* set the error description to be shown to the user */
+	if ( packet->errmsg )
+		errdesc = packet->errmsg;
+	else
+		errdesc = "An internal MXit server error occurred.";
+
+	purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc );
+
+	if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) {
+		/* we are not currently logged in, so we need to reconnect */
+		purple_connection_error( session->con, _( errmsg ) );
+	}
+
+	/* packet command */
+	switch ( packet->cmd ) {
+
+		case CP_CMD_REGISTER :
+		case CP_CMD_LOGIN :
+				if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) {
+					mxit_perform_redirect( session, packet->errmsg );
+					return 0;
+				}
+				else {
+					sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode );
+					purple_connection_error( session->con, _( errmsg ) );
+					return -1;
+				}
+		case CP_CMD_LOGOUT :
+				sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode );
+				purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) );
+				return -1;
+		case CP_CMD_CONTACT :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_RX_MSG :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_TX_MSG :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_STATUS :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_MOOD :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_KICK :
+				/*
+				 * the MXit server sends this packet if we were idle for too long.
+				 * to stop the server from closing this connection we need to resend
+				 * the login packet.
+				 */
+				mxit_send_login( session );
+				break;
+		case CP_CMD_INVITE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_REMOVE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_ALLOW :
+		case CP_CMD_DENY :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_UPDATE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_MEDIA :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_GRPCHAT_CREATE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) );
+				break;
+		case CP_CMD_GRPCHAT_INVITE :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_EXTPROFILE_GET :
+		case CP_CMD_EXTPROFILE_SET :
+				mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) );
+				break;
+		case CP_CMD_SPLASHCLICK :
+				/* ignore error */
+				break;
+		case CP_CMD_PING :
+		case CP_CMD_POLL :
+				break;
+		default :
+				mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) );
+				break;
+	}
+
+	return 0;
+}
+
+
+/*========================================================================================================================
+ * Low-level Packet receive
+ */
+
+#ifdef	DEBUG_PROTOCOL
+/*------------------------------------------------------------------------
+ * Dump a received packet structure.
+ *
+ *  @param p			The received packet
+ */
+static void dump_packet( struct rx_packet* p )
+{
+	struct record*		r	= NULL;
+	struct field*		f	= NULL;
+	int					i;
+	int					j;
+
+	purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount );
+
+	for ( i = 0; i < p->rcount; i++ ) {
+		r = p->records[i];
+		purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount );
+
+		for ( j = 0; j < r->fcount; j++ ) {
+			f = r->fields[j];
+			purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data );
+		}
+	}
+}
+#endif
+
+
+/*------------------------------------------------------------------------
+ * Free up memory used by a packet structure.
+ *
+ *  @param p			The received packet
+ */
+static void free_rx_packet( struct rx_packet* p )
+{
+	struct record*		r	= NULL;
+	struct field*		f	= NULL;
+	int					i;
+	int					j;
+
+	for ( i = 0; i < p->rcount; i++ ) {
+		r = p->records[i];
+
+		for ( j = 0; j < r->fcount; j++ ) {
+			g_free( f );
+		}
+		g_free( r->fields );
+		g_free( r );
+	}
+	g_free( p->records );
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new field to a record.
+ *
+ *  @param r			Parent record object
+ *  @return				The newly created field
+ */
+static struct field* add_field( struct record* r )
+{
+	struct field*	field;
+
+	field = g_new0( struct field, 1 );
+
+	r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) );
+	r->fields[r->fcount] = field;
+	r->fcount++;
+
+	return field;
+}
+
+
+/*------------------------------------------------------------------------
+ * Add a new record to a packet.
+ *
+ *  @param p			The packet object
+ *  @return				The newly created record
+ */
+static struct record* add_record( struct rx_packet* p )
+{
+	struct record*	rec;
+
+	rec = g_new0( struct record, 1 );
+
+	p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) );
+	p->records[p->rcount] = rec;
+	p->rcount++;
+
+	return rec;
+}
+
+
+/*------------------------------------------------------------------------
+ * Parse the received byte stream into a proper client protocol packet.
+ *
+ *  @param session		The MXit session object
+ *  @return				Success (0) or Failure (!0)
+ */
+int mxit_parse_packet( struct MXitSession* session )
+{
+	struct rx_packet	packet;
+	struct record*		rec;
+	struct field*		field;
+	gboolean			pbreak;
+	unsigned int		i;
+	int					res	= 0;
+
+#ifdef	DEBUG_PROTOCOL
+	purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i );
+	dump_bytes( session, session->rx_dbuf, session->rx_i );
+#endif
+
+	i = 0;
+	while ( i < session->rx_i ) {
+
+		/* create first record and field */
+		rec = NULL;
+		field = NULL;
+		memset( &packet, 0x00, sizeof( struct rx_packet ) );
+		rec = add_record( &packet );
+		pbreak = FALSE;
+
+		/* break up the received packet into fields and records for easy parsing */
+		while ( ( i < session->rx_i ) && ( !pbreak ) ) {
+
+			switch ( session->rx_dbuf[i] ) {
+				case CP_SOCK_REC_TERM :
+						/* new record */
+						if ( packet.rcount == 1 ) {
+							/* packet command */
+							packet.cmd = atoi( packet.records[0]->fields[0]->data );
+						}
+						else if ( packet.rcount == 2 ) {
+							/* special case: binary multimedia packets should not be parsed here */
+							if ( packet.cmd == CP_CMD_MEDIA ) {
+								/* add the chunked to new record */
+								rec = add_record( &packet );
+								field = add_field( rec );
+								field->data = &session->rx_dbuf[i + 1];
+								field->len = session->rx_i - i;
+								/* now skip the binary data */
+								res = get_chunk_len( field->data );
+								/* determine if we have more packets */
+								if ( res + 6 + i < session->rx_i ) {
+									/* we have more than one packet in this stream */
+									i += res + 6;
+									pbreak = TRUE;
+								}
+								else {
+									i = session->rx_i;
+								}
+							}
+						}
+						else if ( !field ) {
+							field = add_field( rec );
+							field->data = &session->rx_dbuf[i];
+						}
+						session->rx_dbuf[i] = '\0';
+						rec = add_record( &packet );
+						field = NULL;
+
+						break;
+				case CP_FLD_TERM :
+						/* new field */
+						session->rx_dbuf[i] = '\0';
+						if ( !field ) {
+							field = add_field( rec );
+							field->data = &session->rx_dbuf[i];
+						}
+						field = NULL;
+						break;
+				case CP_PKT_TERM :
+						/* packet is done! */
+						session->rx_dbuf[i] = '\0';
+						pbreak = TRUE;
+						break;
+				default :
+						/* skip non special characters */
+						if ( !field ) {
+							field = add_field( rec );
+							field->data = &session->rx_dbuf[i];
+						}
+						field->len++;
+						break;
+			}
+
+			i++;
+		}
+
+		if ( packet.rcount < 2 ) {
+			/* bad packet */
+			purple_connection_error( session->con, _( "Invalid packet received from MXit." ) );
+			free_rx_packet( &packet );
+			continue;
+		}
+
+		session->rx_dbuf[session->rx_i] = '\0';
+		packet.errcode = atoi( packet.records[1]->fields[0]->data );
+
+		purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode );
+#ifdef	DEBUG_PROTOCOL
+		/* debug */
+		dump_packet( &packet );
+#endif
+
+		/* reset the out ack */
+		if ( session->outack == packet.cmd ) {
+			/* outstanding ack received from mxit server */
+			session->outack = 0;
+		}
+
+		/* check packet status */
+		if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) {
+			/* error reply! */
+			if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) )
+				packet.errmsg = packet.records[1]->fields[1]->data;
+			else
+				packet.errmsg = NULL;
+
+			res = process_error_response( session, &packet );
+		}
+		else {
+			/* success reply! */
+			res = process_success_response( session, &packet );
+		}
+
+		/* free up the packet resources */
+		free_rx_packet( &packet );
+	}
+
+	if ( session->outack == 0 )
+			mxit_manage_queue( session );
+
+	return res;
+}
+
+
+/*------------------------------------------------------------------------
+ * Callback when data is received from the MXit server.
+ *
+ *  @param user_data		The MXit session object
+ *  @param source			The file-descriptor on which data was received
+ *  @param cond				Condition which caused the callback (PURPLE_INPUT_READ)
+ */
+void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond )
+{
+	struct MXitSession*	session		= (struct MXitSession*) user_data;
+	char				ch;
+	int					res;
+	int					len;
+
+	if ( session->rx_state == RX_STATE_RLEN ) {
+		/* we are reading in the packet length */
+		len = read( session->fd, &ch, 1 );
+		if ( len < 0 ) {
+			/* connection error */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) );
+			return;
+		}
+		else if ( len == 0 ) {
+			/* connection closed */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) );
+			return;
+		}
+		else {
+			/* byte read */
+			if ( ch == CP_REC_TERM ) {
+				/* the end of the length record found */
+				session->rx_lbuf[session->rx_i] = '\0';
+				session->rx_res = atoi( &session->rx_lbuf[3] );
+				if ( session->rx_res > CP_MAX_PACKET ) {
+					purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) );
+				}
+				session->rx_state = RX_STATE_DATA;
+				session->rx_i = 0;
+			}
+			else {
+				/* still part of the packet length record */
+				session->rx_lbuf[session->rx_i] = ch;
+				session->rx_i++;
+				if ( session->rx_i >= sizeof( session->rx_lbuf ) ) {
+					/* malformed packet length record (too long) */
+					purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) );
+					return;
+				}
+			}
+		}
+	}
+	else if ( session->rx_state == RX_STATE_DATA ) {
+		/* we are reading in the packet data */
+		len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
+		if ( len < 0 ) {
+			/* connection error */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) );
+			return;
+		}
+		else if ( len == 0 ) {
+			/* connection closed */
+			purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) );
+			return;
+		}
+		else {
+			/* data read */
+			session->rx_i += len;
+			session->rx_res -= len;
+
+			if ( session->rx_res == 0 ) {
+				/* ok, so now we have read in the whole packet */
+				session->rx_state = RX_STATE_PROC;
+			}
+		}
+	}
+
+	if ( session->rx_state == RX_STATE_PROC ) {
+		/* we have a full packet, which we now need to process */
+		res = mxit_parse_packet( session );
+
+		if ( res == 0 ) {
+			/* we are still logged in */
+			session->rx_state = RX_STATE_RLEN;
+			session->rx_res = 0;
+			session->rx_i = 0;
+		}
+	}
+}
+
+
+/*------------------------------------------------------------------------
+ * Log the user off MXit and close the connection
+ *
+ *  @param session		The MXit session object
+ */
+void mxit_close_connection( struct MXitSession* session )
+{
+	purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" );
+
+	if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
+		/* we are already closed */
+		return;
+	}
+	else if ( session->flags & MXIT_FLAG_LOGGEDIN ) {
+		/* we are currently logged in so we need to send a logout packet */
+		if ( !session->http ) {
+			mxit_send_logout( session );
+		}
+		session->flags &= ~MXIT_FLAG_LOGGEDIN;
+	}
+	session->flags &= ~MXIT_FLAG_CONNECTED;
+
+	/* cancel outstanding HTTP request */
+	if ( ( session->http ) && ( session->http_out_req ) ) {
+		purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req );
+		session->http_out_req = NULL;
+	}
+
+	/* remove the input cb function */
+	if ( session->con->inpa ) {
+		purple_input_remove( session->con->inpa );
+		session->con->inpa = 0;
+	}
+
+	/* remove HTTP poll timer */
+	if ( session->http_timer_id > 0 )
+		purple_timeout_remove( session->http_timer_id );
+
+	/* remove queue manager timer */
+	if ( session->q_timer > 0 )
+		purple_timeout_remove( session->q_timer );
+
+	/* remove all groupchat rooms */
+	while ( session->rooms != NULL ) {
+		struct multimx* multimx = (struct multimx *) session->rooms->data;
+
+		session->rooms = g_list_remove( session->rooms, multimx );
+
+		free( multimx );
+	}
+	g_list_free( session->rooms );
+	session->rooms = NULL;
+
+	/* remove all rx chats names */
+	while ( session->active_chats != NULL ) {
+		char* chat = (char*) session->active_chats->data;
+
+		session->active_chats = g_list_remove( session->active_chats, chat );
+
+		g_free( chat );
+	}
+	g_list_free( session->active_chats );
+	session->active_chats = NULL;
+
+	/* free profile information */
+	if ( session->profile )
+		free( session->profile );
+
+	/* free custom emoticons */
+	mxit_free_emoticon_cache( session );
+
+	/* free allocated memory */
+	g_free( session->encpwd );
+	session->encpwd = NULL;
+
+	/* flush all the commands still in the queue */
+	flush_queue( session );
+}
+