Logo Search packages:      
Sourcecode: pidgin version File versions  Download package

protocol.c

/*
 *                            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 = 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;
      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_RECIEVED );
      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*       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, _( 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 {
                              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 );
}


Generated by  Doxygen 1.6.0   Back to index