view libpurple/protocols/mxit/protocol.c @ 31699:a3bfd9439d37

merge of '974d79cf8eb22b7138c9a3d7313e89585fd8b443' and 'a657534f694305a613db4946ca8ea9ab4c8f1c1d'
author Evan Schoenberg <evan.s@dreskin.net>
date Mon, 21 Feb 2011 22:56:48 +0000
parents a8cc50c2279f
children b8d9329dda4a
line wrap: on
line source

/*
 *					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    "internal.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: %d\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 = snprintf( header, sizeof( 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 '%i'\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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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%c"					/* dialingcode\1locale\1 */
								"%i%c%i%c%i",				/* maxReplyLen\1protocolVer\1lastRosterUpdate */
								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, CP_FLD_TERM,
								CP_MAX_FILESIZE, CP_FLD_TERM, MXIT_CP_PROTO_VESION, CP_FLD_TERM, 0
	);

	/* 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, gboolean is_command )
{
	char		data[CP_MAX_PACKET];
	char*		markuped_msg;
	int			datalen;
	int			msgtype = ( is_command ? CP_MSGTYPE_COMMAND : 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( data ),
								"ms=%s",	/* "ms"=splashId */
								splashid
	);

	/* queue packet for transmission */
	mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK );
}


/*------------------------------------------------------------------------
 * Send a message event packet.
 *
 *  @param session		The MXit session object
 *  @param to           The username of the original sender (ie, recipient of the event)
 *  @param id			The identifier of the event (received in message)
 *  @param event		Identified the type of event
 */
void mxit_send_msgevent( struct MXitSession* session, const char* to, const char* id, int event)
{
	char		data[CP_MAX_PACKET];
	int			datalen;

	purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_msgevent: to=%s id=%s event=%i\n", to, id, event );

	/* convert the packet to a byte stream */
	datalen = snprintf( data, sizeof( data ),
								"ms=%s%c%s%c%i",		/* "ms"=contactAddress \1 id \1 event */
								to, CP_FLD_TERM, id, CP_FLD_TERM, event
	);

	/* queue packet for transmission */
	mxit_queue_packet( session, data, datalen, CP_CMD_MSGEVENT );
}


/*------------------------------------------------------------------------
 * 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 = snprintf( data, sizeof( 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 = snprintf( data, sizeof( 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;
	gchar*				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 = &data[datalen];

	size = mxit_chunk_create_senddirect( chunk_data( chunk ), username, filename, buf, buflen );
	if ( size < 0 ) {
		purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size );
		return;
	}

	set_chunk_type( chunk, CP_CHUNK_DIRECT_SND );
	set_chunk_length( chunk, size );
	datalen += MXIT_CHUNK_HEADER_SIZE + 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;
	gchar*				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 = &data[datalen];

	size = mxit_chunk_create_reject( chunk_data( chunk ), fileid );
	if ( size < 0 ) {
		purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size );
		return;
	}

	set_chunk_type( chunk, CP_CHUNK_REJECT );
	set_chunk_length( chunk, size );
	datalen += MXIT_CHUNK_HEADER_SIZE + 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;
	gchar*				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 = &data[datalen];

	size = mxit_chunk_create_get( chunk_data(chunk), fileid, filesize, offset );
	if ( size < 0 ) {
		purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size );
		return;
	}

	set_chunk_type( chunk, CP_CHUNK_GET );
	set_chunk_length( chunk, size );
	datalen += MXIT_CHUNK_HEADER_SIZE + 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;
	gchar*				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 = &data[datalen];

	size = mxit_chunk_create_received( chunk_data(chunk), fileid, status );
	if ( size < 0 ) {
		purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size );
		return;
	}

	set_chunk_type( chunk, CP_CHUNK_RECEIVED );
	set_chunk_length( chunk, size );
	datalen += MXIT_CHUNK_HEADER_SIZE + 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;
	gchar*				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 = &data[datalen];

	size = mxit_chunk_create_set_avatar( chunk_data(chunk), avatar, avatarlen );
	if ( size < 0 ) {
		purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size );
		return;
	}

	set_chunk_type( chunk, CP_CHUNK_SET_AVATAR );
	set_chunk_length( chunk, size );
	datalen += MXIT_CHUNK_HEADER_SIZE + 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;
	gchar*				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 = &data[datalen];

	size = mxit_chunk_create_get_avatar( chunk_data(chunk), mxitId, avatarId, MXIT_AVATAR_SIZE );
	if ( size < 0 ) {
		purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size );
		return;
	}

	set_chunk_type( chunk, CP_CHUNK_GET_AVATAR );
	set_chunk_length( chunk, size );
	datalen += MXIT_CHUNK_HEADER_SIZE + 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*		statusmsg;
	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, CP_PROFILE_FLAGS };

	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 );

	/* 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 );
	}

	/* extract MXitId (from protocol 5.9) */
	if ( records[1]->fcount >= 9 )
		session->uid = g_strdup( records[1]->fields[8]->data );

	/* 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 ) );
	statusmsg = purple_status_get_attr_string( status, "message" );

	if ( ( presence != MXIT_PRESENCE_ONLINE ) || ( statusmsg ) ) {
		/* 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.
		 */
		char* statusmsg1 = purple_markup_strip_html( statusmsg );
		char* statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG );

		mxit_send_presence( session, presence, statusmsg2 );

		g_free( statusmsg1 );
		g_free( statusmsg2 );
	}

	/* 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;
	}

	if ( msgflags & CP_MSG_NOTIFY_DELIVERY ) {
		/* delivery notification is requested */
		if ( records[0]->fcount >= 4 )
			mxit_send_msgevent( session, records[0]->fields[0]->data, records[0]->fields[3]->data, CP_MSGEVENT_DELIVERED );
	}

	/* 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 );

		g_strlcpy( contact->username, rec->fields[0]->data, sizeof( contact->username ) );
		mxit_strip_domain( contact->username );				/* remove dummy domain */
		g_strlcpy( contact->alias, rec->fields[1]->data, sizeof( contact->alias ) );
		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 );

		g_strlcpy( contact->groupname, rec->fields[0]->data, sizeof( contact->groupname ) );
		g_strlcpy( contact->username, rec->fields[1]->data, sizeof( contact->username ) );
		mxit_strip_domain( contact->username );				/* remove dummy domain */
		g_strlcpy( contact->alias, rec->fields[2]->data, sizeof( contact->alias ) );

		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 - flags & subtype */
			contact->flags = atoi( rec->fields[6]->data );
			contact->subtype = rec->fields[7]->data[0];
		}
		if ( rec->fcount > 8 ) {
			/* added in protocol 6.0 - reject message */
			contact->msg = g_strdup( rec->fields[8]->data );
		}

		/* 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\1moodN\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 );
		mxit_update_buddy_avatar( session, rec->fields[0]->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;
	const char*				avatarId	= NULL;
	const char*				statusMsg	= NULL;

	purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId );

	if ( ( records[0]->fields[0]->len == 0 ) || ( session->uid && ( strcmp( session->uid, records[0]->fields[0]->data ) == 0 ) ) ) {
		/* No UserId or Our UserId provided, so this must be our own profile information */
		if ( session->profile == NULL )
			session->profile = g_new0( struct MXitProfile, 1 );
		profile = session->profile;
	}
	else {
		/* is a buddy's profile */
		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_STATUS, fname ) == 0 ) {
			/* status message - just keep a reference to the value */
			statusMsg = fvalue;
		}
		else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) {
			/* avatar id - just keep a reference to the value */
			avatarId = fvalue;
		}
		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 if ( strcmp( CP_PROFILE_REGCOUNTRY, fname ) == 0 ) {
			/* registered country */
			g_strlcpy( profile->regcountry, fvalue, sizeof( profile->regcountry ) );
		}
		else if ( strcmp( CP_PROFILE_FLAGS, fname ) == 0 ) {
			/* profile flags */
			profile->flags = strtoll( fvalue, NULL, 10 );
		}
		else if ( strcmp( CP_PROFILE_LASTSEEN, fname ) == 0 ) {
			/* last seen online */
			profile->lastonline = strtoll( fvalue, NULL, 10 );
		}
		else {
			/* invalid profile attribute */
			purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname );
		}
	}

	if ( profile != session->profile ) {
		/* update avatar (if necessary) */
		if ( avatarId )
			mxit_update_buddy_avatar( session, mxitId, avatarId );

		/* if this is not our profile, just display it */
		mxit_show_profile( session, mxitId, profile );
		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_RECEIVED :
			/* 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 */
		case CP_CMD_MSGEVENT :
				/* event message */
				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, _( errdesc ) );
	}

	/* 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 {
					snprintf( errmsg, sizeof( errmsg ), _( "Login error: %s (%i)" ), errdesc, packet->errcode );
					purple_connection_error( session->con, errmsg );
					return -1;
				}
		case CP_CMD_LOGOUT :
				snprintf( errmsg, sizeof( 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 :
		case CP_CMD_MSGEVENT :
				/* 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 */
	if ( session->uid )
		g_free( session->uid );
	g_free( session->encpwd );
	session->encpwd = NULL;

	/* flush all the commands still in the queue */
	flush_queue( session );
}