Logo Search packages:      
Sourcecode: kmess version File versions

p2papplication.cpp

/***************************************************************************
                          p2papplication.cpp -  description
                             -------------------
    begin                : Mon Nov 22 2004
    copyright            : (C) 2004 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "p2papplication.h"

#include <stdlib.h>
#include <string.h>  // for memcpy

#include <qregexp.h>
#include <qiodevice.h>
#include <qtimer.h>
#include <qbuffer.h>
#include <klocale.h>
#include <kdebug.h>

#include "../mimemessage.h"
#include "../p2pmessage.h"
#include "../../msnobject.h"
#include "../../kmessdebug.h"
#include "../../currentaccount.h"

#ifdef KMESSDEBUG_P2PAPPLICATION
#define KMESSDEBUG_P2PAPPLICATION_GENERAL
#endif

// TODO: Reset global messageTotalSize_ for error messages/ack messages.


/**
 * Constructor, initializes the P2PApplication instance fields.
 *
 * @param  localIP        IP-Address of the switchboard socket.
 * @param  contactHandle  Handle of the other contact
 */
00049 P2PApplication::P2PApplication(const QString &localIP, const QString &contactHandle)
 : Application(localIP),
  buffer_(0),
  contactHandle_(contactHandle),
  dataSource_(0),
  dataType_(P2P_TYPE_NEGOTIATION),
  gotSlpMessage_(false),
  lastUniqueID_(0),
  invitationCSeq_(0),
  invitationSessionID_(0),
  originalAckMessageID_(0),
  originalDataSize_(0),
  originalMessageID_(0),
  originalSessionID_(0),
  originalTotalSize_(0),
  sentCancelMessage_(false),
  sessionID_(0),
  messageID_(0),
  messageOffset_(0),
  messageTotalSize_(0),
  shouldSendAck_(false),
  userShouldAcknowledge_(false),
  waitingState_(P2P_WAIT_DEFAULT)
{
  // Initiaize the timer
  waitingTimer_ = new QTimer(this);
  connect( waitingTimer_, SIGNAL(timeout()), this, SLOT(slotCleanup()) );
}



/**
 * Class destructor.
 */
00083 P2PApplication::~P2PApplication()
{
  waitingTimer_->stop();
  delete waitingTimer_;
  delete buffer_;
}



/**
 * Step one of a contact-started chat: the contact invites the user
 * By default the invitation will be declined unless you override this method.
 */
00096 void P2PApplication::contactStarted1_ContactInvitesUser(const MimeMessage &message)
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "P2PApplication - contactStarted1_ContactInvitesUser (rejects invitation)" << endl;
#endif

  // Cancel the invitation
  sendCancelMessage( CANCEL_NOT_INSTALLED );

  // We can assume the switchboard already displayed a better error message
#ifdef KMESTEST
  kdWarning() << "The Application subclass didn't implement contactStarted1_ContactInvitesUser!" << endl;
#endif

  // Send some debug info to the console
  MimeMessage slpContent(message.getBody());
  kdWarning() << "The contact initiated a MSN6 feature KMess can't handle yet "
              << "(euf-guid=" << slpContent.getValue("EUF-GUID") << " appid=" << slpContent.getValue("AppID") << ")." << endl;

  // Unlike Application::contactStarted1_ContactInvitesUser()
  // we don't quit here!! We should wait for the BYE message from the other contact!
}



/**
 * Step four of a contact-started chat: the contact confirms the data-preparation message.
 */
00124 void P2PApplication::contactStarted4_ContactConfirmsPreparation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - contactStarted4_ContactConfirmsPreparation" << endl;
#endif
}



/**
 * Private utility function to generate an Session and message/sequence ID.
 * The message/sequence ID starts random, between [4, 4294967295].
 */
00137 unsigned long P2PApplication::generateID()
{
  return ((rand() & 0xFFFFFF00) + 4);
}



/**
 * Private utility function to generate a Call ID or Branch ID.
 * This ID has the format of a GUID, but it can in fact by any random string.
 */
00148 QString P2PApplication::generateGUID()
{
  // This code is based on Kopete, but much shorter.
  QString guid = "{"
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + "-"
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + "-"
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + "-"
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + "-"
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + QString::number((rand() & 0xAAFF) + 0x1111, 16)
      + "}";
  return guid.upper();
}



/**
 * Return the branch ID, this identifies the SLP INVITE-message.
 */
00173 QString P2PApplication::getBranch() const
{
  return branch_;
}



/**
 * Return the call ID, this identifies the SLP session.
 */
00183 QString P2PApplication::getCallID() const
{
  return callID_;
}


/**
 * Return the handle of the other contact.
 */
00192 QString P2PApplication::getContactHandle() const
{
  return contactHandle_;
}



/**
 * Return the content type read from the invitation message
 * This method is useful for some contactStarted1_ContactInvitesUser() implementations.
 */
00203 const QString& P2PApplication::getInvitationContentType() const
{
  return invitationContentType_;
}



/**
 * Return the Session ID this class read from the INVITE message.
 * This method is useful for some contactStarted2_UserAccepts() implementations.
 */
00214 unsigned long P2PApplication::getInvitationSessionID() const
{
  return invitationSessionID_;
}



/**
 * Return the MessageID the other contact sent with his/hers last P2P message.
 */
00224 unsigned long P2PApplication::getLastContactMessageID() const
{
  return originalMessageID_;
}



/**
 * Return the UniqueID the other contact sent with his/hers last P2P message.
 */
00234 unsigned long P2PApplication::getLastContactAckMessageID() const
{
  return originalAckMessageID_;
}



/**
 * Return the message ID we sent with the last P2P message.
 */
00244 unsigned long P2PApplication::getLastUserMessageID() const
{
  return messageID_;
}



/**
 * Return the session ID, this identifies the P2P session.
 */
00254 unsigned long P2PApplication::getSessionID() const
{
  return sessionID_;
}



/**
 * Return the Unique ID we sent with the last P2P message.
 */
00264 unsigned long P2PApplication::getLastUserUniqueID() const
{
  return lastUniqueID_;
}




/**
 * Parse the ACK message. In certain situations we need to activate some methods.
 */
00275 void P2PApplication::gotAck(const P2PMessage &message)
{

  // Are we waiting for the contact to accept our invitation (e.g. send "200 OK")?
  if(waitingState_ == P2P_WAIT_CONTACT_ACCEPT)
  {
    // Don't change the waiting state yet
    waitingTimer_->start(30000, true); // 30 sec
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an ACK message (INVITE ACK, still waiting for 200 OK)." << endl;
#endif
    return;
  }


  // Are we waiting for the contact to send the BYE message?
  if(waitingState_ == P2P_WAIT_FOR_SLP_BYE)
  {
    // Don't change the waiting state yet
    waitingTimer_->start(30000, true); // 30 sec
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an ACK message (still waiting for BYE)." << endl;
#endif
    return;
  }


  // Are we waiting for the ACK to confirm our "200 OK" message?
  if(waitingState_ == P2P_WAIT_FOR_SLP_OK_ACK)
  {
    waitingState_ = P2P_WAIT_DEFAULT;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an ACK message (200 OK ACK)." << endl;
#endif

    // This should happen after the "200 OK" message is sent.

    // If the user started the inviation,
    // we send the SLP OK ack, not receive it
    if(isUserStartedApp())
    {
      // We received an 200 OK ACK but didn't expect it.
      kdWarning() << "P2PApplication: P2P message can't be handled, "
                  << "unexpected ACK response, waiting for 200 OK ACK (contact=" << contactHandle_ << ")." << endl;
      shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
      return;
    }


    // App started by contact,
    // this is truely the right message.
    // Dispatch to derived implementation
    contactStarted3_ContactConfirmsAccept( (const MimeMessage&) message ); // The message is empty, but pass it anyway.


#ifdef KMESSTEST
    ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_PREPARE_ACK );
#endif

    // If the contactStarted3() didn't sent anything, we'll wait for incoming file data.
    if(waitingState_ == P2P_WAIT_DEFAULT)
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Waiting state not changed, waiting for file data..." << endl;
#endif

      // For Pictures, the contact must send the data preparation ACK at this point (P2P_WAIT_FOR_PREPARE_ACK)
      // For Files, the contact must initiate the data transfer now. (P2P_WAIT_DEFAULT)
      // TODO: trillian does not initiate the file transfer here, why?!?)
      waitingState_ = P2P_WAIT_FOR_FILE_DATA;
      waitingTimer_->start(30000, true); // 30 sec
    }

    return;
  }


  // Are we waiting for the ACK to confirm our data preparation message?
  if(waitingState_ == P2P_WAIT_FOR_PREPARE_ACK)
  {
    waitingState_ = P2P_WAIT_DEFAULT;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an ACK message (data-preparation ACK)." << endl;
#endif

    // This should happen after the data preparation message is sent.

    // If the user sent the invitation,
    // we shouldn't receive the prepare-ack, but sent it.
    if(isUserStartedApp())
    {
      // We received a data preparation ACK but didn't expect it.
      kdWarning() << "P2PApplication: P2P message can't be handled, "
                  << "unexpected ACK response, waiting for data-preparation-ACK (contact=" << contactHandle_ << ")." << endl;
      shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
      return;
    }


    // Dispatch to derived implementation
    contactStarted4_ContactConfirmsPreparation();
    return;
  }



  // Are we waiting for the ACK to confirm that all data was received?
  if(waitingState_ == P2P_WAIT_FOR_DATA_ACK)
  {
    waitingState_ = P2P_WAIT_DEFAULT;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an ACK message (all-received ACK)." << endl;
#endif

    // This should happen after all file/picture data was sent.


    if(isUserStartedApp())
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: All data sent and confirmed, sending BYE message..." << endl;
#endif

      // We sent the INVITE, now we send the BYE.
      waitingState_ = P2P_WAIT_DEFAULT;
      sendSlpBye();

// Disabled in backport
//      // All data was acked
//      // Telling derived class the transfer is complete.
//      showTransferComplete();

      // Don't terminate yet, that message needs to be ACK-ed as well.
    }
    else
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: All data sent and confirmed, waiting for BYE message..." << endl;
#endif

// Disabled in backport
//      // All data was acked, this is good enough.
//      // The remaining teardown happens behind the scenes.
//      showTransferComplete();

      // The other contact sent the INVITE, so we also wait for the BYE message.
      // This happens for picture transfers for example.

      waitingState_ = P2P_WAIT_FOR_SLP_BYE;
      waitingTimer_->start(30000, true); // 30 sec
    }
    return;
  }



  // Are we waiting for the BYE ACK to send our closing-ACK?
  if(waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK)
  {
    waitingState_ = P2P_WAIT_DEFAULT;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an ACK message (BYE ACK, terminating)." << endl;
#endif

    // Transfer complete.
    endApplication();
    return;
  }



  // Are we waiting for the SLP Error message to be ACK-ed?
  if(waitingState_ == P2P_WAIT_FOR_SLP_ERR_ACK)
  {
    waitingState_ = P2P_WAIT_DEFAULT;

    if(! isUserStartedApp())
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Got an ACK message (Confirmed SLP Error, waiting for BYE)." << endl;
#endif

      // We wait for the BYE
      waitingState_ = P2P_WAIT_FOR_SLP_BYE;
      waitingTimer_->start(30000, true); // 30 sec

      return;
    }
    else
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Got an ACK message (Confirmed SLP Error, terminating)." << endl;
#endif
      // TODO: send BYE here?
      endApplication();
      return;
    }
  }


  // Last option: This should be a normal ACK message.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got an ACK message." << endl;
#endif

#ifdef KMESSTEST
  // If we were waiting: the timer is stopped and you forgot an if-block in this method
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
  if(waitingState_ != P2P_WAIT_DEFAULT)
  {
    kdDebug() << "P2PApplication: Waiting state is " << waitingState_ << "!" << endl;
  }
#endif

  if(waitingState_ != P2P_WAIT_DEFAULT)
  {
    // Make sure the object will be killed eventually.
    waitingTimer_->start(30000, true); // 30 sec
  }
}



/**
 * Got an closing ack (received when our BYE-ACK is transmitted).
 * This method aborts the application.
 */
00510 void P2PApplication::gotClosingAck(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got an Closing-ACK message, terminating session." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_CLOSING_ACK );
#endif

  // Unfortunately we can't rely on this message because many
  // third-party clients don't send this message yet.
  // In that case, we send an "type-4 waiting" message before we quit.
  endApplication();
}



/**
 * Called when data is received
 *
 * @param  message  The P2P message containing the data.
 */
00532 void P2PApplication::gotData(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - gotData" << endl;
#endif
}



/**
 * Called internally when data is received, this eventually calls gotData()
 */
00544 void P2PApplication::gotDataFragment(const P2PMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Received file data" << endl;
#endif
#ifdef KMESSDEBUG
  ASSERT( waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  // TODO: do some verification on the size/offset fields??


  // Dispatch the message to the derived class
  gotData(message);


  // If we received the last part, ACK it and send the BYE
  if( message.isLastFragment() )
  {
    // TODO: We can check (by encrypting, encoding & comparing it to
    // the SHA1D field) if this was really the data we expected.

    // Send the ACK and BYE
    sendP2PAck();  // Strange, MSN6.2 doesn't send this message..?!?

    if(isUserStartedApp())
    {
      // We sent the INVITE, now we send the BYE.
      waitingState_ = P2P_WAIT_DEFAULT;
      sendSlpBye();
      // Don't terminate yet, that message needs to be ACK-ed as well.
    }
    else
    {
      // Otherwise we wait for the BYE
      waitingState_ = P2P_WAIT_FOR_SLP_BYE;
      waitingTimer_->start(30000, true); // 30 sec
    }
  }
  else
  {
    // Wait for more data to arrive.
    waitingTimer_->start(30000, true); // 30 sec
  }
}



/**
 * Called when the data preparation message is received
 */
00595 void P2PApplication::gotDataPreparation(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got the data preparation message" << endl;
#endif


  // Only if the user starts the invitation,
  // the other contact should send a data preparation message
  if(! isUserStartedApp())
  {
    kdWarning() << "P2PApplication: P2P message can't be handled, "
                << "unexpected data preparation message (contact=" << contactHandle_ << ")." << endl;

    // Got an data-preparation message, but didn't expect it.
    shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    endApplication( i18n("The transfer failed.  The contact sent bad data, or KMess doesn't support it.") );
    return;
  }


#ifdef KMESSTEST
  ASSERT( shouldSendAck_ );
  ASSERT( waitingState_ == P2P_WAIT_FOR_PREPARE );
#endif


  // The userStarted3_UserPrepares() method should call
  // sendDataPreparationAck() or sendCancelMessage()
  // to reset "userShouldAcknowledge_"
  shouldSendAck_         = true;
  userShouldAcknowledge_ = true;


  // Dispatch to the derived class
  userStarted3_UserPrepares();


  // ACK still wasn't sent yet, determine now what to send:
  if(userShouldAcknowledge_)
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Data preparation failed, sending reject" << endl;
#endif

    // Derived class still didn't acknowledge or cancel at all
    // (both disable userShouldAcknowledge_). This shouldn't occur!

    // Reject the data-preparation message
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    endApplication( i18n("The transfer failed.  Data preparation failed.") );
    return;
  }


  // Little API trick:
  // If the user didn't acknowledge,
  // but we no longer have to send an ack any more (sendDataPreparationAck() doesn't send it!),
  // it must have been sendCancelMessage(CANCEL_SESSION) calling sendP2PAck(P2PMessage::MSN_FLAG_ERROR).
  if(! shouldSendAck_)
  {
    // No error message here, assume your derived class already displayed something.
    endApplication();
    return;
  }


  // User did acknowledge, we can send the *actual* ACK now.
  // sendDataPreparationAck() only sets a boolean, so we can do the actual stuff here
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Data preparation successful, sending ACK" << endl;
#endif

  sendP2PAck();


  // If data preparation was successful, wait for the data to arrive
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Waiting for file data to arrive..." << endl;
#endif

  waitingState_ = P2P_WAIT_FOR_FILE_DATA;
  waitingTimer_->start(30000, true); // 30 sec
}



/**
 * Received a P2P message with the error-flag set.
 * This method aborts the application.
 */
00687 void P2PApplication::gotErrorAck(const P2PMessage &/*message*/)
{
  // Error in our headers?
  kdWarning() << "P2PApplication: Got a P2P error-response (flag=error contact=" << contactHandle_ << ")." << endl;

  if(dataType_ == P2P_TYPE_NEGOTIATION)
  {
    // We likely made an error in the invitation/bye reponse
    contactAborted( i18n("The contact cancelled the session.") );
  }
  else
  {
    // We were sending or receiving a file/picture
    // Can also happen if the contact can't handle our data for a different reason.
    contactAborted( i18n("The transfer failed.") );
  }
}



/**
 * Parse an incoming message
 *
 * @param  message  A reference to a P2PMessage object.
 */
00712 void P2PApplication::gotMessage(const MimeMessage &message)
{
#ifdef KMESSTEST
  ASSERT( message.isA("P2PMessage") ); // QObject function
#endif

  // If we were explicitly waiting for a packet, stop the timer
  waitingTimer_->stop();


  // Convert the message
  const P2PMessage &p2pMessage = static_cast<const P2PMessage>(message);

  // Store some message parameters for Acknowledgement later
  originalAckMessageID_ = p2pMessage.getAckMessageID();
  originalDataSize_     = p2pMessage.getDataSize();
  originalMessageID_    = p2pMessage.getMessageID();
  originalSessionID_    = p2pMessage.getSessionID();
  originalTotalSize_    = p2pMessage.getTotalSize();


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Message size: " << p2pMessage.getDataSize()
                           << " Total size: "   << p2pMessage.getTotalSize()
                           << " Offset: "       << p2pMessage.getDataOffset() << endl;
#endif


  // If this object was initialized to handle a Bad packet.
  if(getMode() == APP_MODE_ERROR_HANDLER)
  {
    // Make an exception for SLP packets
    bool isSlpMessage = (p2pMessage.getSessionID() == 0 &&
                         p2pMessage.getFlags()     == 0);
    if(! isSlpMessage)
    {
      // We received an unknown message, this object was initialized to send the correct response.
      shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
      return;
    }
  }


  // Handle ACKs before everything else.
  // The application can be terminated here.
  if(p2pMessage.isAck())
  {
    gotAck(p2pMessage);
    return;
  }
  else if(p2pMessage.isClosingAck())
  {
    gotClosingAck(p2pMessage);
    return;
  }
  else if(p2pMessage.isError())
  {
    gotErrorAck(p2pMessage);
    return;
  }
  else if(p2pMessage.isWaiting())
  {
    gotTimeoutAck(p2pMessage);
    return;
  }


  // Send the ack if needed (also applies to INVITE messages)
  shouldSendAck_ = p2pMessage.isLastFragment();


  // Session ID is 0? -> the clients negotiate for session.
  if(p2pMessage.getSessionID() == 0)
  {
    if(p2pMessage.getFlags() == 0)
    {
      // If this object was initialized to handle a Bad packet.
      if(getMode() == APP_MODE_ERROR_HANDLER)
      {
        // This object was initialized to send an error response for an unknown packet (at SLP level).
        emit appInitMessage( i18n("The transfer failed.  The contact sent bad data, or KMess doesn't support it.") );
        sendSlpError("500 Internal Error"); // Waits for ACK and terminates
        return;
      }


      // Parse the fragment
      gotNegotiationFragment(p2pMessage);
    }
    else
    {
      kdWarning() << "P2PApplication: P2P message can't be handled, "
                  << "unknown negotiation message type (flags="   << p2pMessage.getFlags()
                                                   << " contact=" << contactHandle_ << ")." << endl;

      // Don't know what to do with a negotiation message with flags set.
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      endApplication( i18n("The transfer failed.  The contact sent bad data, or KMess doesn't support it.") );
      return;
    }
  }
  // Session ID not zero? -> clients exchange data in the session
  else
  {
    // Check for data preparation messages
    if(p2pMessage.getFlags()     == 0
    && p2pMessage.getDataSize()  == 4
    && p2pMessage.getTotalSize() == 4)
    {
      gotDataPreparation(p2pMessage);
    }
    else if( waitingState_ != P2P_WAIT_FOR_FILE_DATA
         &&  p2pMessage.isPictureData()
         &&  p2pMessage.getDataSize()  == 4
         &&  p2pMessage.getTotalSize() == 4 )
    {
      // HACK: added for Encarta Instant Answers (has data flag set with preparation message).
      kdWarning() << "P2PApplication: Expecting data-preparation message, got message of 4 bytes with incorrect flags (assuming data preparation, contact=" << contactHandle_ << ")!" << endl;
      gotDataPreparation(p2pMessage);
    }
    // Check for data messages
    else if(p2pMessage.isPictureData()
         || p2pMessage.isFileData()
         || p2pMessage.isFragment())
    {
      gotDataFragment(p2pMessage);
    }
    else if(waitingState_ == P2P_WAIT_FOR_FILE_DATA
         && p2pMessage.getFlags() == 0
         && p2pMessage.isFragment())
    {
      // HACK: added for Kopete 0.9.2 code (has no flag set with data messages).
      kdWarning() << "P2PApplication: Expecting data message, got fragmented message with no flags set (assuming file data, contact=" << contactHandle_ << ")!" << endl;
      gotDataFragment(p2pMessage);
    }
    else
    {
      // Unknown p2p message
      kdWarning() << "P2PApplication: P2P message can't be handled, "
                  << " unknown data message type (flags="   << p2pMessage.getFlags()
                                             << " contact=" << contactHandle_ << ")." << endl;

      // Got an P2P binary message we can't handle. (some new kind of flag..?)
      shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      endApplication( i18n("The transfer failed.  The contact sent bad data, or KMess doesn't support it.") );
      return;
    }
  }

#ifdef KMESSTEST
  // At the end of this method, we should have sent the ACK..!
  ASSERT( ! shouldSendAck_ );
#endif
}



/**
 * Got a message fragment with SessionID 0
 * All fragments are buffered and
 * gotNegotiationMessage() is called when all parts are received
 *
 * @param  p2pMessage  The received P2P message
 */
00879 void P2PApplication::gotNegotiationFragment(const P2PMessage &p2pMessage)
{
  if(! p2pMessage.isLastFragment())
  {
    // Not everything received yet, buffer it.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an SLP fragment, buffering it." << endl;
#endif

    if(buffer_ == 0)
    {
      buffer_ = new QBuffer();
      buffer_->open(IO_ReadWrite);
    }

    buffer_->writeBlock( p2pMessage.getData(), p2pMessage.getDataSize() );
  }
  else
  {
    // Read the message or buffer
    QString slpMessage;
    QByteArray bufferData;

    if(buffer_ != 0)
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Got the last SLP fragment." << endl;
#endif

      // Append the last MSNSLP fraqment and call the method.
      buffer_->writeBlock( p2pMessage.getData(), p2pMessage.getDataSize() );
      buffer_->reset();

      // Read the buffer
      bufferData = buffer_->readAll();
      slpMessage = QString::fromUtf8( bufferData.data(), bufferData.size() );

      delete buffer_;
      buffer_ = 0;
    }
    else
    {
      // Read the data directly from the message
      slpMessage = QString::fromUtf8( p2pMessage.getData(), p2pMessage.getDataSize() );
    }


    // Parse the negotiation message
    gotNegotiationMessage( slpMessage );
  }
}



/**
 * Parse SLP "session negotiation messages".
 * Those messages don't have a sessionID set, and have
 * MSNSLP Mime fields in the body.
 *
 * @param  slpMessage  The MSNSLP message to parse.
 */
00940 void P2PApplication::gotNegotiationMessage(const QString &slpMessage)
{
  // Parse the "negotiation message" (P2PMessage with sessionID 0)

  // Parse the MIME-fields (skip the first line, because it's not in MIME format)
  int      preambleEnd = slpMessage.find("\r\n");
  QString  preamble    = slpMessage.left( (preambleEnd == -1) ? 20 : preambleEnd     );
  QString  mimeFields  = slpMessage.mid(  (preambleEnd == -1) ?  0 : preambleEnd + 2 );
  MimeMessage slpMimeMessage( mimeFields );


  // Verify it is indeed directed to us..
  QString msgTo = slpMimeMessage.getValue("To");

  if(! msgTo.isEmpty())
  {
    // Only test if the field is available. If it's not, either something
    // is wrong in this application, or the client sent a bad message and
    // we should respond with an 500 error instead.
    if( msgTo != "<msnmsgr:" + CurrentAccount::instance()->getHandle() + ">" )
    {
      if(shouldSendAck_) sendP2PAck();

      // In the future, the <msnmsgr: > string might be replaced with
      // something to support MSN to AOL chats, etc..
      kdWarning() << "P2PApplication: P2P message can't be handled, "
                  << "addressed to someone else (contact=" << contactHandle_ << ")." << endl;

      emit appInitMessage( i18n("The invitation was cancelled.  Message was not directed to us.") );
      sendSlpError("404 Not Found"); // Waits for ACK and terminates
      return;
    }
  }


  // Find out what kind of message this is:
  if(preamble.startsWith("INVITE MSNMSGR"))
  {
    // Don't accept invites if we sent one.
    if(messageID_ != 0 && isUserStartedApp())
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Got an INVITE response, rejecting (contact is likely using MSN7)." << endl;
#endif

      unsigned long tempSessionID = sessionID_;
      sessionID_ = 0;
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      sessionID_ = tempSessionID;
      return;
    }

    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Got an invitation!
    gotSlpInvite(slpMimeMessage);
  }
  else if(preamble.startsWith("MSNSLP/"))
  {
    // Error message, or "MSNSLP/1.0 200 OK" message
    gotSlpStatus(slpMimeMessage, preamble);
  }
  else if(preamble.startsWith("BYE MSNMSGR"))
  {
    // Got a BYE message
    gotSlpBye(slpMimeMessage);
  }
  else
  {
    // Unknown SLP message
    kdWarning() << "P2PApplication: P2P message can't be handled, "
                << "unsuppored SLP negotiation message (preamble=" << preamble << " contact=" << contactHandle_ << ")." << endl;

    // Got an unknown SLP preamble
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
    return;
  }
}



/**
 * Got an MSNSLP INVITE message
 */
01028 void P2PApplication::gotSlpBye(const MimeMessage &/*slpMimeMessage*/)
{
  // Don't accept BYE messages if we sent the INVITE.
  if(isUserStartedApp())
  {
    kdDebug() << "P2PApplication: WARNING - P2P message can't be handled, "
              << "unexpected SLP BYE message (contact=" << contactHandle_ << ")." << endl;

    // We should be sending the BYE..
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
    return;
  }

  // If the BYE was received early, it may be an indication we
  // didn't sent something and the other client decided to quit.
  //
  // There appears to be a small bug in MSN 7.0. It doesn't sent the final "all data received" ACK,
  // but sends the BYE directly. After we ACK it, we get a "BYE sent" message back.
  // This issue appears to be fixed in MSN 7.5.
  if( waitingState_ != P2P_WAIT_FOR_SLP_BYE && waitingState_ != P2P_WAIT_FOR_DATA_ACK )
  {
    kdWarning() << "P2PApplication: Unexpectedly received a SLP BYE message (contact=" << contactHandle_ << ")." << endl;
  }


  if(shouldSendAck_)
  {
    sendP2PAck();
  }

#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_BYE || waitingState_ == P2P_WAIT_FOR_DATA_ACK );
#endif

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got SLP BYE message, closing application." << endl;
#endif

  endApplication();
  return;
}



/**
 * Got an MSNSLP INVITE message
 */
01076 void P2PApplication::gotSlpInvite(const MimeMessage &slpMimeMessage)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got SLP INVITE message" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  // Reset the waiting state if this is the second INVITE for a file transfer
  if(waitingState_ == P2P_WAIT_FOR_FILE_DATA)
  {
    waitingState_ = P2P_WAIT_DEFAULT;
  }

  // Session invitation

  // Extract the fields of the message. This is required for userRejected()
  MimeMessage slpMimeContent(slpMimeMessage.getBody());

  // Set global fields from INVITE message.
  QString slpVia         = slpMimeMessage.getValue("Via");
  callID_                = slpMimeMessage.getValue("Call-ID");
  invitationCSeq_        = slpMimeMessage.getValue("CSeq").toInt();
  invitationContentType_ = slpMimeMessage.getValue("Content-Type");

  if(invitationContentType_ == "application/x-msnmsgr-sessionreqbody")
  {
    invitationSessionID_ = slpMimeContent.getValue("SessionID").toULong();
  }

  // Extract branch from the "Via" parameter
  QRegExp callRE(";branch=(.+)");     // don't use a guid-pattern here, msn6 seams to accept random strings.
  callRE.search(slpVia);
  branch_ = callRE.cap(1);


  // Don't forget the initialize the base class
  startByInvite(generateCookie());


  // Indicate this is an SLP message (sendCancelMessage() uses this)
  // This value is reset once a message is sent.
  gotSlpMessage_ = true;


  // Validate the content type
  if(invitationContentType_ != "application/x-msnmsgr-sessionreqbody" &&
     invitationContentType_ != "application/x-msnmsgr-transreqbody" &&   // HACK: added for KMess 1.4.2 (sent incorrect value)
     invitationContentType_ != "application/x-msnmsgr-transrespbody" )
  {
    kdWarning() << "P2PApplication: Received unexpected Content-Type: " << invitationContentType_ << "." << endl;

    // Indicate we don't like that content-type:
    sendCancelMessage(CANCEL_INVALID_SLP_CONTENT_TYPE);
    // Don't QUIT, the error will be ACK-ed.
    return;
  }


  // Extract the body of the SLP message
  MimeMessage slpContent(slpMimeMessage.getBody());

  // Tell the derived class we've got an invitation
  contactStarted1_ContactInvitesUser(slpContent);
}



/**
 * Got an MSNSLP Status header
 */
01148 void P2PApplication::gotSlpStatus(const MimeMessage &slpMimeMessage, const QString &preamble)
{
  // An Error message, or "MSNSLP/1.0 200 OK" message
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got SLP status message (" << preamble << ")" << endl;
#endif


  // Should only be received if we started it.
  if(! isUserStartedApp())
  {
    kdWarning() << "P2PApplication: P2P message can't be handled, "
                << "unexpected SLP status response (contact=" << contactHandle_ << ")." << endl;

    sessionID_ = 0;
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
    return;
  }


  // Parse the preamble
  QRegExp re("MSNSLP/\\d+\\.\\d+ (\\d+) ([^\r\n]+)");
  re.search(preamble);
  int     code   = re.cap(1).toUInt();
  QString detail = re.cap(2);


  // Reset the waiting state.
  if(waitingState_ == P2P_WAIT_CONTACT_ACCEPT)
  {
    waitingState_ = P2P_WAIT_DEFAULT;
  }

  // Handle the status codes


  // If the invitation was accepted
  if(code == 200)
  {
    // We don't need to do much here:
    // The message will be ACK-ed automatically with the correct session ID
    // because that ID was given in the sendSlpInvitation() method.

    // This the first ACK to be sent with the SessionID set.
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Dispatch to derived class,
    // It's invitation was accepted
    userStarted2_ContactAccepts(slpMimeMessage);

    // Waiting for prepare message
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Waiting for contact to send some prepare message..." << endl;
#endif

    // TODO: I guess this will be different for file transfers..
    waitingState_ = P2P_WAIT_FOR_PREPARE;
    waitingTimer_->start(30000, true); // 30 sec
    return;
  }


  // We sent a bad address in the to: header
  if(code == 404)
  {
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Inform the user KMess has an error
    kdWarning() << "P2PApplication: KMess sent a bad address in the 'to:' header." << endl;
    contactAborted( i18n("The contact rejected the invitation.  An internal error occured.") );
    return;
  }


  // Something was not supported
  if(code == 500)
  {
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    if(slpMimeMessage.getValue("Content-Type") == "null")
    {
      // The Content-Type was not supported
      kdWarning() << "P2PApplication: Content-Type of our invitation was not understood" << endl;
    }
    else
    {
      // Bad header sent in SLP fields, or invitation type was not supported
      kdWarning() << "P2PApplication: Our invitation was not supported" << endl;
    }

    contactAborted( i18n("The contact rejected the invitation.  An internal error occured.") );
    return;
  }


  // The invitation was declined
  if(code == 603)
  {
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Contact Declined
    contactRejected();
    return;
  }


  // Last option: tell the contact it sent an unknown message

  kdWarning() << "P2PApplication: P2P message can't be handled, "
              << "unsupported SLP response (preamble=" << preamble << " contact=" << contactHandle_ << ")." << endl;

  // Got an unknown SLP status code
  sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
  endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
}



/**
 * Received a message with the timeout-flag set.
 * This method aborts the application.
 */
01283 void P2PApplication::gotTimeoutAck(const P2PMessage &/*message*/)
{
  // Error in our headers?
  if(isWaitingForUser())
  {
    // Contact aborted this session because we didn't press "accept/cancel" within 30 secs.
    contactAborted( i18n("The transfer failed.  Timeout while waiting for user to accept.") );
  }
  else
  {
    // Apparently the contact was waiting for something, likely some packet we had to send.
    kdDebug() << "P2PApplication: WARNING - Got a P2P error-response (flag=waiting contact=" << contactHandle_ << ")." << endl;
    contactAborted( i18n("The transfer failed.  An internal error occured.") );
  }
}



/**
 * Send a cancel message. Some errors also terminate the application eventually.
 *
 * @param  cancelReason  The reason why the application is cancelled.
 *
 * MSNSLP related notes:
 *   Error reasons sending an MSNSLP error messages should only be
 *   used in the contactStarted1 / contactStarted2 methods..
 *   Once the MSNSLP error message is sent, P2PApplication waits for the
 *   contact to ACK, and terminates the application afterwards.
 *   Hence, you don't have to call endApplication() from contactStarted1 / contactStarted2.
 *
 *   When an MSNSLP error is sent at a different moment, this will likely result in
 *   undefined responses from the other client (or the error message might be ignored).
 *   If KMess is compiled in debug mode, an assertion in sendSlpError() will detect this.
 *
 *
 * The following cancelReason values are supported:
 * - CANCEL_INVITATION
 *     The user declined the invitation.
 *
 *     This sends an "MSNSLP 603 Decline" message, waits for
 *     the contact to ACK and terminates this application afterwards.
 *
 *     This reason will be used automatically.
 *
 * - CANCEL_SESSION / CANCEL_FAILED
 *     The user aborted the session.
 *
 *     If called from contactStarted1_ContactInvitesUser(),
 *     this sends an "MSNSLP 603 Decline" message, waits for
 *     the contact to ACK and terminates this application afterwards.
 *
 *     From other methods an P2P ACK message is sent with the "error flag" set (type 8).
 *
 *     Within the userStarted3_UserPrepares() method, CANCEL_SESSION is the only
 *     valid response. If KMess is compiled in debug mode, an assertion will be triggered otherwise.
 *
 * - CANCEL_NOT_INSTALLED
 *     The requested service/application is not installed.
 *
 *     This sends an "MSNSLP 500 Internal Error" message, waits for
 *     the contact to ACK and terminates this application afterwards.
 *
 *     This reason should only be used in contactStarted1_ContactInvitesUser().
 *     The default implementation in Application::contactStarted1_ContactInvitesUser()
 *     uses this reason automatically to cancel the invitation. Hence the base class
 *     is used to reject unknown services automatically.
 *
 * - CANCEL_TIMEOUT
 *     There was a timeout waiting for the contact to accept or send certain P2P data.
 *     Usually this reason is used internally by certain timeout functions.
 *     This sends an P2P message with the "waiting flag" set (type 4).
 *
 * - CANCEL_INVALID_SLP_CONTENT_TYPE
 *     Sends an "MSNSLP 500 Internal Error" message back, but without an Content-Type set.
 *
 *     This reason should only be used from contactStarted1_ContactInvitesUser()
 *     or userStarted2_ContactAccepts() to indicate the given Content-Type is not supported.
 *     Other invocations result in undefined responses from the other client (it will likely be ignored).
 *
 */
01363 void P2PApplication::sendCancelMessage(const ApplicationCancelReason cancelReason)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - sendCancelMessage (reason=" << cancelReason << ")." << endl;
#endif

  // We'll send the cancel message!
  sentCancelMessage_ = true;

  // I don't care what the reason is if you had
  // to send the data-preparation ACK
  if(userShouldAcknowledge_)
  {
#ifdef KMESSTEST
    ASSERT( cancelReason == CANCEL_SESSION );
#endif

    // Cancelled the data preparation message
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    userShouldAcknowledge_ = false;
    shouldSendAck_         = false;
    return;
  }



  // Declined the invitation
  if(cancelReason == CANCEL_INVITATION)
  {
    // Use the cancel-reason to send the correct message:
    sendSlpError("603 Decline", invitationSessionID_, invitationContentType_);
    return;
  }


  // Timeout waiting for some P2P data
  // ...or timeout waiting for contact to accept.
  if(cancelReason == CANCEL_TIMEOUT)
  {
    sendP2PMessage(0, P2PMessage::MSN_FLAG_WAITING, 0);
    shouldSendAck_ = false;
    return;
  }


  // The application type is not installed
  if(cancelReason == CANCEL_NOT_INSTALLED && gotSlpMessage_)
  {
    // Usually this particular error is invoked from Application::contactStarted1_ContactInvitesUser().
    sendSlpError("500 Internal Error", invitationSessionID_, invitationContentType_);
    return;
  }


  // User or application wants to abort
  if(cancelReason == CANCEL_SESSION || cancelReason == CANCEL_FAILED)
  {
    if(gotSlpMessage_)
    {
      // Cancelled while in SLP negotiate mode.
      // Include the original content-type to tell the other
      // client that part of the message was not in error.
      sendSlpError("500 Internal Error", invitationSessionID_, invitationContentType_);
      return;
    }
    else
    {
      // TODO: these error messages are somehow rejected with
      // another "error in binary fields" error-message response.
      // One way of testing this code is causing the file-open code
      // in P2PPictureTransfer::userStarted3_UserPrepares() to fail.

      // Cancelled while receiving data
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      return;
    }
  }


  // Invalid content type received
  if(cancelReason == CANCEL_INVALID_SLP_CONTENT_TYPE)
  {
    // Send an 500 error message without content-type set
    sendSlpError("500 Internal Error");
    return;
  }


  // Last option, unknown cancel message
#ifdef KMESSDEBUG_MIMEAPPLICATION
  kdWarning() << "MimeApplication: unknown cancelReason used." << endl;
#endif

  sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
}



/**
 * Send data packets from an data source. A timer/thread starts that transmits
 * all data packets automatically. It also uses the dataSource object
 * to determine the data size.
 *
 * @param  dataSource  Source of the data to send
 * @param  dataType    Type of the data, a member of the P2PDataType enum.
 */
01469 void P2PApplication::sendData(QIODevice *dataSource, P2PDataType dataType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Begin of data transfer" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_ != 0 );
  ASSERT( dataSource != 0 );
#endif

  // Make sure we don't activate a slot without a data source.
  if(dataSource == 0)
  {
    kdWarning() << "P2PApplication: Couldn't send data, data source is null." << endl;


    emit appInitMessage( i18n("The transfer failed.  Couldn't open data source.") );

    // TODO: Not sure this will work though..
    gotSlpMessage_ = true;            // Make sure the right cancel message is sent.
    sendCancelMessage(CANCEL_FAILED); // MSNSLP messages terminate the application eventually.
    return;
  }

  // Set the global data fields
  dataSource_ = dataSource;
  dataType_   = dataType;

  // Set the total size field, this causes sendP2PMessage()
  // to handle fragmented messages correctly.
  messageTotalSize_ = dataSource->size();

  // Run the slot after 10ms
  QTimer::singleShot( 10, this, SLOT(slotSendData()) );
}



/**
 * Send the data preparation message.
 *
 * The data message consists of a P2P message with 0x00000000 as message body.
 */
01512 void P2PApplication::sendDataPreparation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending data preparation message." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_    != 0 );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  QByteArray p2pMessage(4);
  p2pMessage.fill(0x00);
  sendP2PMessage(p2pMessage, 0, 1);

  // Wait until our preparation message is ACK-ed
  waitingState_ = P2P_WAIT_FOR_PREPARE_ACK;
  waitingTimer_->start(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Waiting for data preparation ACK..." << endl;
#endif
}



/**
 * A special method to handle certain acknowledgements.
 *
 * You need to call this method from userStarted3_UserPrepares()
 * to accept the data-preparation message.
 */
01543 void P2PApplication::sendDataPreparationAck()
{
  if(userShouldAcknowledge_)
  {
    // This value is checked again after the
    // userStarted3_UserPrepares() method returns.
    userShouldAcknowledge_ = false;
  }
  else
  {
#ifdef KMESSTEST
    kdWarning() << "P2PApplication: call to sendDataPreparationAck() not expected!" << endl;
#endif
  }
}



/**
 * Send a P2P ACK message.
 * MSN sends an P2P ACK for each received P2P message, which
 * turns the P2P communication into a stop-and-wait protocol.
 *
 * @param  ackType  The ACK type (flag) to use, defaults to MSN_FLAG_ACK.
 */
01568 void P2PApplication::sendP2PAck(int ackType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if(ackType == P2PMessage::MSN_FLAG_ACK)
  {
    if(messageID_ == 0)
    {
      kdDebug() << "P2PApplication: Sending P2P ACK (INVITE-ACK)" << endl;
    }
    else
    {
      kdDebug() << "P2PApplication: Sending P2P ACK" << endl;
    }
  }
  else if(ackType == P2PMessage::MSN_FLAG_ERROR)
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Error-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_WAITING)
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Timeout-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_CLOSING_ACK)
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Closing-ACK)" << endl;
  }
  else
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Unknown-type)" << endl;
  }
#endif
#ifdef KMESSTEST
  ASSERT( shouldSendAck_ );
#endif

  MimeMessage message;


  // Fill the header:
  //
  // 0    4    8        16        24   28   32   36   40        48
  // |....|....|....|....|....|....|....|....|....|....|....|....|
  // |sid |mid |offset   |totalsize|size|flag|asid|auid|a-datasz |
  //

  QByteArray header(48);
  header.fill(0x00);

  // Determine the message ID
  if(messageID_ == 0)
  {
    // We don't have a message ID set yet, we're acknowledging
    // the first INVITE message. Generate an ID

    messageID_ = generateID();
    P2PMessage::insertBytes(header, messageID_, 4);

    // Somehow, the next message we send (200 OK) should contain a messageID - 3:
    // We decrement the messageID with 4 because it will be incremented in the next call.
    messageID_ -= 4;
  }
  else
  {
    messageID_++;
    P2PMessage::insertBytes(header, messageID_, 4);
  }


  // Insert the bytes
  P2PMessage::insertBytes(header, originalSessionID_,     0); // SessionID
  // Message ID was set earlier
  P2PMessage::insertBytes(header, originalTotalSize_,    16); // Confirm total size set in previous message
  P2PMessage::insertBytes(header, ackType,               28); // Set the ACK flag.
  P2PMessage::insertBytes(header, originalMessageID_,    32); // Set the ACK message ID
  P2PMessage::insertBytes(header, originalAckMessageID_, 36); // Set the ACK Unique ID field.
  P2PMessage::insertBytes(header, originalDataSize_,     40); // Set the ACK data size with the previous message size.


  // Footer
  char footer[4];
  footer[0] = 0x00;
  footer[1] = 0x00;
  footer[2] = 0x00;
  footer[3] = 0x00;


  // Assign the message fields
  message.addField("MIME-Version", "1.0");
  message.addField("Content-Type", "application/x-msnmsgrp2p");
  message.addField("P2P-Dest",     contactHandle_);
  message.setBinaryData(header.data(), 0, footer, 0);


  // Send the message
  emit putMsg(message);
  shouldSendAck_ = false;


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: ACK Transmitted (flags=" << ackType << ")." << endl;
#endif
}



/**
 * Constructs and sends an P2P Message.
 *
 * This method includes some of the global fields in the message body.
 *
 * @param messageData    The message to send. This could be a binary file data block.
 * @param flagField      The flag-field content.
 * @param footerCode     The message footer.
 */
01682 void P2PApplication::sendP2PMessage(const QByteArray &messageData, int flagField, uint footerCode)
{
#ifdef KMESSTEST
  ASSERT( messageData.size() <= 1202 );
#endif

  MimeMessage message;

  // Reset this flag to make sure no other
  // "500 Internal Error" will be sent afterwards.
  gotSlpMessage_ = false;

  // Initialize the header
  QByteArray header(48);
  header.fill(0x00);


  // Determine the message ID:
  if(messageID_ == 0)
  {
    // This is the first P2P message we send, initialize message ID
    messageID_ = generateID();
  }
  else if(messageOffset_ == 0)
  {
    // Only increments the ID if this is not a "fragmented message".
    messageID_++;
  }


  // Determine the message size:
  uint messageSize = messageData.size();
  uint totalSize;
  uint offsetField;

  if(messageTotalSize_ == 0)
  {
    // Normal message:
    totalSize   = messageData.size();
    offsetField = 0;
  }
  else
  {
    // Splitted message:
    totalSize   = messageTotalSize_;
    offsetField = messageOffset_;
  }


  // Generate Unique ID for this message
  lastUniqueID_ = generateID();



  // Update the header
  P2PMessage::insertBytes(header, sessionID_,     0);    // 1: Session ID
  P2PMessage::insertBytes(header, messageID_,     4);    // 2: BaseIdentifier (starts random [4, 4294967295])
  P2PMessage::insertBytes(header, offsetField,    8);    // 3: Offset (if more then 1202 bytes to send)
  P2PMessage::insertBytes(header, totalSize,      16);   // 4: Total size of the message
  P2PMessage::insertBytes(header, messageSize,    24);   // 5: This message size, size of previous if no data
  P2PMessage::insertBytes(header, flagField,      28);   // 6: Message type (flags)
  P2PMessage::insertBytes(header, lastUniqueID_,  32);   // 7: Field2 of previous for ACK-messages (base message ID)
//P2PMessage::insertBytes(header, 0,              36);   // 8: Field7 of previous for ACK-messages (unique ID)
//P2PMessage::insertBytes(header, 0,              40);   // 9: Field4 of previous for ACK-messages (size)


  // Update the offset fields for splitted messages
  if(messageTotalSize_ != 0)
  {
    messageOffset_ += messageSize;
    // If we transmitted all parts, reset the fields for the next normal message
    if(messageOffset_ >= messageTotalSize_)
    {
      messageOffset_    = 0;
      messageTotalSize_ = 0;
    }
  }


  // At the end of the message we need to add an footer of 4 bytes.
  // This is little endian format, so we can't use P2PMessage::insertBytes()
  //
  // Possible values:
  //  0x00 session
  //  0x01 for display/emoticon
  //  0x02 filetransfer, or start menu code

  char footer[4];
  footer[0] = (char) ((footerCode >> 24) & 0xFF);
  footer[1] = (char) ((footerCode >> 16) & 0xFF);
  footer[2] = (char) ((footerCode >>  8) & 0xFF);
  footer[3] = (char) ( footerCode        & 0xFF);


  // Assign the message fields
  message.addField("MIME-Version", "1.0");
  message.addField("Content-Type", "application/x-msnmsgrp2p");
  message.addField("P2P-Dest",     contactHandle_);
  message.setBinaryData(header.data(), messageData.data(), footer, messageData.size());


  // Send the message
  emit putMsg(message);


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Message transmitted "
            << "(SID="         << sessionID_
            << " MID="         << messageID_
            << " UID="         << lastUniqueID_
            << " flags="       << flagField
            << " size="        << messageSize
            << ")" << endl;
#endif
}



/**
 * Close the session by sending a SLP BYE message.
 * This should only be called if we sent the INVITE as well.
 *
 * Once the BYE message is sent, it waits for the last ACK to arrive,
 * ACKs that message and terminates the application instance afterwards.
 *
 * If the last ACK doesn't arrive in a certain timeout, the application is also terminated.
 */
01809 void P2PApplication::sendSlpBye()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending SLP BYE message." << endl;
#endif
#ifdef KMESSTEST
  // Only send a BYE if we started it
  ASSERT( isUserStartedApp() );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
#endif

  // Content-Type:
  QString contentType;
  contentType = "application/x-msnmsgr-sessionclosebody";

  // Handle
  QString myHandle = CurrentAccount::instance()->getHandle();


  // Create the message
  QString slpMessage;
  slpMessage = "BYE MSNMSGR:" + contactHandle_ + " MSNSLP/1.0\r\n"
               "To: <msnmsgr:" + contactHandle_ + ">\r\n"
               "From: <msnmsgr:" + myHandle + ">\r\n"
               "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n" // from our INVITE
               "CSeq: 0\r\n"
               "Call-ID: " + callID_ + "\r\n"  // from our INVITE
               "Max-Forwards: 0\r\n"
               "Content-Type: " + contentType + "\r\n"
               "Content-Length: 3\r\n"
               "\r\n";

  // Send the message
  sendSlpMessage(slpMessage);

  // Don't run endApplication() yet, there is one ACK we wait for...
  waitingState_ = P2P_WAIT_FOR_SLP_BYE_ACK;
  waitingTimer_->start(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Waiting for BYE ACK..." << endl;
#endif
}



/**
 * Send an error response at SLP level.
 * This can be done to decline a file tranfer or tell the other client it sent bad SLP data.
 *
 * @param  statusLine          The status line, for example "200 OK", or "603 Decline"
 * @param  sessionID           ID of the current session (can be empty)
 * @param  messageContentType  Contact type of the message which is in error (can be empty)
 *
 * The session ID can be 0 to indicate there was an error in the Content-Type. Setting the
 * session ID and Content-Type to the values used in the INVITE message indicates
 * you like to cancel that invitation.
 *
 * Once this method returns the SessionID is set to 0 (which shouldn't be a problem
 * since you use this method to end a session). This is because SLP messages are always
 * sent without an SessionID.
 */
01871 void P2PApplication::sendSlpError(const QString &statusLine,
                                  const uint sessionID, const QString &messageContentType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending SLP error response: " << statusLine << endl;
#endif

#ifdef KMESSTEST
  // Test, as other invocations likely result in undefined client behavour
  // (
  ASSERT( ! shouldSendAck_ );
  ASSERT(   gotSlpMessage_ );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
#endif

  QString myHandle = CurrentAccount::instance()->getHandle();

  // Determine Content-Type and Content-Length fields
  QString content;
  QString contentType;
  uint    contentLength;

  if(sessionID == 0)
  {
    // Used to indicate a general error
    content       = QString::null;
    contentType   = "null";
    contentLength = 0;
  }
  else
  {
#ifdef KMESSTEST
    ASSERT(messageContentType != 0 && ! messageContentType.isEmpty());
#endif
    // Used to indicate an error in the Invite, or decline a session
    content       = "SessionID: " + QString::number(sessionID) + "\r\n\r\n\0";
    contentLength = content.length();
    contentType   = messageContentType;
  }

  // Create the message
  QString slpMessage;
  slpMessage = "MSNSLP/1.0 " + statusLine + "\r\n"
               "To: <msnmsgr:" + contactHandle_ + ">\r\n"
               "From: <msnmsgr:" + myHandle + ">\r\n"
               "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n"  // Indicates which INVITE was in error.
               "CSeq: 0\r\n"
               "Call-ID: " + callID_ + "\r\n"
               "Max-Forwards: 0\r\n"
               "Content-Type: " + contentType + "\r\n"
               "Content-Length: " + QString::number(contentLength) + "\r\n"
               + (contentLength > 0 ? "\r\n" : "")
               + content;

  // Send the message
  sendSlpMessage(slpMessage);

  // Wait for the message to be ACK-ed.
  waitingState_ = P2P_WAIT_FOR_SLP_ERR_ACK;
  waitingTimer_->start(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Waiting for SLP ACK..." << endl;
#endif
}



/**
 * Start a new MSNSLP session
 *
 * @param  sessionID    Identifies the Session ID you've chosen.
 * @param  contentType  Content-Type of the message body.
 * @param  message      Invitation message body.
 */
01946 void P2PApplication::sendSlpInvitation(uint sessionID, const QString &contentType, const MimeMessage &message)
{
#ifdef KMESSTEST
  ASSERT(! contentType.isEmpty()    );
  ASSERT(! contactHandle_.isEmpty() );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
#endif

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending INVITE message" << endl;
#endif

  // Mime message conversion
  QString mimeBody = message.getFields();

  // Content handles
  QString myHandle = CurrentAccount::instance()->getHandle();

  // Identifiers
  callID_          = generateGUID(); // Identifies the session
  branch_          = generateGUID(); // Identifies the INVITE request

  // TODO: this is a QCString now but it must finish with a \0.

  // Create the message
  QString slpMessage;
  slpMessage = "INVITE MSNMSGR:" + contactHandle_ + " MSNSLP/1.0\r\n"
               "To: <msnmsgr:" + contactHandle_ + ">\r\n"
               "From: <msnmsgr:" + myHandle + ">\r\n"
               "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n"
               "CSeq: 0\r\n"
               "Call-ID: " + callID_ + "\r\n"
               "Max-Forwards: 0\r\n"
               "Content-Type: " + contentType + "\r\n"   // Starts MSNSLP Session.
               "Content-Length: " + QString::number(mimeBody.length() + 5) + "\r\n"
               "\r\n"
               + mimeBody +
               "\r\n";


  // Send the message
  sessionID_        = 0;              // Session ID is zero for negoriations.
  messageOffset_    = 0;
  messageTotalSize_ = 0;

  sendSlpMessage(slpMessage);

  // If the client accepts our invitation, we
  // use the session ID in the next ACK message.
  sessionID_        = sessionID;

  // Wait for the contact to accept
  waitingState_     = P2P_WAIT_CONTACT_ACCEPT;
  waitingTimer_->start(30000, true); // 30 sec (MSN6 also has a 30 sec limit for this)

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Waiting for contact to accept..." << endl;
#endif
}



/**
 * Send a given string using sendP2PMessage().
 * The SLP message is always sent with a sessionid of zero.
 */
02012 void P2PApplication::sendSlpMessage(const QString &slpMessage)
{
  // Make sure the message is sent with sessionid=0 (for BYE messages)
  unsigned long oldSessionID = sessionID_;
  sessionID_ = 0;

  // Get the raw utf8 encoded bytearray
  QCString utf8Message = slpMessage.utf8();

  // Convert the QCString to QByteArray directly,
  // don't try to remove the extra padded '\0' character
  // (like MimeMessage::getMessage() does)
  // because MSN6/7 actually relies on this.

  // Send the message with the generic sendP2PMessage() method
  sendP2PMessage(utf8Message);

  // Restore the session ID
  sessionID_ = oldSessionID;
}



/**
 * Send the 200 OK message to accept the invitation.
 * Most likely the content message should contain the SessionID sent in the invitation.
 * Once the confirmation message has been sent, this class will automatically
 * use the correct Session ID (because it was read when the invite was received).
 *
 * @param message  Body of the SLP message
 */
02043 void P2PApplication::sendSlpOkMessage(const MimeMessage &message)
{
#ifdef KMESSTEST
  ASSERT(! branch_.isEmpty()                );
  ASSERT(! contactHandle_.isEmpty()         );
  ASSERT(! invitationContentType_.isEmpty() );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
#endif

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending MSNSLP/1.0 200 OK message." << endl;
#endif

  // Get message content and Content-Type
  bool gotTransferInvitation = (invitationContentType_ == "application/x-msnmsgr-transreqbody");
  QString content     = message.getFields();
  QString contentType = (gotTransferInvitation ? "application/x-msnmsgr-transrespbody" : invitationContentType_);

  QString myHandle = CurrentAccount::instance()->getHandle();

  // Send the SLP OK message to tell the other client you'd like to start the transfer.
  QString slpMessage;
  slpMessage = "MSNSLP/1.0 200 OK\r\n"
               "To: <msnmsgr:" + contactHandle_ + ">\r\n"
               "From: <msnmsgr:" + myHandle + ">\r\n"
               "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n"
               "CSeq: " + QString::number(invitationCSeq_ + 1) + "\r\n"
               "Call-ID: " + callID_ + "\r\n"
               "Max-Forwards: 0\r\n"
               "Content-Type: " + contentType + "\r\n"
               "Content-Length: " + QString::number(content.length() + 3) + "\r\n"
               "\r\n"
               + content + "\r\n";

  sendSlpMessage(slpMessage);

  // Now that we've confirmed the session, use the sessionID
  // in the next messages:
  sessionID_ = invitationSessionID_;

  // Wait until our OK message is ACK-ed
  waitingState_ = P2P_WAIT_FOR_SLP_OK_ACK;
  waitingTimer_->start(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Waiting for contact to ACK the 200 OK message..." << endl;
#endif
}



/**
 * Cleanup function, is called if we never received the data we were waiting for.
 */
02097 void P2PApplication::slotCleanup()
{
#ifdef KMESSTEST
  ASSERT( waitingState_ != P2P_WAIT_DEFAULT );
#endif

  // Tell the other client we were waiting for a message to receive!
  sendCancelMessage(CANCEL_TIMEOUT);   // MSN_FLAG_WAITING message

  // Important note:
  // Each case calling endApplication() contains a "return;"
  // All debug cases use "break;" so the final endApplication() is called.

  // Put a message at the console
  switch(waitingState_)
  {
    case P2P_WAIT_CONTACT_ACCEPT:
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Contact didn't accept the invitation, terminating manually." << endl;
#endif

      endApplication( i18n("The invitation was cancelled.  A timeout occured waiting for the contact to accept.") );
      return;
    }

    case P2P_WAIT_FOR_SLP_OK_ACK:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for SLP OK ACK (contact=" << contactHandle_ << ")." << endl;
      break;
    }

    case P2P_WAIT_FOR_PREPARE:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for data-preparation message (contact=" << contactHandle_ << ")." << endl;
      break;
    }

    case P2P_WAIT_FOR_PREPARE_ACK:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for data-preparation ACK (contact=" << contactHandle_ << ")." << endl;
      break;
    }

    case P2P_WAIT_FOR_FILE_DATA:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for file data transfer (contact=" << contactHandle_ << ")." << endl;
      break;
    }

    case P2P_WAIT_FOR_SLP_BYE:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for BYE message (contact=" << contactHandle_ << ")." << endl;
      endApplication(); // No message required, already got what we wanted
      return;
    }

    case P2P_WAIT_FOR_SLP_BYE_ACK:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for BYE-ACK (contact=" << contactHandle_ << ")." << endl;
      endApplication(); // No message required, already got what we wanted
      return;
    }

    case P2P_WAIT_FOR_SLP_ERR_ACK:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for SLP Error-ACK (contact=" << contactHandle_ << ")." << endl;
      endApplication(); // No message required, already got what we wanted
      return;
    }

    case P2P_WAIT_FOR_CLOSING_ACK:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for closing-ACK (contact=" << contactHandle_ << ")." << endl;
      endApplication(); // No message required, already got what we wanted
      return;
    }

    default:
    {
      kdDebug() << "P2PApplication: WARNING - Timeout waiting for ...? (contact=" << contactHandle_ << ")" << endl;
    }
  }

  endApplication( i18n("The invitation was cancelled.  A timeout occured waiting for data.") );
}



/**
 * A slot which sends the file data.
 * This slot is called from a timer event.
 */
02190 void P2PApplication::slotSendData()
{
  // KMess would crash otherwise...
  if( dataSource_ == 0 )
  {
    kdDebug() << "P2PApplication: WARNING - Couldn't send more data, data source is null." << endl;

    // Don't send an error message, already happened before.
    return;
  }

  // Read the data from the file.
  char buffer[1202]; // 1202 is the max P2P data size.
  int bytesRead = dataSource_->readBlock( buffer, 1202 );

  QByteArray data;
  data.duplicate( buffer, bytesRead );

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending data."
            << " offset = " << messageOffset_
            << " buffer = " << bytesRead
            << endl;
#endif


  // Determine the flags:
  // Funny, MSN6 didn't seam to care which flag was set
  // while transferring the file. KMess however, does.
  if( dataType_ == P2P_TYPE_PICTURE )
  {
    sendP2PMessage(data, P2PMessage::MSN_FLAG_DATA, 1);
  }
  else if( dataType_ == P2P_TYPE_FILE )
  {
    sendP2PMessage(data, P2PMessage::MSN_FLAG_FILETRANSFER, 2);
  }
  else
  {
    if(messageOffset_ == 0) kdDebug() << "P2PApplication: Unknown data transfer mode." << endl;
    sendP2PMessage(data, 0, 1); // The best solution in this situation
  }

// Disabled in backport
//  // Dispatch the progress to the derived class
//  showTransferProgress( messageOffset_ + bytesRead );

  // Finished?
  if(messageTotalSize_ > 0)
  {
    // Run this slot again after 10 ms.
    QTimer::singleShot( 10, this, SLOT(slotSendData()) );
  }
  else
  {
    // Transfer finished!
#ifdef KMESSTEST
    ASSERT( dataSource_->atEnd() );
#endif

    // Reset data source
    dataSource_   = 0;
    dataType_     = P2P_TYPE_NEGOTIATION;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Finished sending data, waiting for ACK..." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_DATA_ACK;
    waitingTimer_->start(30000, true); // 30 sec
  }
}



#include "p2papplication.moc"

Generated by  Doxygen 1.6.0   Back to index