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

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 <qptrlist.h>
#include <qdatetime.h>
#include <qiodevice.h>
#include <qtimer.h>
#include <qbuffer.h>
#include <klocale.h>
#include <kdebug.h>

#include "applicationlist.h"
#include "../mimemessage.h"
#include "../p2pmessage.h"
#include "../extra/msndirectconnection.h"
#include "../../msnobject.h"
#include "../../kmessdebug.h"
#include "../../currentaccount.h"

#ifdef KMESSDEBUG_P2PAPPLICATION
#define KMESSDEBUG_P2PAPPLICATION_GENERAL
#endif

// Settings for debugging.
#define P2PAPPLICATION_AVOID_DC_CLIENT 0
#define P2PAPPLICATION_AVOID_DC_SERVER 0

// Timeout settings, to prevent stale instances.
// Measured an ACK response time of 90 seconds once for an overloaded machine (Pentium 2 running Skype + WLM).
#define P2PAPPLICATION_TIMEOUT_ACCEPT   120000
#define P2PAPPLICATION_TIMEOUT_ACK      120000
#define P2PAPPLICATION_TIMEOUT_DATA     120000
#define P2PAPPLICATION_TIMEOUT_MOREDATA 120000
#define P2PAPPLICATION_TIMEOUT_SLP      120000  // 120 seconds
#define P2PAPPLICATION_TIMEOUT_DESTROY   10000  // 10 seconds


/**
 * @brief Constructor, initializes the P2PApplication instance fields.
 *
 * The ApplicationList object contains the direct connection, which is shared between all P2P applications of one same contact.
 * This object is also used to deliver messages back to the contact.
 *
 * @param  applicationList  The application list object for the contact
 */
00066 P2PApplication::P2PApplication(ApplicationList *applicationList)
: Application( applicationList->getContactHandle() ),
  aborting_(false),
  applicationList_(applicationList),
  buffer_(0),
  dataSource_(0),
  dataType_(P2P_TYPE_NEGOTIATION),
  fragmentMessageID_(0),
  fragmentOffset_(0),
  fragmentTotalSize_(0),
  gotSlpMessage_(false),
  invitationCSeq_(0),
  invitationSessionID_(0),
  sessionID_(0),
  nextMessageID_(0),
  shouldSendAck_(false),
  userShouldAcknowledge_(false),
  waitingState_(P2P_WAIT_DEFAULT)
{
  // Reset other internal fields
  lastIncomingMessage_.dataSize     = 0;
  lastIncomingMessage_.messageID    = 0;
  lastIncomingMessage_.messageType  = P2P_MSG_UNKNOWN;
  lastIncomingMessage_.sentTime     = 0;
  lastIncomingMessage_.sessionID    = 0;
  lastIncomingMessage_.totalSize    = 0;
  lastIncomingMessage_.ackSessionID = 0;

  // Enable auto-deletion on ack queue properties.
  outgoingMessages_.setAutoDelete(true);

  // Initiaize the timer
  waitingTimer_ = new QTimer(this);
  connect( waitingTimer_, SIGNAL(timeout()), this, SLOT(slotCleanup()) );
}



/**
 * @brief Class destructor.
 *
 * Cleans up buffers and timers.
 */
00109 P2PApplication::~P2PApplication()
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "P2PApplication: entering destructor,"
               " session=" << sessionID_ <<
               " closing=" << isClosing() <<
               " timer=" << waitingTimer_->isActive() << 
               " state=" << waitingState_ <<
               " unackedCount=" << outgoingMessages_.count() << "." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( outgoingMessages_.isEmpty() );
#endif

  // Stop the timer
  waitingTimer_->stop();
  applicationList_ = 0;
  dataSource_ = 0;

  // Delete pointers
  delete waitingTimer_;
  delete buffer_;
}



/**
 * @brief  Make sure no more data will be sent.
 */
00138 void P2PApplication::abortDataSending()
{
  // Abort sending.
  if( dataSource_ != 0 )
  {
#ifdef KMESSDEBUG_APPLICATION
    kdDebug() << "P2PApplication::abortDataSending() - application was still sending data, resetting." << endl;
#endif

    dataSource_ = 0;
    dataType_   = P2P_TYPE_NEGOTIATION;
    applicationList_->unregisterDataSendingApplication( this );
  }
}



/**
 * @brief The contact aborted the session
 *
 * This method is overwritten from the base class, so
 * endApplicationLater() is called instead of endApplication().
 * This leaves the P2P session open for a short while, to accept any remaining
 * packets that could be sent (like TCP's TIME_WAIT state).
 *
 * @param message Optional message to display, defaults to getContactAbortMessage().
 */
00165 void P2PApplication::contactAborted(const QString &message)
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "P2PApplication::contactAborted() - state=" << waitingState_ << endl;
#endif

  // Make sure a second message is not displayed here.
  // The contact could be sending multiple error messages.
  if( waitingState_ == P2P_WAIT_END_APPLICATION )
  {
#ifdef KMESSDEBUG_APPLICATION
    kdDebug() << "P2PApplication::contactAborted() - Abort the application again, "
                 "application was already awaiting termination." << endl;
#endif

    // Make sure the timer is started again.
    endApplicationLater();
    return;
  }
  else if( isClosing() )
  {
    kdWarning() << "P2PApplication::contactAborted: Attempted to close application twice "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=endapplicationlater)" << endl;

    // Make sure the timer is started again.
    endApplicationLater();
    return;
  }


  bool isTransferComplete = fragmentTracker_.isComplete();
  if( isTransferComplete )
  {
    // Make sure the user won't get a "transfer complete" message
    // followed by a "contact aborted" message.
    //
    // This could happen if:
    // - WLM sends data
    // - KMess acks
    // - WLM closes the SB
    // - contactAborted() called for every app.

    // Also interesting:
    // - signout / signin with WLM.
    // - establish a SB connection again by starting a chat.
    // - The P2P session is still active in WLM,
    //   and has a bridge now, so WLM sends the SLP BYE.
    // - kmess will ack it with a temporary p2p session.
    kdWarning() << "P2PApplication::contactAborted() - not displaying message because data transfer is complete. "
                   "The switchboard was likely closed after receiving all data. "
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=endapplicationlater)" << endl;
  }
  else
  {
    if( message.isEmpty() )
    {
      showEventMessage( getContactAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, true );
    }
    else
    {
      showEventMessage( message, ChatMessage::CONTENT_APP_CANCELED, true );
    }
  }

  endApplicationLater();
}



/**
 * @brief Step 1 of a contact-started chat: the contact invites the user
 *
 * This default implementation automatically declines the invitation with a "feature not supported" notification.
 *
 * Override this method to handle the invitation instead.
 * It can typically do one of the following things:
 * - parse the 'Context' field of the invitation and store the information.
 * - display the accept/cancel link with offerAcceptOrReject().
 * - automatically accept the invitation by calling contactStarted2_UserAccepts().
 * - decline the invitation by calling sendCancelMessage().
 *
 * @param  message  The body (payload fields) of the SLP INVITE message,
 *                  which typically has the fields 'Context' and 'AppID'.
 *                  The 'SessionID' and 'EUF-GUID' fields are be processed automatically.
 */
00257 void P2PApplication::contactStarted1_ContactInvitesUser(const MimeMessage &message)
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "P2PApplication - contactStarted1_ContactInvitesUser (rejects invitation)" << endl;
#endif

  // 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 slpMimeBody(message.getBody());
  kdWarning() << "The contact sent an MSNSLP invitation type KMess can't handle yet "
                 "(euf-guid=" << slpMimeBody.getValue("EUF-GUID") <<
                 " appid="    << slpMimeBody.getValue("AppID")    <<
                 " action=send500)." << endl;

  // Display a warning to the user
  showSystemMessage( i18n("The contact initiated a MSN6 feature KMess can't handle yet."), ChatMessage::CONTENT_SYSTEM_NOTICE, true );


  // Cancel the invitation
  sendCancelMessage( CANCEL_NOT_INSTALLED );

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



/**
 * @brief Step 4 of a contact-started chat: the contact confirms the data-preparation message.
 *
 * This method was added for P2P-style chats to support the last step of a P2P session.
 * When the contact requests to send data, call sendData() here to start the transfer.
 */
00294 void P2PApplication::contactStarted4_ContactConfirmsPreparation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - contactStarted4_ContactConfirmsPreparation" << endl;
#endif
}



/**
 * @brief Scheduelle termination, but wait for last incoming packets
 */
00306 void P2PApplication::endApplicationLater()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::endApplicationLater() - setting timeout to destroy object, allow contact to send a final message." << endl;
#endif

  // If a transfer was active, make sure it ends.
  abortDataSending();

  if( waitingState_ != P2P_WAIT_END_APPLICATION )
  {
    // Test if there are still unacked messages (make sure it's only tested once).
    // This is done explicitly before the data transfer ends.
    testUnAckedMessages( false );
  }

  // Set timeout to destroy
  waitingState_ = P2P_WAIT_END_APPLICATION;
  waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DESTROY, true );

  // Set flag to avoid second attempts.
  setClosing( true );
}



/**
 * @brief Private utility function to generate an Session and message/sequence ID.
 * @return A random value between 4 and 4294967295.
 */
00336 unsigned long P2PApplication::generateID()
{
  return (rand() & 0x00FFFFFC);
}



/**
 * @brief Private utility function to generate a Call ID or Branch ID.
 * @return A randomly generated GUID value.
 */
00347 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();
}



/**
 * @brief Return the identifier the SLP INVITE message.
 * @return The Branch-ID value from the SLP INVITE message.
 */
00373 QString P2PApplication::getBranch() const
{
  return branch_;
}



/**
 * @brief Return the identifier of the SLP session.
 * @return The Call-ID value from the SLP INVITE message.
 */
00384 QString P2PApplication::getCallID() const
{
  return callID_;
}



/**
 * @brief Return the Content-Type of the invitation message
 *
 * This method is useful for some contactStarted1_ContactInvitesUser() implementations.
 *
 * @return The Content-Type value, e.g. <tt>application/x-msnmsgr-sessionreqbody</tt>.
 */
00398 const QString& P2PApplication::getInvitationContentType() const
{
  return invitationContentType_;
}



/**
 * @brief Return the identifier of the SLP INVITE message.
 *
 * This method is useful for some contactStarted2_UserAccepts() implementations.
 * It's possible getSessionID() still returns zero, because the client should confirm the Session-ID first.
 *
 * @return The 'SessionID' field from the SLP payload of the invitation message.
 */
00413 unsigned long P2PApplication::getInvitationSessionID() const
{
  return invitationSessionID_;
}



/**
 * @brief Return the message ID used in the previous message from the contact.
 *
 * This value is used to match fragmented messages.
 * It's the second field of the binary P2P header.
 *
 * @return The message ID field used in the binary P2P header.
 */
00428 unsigned long P2PApplication::getLastContactMessageID() const
{
  return lastIncomingMessage_.messageID;
}



/**
 * @brief Returns the nonce that's being expected.
 *
 * This value is used to authenticate the direct connection.
 *
 * @return The Nonce value; a randonly generated GUID.
 */
00442 const QString & P2PApplication::getNonce() const
{
  return nonce_;
}



/**
 * @brief Return the session ID, this identifies the P2P session.
 *
 * It's the first field of the binary P2P header.
 * When a negotiation message is sent, it's session ID field will always be zero,
 * regardless of the real session ID.
 *
 * @return The session ID field used in the binary P2P header.
 */
00458 unsigned long P2PApplication::getSessionID() const
{
  return sessionID_;
}



/**
 * @brief Find the unacked message which corresponds with the received P2P ACK message.
 *
 * This method compares the binary P2P fields to find the original message which was sent.
 * The returned 'messageType' field is used later in gotAck() to handle ACKs for some specific messages.
 *
 * @param  ackMessage  The ACK message to test for.
 * @return The meta information about the original message ACKed by the contact.
 */
00474 P2PApplication::UnAckedMessage * P2PApplication::getUnAckedMessage(const P2PMessage &ackMessage) const
{
#ifdef KMESSTEST
  ASSERT( ackMessage.getFlags() != 0 && ackMessage.getDataSize() == 0 );
#endif

  unsigned long ackSessionID = ackMessage.getAckSessionID();  // ackSid
  unsigned long ackUniqueID  = ackMessage.getAckUniqueID();   // ackUid
  unsigned long ackFlag      = ackMessage.getFlags();

  QPtrListIterator<UnAckedMessage> it(outgoingMessages_);
  while( it.current() != 0 )
  {
    UnAckedMessage *unAcked = it.current();

    // This works for normal ACKs (0x02) to P2P messages.
    // Ack message   | original message
    // -----------------------------------------
    // ackSid        = messageID
    // ackUid        = ackSid
    // ackSize       = size
    if( ackUniqueID == unAcked->ackSessionID )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::getUnAckedMessage() - message is matched, option 1: "
                   "ackUniqueID = original uniqueID " << ackUniqueID << "." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( ackFlag == P2PMessage::MSN_FLAG_ACK );
#endif
      // Found it, save reference
      return unAcked;
    }


    // This works for control messages (like 0x08):
    // uniqueID is reffers to uniqueID of original message.
    // Ack message   | original message
    // -----------------------------------------
    // total         = total
    // ackSid        = ackSid
    // ackUid        = 0
    // ackSize       = 0
    if( ackSessionID == unAcked->ackSessionID && ackUniqueID == 0 )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::getUnAckedMessage() - message is matched, option 2: "
                   "ackSessionID = original uniqueID " << ackSessionID << "." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( ackFlag == P2PMessage::MSN_FLAG_ERROR );
#endif
      // Found it, save reference
      return unAcked;
    }

    // Also attempt to match on the messageID alone if:
    // - it's a 0x80 message. In this case sendP2PMessage() has overwritten the previous uniqueID.
    // - the UniqueID is not set. (also happens with normal ack messages in GAIM 1.5).
    //
    // This happens with 0x01 control messages:
    // Ack message   | original message
    // -----------------------------------------
    // ackSid        = messageID
    // ackUid        = 0
    // ackSize       = bytepos
    //
    // This happens with 0x80 messages:
    // Ack message   | original message
    // -----------------------------------------
    // ackSid        = messageID
    // ackUid        = new
    // ackSize       = size
    if( ackSessionID == unAcked->messageID
    &&  ( ackFlag == P2PMessage::MSN_FLAG_ABORTED_RECEIVING || ackUniqueID == 0 ) )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::getUnAckedMessage() - message is matched, option 3: "
                   "ackMessageID = original messageID " << ackSessionID << "." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( ackFlag == P2PMessage::MSN_FLAG_ABORTED_RECEIVING
           || ackFlag == P2PMessage::MSN_FLAG_NEGATIVE_ACK );
#endif

      // Found it, save reference
      return unAcked;
    }

    ++it;
  }

  return 0;
}



/**
 * @brief Find the unacked message which corresponds with given message type.
 *
 * @param  messageType  The P2P message type.
 * @return The message unacked message details.
 */
00577 P2PApplication::UnAckedMessage * P2PApplication::getUnAckedMessage(const P2PMessageType messageType) const
{
  QPtrListIterator<UnAckedMessage> it( outgoingMessages_ );
  while( it.current() != 0 )
  {
    if( it.current()->messageType == messageType )
    {
      return it.current();
    }

    ++it;
  }

  return 0;
}



/**
 * @brief Parse the received ACK message.
 *
 * The messageType field is used to detect actions
 * like "contact ACKed the OK message" or "contact received all data".
 *
 * It handles the following ACK message types:
 * - ACK of a SLP INVITE message invokes gotAck_SlpSessionInvitation().
 * - ACK of a SLP session OK message invokes gotAck_slpSessionOk().
 * - ACK of a SLP tranfer INVITE message invokes gotAck_slpTransferInvitation().
 * - ACK of a SLP tranfer OK message invokes gotAck_slpTransferOk().
 * - ACK of a SLP transfer decline message invokes gotAck_slpTransferDecline().
 * - ACK of the data preparation message invokes gotAck_dataPreparation()
 * - ACK of the sent file data invokes gotAck_dataReceived().
 * - ACK of the SLP BYE message, invokes gotAck_slpBye().
 * - ACK of a SLP error message invokes gotAck_slpError().
 * - all other ACKs are not treated specially.
 *
 * @param  message      The ACK message.
 * @param  messageType  The type of the original message this ACK corresponds with.
 */
00616 void P2PApplication::gotAck(const P2PMessage &message, const P2PMessageType ackedMessageType)
{
#ifndef KMESSTEST
  Q_UNUSED( message ); // Avoid compiler warning
#endif

#ifdef KMESSTEST
  if( message.isAck() )  // normal 0x02 ack
  {
    ASSERT( message.getDataSize()     == 0 );
    ASSERT( message.getTotalSize()    == 0 );
    ASSERT( message.getAckSessionID() != 0 );
    ASSERT( message.getAckUniqueID()  != 0 );
    ASSERT( message.getAckDataSize()  != 0 );
  }
#endif

  switch( ackedMessageType )
  {
    // Are we waiting for the contact to accept our invitation (e.g. send "200 OK")?
    case P2P_MSG_SESSION_INVITATION:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message (INVITE ACK)." << endl;
#endif

      gotAck_slpSessionInvitation();
      return;


    // Are we waiting for the ACK to confirm our "200 OK" message?
    case P2P_MSG_SESSION_OK:

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

      gotAck_slpSessionOk();
      return;


    // Are we waiting for the contact to accept our transfer invitation (e.g. send "200 OK")?
    case P2P_MSG_TRANSFER_INVITATION:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message (INVITE transfer ACK)." << endl;
#endif

      gotAck_slpTransferInvitation();
      return;


    // Are we waiting for the ACK to confirm our "200 OK" message?
    case P2P_MSG_TRANSFER_OK:

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

      gotAck_slpTransferOk();
      return;


    // Are we waiting for the ACK to confirm our data preparation message?
    case P2P_MSG_DATA_PREPARATION:

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::getAck() - Got an ACK message (data-preparation ACK)." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( message.getAckDataSize() == 4 );
#endif

      gotAck_dataPreparation();
      return;


    // Are we waiting for the ACK to confirm that all data was received?
    case P2P_MSG_DATA:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message (all-received ACK)." << endl;
#endif

      gotAck_dataReceived();
      return;


    // Are we waiting for the BYE ACK to quit the session/application?
    case P2P_MSG_SESSION_BYE:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message (BYE ACK, terminating)." << endl;
#endif

      gotAck_slpBye();
      return;


    // Are we expecting a ACK for our decline of the transfer?
    case P2P_MSG_TRANSFER_DECLINE:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message (transfer decline ACK)." << endl;
#endif

      gotAck_slpTransferDecline();
      return;


    // Are we waiting for the SLP Error message to be ACK-ed?
    case P2P_MSG_SLP_ERROR:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message (Confirmed SLP Error)." << endl;
#endif

      gotAck_slpError();
      return;


    // Standard ACK message or unhandled ACK message.
    default:

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotAck() - Got an ACK message. "
                   "waitingState=" << waitingState_ << " ackedMessageType=" << ackedMessageType << "." << endl;
#endif

      // Avoid warnings when we're waiting for something else, and the ACK is not important.
      if( waitingState_ == P2P_WAIT_FOR_CONNECTION )
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication::gotAck() - No special action taken (waiting for a connection)." << endl;
#endif
      }
      else if( waitingState_ != P2P_WAIT_DEFAULT || ackedMessageType != P2P_MSG_UNKNOWN )
      {
        // Unexpected, this should be a normal ACK message.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdWarning() << "P2PApplication::gotAck() - Unhandled state for ACK message:"
                       " waitingState=" << waitingState_ << " ackedMessageType=" << ackedMessageType << "!" << endl;
#endif
        // If we were waiting: the timer is stopped and
        // we forgot an if-block in this method.
        // So make sure the object will still be killed eventually.
        waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
        return;
      }
  }
}



/**
 * @brief Called when the ACK for the data preparation was received.
 *
 * This invokes initiateTransfer(), unless the the application
 * waits for an ACK to a SLP transfer OK message.
 */
00770 void P2PApplication::gotAck_dataPreparation()
{
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_PREPARE_ACK || waitingState_ == P2P_WAIT_FOR_CONNECTION );
#endif

  waitingState_ = P2P_WAIT_DEFAULT;

  // 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::gotAck_dataPreparation() - P2P message can't be handled, "
                   "this ack is not expected for applications started by the user "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)." << endl;

    // Send error back.
    shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);

    showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, false );
    endApplicationLater();
    return;
  }


  // Keep waiting if a direct connection setup is about to be complete.
  if( hasUnAckedMessage(P2P_MSG_TRANSFER_OK) )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_dataPreparation() - Application is waiting for a 200 transfer OK ACK, "
                 "not initiating transfer yet." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_TRANSFER_ACK;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_SLP, true );
    return;
  }


  // Dispatch to derived implementation
  initiateTransfer();
}



/**
 * @brief Called when the ACK for the sent file data was received.
 *
 * This method invokes showTransferComplete(), and sends the SLP BYE if needed.
 */
00827 void P2PApplication::gotAck_dataReceived()
{
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_DATA_ACK );
#endif

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

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

    // 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 gotAck_dataReceived
    kdDebug() << "P2PApplication::gotAck_dataReceived() - All data sent and confirmed, "
                 "waiting for BYE message..." << endl;
#endif

    // 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( P2PAPPLICATION_TIMEOUT_SLP, true );
  }
}



/**
 * @brief Called when the ACK for the SLP BYE message was received.
 *
 * This schedules the application for termination.
 */
00876 void P2PApplication::gotAck_slpBye()
{
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK );
#endif

  // See if the data was not acked.
  UnAckedMessage *unAckedDataMessage = getUnAckedMessage( P2P_MSG_DATA );
  if( unAckedDataMessage != 0 )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpBye() - Data was not acked, sending 0x40 to abort ack request." << endl;
#endif

    // It's possible WLM also closed it's data stream at this point,
    // so ignores the 0x40 and sends a 0x80 at the same time back.
    sendP2PAck( P2PMessage::MSN_FLAG_ABORTED_SENDING, unAckedDataMessage );
    outgoingMessages_.remove( unAckedDataMessage );
  }

  waitingState_ = P2P_WAIT_DEFAULT;

  // Session complete.
  endApplicationLater();
}



/**
 * @brief Called when the ACK for a SLP Error was received.
 *
 * if the contact started the application, this will wait for the SLP BYE message.
 * Otherwise the application is terminated.
 */
00910 void P2PApplication::gotAck_slpError()
{
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_ERR_ACK );
#endif
  waitingState_ = P2P_WAIT_DEFAULT;

  if(! isUserStartedApp())
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpError() - The contact started this application, waiting for BYE." << endl;
#endif

    // We wait for the BYE
    waitingState_ = P2P_WAIT_FOR_SLP_BYE;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_SLP, true );
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpError() - The user started this application, terminating." << endl;
#endif

    // TODO: send BYE after the client confirmed our SLP error?
    endApplicationLater();
  }
}



/**
 * @brief Called when the ACK for the first SLP INVITE message was received.
 *
 * This only changes the waiting state, to wait for the SLP OK message.
 */
00945 void P2PApplication::gotAck_slpSessionInvitation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotAck_slpSessionInvitation() - Waiting for 200 OK message..." << endl;
#endif

  // Continue to wait for the contact to send the accept message (SLP OK).
  waitingState_ = P2P_WAIT_CONTACT_ACCEPT;
  waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACCEPT, true );

  // note KMess uses a fixed time user to accept/decline here.
  // WLM8 has no timeout here for file transfer.
}



/**
 * @brief Called when the ACK of the SLP OK message was received.
 *
 * This method invokes contactStarted3_ContactConfirmsAccept()
 */
00966 void P2PApplication::gotAck_slpSessionOk()
{
  // Reset, will be changed by initiateTransfer->contactStarted3..->sendDataPreparationAck.
  // TODO: rewrite the sending of the data-preparation message using the incoming/outgoing queue.
  waitingState_ = P2P_WAIT_DEFAULT;


  // 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::gotAck_sessionOk() - P2P message can't be handled, "
                   "this ack is not expected for applications started by the user "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)." << endl;
    // TODO: cancel session in a different way, can't respond with ACKs on ACKs.
    shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);


    showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, false );
    endApplicationLater();
    return;
  }

  // SLP OK message ACKed
  // For P2P apps, we can't prove any mime fields here, so an empty constructor is what's left..
  // This is where the data preparation ACK could be sent.
  contactStarted3_ContactConfirmsAccept(MimeMessage());

#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::gotAck_slpSessionOk() - 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)
    waitingState_ = P2P_WAIT_FOR_FILE_DATA;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
  }
}



/**
 * @brief Called when the ACK for the SLP transfer decline message was received.
 *
 * This only changes the waiting state,
 * the contact should start to send the data now.
 */
01028 void P2PApplication::gotAck_slpTransferDecline()
{
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_ERR_ACK || waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  if( waitingState_ == P2P_WAIT_FOR_FILE_DATA || ! fragmentTracker_.isEmpty() )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpTransferDecline() - Got ack while data transfer is already active." << endl;
#endif
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpTransferDecline() - Waiting for file data..." << endl;
#endif

    // Continue to wait for the contact to send the file data
    waitingState_ = P2P_WAIT_FOR_FILE_DATA;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
  }
}



/**
 * @brief Called when the ACK for the SLP transfer INVITE message was received.
 *
 * This only changes the waiting state, to wait for the SLP OK message.
 */
01059 void P2PApplication::gotAck_slpTransferInvitation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotAck_slpTransferInvitation() - Waiting for 200 OK message..." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_FOR_INVITE_TR_ACK );
#endif

  // Continue to wait for the contact to send the accept message (SLP OK).
  waitingState_ = P2P_WAIT_FOR_TRANSFER_ACCEPT;
  waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACCEPT, true );

  // note KMess uses a fixed time user to accept/decline here.
  // WLM8 has no timeout here for file transfer.
}



/**
 * @brief Called when the ACK for the SLP transfer OK mesages was received.
 *
 * This invokes initiateTransfer() or waits
 * for an other direct connection to complete.
 */
01084 void P2PApplication::gotAck_slpTransferOk()
{
  // For some reason, our "MSNSLP/1.0 200 OK" message can be ACKed when we just ACKed all data is received.
  // Detect this, otherwise the chat window is filled with "File transfer complete.. Contact aborted" messages.
  if( waitingState_ == P2P_WAIT_FOR_SLP_BYE || fragmentTracker_.isComplete() )
  {
    kdWarning() << "P2PApplication::gotAck_slpTransferOk() - Received ACK for 'transrespbody' message "
                   "while closing the session "
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=return)." << endl;

    // Make sure the timer starts again.
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_SLP, true );
    return;
  }

  waitingState_ = P2P_WAIT_DEFAULT;

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

  // SLP ransfer OK message ACKed
  if( ! applicationList_->hasDirectConnection()
  &&  ! applicationList_->hasPendingConnections() )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpTransferOk() - No connection attempts were made, "
                 "so starting transfer immediately." << endl;
#endif
    // TODO: the contact may actually send an INVITE back to become the server.
    initiateTransfer();
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotAck_slpTransferOk() - 200 transfer OK ACK received, "
                 "but waiting for connection attempts to succeed or fail..." << endl;
#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::gotAck_slpTransferOk() - Waiting state not changed, "
                 "waiting for file data..." << endl;
#endif

    // At some slow systems, it seams WLM can already start
    // the data transfer before waiting for a connection.
    if( ! fragmentTracker_.isEmpty() )
    {
      kdWarning() << "P2PApplication::gotAck_slpTransferOk() - already received " <<
                     fragmentTracker_.getTransferredBytes() << " bytes before receiving the 200 transfer OK ACK "
                     "(state="   << waitingState_ <<
                     " dc="      << applicationList_->hasDirectConnection() <<
                     " dcauth="  << applicationList_->hasAuthorizedDirectConnection() <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=continue)." << endl;
    }

    // For Files, the contact must initiate the data transfer now. (P2P_WAIT_DEFAULT)
    waitingState_ = P2P_WAIT_FOR_FILE_DATA;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
  }
}



/**
 * @brief Got an direct connection handshake ack.
 *
 * This method verifies the Nonce field and marks the connection as authenticated.
 * If the Nonce field is invalid, the connection will be closed without any notice.
 *
 * @param  message  The handshake message received from the direct connection.
 */
01165 void P2PApplication::gotDirectConnectionHandshake(const P2PMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotDirectConnectionHandshake() - Got a direct connection handshake message." << endl;
#endif

  // If the contact was sending a handshake when we dropped the connection,
  // it's possible this packet will be delivered over the switchboard.
  if( ! applicationList_->hasDirectConnection() )
  {
    // Ignore the packet
    kdWarning() << "P2PApplication::gotDirectConnectionHandshake() - Received direct connection handshake at the switchboard "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=return)." << endl;
    return;
  }

  MsnDirectConnection *directConnection = applicationList_->getDirectConnection();

  // Get connection info
  QString nonce    = message.getNonce();
  bool    isServer = directConnection->isServer();

  // Verify the nonce
  // Nonce is already used in ApplicationList to find this application,
  // but this is kept here for consistency, and if ApplicationList would change one time.
  if( nonce != nonce_ )
  {
    // Invalid nonce! Drop connection
    kdWarning() << "P2PApplication::gotDirectConnectionHandshake() - Contact sent invalid nonce "
                   "(received=" << nonce <<
                   " expected=" << nonce_ <<
                   " contact="  << getContactHandle() <<
                   " session="  << sessionID_ <<
                   " class="    << className() <<
                   " action=closeconnection)." << endl;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotDirectConnectionHandshake() - Closing connection, moving transfer to switchboard." << endl;
#endif

    // Like the official client, close the connection without any notice.
    // The transfer continues over the switchboard instead.
    directConnection->closeConnection();    // DirectConnectionPool deletes it.
  }
  else if( isServer )
  {
    // If we're the server, we still need to send a handshake back
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotDirectConnectionHandshake() - Contact sent correct nonce, "
                 "sending direct connection handshake ACK." << endl;
#endif
#ifdef KMESSTEST
    ASSERT( waitingState_ == P2P_WAIT_FOR_HANDSHAKE || ! fragmentTracker_.isEmpty() );
#endif

    // KMess is the server, it should authenticate the handshake message.
    sendDirectConnectionHandshake();
  }
  else
  {
    // KMess is the client, it should verify the handshake response only.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotDirectConnectionHandshake() - Contact sent correct nonce, "
                 "marking connection as authorized." << endl;
#endif

#ifdef KMESSTEST
    ASSERT( waitingState_ == P2P_WAIT_FOR_HANDSHAKE_OK || ! fragmentTracker_.isEmpty()  );
#endif

    // Detect when the contact sent a second INVITE, runs the direct connection server, but sends the nonce first.
    // This happened once with WLM8 after the data transfer because KMess didn't send the handshake.
    if( waitingState_ == P2P_WAIT_FOR_HANDSHAKE )
    {
      kdWarning() << "P2PApplication::gotDirectConnectionHandshake() - received nonce from contact first, "
                      "but it's running the server"
                      "(contact=" << getContactHandle() <<
                      " session=" << sessionID_ <<
                      " class="   << className() <<
                      " action=send0x100)!" << endl;

      sendDirectConnectionHandshake();
    }
  }


  // See if the transfer was already started.
  if( fragmentTracker_.isComplete() )
  {
    kdWarning() << "P2PApplication::gotDirectConnectionHandshake() - connection is authorized "
                   "but data transfer is already complete"
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x100)!" << endl;
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotDirectConnectionHandshake() - marking connection as authorized, transfer should start." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_FILE_DATA;
  }

  // Mark connection as authorized
  // This also starts signals which call slotConnectionAuthorized()
  directConnection->setAuthorized(true);
}



/**
 * @brief Called when data is received.
 *
 * This method should be overwritten in the derived class
 * to store the data in a file, buffer, etc..
 *
 * The order of the data fragments is not guaranteed by WLM, especially when it's switching to a direct connection.
 * The writeP2PDataToFile() function can be used to cope with this behavour.
 * Also don't rely on P2PMessage::isLastFragment() to determine when the transfer is complete.
 * Overwrite showTransferComplete() instead.
 *
 * @param  message  The P2P message containing the data.
 */
01294 void P2PApplication::gotData(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdWarning() << "P2PApplication::gotData() - Data not handeled by derived class." << endl;
#endif
}



/**
 * @brief Called internally when data is received, this eventually calls gotData()
 *
 * It validates the current state, calls showTransferProgress() and gotData().
 * After receiving the last message, it calls showTransferComplete() and sends the SLP BYE message if needed.
 *
 * @param  message  The received data message.
 */
01311 void P2PApplication::gotDataFragment(const P2PMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotDataFragment() - Received file data, calling gotData()." << endl;
#endif
#ifdef KMESSDEBUG
  ASSERT( waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  unsigned long dataOffset = message.getDataOffset();

  // See if the class is trying to abort.
  // Avoid bothering the derived class with these issues.
  if( waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK )
  {
    kdWarning() << "P2PApplication::gotDataFragment() - Still receiving data while BYE is sent "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)." << endl;
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    return;
  }
  else if( waitingState_ == P2P_WAIT_FOR_SLP_BYE )
  {
    // This actually happened when there were some delayed packets in the buffer with WLM.
    // When switching bridges (SB/DC) it actually cannot guarantee the messages will be sent in order.
    // This is visible at slow computers.
    kdWarning() << "P2PApplication::gotDataFragment() - Still receiving data while last fragment was received "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)." << endl;
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    return;
  }

  // See if data is already sent before establishing a connection.
  // Happens with WLM, likely at slow computers.
  if( dataOffset == 0 && ! applicationList_->hasDirectConnection()
  &&  ( applicationList_->hasPendingConnections() || applicationList_->hasPendingConnectionInvitation() ) )
  {
    kdWarning() << "P2PApplication::gotDataFragment() - Contact is already sending data "
                   "while attempting to establish a direct connection "
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)." << endl;
  }

  // Initialize at the first message
  if( dataOffset == 0 )
  {
    fragmentTracker_.initialize( message.getMessageID(), message.getTotalSize() );
  }
  else
  {
    // Check if the new data fragment belongs to the current one.
    if( ! fragmentTracker_.isInitialized( message.getMessageID() ) )
    {
      // Send negative ack message, like WLM does (0x01)
      kdWarning() << "P2PApplication::gotDataFragment() - Received unexpected offset for message "
                     "(offset="    << dataOffset <<
                     " messageID=" << message.getMessageID() <<
                     " contact="   << getContactHandle() <<
                     " session="   << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x01)." << endl;
      sendP2PAck(P2PMessage::MSN_FLAG_NEGATIVE_ACK);
      sendCancelMessage( CANCEL_FAILED );   // sends slp bye
      return;
    }
  }

  // Update the tracker
  fragmentTracker_.registerFragment( message.getDataOffset(), message.getDataSize() );

  // Dispatch the message to the derived class
  showTransferProgress( fragmentTracker_.getTransferredBytes() );
  gotData(message);

  // See if the transfer should be complete now.
  bool isComplete = fragmentTracker_.isComplete();
  if( ! isComplete )
  {
    // Wait for more data to arrive.
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_MOREDATA, true );

    if( message.isLastFragment() )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotDataFragment() - Got the last fragment, "
                   "but some messages are not received yet." << endl;
      kdDebug() << "Current message is " << fragmentTracker_.getDebugMap() << endl;
#endif
      shouldSendAck_ = false;   // could be true if received out of order.
    }
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotDataFragment() - Got the last fragment. About to send ACK, but calling showTransferComplete() first so it can still abort." << endl;
#endif

    // After all data is received, ACK it and send the BYE
    shouldSendAck_ = true;   // could be false if received out of order.

    // Signal derived class the transfer is complete
    // By calling this first, the derived class can confirm the data transfer was successful.
    showTransferComplete();

    // If the derived class didn't send a message, send the ACK.
    if( shouldSendAck_ )
    {
      // TODO: if the messages were received out of order, should we ACK the final message instead?
      sendP2PAck();
    }

    // Determine what to send next
    if(isUserStartedApp())
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotDataFragment() - Application was started by the user, also send SLP BYE." << endl;
#endif

      // 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
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotDataFragment() - Application was started by the contact, waiting for SLP BYE..." << endl;
#endif

      // Otherwise we wait for the BYE
      waitingState_ = P2P_WAIT_FOR_SLP_BYE;
      waitingTimer_->start( P2PAPPLICATION_TIMEOUT_SLP, true );
    }
  }
}



/**
 * @brief Called when the data preparation message is received.
 *
 * It calls initiateTransfer() to inform the derived class.
 * When sendDataPreparationAck() is not called by the derived class,
 * the session will abort automatically.
 *
 * @param  message  The P2P data preparation message.
 */
01466 void P2PApplication::gotDataPreparation(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotDataPreparation() - Got the data preparation message" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( shouldSendAck_ );
  // WLM invites for a DC first before sending the data preparation.
  // Since KMess declines this with a 603, P2P_WAIT_FOR_SLP_ERR_ACK is also a valid state here.
  ASSERT( waitingState_ == P2P_WAIT_FOR_PREPARE || waitingState_ == P2P_WAIT_FOR_SLP_ERR_ACK );
#endif


  // Tell the derived class it can send the data.
  // You typically want to call sendDataPreparation() or sendCancelMessage() here.
  // sendDataPreparationAck() only sets a boolean, so we're able to send the
  // correct ack or error message here.
  // The methods changes one the following variables:
  shouldSendAck_         = true;
  userShouldAcknowledge_ = true;

  initiateTransfer();


  // sendDataPreparation() was not called, determine now what to send:
  if(userShouldAcknowledge_)
  {
    kdDebug() << "P2PApplication::gotDataPreparation() - Handling of data preparation failed "
                 "(contact=" << getContactHandle() << " action=send0x08)" << endl;

    // 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);
    showEventMessage( i18n("The transfer failed.  Data preparation failed."), ChatMessage::CONTENT_APP_FAILED, false );
    endApplicationLater();
    return;
  }


  // If the user did acknowledge something, we no longer have to send an ack any more
  // it must have been sendCancelMessage(CANCEL_ABORT) calling sendP2PAck(P2PMessage::MSN_FLAG_ERROR).
  if(! shouldSendAck_)
  {
    // No error message here, assume your derived class already displayed something.
    // If sendP2PAck() was called, the app won't terminate, so end it now.
    endApplicationLater();
    return;
  }


  // TODO: for WLM8, we need to delay the P2P ack message until the DC is established.

  // User did acknowledge, we can send the *actual* preparation ACK now.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotDataPreparation() - Data preparation successful, sending ACK" << endl;
#endif

  sendP2PAck();


  // Now wait for the data to arrive
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotDataPreparation() - Waiting for file data to arrive..." << endl;
#endif

  waitingState_ = P2P_WAIT_FOR_FILE_DATA;
  waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
}



/**
 * @brief Called when a P2P message was received with the error-flag set.
 *
 * This method aborts the application by calling contactAborted().
 *
 * @param  message  The received error message.
 */
01546 void P2PApplication::gotErrorAck(const P2PMessage &message)
{
#ifdef KMESSTEST
  ASSERT( message.getDataSize()     == 0 );

  if( message.isNegativeAck() )
  {
    ASSERT( message.getTotalSize()  == 0 );
  }
  else if( message.isError() )
  {
    ASSERT( message.getTotalSize()  != 0 );
  }

  ASSERT( message.getAckSessionID() != 0 );
  ASSERT( message.getAckUniqueID()  == 0 );
  ASSERT( message.getAckDataSize()  == 0 );
#endif

  // When the contact aborted, it could send some 0x08 messages back for the
  // next fragments which were already sent (e.g. already in the DC at that time).
  // Ignore these error messages.
  if( isClosing() )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotErrorAck() - Got an P2P error-response while closing "
                   "(flag=0x"  << QString::number( message.getFlags(), 16 ) <<
                   ", ignoring message)." << endl;
#endif

    // Make sure the timer resumes.
    endApplicationLater();
    return;
  }

  if( message.isNegativeAck() )
  {
    // Received when the transfer failed, e.g. write errors at the direct connection.
    kdWarning() << "P2PApplication::gotErrorAck() - Got an unexpected P2P error-response "
                  "(flag=nak" <<
                  " byte="    << message.getDataSize() <<
                  " state="   << waitingState_ <<
                  " contact=" << getContactHandle() <<
                  " session=" << sessionID_ <<
                  " class="   << className() <<
                  " action=continue)." << endl;

    abortDataSending();

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotErrorAck() - Waiting for contact to send a BYE message." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_SLP_BYE;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_SLP, true );
  }
  else
  {
    // Error in our headers?
    kdWarning() << "P2PApplication::gotErrorAck() - Got an unexpected P2P error-response "
                  "(flag=0x"  << QString::number( message.getFlags(), 16 ) <<
                  " state="   << waitingState_ <<
                  " contact=" << getContactHandle() <<
                  " session=" << sessionID_ <<
                  " class="   << className() <<
                  " action=contactaborted)." << endl;


    if( dataType_ == P2P_TYPE_NEGOTIATION && ! P2P_WAIT_FOR_DATA_ACK )
    {
      // We likely made an error in the invitation/bye reponse
      contactAborted( i18n("The contact rejected the invitation.  An internal error occured.") );
    }
    else
    {
      // TODO: make this error custom.
      // 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.") );
    }
  }
}



/**
 * @brief Main entry function for incoming incoming messages.
 *
 * This method receives the incoming P2P messages, analyses it's
 * flags and delivers it to various handling methods:
 * - ACK messages are delivered to gotAck(), gotDirectConnectionHandshake(), gotErrorAck() or gotTimeoutAck().
 * - SLP negotiation messages are delivered to gotNegotiationFragment().
 * - P2P data preparation messages are delivered to gotDataPreparation().
 * - P2P data messages are delivered to gotDataFragment().
 *
 * Other P2P messages are rejected with an error message.
 *
 * @param  p2pMessage  A reference to a P2PMessage object.
 */
01645 void P2PApplication::gotMessage(const P2PMessage &p2pMessage)
{
  // Reset session variables.
  waitingTimer_->stop();   // stop timer
  gotSlpMessage_ = false;  // tell this is not a slp message until proven otherwise

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if( nextMessageID_ == 0 )
  {
    // No messages sent, new application
    kdDebug() << "P2PApplication::gotMessage() - P2P message from " << getContactHandle() << " is handled by a newly created session." << endl;
  }
  else
  {
    // s/found/received/ since ApplicationList::gotMessage() finds it, but this makes it easier to filter the logs.
    kdDebug() << "P2PApplication::gotMessage() - New P2P message from " << getContactHandle() << " is handled by session " << sessionID_ << "." << endl;
  }
#endif

  // Cancel termination if a new message is received
  // while the application was already scheduelled to be closed.
  // Ignore typical messages which are sent while a transfer is aborting.
  if( waitingState_ == P2P_WAIT_END_APPLICATION
  &&  ! p2pMessage.isError()
  &&  ! p2pMessage.isAbortedReceivingAck()
  &&  ! p2pMessage.isAbortedSendingAck() )
  {
    kdWarning() << "P2PApplication::gotMessage() - received another message while awaiting termination "
                   "(flags=0x" << QString::number( p2pMessage.getFlags(), 16 ) <<
                   " size="    << p2pMessage.getDataSize() <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=unsetclosing)." << endl;

    // Don't reset the waiting state. should happen by next method which responds to the message.
    // It could be a SLP BYE for example or "481 No Such Call".
    aborting_ = false;
    setClosing( false );
  }


  // 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.getDataSize()  > 20 &&   // for cvs produced data-preparation messages without a session-id
                         p2pMessage.getFlags()     == 0);
    if(! isSlpMessage)
    {
      // We received an unknown control message, this object was initialized to send the correct response.
      shouldSendAck_ = true;  // ASSERT in sendP2PAck would fail otherwise
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
      showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, ! isUserStartedApp() );
      endApplication();
      return;
    }
  }


  // Handle ACKs before everything else.
  if( p2pMessage.getDataSize() == 0 )  // HACK: for Bot2K3 4.1: no testing for flags
  {
    UnAckedMessage *unAcked         = getUnAckedMessage(p2pMessage);
    P2PMessageType ackedMessageType = P2P_MSG_UNKNOWN;
    if( unAcked == 0 )
    {
      if( p2pMessage.isAck() )  // other messages can't relate have the control flags set.
      {
        kdWarning() << "P2PApplication::gotMessage() - Unable to handle ACK message, "
                       "original message not found "
                       "(flags=0x" << QString::number(p2pMessage.getFlags(), 16) <<
                       " size=0 state=" << waitingState_ <<
                       " contact=" << getContactHandle() <<
                       " session=" << sessionID_ <<
                       " class="   << className() <<
                       " action=return)." << endl;
        return;
      }
    }
    else
    {
      // Remove record, auto-deletes
      // Avoids that hasUnAckedMessage() returns the message we just received.
      ackedMessageType = unAcked->messageType;
      outgoingMessages_.remove(unAcked);
    }


    if( p2pMessage.isAck() )  // 0x02
    {
      gotAck( p2pMessage, ackedMessageType );
    }
    else if(p2pMessage.isConnectionHandshake()) // 0x100
    {
      gotDirectConnectionHandshake( p2pMessage );
    }
    else if(p2pMessage.isNegativeAck())  // 0x01
    {
      gotErrorAck( p2pMessage );
    }
    else if(p2pMessage.isWaitingForReply())  // 0x04
    {
      gotTimeoutAck( p2pMessage );
    }
    else if(p2pMessage.isWaitingForAck())  // 0x06
    {
      gotTimeoutAck( p2pMessage );
    }
    else if(p2pMessage.isError())  // 0x08
    {
      gotErrorAck( p2pMessage );
    }
    else if(p2pMessage.isAbortedSendingAck()) // 0x40
    {
      gotTransferAbortedAck( p2pMessage );
    }
    else if(p2pMessage.isAbortedReceivingAck()) // 0x80
    {
      gotTransferAbortedAck( p2pMessage );
    }
    else
    {
      kdWarning() << "P2PApplication::gotMessage() - Unable to handle ACK message, "
                     "unknown flag value encountered "
                     "(flags=0x" << QString::number(p2pMessage.getFlags(), 16) <<
                     " size=0 state=" << waitingState_ <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=assumerror)." << endl;

      // Make sure the application does abort somehow.
      gotErrorAck( p2pMessage );
    }

    return;
  }


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

  // Store some message parameters for Acknowledgement later, or to send an error message.
  // This should not be inserted in a if(shouldSendAck_) line, this info is used to resolve fragmented messages.
  lastIncomingMessage_.dataSize     = p2pMessage.getDataSize();
  lastIncomingMessage_.messageID    = p2pMessage.getMessageID();
  lastIncomingMessage_.messageType  = P2P_MSG_UNKNOWN;
  lastIncomingMessage_.sentTime     = 0;
  lastIncomingMessage_.sessionID    = p2pMessage.getSessionID();
  lastIncomingMessage_.totalSize    = p2pMessage.getTotalSize();
  lastIncomingMessage_.ackSessionID = p2pMessage.getAckSessionID();


  // Session ID is 0? -> the clients negotiate for session.
  if(p2pMessage.getSessionID() == 0)
  {
    if(p2pMessage.getFlags() == 0)
    {
      // Parse the fragment
      gotNegotiationFragment(p2pMessage);
    }
    else
    {
      kdWarning() << "P2PApplication:gotMessage() - P2P message can't be handled, "
                     "unknown negotiation message type "
                     "(flags=0x" << QString::number(p2pMessage.getFlags(), 16) <<
                     " size="    << p2pMessage.getDataSize() <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x08)." << endl;

      // Don't know what to do with a negotiation message with flags set.
      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);

      // Abort the application
      showEventMessage( i18n("The transfer failed.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, true );
      endApplicationLater();
      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.isMsnObjectData()
         &&  p2pMessage.getDataSize()  == 4
         &&  p2pMessage.getTotalSize() == 4 )
    {
      // HACK: added for Encarta Instant Answers (has data flag set with preparation message).
      kdWarning() << "P2PApplication::gotMessage() - Expecting data-preparation message, "
                     "got message of 4 bytes with incorrect flags "
                     "(assuming data preparation,"
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() << ")!" << endl;
      gotDataPreparation(p2pMessage);
    }
    // Check for data messages
    else if(p2pMessage.isMsnObjectData()
         || p2pMessage.isFileData())
    {
      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::gotMessage() - Expecting data message, "
                     "got fragmented message with no flags set "
                     "(assuming file data,"
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() << ")!" << endl;
      gotDataFragment(p2pMessage);
    }
    else
    {
      // Unknown p2p message
      kdWarning() << "P2PApplication::gotMessage() - P2P message can't be handled, "
                     "unknown data message type "
                     "(flags=0x" << QString::number(p2pMessage.getFlags(), 16) <<
                     " size="    << p2pMessage.getDataSize() <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x08)." << 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);

      // Abort the application
      showEventMessage( i18n("The transfer failed.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, true );
      endApplicationLater();
      return;
    }
  }

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



/**
 * @brief Called when a SLP message fragment was received.
 *
 * The fragments is buffered until all parts are received.
 * The gotNegotiationMessage() will be called afterwards.
 *
 * @param  p2pMessage  The received P2P message with SLP payload.
 */
01910 void P2PApplication::gotNegotiationFragment(const P2PMessage &p2pMessage)
{
  if(! p2pMessage.isLastFragment())
  {
    // Not everything received yet, buffer it.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotNegotiationFragment() - 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 slpData;
    QByteArray bufferData;

    if(buffer_ != 0)
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotNegotiationFragment() - 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();
      slpData    = QString::fromUtf8( bufferData.data(), bufferData.size() );

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

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

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



/**
 * @brief Called when an complete SLP message is received.
 *
 * These messages are used to negotiate session parameters.
 * The SLP message itself also has a MIME header and MIME body.
 *
 * This method validates the 'To' field,
 * and dispatches the to the correct handler:
 * - messages starting with <tt>INVITE MSNMSGR</tt> are delivered to gotSlpInvite().
 * - messages starting with <tt>MSNSLP/</tt> are delivered to gotSlpStatus().
 * - messages starting with <tt>BYE MSNMSGR</tt> are delivered to gotSlpBye().
 * - other messages are rejected with an P2P error.
 *
 * The P2P message is not ACKed yet, each handler does this
 * when it's able to parse the SLP body.
 *
 * A preamble could look like one of the following:
 * @code
INVITE MSNMSGR:user@kmessdemo.org MSNSLP/1.0
@endcode
@code
MSNSLP/1.0 200 OK
@endcode
@code
BYE MSNMSGR:user@kmessdemo.org MSNSLP/1.0
@endcode
 *
 * The message itself looks like:
 * @code
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
Via: MSNSLP/1.0/TLP ;branch={068676F2-9B0C-4945-8ABA-4E6F585F710F}
CSeq: 0 
Call-ID: {E9BCFBDB-2768-4BD3-9E4C-A97DC70261E8}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-sessionreqbody
Content-Length: 348

{the message contents}
@endcode
 *
 * @param  slpMessage  The payload extracted from the P2P message fragments (the MIME fields and body).
 * @param  preamble    The first status line sent before the MIME fields, which describes the message type.
 */
02014 void P2PApplication::gotNegotiationMessage(const MimeMessage &slpMessage, const QString &preamble)
{
  // Parse the "negotiation message" (P2PMessage with sessionID 0)
  // Verify it is indeed directed to us..
  QString msgTo = slpMessage.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.
    // For some reason this can be received in upper case too if the user entered it that way (likely happens with 3rd party clients).
    if( msgTo.lower() != "<msnmsgr:" + CurrentAccount::instance()->getHandle().lower() + ">" )
    {
      // In the future, the <msnmsgr: > string might be replaced with
      // something to support MSN to AOL chats, etc..
      kdWarning() << "P2PApplication::gotNegotiationMessage() - P2P message can't be handled, "
                      "addressed to someone else "
                      "(to="       << msgTo <<
                      " contact="  << getContactHandle() <<
                      " session="  << sessionID_ <<
                      " class="   << className() <<
                      " action=send404)." << endl;

      showEventMessage( i18n("The invitation was cancelled. Message was not directed to us."), ChatMessage::CONTENT_APP_CANCELED, true );

      // Send error message back
      sendSlpError("404 Not Found"); // Waits for ACK and terminates
      return;
    }
  }

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotNegotiationMessage() - Got an SLP message, preamble=" << preamble << "." << endl;
#endif



  // Find out what kind of message this is:
  if( preamble.startsWith("INVITE MSNMSGR") )
  {
    // Got an invitation!
    gotSlpInvite( slpMessage );
  }
  else if( preamble.startsWith("MSNSLP/") )
  {
    // Error message, or "MSNSLP/1.0 200 OK" message
    gotSlpStatus( slpMessage, preamble );
  }
  else if( preamble.startsWith("ACK MSNMSGR") )
  {
    // Got a SLP ACK message (Windows Live Messenger feature)
    gotSlpAck( slpMessage );
  }
  else if( preamble.startsWith("BYE MSNMSGR") )
  {
    // Got a BYE message
    gotSlpBye( slpMessage );
  }
  else
  {
    // Unknown SLP message
    kdWarning() << "P2PApplication::gotNegotiationMessage() - P2P message can't be handled, "
                   "unsuppored SLP negotiation message "
                   "(preamble=" << preamble <<
                   " contact="  << getContactHandle() <<
                   " session="  << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)." << endl;

    // Got an unknown SLP preamble
    sendP2PAck( P2PMessage::MSN_FLAG_ERROR );

    // Abort the application
    showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, true );
    endApplicationLater();
    return;
  }
}



/**
 * @brief Called when an SLP ACK message was received.
 *
 * This message should not be confused with a P2P ACK.
 *
 * Windows Live Messenger uses this message to acknowlegde
 * IP/port information for a direct connection.
 *
 * This method is currently a STUB to keep Windows Live Messenger happy.
 *
 * One of the observed messages is:
 * @code
ACK MSNMSGR:user@kmessdemo.org MSNSLP/1.0
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
Via: MSNSLP/1.0/TLP ;branch={67683AAA-A6F2-4B11-895F-EF46B483F3A9}
CSeq: 0
Call-ID: {00000000-0000-0000-0000-000000000000}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-transudpswitch
Content-Length: 157

IPv4ExternalAddrsAndPorts: 111.222.333.444:3495
IPv4InternalAddrsAndPorts: 192.168.1.12:3495
SessionID: 15076776
SChannelState: 0
Capabilities-Flags: 1
@endcode
 *
 * @code
ACK MSNMSGR:user@kmessdemo.org MSNSLP/1.0
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
Via: MSNSLP/1.0/TLP ;branch={BBE6AAC0-10F4-4419-B01C-1F7C2838BC3D}
CSeq: 0
Call-ID: {00000000-0000-0000-0000-000000000000}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-transdestaddrupdate
Content-Length: 197\r\n
\r\n
IPv4ExternalAddrsAndPorts: 11.222.3.44:62570\r\n
IPv4InternalAddrsAndPorts: 192.168.1.69:1343\r\n
IPv4External-Connecting-Port-End-Range: 62576\r\n
SessionID: 15751111\r\n
SChannelState: 0
Capabilities-Flags: 1
@endcode
 *
 * @param  slpMessage  The parsed SLP message.
 */
02146 void P2PApplication::gotSlpAck(const MimeMessage &slpMessage )
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpAck() - Got SLP ACK message, usage is unknown. Message dump follows.\n"
            << slpMessage.getFields() << "\n"
            << slpMessage.getBody() << endl;
#else
  Q_UNUSED( slpMessage );
#endif

  bool emptySession = ( nextMessageID_ == 0 || getMode() == APP_MODE_ERROR_HANDLER );

  // This is some internal message of WLM8.
  // Accept it for now.
  if(shouldSendAck_)
  {
    sendP2PAck();
  }

  // Also terminate this instance if it was just created for this SLP ACK messasge.
  if( emptySession )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpAck() - Directly terminating temporary P2PApplication instance for SLP ACK message." << endl;
#endif
    endApplication();
  }
}



/**
 * @brief Called when an SLP BYE message was received.
 *
 * It sends the ACK back, and calls endApplication() to terminate the session.
 * A contact may sent a BYE message to abort the transfer.
 * In this case, contactAborted() will be called instead.
 *
 * The SLP BYE message looks like:
 * @code
BYE MSNMSGR:user@kmessdemo.org MSNSLP/1.0
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
Via: MSNSLP/1.0/TLP ;branch={A1DBCADF-6DA2-43FE-BE27-0AEE26816D91}
CSeq: 0
Call-ID: {E9BCFBDB-2768-4BD3-9E4C-A97DC70261E8}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-sessionclosebody
Content-Length: 40

SessionID: 114164
SChannelState: 0
@endcode
 *
 * @param  slpMessage  The parsed SLP message.
 */
02202 void P2PApplication::gotSlpBye( const MimeMessage &slpMessage )
{
  Q_UNUSED( slpMessage ); // Avoid compiler warning

  // If the BYE was received early, it means the session was aborted prematurely.
  //
  // 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.
  //
  // Microsoft Messenger for the Mac (6.0.3) takes this even further with picture transfers.
  // It sends the BYE first and the "all data received" ACK afterwards.
  // This happens when KMess is already in the P2P_WAIT_END_APPLICATION state (see "action=unsetclosing").

  // Also terminate without errors if we're waiting for a BYE ACK and instead receive another BYE. This
  // means that both clients have finished sending, so don't issue errors. This fixes the double message
  // "transfer completed" followed by "transfer canceled" when sending files to Mercury Messenger.

  if(shouldSendAck_)
  {
    sendP2PAck();
  }

  bool isComplete = fragmentTracker_.isComplete();  // TODO: this only works for incoming data.

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

  // Make sure the data is not sent anymore
  abortDataSending();


  if( waitingState_ != P2P_WAIT_FOR_SLP_BYE
  &&  waitingState_ != P2P_WAIT_FOR_SLP_BYE_ACK
  &&  waitingState_ != P2P_WAIT_FOR_DATA_ACK )   // HACK: added for Messenger for the Mac 6.0.3 and MSN 7.0
  {
    // Make a final check if our states have been messed up. Had the following situation once:
    // - WLM invites to send a file
    // - KMess hosted the server socket.
    // - WLM started to send data.
    // - transfer was complete, acked, waiting for BYE
    // - WLM establishes the connection with the socket.
    // - connection is authenticated.
    // - sockets are disconnected
    // - WLM sends BYE
    if( isComplete )
    {
      kdWarning() << "P2PApplication::gotSlpBye() - Transfer seams complete buit state suggests otherwise "
                     "(state=" << waitingState_ <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=continue)" << endl;
      endApplicationLater();
    }
    else if( waitingState_ == P2P_WAIT_END_APPLICATION )
    {
      // When WLM aborts at a very late stage, it actually processes our final data messages,
      // ack's those, handle our BYE, and send it's "aborting BYE" afterwards.
      kdWarning() << "P2PApplication::gotSlpBye() - Received BYE to abort when all data is already transferred "
                     "(state="   << waitingState_ <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplicationlater)" << endl;

      // Resume timer
      endApplicationLater();
    }

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpBye() - BYE message was unexpected; contact aborted "
              << "(state=" << waitingState_ << " contact=" << getContactHandle() << ")" << endl;
#endif

    // End with failure, bye sent at a different moment.
    contactAborted();   // closes the application

    // TODO: after this, the contact should send a 0x40 or 0x80 ack of the data stream.
    //       the message flag indicates whether the contact aborted it's own stream, or aborted ours.
  }
  else
  {
    // Normal end
    endApplicationLater();
  }
}



/**
 * @brief Called when a SLP INVITE message was received.
 *
 * It extracts the header fields, updates the instance fields, and parses the MIME body.
 * The invitation message is delivered to one of the following methods:
 * - The <tt>application/x-msnmsgr-sessionreqbody</tt> message is delivered to gotSlpSessionInvitation().
 * - The <tt>application/x-msnmsgr-transreqbody</tt> message is delivered to gotSlpTransferInvitation().
 * - The <tt>application/x-msnmsgr-transrespbody</tt> message is delivered to gotSlpTransferResponse(),
 *   with the <tt>secondInvite</tt> parameter set to <tt>true</tt>.
 * - Other messages are rejected as "unrecognized content-type".
 *
 * @param  slpMessage  The parsed SLP message.
 */
02307 void P2PApplication::gotSlpInvite(const MimeMessage &slpMessage)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpInvite() - Got SLP INVITE message" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT         // First invitation
       || waitingState_ == P2P_WAIT_FOR_FILE_DATA   // Second invitation for file transfers.
       || waitingState_ == P2P_WAIT_FOR_PREPARE     // WLM8: reverse invitation for picture transfer, holds back it's data-preparation message.
       || waitingState_ == P2P_WAIT_FOR_PREPARE_ACK // WLM8: second invitation for picture transfer, we already sent data-preparation ack.
        );
#endif

  // Reset the waiting state if this is the second INVITE for a file transfer
  // TODO: test what to do for the new WLM8 feature (sending transfer invites before data preparation).
  if(waitingState_ == P2P_WAIT_FOR_FILE_DATA)
  {
    waitingState_ = P2P_WAIT_DEFAULT;
  }

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


  // Session invitation
  // Extract the fields of the message. This is required for userRejected()
  MimeMessage slpMimeBody( slpMessage.getBody() );

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

  // 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
  // The cookie is empty if this is a contact-started session.
  if( getCookie().isEmpty() )
  {
    startByInvite(generateCookie());
  }


  // Handle invitation based on the Content-Type field
  if( invitationContentType_ == "application/x-msnmsgr-sessionreqbody" )
  {
    // Send the ACK message
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Client invites us for a session
    // Will set the waitingState_ to P2P_WAIT_USER_ACCEPT later.
    gotSlpSessionInvitation( slpMimeBody );
  }
  else if( invitationContentType_ == "application/x-msnmsgr-transreqbody" )
  {
    // Send the ACK message
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Client requested to transfer the session
    // Handle the invitation internally
    gotSlpTransferInvitation( slpMimeBody );
  }
  else if( invitationContentType_ == "application/x-msnmsgr-transrespbody" )
  {
    // Send the ACK message
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Client sent a second invitation to become a direct-connection server because we can't be.
    gotSlpTransferResponse( slpMimeBody, true );
  }
  else
  {
    // Send the ACK message
    if(shouldSendAck_)
    {
      sendP2PAck();
    }

    // Client sent an unknown invitation type
    kdWarning() << "P2PApplication::gotSlpInvite() - Received unexpected Content-Type "
                   "(type="    << invitationContentType_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send500)." << 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;
  }
}



/**
 * @brief Called when a SLP 200 OK message was received.
 *
 * This method is invoked from gotSlpStatus().
 * It extracts the Content-Type status code, depending on the value it proceeds with a different action:
 * - The payload of the <tt>application/x-msnmsgr-sessionreqbody</tt> message
 *   is delivered to userStarted2_ContactAccepts().
 * - The payload of the <tt>application/x-msnmsgr-transrespbody</tt> message
 *   is delivered to gotSlpTransferResponse().
 * - Any other message is rejected with a SLP error.
 *
 * A session confirmation looks like:
 * @code
MSNSLP/1.0 200 OK
To: <msnmsgr:contact@kmessdemo.org>
From: <msnmsgr:user@kmessdemo.org>
...
Content-Type: application/x-msnmsgr-sessionreqbody
Content-Length: 22

SessionID: 114164
@endcode
 *
 * For an example of a transfer confirmation, see gotSlpTransferResponse().
 *
 * @param  slpMessage  The parsed SLP message.
 */
02444 void P2PApplication::gotSlpOk(const MimeMessage &slpMessage)
{
  // With a 200 code, we reset the waiting state for every situation,
  // to make the next check work.
  waitingState_ = P2P_WAIT_DEFAULT;

  // Parse the data of the message
  QString     contentType = slpMessage.getValue("Content-Type");
  MimeMessage slpMimeBody( slpMessage.getBody() );  // body consists of more mime fields


  // The content-type tells us which fields are present
  if( contentType == "application/x-msnmsgr-sessionreqbody" )
  {
    // Message confirms the SessionID
#ifdef KMESSTEST
    ASSERT( slpMimeBody.getValue("SessionID").toULong() == getSessionID() );
#endif

    // Tell the derived class the invitation was accepted
    userStarted2_ContactAccepts(slpMimeBody);
  }
  else if( (contentType == "application/x-msnmsgr-transrespbody") ||
            // HACK: allow transreqbody too for broken clients, including KMess 1.4.2:
           (contentType == "application/x-msnmsgr-transreqbody" && slpMimeBody.hasField("Nonce") ))
  {
    // Message tells us how we can create the direct connection.
    // This response is handled here, so it's transparent for the derived class.
    gotSlpTransferResponse( slpMimeBody );
  }
  else
  {
    // Unsupported response type.
    kdWarning() << "P2PApplication::gotSlpOk() - Received unexpected 200/OK message "
                   "(type="    << contentType <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send500)!" << endl;

    showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, true );

    // Send error message back
    sendCancelMessage( CANCEL_INVALID_SLP_CONTENT_TYPE );
    return;
  }


  // Determine whether we should be waiting for the data-preparation message.
  // If we didn't send anything that changed the state and no transfer has started,
  // we're likely waiting for some kind of prepare message
  if(waitingState_ == P2P_WAIT_DEFAULT && dataType_ == P2P_TYPE_NEGOTIATION)
  {
    // Waiting for prepare message
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpOk() - Waiting for contact to send some prepare message..." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_PREPARE;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACK, true );
  }
}


/**
 * @brief Called when a SLP session invitation was received.
 *
 * This method stores the session ID and
 * invokes contactStarted1_ContactInvitesUser().
 * That method should parse the 'Context' field to handle the invitation.
 *
 * An SLP INVITE message for a picture transfer looks like:
 * @code
INVITE MSNMSGR:user@kmessdemo.org MSNSLP/1.0
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
...
Content-Type: application/x-msnmsgr-sessionreqbody
Content-Length: 348

EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}
SessionID: 114164
AppID: 12
Context: PG1zbm9ia..
@endcode
 *
 * @param  slpMimeBody  The SLP message with the fields 'EUF-GUID', 'SessionID', 'AppID' and 'Context'.
 */
02532 void P2PApplication::gotSlpSessionInvitation( const MimeMessage &slpMimeBody )
{
  // Read requested session id from the message
  invitationSessionID_ = slpMimeBody.getValue("SessionID").toULong();

  // Don't accept invites if we sent one.
  if( nextMessageID_ != 0 && isUserStartedApp() )
  {
    kdWarning() << "P2PApplication::gotSlpSessionInvitation() - Got an INVITE response for "
                   "user-started application, rejecting "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send500)." << endl;

    sendCancelMessage( CANCEL_INVALID_SLP_CONTENT_TYPE );
    return;
  }

  // Tell the derived class we've got an invitation
  waitingState_ = P2P_WAIT_USER_ACCEPT;   // also used later to abort with 500 instead of BYE.
  contactStarted1_ContactInvitesUser( slpMimeBody );
}



/**
 * @brief Called when a SLP status message was received.
 *
 * This method extracts the status code, depending on the status code it proceeds with a different action:
 * - The <tt>200 OK</tt> message invokes gotSlpOk().
 * - The <tt>404 Not Found</tt> message invokes contactAborted().
 * - THe <tt>481 No Such Call</tt> message invotes contactAborted().
 * - The <tt>500 Internal Error</tt> message invokes contactAborted().
 * - The <tt>603 Decline</tt> message invokes contactRejected().
 * - Any other message is rejected with a P2P error.
 *
 * @param  slpMessage  The parsed SLP message.
 * @param  preamble    The status line sent before the MIME fields,
 *                     which contains the error code (much like HTTP status codes).
 */
02573 void P2PApplication::gotSlpStatus(const MimeMessage &slpMessage, const QString &preamble)
{
  // An Error message, or "MSNSLP/1.0 200 OK" message
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpStatus() - Got SLP status message (" << preamble << ")" << endl;
#endif

  // See 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).
    showSystemMessage( i18n("The transfer failed. The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_SYSTEM_NOTICE, true );
    sendSlpError("500 Internal Error"); // Waits for ACK and terminates
    return;
  }

  // Should only be received if we started it.
  if(! isUserStartedApp())
  {
    kdWarning() << "P2PApplication::gotSlpStatus() - P2P message can't be handled, "
                   "unexpected SLP response containing status code "
                   "(preamble=" << preamble <<
                   " contact="  << getContactHandle() <<
                   " session="  << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)." << endl;

    sessionID_ = 0;
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);

    // Abort the application
    showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, true );
    endApplicationLater();
    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 we were waiting.
  if( waitingState_ == P2P_WAIT_CONTACT_ACCEPT )
  {
    waitingState_ = P2P_WAIT_DEFAULT;
  }

  // Handle the status codes
  switch( code )
  {
    // If the invitation was accepted
    case 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();
      }

      // Parse in separate function
      gotSlpOk( slpMessage );
      break;


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

      // Inform the user KMess has an error
      kdWarning() << "P2PApplication::gotSlpStatus() - KMess sent a bad address in the 'to:' header "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=contactaborted)." << endl;
      contactAborted( i18n("The contact rejected the invitation.  An internal error occured.") );
      break;


    // Somehow a message was sent twice (e.g. BYE)
    // or the contact aborts exactly when all data is being received + acked (so we sent the BYE).
    case 481:  // No Such Call
      if( shouldSendAck_ )
      {
        sendP2PAck();
      }

      // Content-Type of this message is "application/x-msnmsgr-session-failure-respbody"

      // Inform the user KMess has an error
      kdWarning() << "P2PApplication::gotSlpStatus() - KMess sent an invalid Call-ID "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=contactaborted)." << endl;
      contactAborted( i18n("The contact rejected the invitation.  An internal error occured.") );
      break;


    // Something was not supported
    case 500:
      if(shouldSendAck_)
      {
        sendP2PAck();
      }

      if( slpMessage.getValue("Content-Type") == "null" )
      {
        // The Content-Type was not supported
        kdWarning() << "P2PApplication::gotSlpStatus() - Content-Type of our invitation was not understood "
                       "(type=" << invitationContentType_ <<
                       " contact=" << getContactHandle() <<
                       " session=" << sessionID_ <<
                       " class="   << className() <<
                       " action=contactaborted)!" << endl;
      }
      else
      {
        // Bad header sent in SLP fields, or invitation type was not supported
        kdWarning() << "P2PApplication::gotSlpStatus() - Our invitation was not supported "
                       "or the contact client had an internal error "
                       "(contact=" << getContactHandle() <<
                       " session=" << sessionID_ <<
                       " class="   << className() <<
                       " action=contactaborted)" << endl;
      }

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


    // The invitation was declined
    case 603:
      if(shouldSendAck_)
      {
        sendP2PAck();
      }

      // Contact Declined
      contactRejected();
      break;

    // Last option: tell the contact it sent an unknown message
    default:
      kdWarning() << "P2PApplication::gotSlpStatus() - P2P message can't be handled, "
                     "unsupported SLP response "
                     "(preamble=" << preamble <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x08)." << endl;

      // Got an unknown SLP status code
      sendP2PAck( P2PMessage::MSN_FLAG_ERROR );

      // Scheduelle the application to terminate
      showEventMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it."), ChatMessage::CONTENT_APP_CANCELED, true );
      endApplicationLater();
  }
}



/**
 * @brief Called when a SLP transfer invitation was received.
 *
 * This is typically received when the contact initiated the file transfer session.
 * The Content-Type of the message <tt>application/x-msnmsgr-transreqbody</tt>.
 * The message contains meta-information about the contact's network configuration.
 *
 * A typical incoming message looks like:
 * @code
INVITE MSNMSGR:user@kmessdemo.org MSNSLP/1.0
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
...
Content-Type: application/x-msnmsgr-transreqbody
Content-Length: 193

Bridges: TRUDPv1 TCPv1
NetID: -789490475
Conn-Type: IP-Restrict-NAT
UPnPNat: true
ICF: false
Hashed-Nonce: {754C0129-F286-4882-5793-70BA45D62B6A}
SessionID: 114164
SChannelState: 0
@endcode
 *
 * As of Windows Live Messenger 8.1, the message body looks like:
@code
Bridges: TRUDPv1 TCPv1 SBBridge TURNv1
NetID: 140808277
Conn-Type: Port-Restrict-NAT
TCP-Conn-Type: Port-Restrict-NAT
UPnPNat: false
ICF: false
Hashed-Nonce: {78F2E9F6-06B7-9080-1D83-A222F143F681}
SessionID: 2674976
SChannelState: 0
Capabilities-Flags: 1
@endcode
 *
 * Currently the <tt>Hashed-Nonce</tt> is ignored, a normal <tt>Nonce</tt> is sent back.
 *
 * This method attempts to create a server socket and send the possible connection options back to the contact.
 * The response message also contains a <tt>Listening</tt> field
 * and supported transport layers (<tt>Bridge</tt> field).
 * It optionally contains one of the following:
 * - a generated nonce, the internal/external IP addresses (<tt>IPv4Internal-Addrs</tt> and <tt>IPv4external-Addrs</tt> values), or
 * - a null nonce to indicate the file data must be sent over the switchboard connection.
 *
 * See gotSlpTransferResponse() for an example message.
 *
 * The other contact can decide to start a server socket instead (sending a second invitation message),
 * or transfer the file data over the switchboard.
 *
 * @param  slpMimeBody  The SLP message with the fields "Bridges", "NetID", "Conn-Type", "UPnPNat", "ICF".
 */
02800 void P2PApplication::gotSlpTransferInvitation(const MimeMessage &slpMimeBody)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpTransferInvitation() - Invite message is a request to transfer the session." << endl;
#endif

  // For some reason it's also possible to receive SLP transfer invitations
  // with an entirely different callid while trying to setup a direct connection.
  // Perhaps if some packet is stalled in the outgoing message channel?
  if( sessionID_ == 0 )
  {
    kdWarning() << "P2PApplication::gotSlpTransferInvitation() - Received an invitation for a direct connection,"
                   " but no session is set up "
                   "(maybe a msn7 feature, "
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)." << endl;
  }


  // See if the connection already exists
  if( applicationList_->hasDirectConnection() )
  {
    kdWarning() << "P2PApplication::gotSlpTransferInvitation() - Received an invitation "
                   "to start a direct connection, but a direct connection is already available "
                   "(authorized=" << applicationList_->hasAuthorizedDirectConnection() <<
                   " contact="    << getContactHandle() <<
                   " session="    << sessionID_ <<
                   " class="      << className() <<
                   " action=closeconnection)." << endl;

    // Drop the previous connection. HACK: added for amsn 0.97.
    // aMsn 0.97 does some interesting things:
    // - DC's it hosted are directly closed after sending all file data.
    // - DC's it connected to are left open forever. P2P messages are still received at it,
    //   but somehow all responses are sent over the SB back as if amsn doesn't know it has a DC.
    applicationList_->getDirectConnection()->closeConnection();
  }


  // Abort if the file data was just received.
  // For some reason WLM8 sometimes sends the invitation directly after the file was sent.
  // First cause some invites to fail (don't accept/cancel).
  // Then accept one but send an "transfer accept" with invalid IP/port.
  // Not sure it can always be reproduced this way though.
  if( waitingState_ == P2P_WAIT_FOR_SLP_BYE )
  {
    kdWarning() << "P2PApplication::gotSlpTransferInvitation() - Received an invitation for "
                   "a direct connection, but expecting BYE instead "
                   "(file transfer just completed, "
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=slpcancel)." << endl;
    sendCancelMessage( CANCEL_ABORT );
    return;
  }


  // Quick fix for WLM8: don't allow direct connection invite for picture transfer
  if( isUserStartedApp() )
  {
    // Receiving invitations for your own sessiononly happens for picture transfer at the moment.
    // Supporting this *now* opens a whole new can of worms, so decline it.
    // It means KMess needs:
    // - some way to delay the data preparation acks
    // - the ability to detect if a SLP error was sent for the transfer INVITE only.
    // - fixes for new issues with the state tracking.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpTransferInvitation() - Received invitation to for a direct connection, this is currently not supported for user started applications." << endl;
#endif

    // Decline the transfer invitation.
    // This doesn't kill the session though.
    waitingState_ = P2P_WAIT_DEFAULT;  // avoid assertion.
    sendSlpError("603 Decline", invitationSessionID_, invitationContentType_, P2P_MSG_TRANSFER_DECLINE);
    return;
  }


  // Create the accept message
  MimeMessage acceptMessage;
  bool startListening = false;

  // Get network details
  QString localIp    = getLocalIp();
  QString externalIp = getExternalIp();

  // Read the data from the message
  QString bridges        = slpMimeBody.getValue("Bridges");       // Transport layers the client supports
//  int     netID          = slpMimeBody.getValue("NetID").toInt(); // Some ID, unused
  QString connectionType = slpMimeBody.getValue("Conn-Type");     // Suggested connection type
  QString hasUPnPNat     = slpMimeBody.getValue("UPnPNat");       // uses UPnP-enabled NAT router
  QString hasICF         = slpMimeBody.getValue("ICF");           // uses Internet Connection Firewall


  // Based on the contact's network configuration,
  // we choose how we want to send the file.
  //
  // The client can include a Hashed-Nonce field,
  // and we can respond with another Hashed-Nonce field.
  // This algorithm is not known, but including a normal Nonce field
  // in the accept message seams to work (the client has some fallback).

  // Listen at the internal port
  // The connection can be used for both the internal and external port
  // because it only accepts one incoming packet, the second one will be rejected.
#if P2PAPPLICATION_AVOID_DC_SERVER
  int listeningPort = 0;  // for debugging.
#else
  int listeningPort = applicationList_->addServerConnection();
#endif

  if( listeningPort != 0 )
  {
    // Get parameters for message
    nonce_ = generateGUID();  // used for authentication

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpTransferInvitation() - Generating Nonce value "
            << nonce_ << " for incoming connections." << endl;
#endif

    acceptMessage.addField( "Bridge",    "TCPv1" );
    acceptMessage.addField( "Listening", "true"  );
    acceptMessage.addField( "Nonce",     nonce_  );

    if(localIp != externalIp)
    {
      acceptMessage.addField("IPv4Internal-Addrs", localIp    );
      acceptMessage.addField("IPv4Internal-Port",  QString::number(listeningPort) );
    }

    acceptMessage.addField("IPv4External-Addrs", externalIp );   // TODO: include other aliases
    acceptMessage.addField("IPv4External-Port",  QString::number(listeningPort) );

    // Inform the user about the file transfer progress.
    showTransferMessage( i18n("Awaiting connection at %1, port %2").arg(externalIp).arg(listeningPort) );

    // Prepare to send the nonce too when connection to other contact is established
    connect( applicationList_, SIGNAL(     connectionEstablished() ),   // when connected, send nonce
             this,             SLOT  ( slotConnectionEstablished() ));
    connect( applicationList_, SIGNAL(      connectionAuthorized() ),   // when authorized, send data
             this,             SLOT  (  slotConnectionAuthorized() ));
    connect( applicationList_, SIGNAL(          connectionFailed() ),   // when failed, revert to switchboard
             this,             SLOT  (      slotConnectionFailed() ));

    startListening = true;  // avoids double-checking certain conditions.
  }
  else
  {
    // Could not listen at a local port, use switchboard instead.
    kdWarning() << "P2PApplication::gotSlpTransferInvitation() - Could not listen at local port, "
                   "redirecting transfer to the switchboard "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)." << endl;

    // This configuration sends each file over the switchboard:
    acceptMessage.addField( "Bridge",    "TCPv1" );
    acceptMessage.addField( "Listening", "false" );
    acceptMessage.addField( "Nonce",     "{00000000-0000-0000-0000-000000000000}");

    // Inform the user about the file transfer progress.
    showTransferMessage( i18n("Reverting to indirect file transfer (this could be slow).") );
    // TODO: at this point, it's assumend the other client will initiate the transfer.
    //       perhaps set the waitingState_ to detect when the contact does not return a request?
    //       Windows Live Messenger is also capable of sending a new INVITE message so it becomes the server instead.
  }

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::gotSlpTransferInvitation() - Sending 200 transfer OK message, "
            << "listening=" << (listeningPort != 0) << "." << endl;
#endif

  // Confirm the session
  sendSlpOkMessage( acceptMessage );

  // Wait for the contact to ACK the message..
  if( startListening )
  {
    waitingState_ = P2P_WAIT_FOR_CONNECTION;
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpTransferInvitation() - Waiting for a connection to succeed or fail..." << endl;
#endif
  }
  else
  {
    // Expecting file data now we've rejected the invite.
    // TODO: it's also possible another invite is sent, hoping for a second chance.
    waitingState_ = P2P_WAIT_FOR_FILE_DATA;
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpTransferInvitation() - Waiting for contact to send file data..." << endl;
#endif
  }
  waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
}



/**
 * @brief Called when a response to a SLP transfer message was received.
 *
 * The message body contains the IP address to connect to.
 * It can be received the following situations:
 * - We sent the transfer invitation with a sendSlpTransferInvitation() call.
 *   The contact sends the "200 OK" message with the ipaddress/port information.
 * - We sent a transfer response back with "Listening: false" field.
 *   The contact sends a second SLP INVITE to become the server instead.
 *
 * When the contact starts a server, it sends the following message:
 * @code
MSNSLP/1.0 200 OK
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
...
Content-Type: application/x-msnmsgr-transrespbody
Content-Length: 144

Bridge: TCPv1
Listening: true
Nonce: {9BA191DB-3B68-93DA-91A2-BB9031D5B92F}
IPv4External-Addrs: 111.222.333.444
IPv4External-Port: 6891
IPv4Internal-Addrs: 192.168.0.111
IPv4Internal-Port: 6891
@endcode
 * As of Windows Live Messenger 8.0, a <code>SChannelState: 0</code> field is added.
 * Version 8.1 also adds a <code>Capabilities-Flags: 1</code>, <code>Conn-Type</code> and <code>TCP-Conn-Type</code> field.
 *
 * When the contact is not able to open a port, it sends the following message:
 * @code
MSNSLP/1.0 200 OK
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
...
Content-Type: application/x-msnmsgr-transrespbody
Content-Length: 83

Bridge: TCPv1
Listening: false
Nonce: {00000000-0000-0000-0000-000000000000}
@endcode
 *
 * When the contact likes to become the server instead it sends the following message:
 * @code
INVITE MSNMSGR:user@kmessdemo.org MSNSLP/1.0
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
...
Content-Type: application/x-msnmsgr-transrespbody
Content-Length: 146

Bridge: TCPv1
Listening: true
Hashed-Nonce: {B00B2A8A-B3B6-95EB-322F-379747842156}
IPv4Internal-Addrs: 10.0.0.152
IPv4Internal-Port: 1182
@endcode
 *
 * @param  slpMimeBody   The SLP message with the fields "Listening", "Nonce",
 *                       and optionally ipaddress and port information.
 * @param  secondInvite  Used to indicate the contact sent a second invitation.
 */
03066 void P2PApplication::gotSlpTransferResponse(const MimeMessage &slpMimeBody, bool secondInvite)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if( ! secondInvite )
  {
    kdDebug() << "P2PApplication::gotSlpTransferResponse() - Parsing 200 OK message." << endl;
  }
  else
  {
    kdDebug() << "P2PApplication::gotSlpTransferResponse() - Parsing 2nd transfer INVITE message, "
              << "contact offers to become the direct connection server." << endl;
  }
#endif


  // Test for an existing (possibly even authorized) direct connection.
  // This could happen with local-lan file transfer, the roundtrip of the switchboard
  // response message could take longer then the time to authenticate the connection.
  if( applicationList_->hasDirectConnection() )
  {
    if( applicationList_->hasAuthorizedDirectConnection() )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotSlpTransferResponse() - Connection already authorized "
                << "before invitation response was received, starting transfer." << endl;
#endif
      initiateTransfer();
    }
    else
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::gotSlpTransferResponse() - Connection already established "
                << "before invitation response was received, waiting for connection to authorize..." << endl;
#endif
      // Slots already connected at this point.
    }

    return;
  }


  // Handle some special situations when the contact sent an INVITE message back instead.
  // This happens when:
  // - gotSlpTransferInvitation() was called become the contact wanted to initialize a direct connection.
  // - we sent a "200 OK" message back with "Listening: false" to indicate we can't be the server.
  // - this method gets called because the contact sends an INVITE to become the server instead.
  if( secondInvite )
  {
    // TODO: send SLP 200/OK message back when contact sent a second INVITE message?

    // The 'connectionAuthenticated()' and 'connectionFailed()' slots are connected in gotSlpTransferInvitation()
    // The 'connectionEstablished()' signal was not connected because we were supposed to become the server.
    connect( applicationList_, SIGNAL(     connectionEstablished() ),   // when connected, send nonce
             this,             SLOT  ( slotConnectionEstablished() ));
  }


  // The Conn-type field of the message is the least important here,
  // the "Listening", "ICF" and "UPnPNat" tell us more about the situation of the contact,
  // and the probability to create a successful direct connection in either way.

  // Get fields from message
  bool listening      = slpMimeBody.getValue("Listening") == "true";
  bool listenInternal = slpMimeBody.hasField("IPv4Internal-Addrs");
  bool listenExternal = slpMimeBody.hasField("IPv4External-Addrs");
  nonce_              = slpMimeBody.getValue("Nonce");

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication:gotSlpTransferResponse() - Storing contact nonce value "
            << nonce_ << " to authenticate later." << endl;
#endif

#if P2PAPPLICATION_AVOID_DC_CLIENT
  listening = false;  // for debugging.
#endif


  // If the contact is not listening, we can do the following things:
  // - invite the contact to connect to connect to this host instead (not implemented yet, TODO)
  // - send the data over the switchboard instead. (always works, but it's slow).

  // If the contact is listening, try to create a direct connection to the contact.
  if( listening && (listenExternal || listenInternal) )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpTransferResponse() - Contact is listening, "
              << "attempting to connect to the given IP-address." << endl;
#endif

    // Get the IP addresses.
    QStringList externalIpAddresses;
    QStringList internalIpAddresses;
    uint        externalPort;
    uint        internalPort;

    if( listenInternal )
    {
      internalIpAddresses = QStringList::split( " ", slpMimeBody.getValue("IPv4Internal-Addrs") );
      internalPort        = slpMimeBody.getValue("IPv4Internal-Port").toUInt();
    }

    if( listenExternal )
    {
      externalIpAddresses = QStringList::split( " ", slpMimeBody.getValue("IPv4External-Addrs") );
      externalPort        = slpMimeBody.getValue("IPv4External-Port").toUInt();
    }

    // See if the clients are likely at the same lan.
    bool sameLan = ( listenInternal && externalIpAddresses.contains( CurrentAccount::instance()->getExternalIp() ) );

    // Avoid sending slotAllConnectionsFailed() if one connection attempt fails immediately.
    applicationList_->setAddingConnections( true );

    // Connect to the internal IP address
    if( listenInternal )
    {
      // Add connection attempt for each local IP address
      for ( QStringList::Iterator it = internalIpAddresses.begin(); it != internalIpAddresses.end(); ++it )
      {
        // Ignore when connection was made
        if( applicationList_->hasDirectConnection() )
        {
          break;
        }

        // Show "connecting to <internal ip>" message when connecting
        if( applicationList_->addConnection( *it, internalPort ) )
        {
          showTransferMessage( i18n("Connecting to %1, port %2").arg( *it ).arg( internalPort ) );
        }
      }
    }

    // Connect to the external IP address
    if( listenExternal )
    {
      // Add connection attempt for each external IP address
      for ( QStringList::Iterator it = externalIpAddresses.begin(); it != externalIpAddresses.end(); ++it )
      {
        // Ignore when connection was made
        if( applicationList_->hasDirectConnection() )
        {
          break;
        }

        // Show "connecting to <external ip>" message message unless the hosts are _likely_ at the same lan.
        // In that case, a previous "Connecting to ..." message should stay visible.
        if( applicationList_->addConnection( *it, externalPort ) && ! sameLan )
        {
          showTransferMessage( i18n("Connecting to %1, port %2").arg( *it ).arg( externalPort ) );
        }
      }
    }

    // Reset again.
    applicationList_->setAddingConnections( false );
  }


  // After passing the connections to addConnection() it's possible
  // some slots are already fired and the connection was established.
  if( applicationList_->hasDirectConnection() )
  {
    return;
  }


  // See if there aren't connections established already.
  if( applicationList_->hasPendingConnections() )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpTransferResponse() - Waiting for connection attempt to succeed..." << endl;
#endif

    // Wait for connection to establish
    waitingState_ = P2P_WAIT_FOR_CONNECTION2;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_DATA, true );
  }
  else
  {
    // Nothing is pending, nothing is connected.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotSlpTransferResponse() - No connection possible, "
              << "informing ApplicationList." << endl;
#endif

    // If no direct connection attempt will be made.
    // Reset the pending invitation connection flag, ApplicationList sends the connectionFailed signal.
    // This also signals other applications the connection attempt failed.
    applicationList_->setPendingConnectionInvitation(false);
  }
}



/**
 * @brief Called when a P2P message with timeout-flag was received.
 *
 * This method aborts the application with contactAborted().
 *
 * @param  message  The received P2P message.
 */
03268 void P2PApplication::gotTimeoutAck(const P2PMessage &message)
{
  // Error in our headers?
  if(isWaitingForUser())
  {
    // Contact aborted this session because we didn't press "accept/cancel" within 120 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.
    kdWarning() << "P2PApplication::gotTimeoutAck() - Got an unexpected P2P timeout-error response "
                   "(flag="    << QString::number( message.getFlags(), 16 ) <<
                   " state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=contactaborted)." << endl;

    if( dataType_ == P2P_TYPE_NEGOTIATION )
    {
      contactAborted( i18n("The contact rejected the invitation.  An internal error occured.") );
    }
    else
    {
      contactAborted( i18n("The transfer failed.  An internal error occured.") );
    }
  }
}



/**
 * @brief Got a transfer aborted ack.
 *
 * This message acknowledges the data transfer was aborted.
 * The flag type indicates whether the sender aborted, or the receiver aborted the transfer.
 *
 * @param  message  The received error message.
 */
03308 void P2PApplication::gotTransferAbortedAck(const P2PMessage &message)
{
  // Got some kind of confirmation when we indicated to abort.
  // Accept it by closing directly
  // Error in our headers?

  if( ! isClosing() )
  {
    kdWarning() << "P2PApplication::gotTransferAbortedAck() - Got an unexpected P2P transfer aborted-response "
                   "(flag="    << QString::number( message.getFlags(), 16 ) <<
                   " state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=endapplication)." << endl;

    // Make sure the app still quits.
    abortDataSending();
    endApplicationLater();
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotTransferAbortedAck() - Message confirms abort, requesting terination." << endl;
#endif

    // Make sure the app will exist, but leave it half-open to receive the final 0x08 messages
    // for all messages which were already being sent whle the BYE was underway.
    abortDataSending();
    endApplicationLater();
  }
}



/**
 * @brief  Returns whether the application can handle the given ACK message.
 *
 * It checks whether an unacked message exists for the given ACK message.
 *
 * @return Whether the application can handle the ACK message.
 */
03350 bool P2PApplication::hasUnAckedMessage(const P2PMessage &p2pMessage)
{
  // Simply test using the internal function, don't expose the UnAckedMessage pointer to external classes.
  return (getUnAckedMessage(p2pMessage) != 0);
}



/**
 * @brief Return whether a given message type is still unacked.
 *
 * @return Whether the given message is still unacked.
 */
03363 bool P2PApplication::hasUnAckedMessage(const P2PMessageType messageType)
{
  return (getUnAckedMessage(messageType) != 0);
}



/**
 * @brief Notify the derived class it can initiate the file transfer.
 *
 * It either calls userStarted3_UserPrepares() or contactStarted4_ContactConfirmsPreparation(),
 * depending who started the application. The timeout timer will also be stopped.
 */
03376 void P2PApplication::initiateTransfer()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::initiateTransfer() - Signalling implementation class "
            << "to start the file transfer." << endl;
#endif

  if( waitingState_ == P2P_WAIT_FOR_CONNECTION
  ||  waitingState_ == P2P_WAIT_FOR_CONNECTION2 )
  {
    waitingState_ = P2P_WAIT_DEFAULT;
  }

  // When we start sending information, it could take a while before
  // the contact returns messages which disable the waiting timer.
  // Avoid this by disabling it now, before we kill our own transfer.
  if( waitingTimer_->isActive() )
  {
    waitingTimer_->stop();
    kdWarning() << "P2PApplication::initiateTransfer() - timeout detection timer was still running, stopped "
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)." << endl;
  }


  // Call the connect implementation based on who invoked this application.
  // The implementations of these methods can call sendData(), and
  // the actual transfer will be handled transparently here.
  if( isUserStartedApp() )
  {
    userStarted3_UserPrepares();
  }
  else
  {
    contactStarted4_ContactConfirmsPreparation();
  }
}



/**
 * @brief Send a cancel message.
 *
 * The application will be terminated later, either directly or after the contact ACKed the error.
 *
 * The following cancelReason values are supported:
 * - <tt>CANCEL_INVITATION</tt>
 *     The user declined the invitation. This sends an SLP <tt>603 Decline</tt> message.
 *
 * - <tt>CANCEL_ABORT</tt> / <tt>CANCEL_FAILED</tt>
 *     The user aborted the session.
 *     If called from contactStarted1_ContactInvitesUser() with <tt>CANCEL_FAILED</tt>,
 *     this sends an SLP <tt>500 Internal Error</tt> message.
 *     Otherwise it sends a SLP BYE or P2P error message.
 *
 * - <tt>CANCEL_NOT_INSTALLED</tt>
 *     The requested service/application is not installed.
 *     This sends an SLP <tt>500 Internal Error</tt> message.
 *
 * - <tt>CANCEL_TIMEOUT</tt>
 *     There was a timeout waiting for the contact to accept or send certain P2P data.
 *     This sends an P2P message with the "waiting flag" set (type 4) and terminates the application direclty.
 *     This reason is used internally by certain timeout functions.
 *
 * - <tt>CANCEL_INVALID_SLP_CONTENT_TYPE</tt>
 *     Sends an "MSNSLP 500 Internal Error" message back, but with a <tt>null</tt> Content-Type value set.
 *
 * The error messages which send an SLP message can only be used from
 * contactStarted1_ContactInvitesUser() and userStarted2_ContactAccepts().
 * 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() at all.
 *
 * 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 could be ignored).
 * If KMess is compiled in debug mode, an assertion in sendSlpError() detects this.
 *
 * The <tt>CANCEL_ABORT</tt> reason is the only valid response to reject
 * a data-preparation message in the userStarted3_UserPrepares() method.
 * If KMess is compiled in debug mode, an assertion will be triggered otherwise.
 *
 * @param  cancelReason  The reason why the application is cancelled.
 */
03462 void P2PApplication::sendCancelMessage(const ApplicationCancelReason cancelReason)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendCancelMessage() - request to abort "
            << "(reason=" << cancelReason << ", state=" << waitingState_ << ")." << endl;
#endif

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

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

    // TODO: send bye here to? wait for bye? just terminate?
    kdWarning() << "P2PApplication::sendCancelMessage() - data-preparation stage failed "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=endapplicationlater)." << endl;
    endApplicationLater();
    return;
  }


  switch(cancelReason)
  {
    // Declined the invitation
    case CANCEL_INVITATION:
#ifdef KMESSTEST
      ASSERT( ! invitationContentType_.isEmpty() );
      ASSERT( invitationSessionID_ != 0 );
#endif

      // 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.
    case CANCEL_TIMEOUT:
      // Timeout handling occurs via slotCleanup(), not here.
      kdWarning() << "P2PApplication::sendCancelMessage() - CANCEL_TIMEOUT is not supported here "
                     "(state=" << waitingState_ <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x04,endapplicationlater)" << endl;
      sendP2PMessage(0, P2PMessage::MSN_FLAG_WAITING, 0);
      shouldSendAck_ = false;
      endApplicationLater();
      return;


    // The application type is not installed
    case CANCEL_NOT_INSTALLED:
      if( gotSlpMessage_)
      {
        // Usually this particular error is invoked from Application::contactStarted1_ContactInvitesUser().
        sendSlpError("500 Internal Error", invitationSessionID_, "null");
      }
      else
      {
        kdWarning() << "P2PApplication::sendCancelMessage() - CANCEL_NOT_INSTALLED is not supported here "
                       "(state="   << waitingState_ <<
                       " contact=" << getContactHandle() <<
                       " session=" << sessionID_ <<
                       " class="   << className() <<
                       " action=return)" << endl;
      }
      return;


    // User or application wants to abort
    case CANCEL_ABORT:
    case CANCEL_FAILED:
      // Make sure sending is aborted.
      abortDataSending();

      // Determine which error message to send back.
      if( gotSlpMessage_ )
      {
        // A: Special case for SLP messages.
        if( waitingState_ == P2P_WAIT_USER_ACCEPT && cancelReason == CANCEL_ABORT )
        {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
          kdDebug() << "P2PApplication::sendCancelMessage() - Cancelling after receiving an SLP INVITE, "
                    << "sending BYE back." << endl;
#endif
#ifdef KMESSTEST
          ASSERT( isUserStartedApp() );
#endif
          // Send bye early, contact should detect this.
          sendSlpBye();
        }
        else
        {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
          kdDebug() << "P2PApplication::sendCancelMessage() - Aborting after receiving an SLP message, "
                    << "sending 500 Internal Error back." << endl;
#endif
          // 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_);
        }
      }
      else if( isUserStartedApp() )
      {
        // B: User started application
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication::sendCancelMessage() Cancelling an application "
                  << "we've started ourselves, sending BYE early." << endl;
#endif
        // Mark the logs if there is something weird going on.
        // It's possible the contact doesn't receive the INVITE anymore
        // while KMess can't detect yet it's closed.
        // This happens sometimes when KMess sent a file before which was aborted by WLM.
        if( hasUnAckedMessage( P2P_MSG_SESSION_INVITATION ) )
        {
          kdWarning() << "P2PApplication::sendCancelMesage() - aborting while INVITE ack was not received "
                         "(state="   << waitingState_ <<
                         " contact=" << getContactHandle() <<
                         " session=" << sessionID_ <<
                         " class="   << className() <<
                         " action=continue,sendslpbye)." << endl;
        }

        // Send bye early, contact should detect this.
        sendSlpBye();
      }
      else
      {
        // C: Contact started application
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication::sendCancelMessage() Cancelling an application "
                  << "started by the other contact, sending BYE early." << endl;
#endif
        // Send bye early, contact should detect this.
        sendSlpBye();

        //  TODO: after the message is ACKed, the contact sends a 0x40 "I've sent the bye early" message.
        // KMess does not do this here.
      }

      return;


    // Invalid content type received
    case CANCEL_INVALID_SLP_CONTENT_TYPE:
#ifdef KMESSTEST
      ASSERT( gotSlpMessage_ );
#endif
      // Send an 500 error message without content-type set
      sendSlpError("500 Internal Error");
      return;


    // Last option, unknown cancel message
    default:
      kdWarning() << "P2PApplication::sendCancelMessage() - unknown cancelReason used "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x08)." << endl;

      sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
  }
}



/**
 * @brief Send file data in P2P packets.
 *
 * A timer starts that transmits all data packets automatically.
 * It uses the dataSource object to determine the total size of the data to send.
 *
 * @param  dataSource  Source of the data to send
 * @param  dataType    Type of the data, used to set the correct flag in the P2P footer code.
 */
03650 void P2PApplication::sendData(QIODevice *dataSource, P2PDataType dataType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendData() - Begin of data transfer" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_ != 0 );
  ASSERT( dataSource != 0 );
  ASSERT( nextMessageID_ != 0 );
#endif

  // Make sure we don't activate a slot without a data source.
  if(dataSource == 0)
  {
    kdWarning() << "P2PApplication::sendData() - Couldn't send data, data source is null "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send500)." << endl;

    showEventMessage( i18n("The transfer failed. Couldn't open data source."), ChatMessage::CONTENT_APP_FAILED, false );

    // TODO: Official client sends BYE when data preparation failed.
    sendCancelMessage(CANCEL_FAILED); // MSNSLP messages terminate the application eventually.
    return;
  }

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

  // Reserve the next message ID.
  nextMessageID_++;

  // Set the total size field, this causes sendP2PMessage()
  // to handle fragmented messages correctly.
  // The 'fragmentMessageID_' is set so KMess sends all fragments with the same message ID.
  // It could happen the client receives a SLP ACK meanwhile, and ACKs it with a new message ID.
  // The current fragmented message should not be affected.
  fragmentMessageID_ = nextMessageID_;
  fragmentOffset_    = 0;
  fragmentTotalSize_ = dataSource->size();

  // Inform the application list this application wants to send a file.
  // It will call sendNextDataParts() each time the connection or switchboard can accept new packets.
  applicationList_->registerDataSendingApplication( this );
}



/**
 * @brief Send the data preparation message.
 *
 * This is a consists of a P2P message with <tt>0x00000000</tt> as message body.
 * The contact should return with a data-preparation ACK, which initiates the file transfer.
 * Note that Windows Live Messenger (v8) will start a direct connection setup first
 * before it sends the data preparation ACK.
 */
03708 void P2PApplication::sendDataPreparation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendDataPreparation() - Sending data preparation message." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_    != 0 );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  // TODO: Check the various footer codes send for certain transfers.
  QByteArray p2pMessage(4);
  p2pMessage.fill(0x00);
  sendP2PMessage(p2pMessage, 0, 1, P2P_MSG_DATA_PREPARATION);

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

  // TODO: after the data preparation is sent, WLM8 starts a direct connection setup:
  // - it does not send an ACK yet
  // - it sends the transreq message,
  // - when the invitation finished (or the DC is up..??) it sends the data preparation ACK.
  // - after that it sends the ACK of the last "SLP 200 OK" message.
  // - at this point, the data preparation ACK triggers KMess to send the picture data.

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



/**
 * @brief Send the data preparation ACK.
 *
 * You need to call this method from userStarted3_UserPrepares()
 * to accept the data-preparation message.
 *
 * This method will actually set a flag only,
 * which is inspected when userStarted3_UserPrepares() returns.
 */
03750 void P2PApplication::sendDataPreparationAck()
{
  if(userShouldAcknowledge_)
  {
    // This value is checked again after the
    // userStarted3_UserPrepares() method returns.
    userShouldAcknowledge_ = false;
  }
  else
  {
    kdWarning() << "P2PApplication::sendDataPreparationAck() - Call not expected here! "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)" << endl;
  }
}



/**
 * @brief Send the direct connection handshake
 *
 * This message contains the Nonce field,
 * used by the other contact to authenticate the direct connection.
 * The confirmation message is identical to the handshake message.
 */
03777 void P2PApplication::sendDirectConnectionHandshake()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendDirectConnectionHandshake() - Sending direct connection handshake "
            << "(nonce=" << nonce_ << ")." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( applicationList_->hasDirectConnection() );
#endif
#ifdef KMESSTEST
  // NOTE: the handshake could happen while the file transfer was in progress, or even completed already.
  ASSERT( waitingState_ == P2P_WAIT_FOR_CONNECTION2   // we just established connection, send the first handshake
       || waitingState_ == P2P_WAIT_FOR_HANDSHAKE     // contact established connection, waiting to receive first handshake
       || waitingState_ == P2P_WAIT_FOR_HANDSHAKE_OK  // we sent the first handshake, waiting for contact to return second.
       );
#endif

  // Message flag is 0x100
  const int handshakeFlag = P2PMessage::MSN_FLAG_DC_HANDSHAKE;

  // Prepare the header
  QByteArray header(48);
  header.fill(0x00);
  nextMessageID_++;

  // Insert the bytes
  P2PMessage::insertBytes(header, 0,               0);   // 1: SessionID
  P2PMessage::insertBytes(header, nextMessageID_,  4);   // 2: MessageID
  // offset and total size are 0
  P2PMessage::insertBytes(header, handshakeFlag,  28);   // 6: Message type (flags)

  // Insert the nonce at fields 7-9
  P2PMessage::insertNonce(header, nonce_);


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  // Display binary data as hexadecimal string
  const int nonceOffset = 32;
  const char hexMap[] = "0123456789ABCDEF";
  QString hex;
  for(int i = 0; i < 16; i++)
  {
    if( i == 4 || i == 6 || i == 8 || i == 10 )
    {
      hex += "-";
    }
    int upper = (header[nonceOffset + i] & 0xf0) >> 4;
    int lower = (header[nonceOffset + i] & 0x0f);
    hex += hexMap[upper];
    hex += hexMap[lower];
  }
  kdDebug() << "P2PApplication::sendDirectConnectionHandshake() - copied nonce '" << nonce_ << "' to p2p message header as '" << hex << "'." << endl;
#endif

  // Forcefully Send the message over the direct connection
  applicationList_->getDirectConnection()->sendMessage( header );

  // Not updating the waiting state here.
  // The handshake process could happen while the data transfer is already in progress
}



/**
 * @brief Called when the connection is ready to send more file data.
 * @param preferredFragments  The preferred number of fragments to send at once.
 *
 * The sendData() method calls ApplicationList::registerDataSendingApplication()
 * to start this notification process. Each time the direct connection is
 * ready to send more data, ApplicationList::slotConnectionReadyWrite() is called which
 * calls this method.
 *
 * @returns  Returns false if something failed, and this method should not be called again.
 */
03851 bool P2PApplication::sendNextDataParts( int preferredFragments )
{
  // Abort if the data sending was reset.
  // Also fixes race conditions with signals.
  if( dataType_ == P2P_TYPE_NEGOTIATION || isClosing() || KMESS_NULL(dataSource_) )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendNextDataParts() - called while aborting "
                 "(datatype=" << dataType_ <<
                 " isclosing=" << isClosing() <<
                 " aborting=" << aborting_ <<  // sort-of reserved for aborts from ApplicationList.
                 " datasource=" << dataSource_ <<
                 " waitingState=" << waitingState_ <<
                 " contact=" << getContactHandle() << ")." << endl;
#endif

    // Request caller not to call us again.
    return false;
  }


  // Allow the caller to specify the number of loops,
  // e.g. more for a direct connection, less for parallel transfers.
  int loops = QMAX( preferredFragments, 1 );
  while( --loops >= 0 && fragmentOffset_ < fragmentTotalSize_ && ! dataSource_->atEnd() )
  {
    // Read the data from the file.
    char buffer[1202]; // 1202 is the max P2P data size.
    int bytesRead = dataSource_->readBlock( buffer, 1202 );
    if( bytesRead < 0 )
    {
      kdWarning() << "P2PApplication::sendNextDataParts() - Couldn't send more data, "
                     "read error at " << fragmentOffset_ << " of " << fragmentTotalSize_ << " bytes "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=slpcancel)." << endl;
      sendCancelMessage( CANCEL_FAILED );
      return ( dataSource_ == 0 );  // return true if already aborted by sendCancelMessage().
    }
    else if( bytesRead == 0 )
    {
      break;
    }

    // Wrap in buffer, without copying.
    QByteArray data;
    data.setRawData( buffer, bytesRead );

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendNextDataParts() - Sending data:"
              << " offset=" << fragmentOffset_
              << " buffer=" << bytesRead
              << endl;
#endif

    // Determine the progress value now because mesageOffset_ it reset by the last sendP2PMessage() call.
    unsigned long nextProgressValue = ( fragmentOffset_ + bytesRead );

    // Determine the flags:
    // Funny, MSN6 doesn't seam to care which flag was set
    // while transferring the file. KMess however, does.
    bool writeSuccess = false;
    switch( dataType_ )
    {
      case P2P_TYPE_PICTURE:
        writeSuccess = sendP2PMessage( data, P2PMessage::MSN_FLAG_OBJECT_DATA, 1, P2P_MSG_DATA, fragmentMessageID_ );
        break;

      case P2P_TYPE_FILE:
        writeSuccess = sendP2PMessage( data, P2PMessage::MSN_FLAG_FILE_DATA, 2, P2P_MSG_DATA, fragmentMessageID_ );
        break;

      default:
        // Display error message for the first message only.
        if( fragmentOffset_ == 0 )
        {
          kdWarning() << "P2PApplication::sendNextDataParts() - Unknown data transfer mode "
                         "(mode=" << dataType_ <<
                         " contact=" << getContactHandle() <<
                         " session=" << sessionID_ <<
                         " class="   << className() <<
                         " action=tryflag)." << endl;
        }

        writeSuccess = sendP2PMessage( data, 0, 1, P2P_MSG_DATA, fragmentMessageID_ ); // The best solution in this situation
    }

    // Make sure it's reset before returning.
    data.resetRawData( buffer, bytesRead );

    // Avoid next round if write was blocked already.
    // This could happen if a direct connection can't write all data.
    // The DirectConnectionBase class will automatically send the remaining parts, so don't worry about that here.
    if( ! writeSuccess )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::sendNextDataParts() - message could not be written entirely, leaving loop early." << endl;
#endif
      break;
    }

    // Dispatch the progress to the derived class
    showTransferProgress( nextProgressValue );
  }


  // Rounds completed, make final assertions.
  // Be verbose if something internally goes wrong.
  if( dataSource_->atEnd() && fragmentOffset_ < fragmentTotalSize_ )
  {
    kdWarning() << "P2PApplication::sendNextDataParts() - data source is at the end, "
                   "but offset byte count is not "
                   "(fragmentoffset="    << fragmentOffset_ <<
                   " fragmenttotalsize=" << fragmentTotalSize_ <<
                   " sourcestatus="      << dataSource_->status() <<
                   " sourcesize="        << dataSource_->size() <<
                   " datatype="          << dataType_ <<
                   " contact="           << getContactHandle() <<
                   " session="           << sessionID_ <<
                   " class="             << className() <<
                   " action=resetstate)!" << endl;

    // Reset for next p2p message.
    fragmentTotalSize_ = 0;
    fragmentOffset_    = 0;
  }

  // See if transfer completed,
  // if so tell the application list to stop calling this method.
  if( fragmentOffset_ == 0 || fragmentOffset_ >= fragmentTotalSize_ )
  {
    dataSource_ = 0;
    dataType_   = P2P_TYPE_NEGOTIATION;
    applicationList_->unregisterDataSendingApplication( this );

    // Now wait for the contact to ACK that it received all data.
    waitingState_ = P2P_WAIT_FOR_DATA_ACK;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACK, true );

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendSlpBye() - Waiting for all data received ACK..." << endl;
#endif

    // no return false, will unregister the app twice.
  }

  return true;
}



/**
 * @brief Send a P2P ACK message.
 *
 * This is called after each received P2P message.
 * The ACK system turns the P2P communication into a stop-and-wait protocol.
 * The other client waits until it receives an ACK or error before it continues with the next step.
 *
 * @param  ackType              The ACK type (flag field) to use, defaults to <tt>MSN_FLAG_ACK</tt>.
 * @param  originalMessageData  The meta data of the original message, used to fill some parts of the ACK message.
 */
04013 void P2PApplication::sendP2PAck(int ackType, UnAckedMessage *originalMessageData)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if(ackType == P2PMessage::MSN_FLAG_ACK)
  {
    if( nextMessageID_ == 0 && getMode() != APP_MODE_ERROR_HANDLER )
    {
      kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (INVITE-ACK)" << endl;
    }
    else
    {
      kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK" << endl;
    }
  }
  else if(ackType == P2PMessage::MSN_FLAG_ERROR)
  {
    kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (Error-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_WAITING)
  {
    kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (Timeout-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_WAITING_FOR_ACK)
  {
    kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (ACK-timeout-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_ABORTED_SENDING)
  {
    kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (Aborted-myself-ACK)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_ABORTED_RECEIVING)
  {
    kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (Aborted-you-ACK)" << endl;
  }
  else
  {
    kdDebug() << "P2PApplication::sendP2PAck() - Sending P2P ACK (Unknown-type)" << endl;
  }
#endif
#ifdef KMESSTEST
  ASSERT( shouldSendAck_ );   // TODO: test for control messages, which do not have to obey this rule.
#endif

  // Default to previous message if no argument is given
  if( originalMessageData == 0 )
  {
    originalMessageData = &lastIncomingMessage_;
  }


  // 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
  unsigned long messageID;
  if( nextMessageID_ == 0 )
  {
    // We don't have a message ID set yet, we're acknowledging
    // the first INVITE message. Generate an ID

    nextMessageID_ = generateID();
    messageID      = nextMessageID_;

    // 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.
    nextMessageID_ -= 4;
  }
  else
  {
    nextMessageID_++;
    messageID = nextMessageID_;
  }

  // Determine the ack settings.
  unsigned long ackTotalSize = 0;
  unsigned long ackSessionID = 0;
  unsigned long ackUniqueID  = 0;
  unsigned long ackDataSize  = 0;

  // New ack field schema seen with Windows Live Messenger:
  if( ackType == P2PMessage::MSN_FLAG_ERROR )
  {
    // This ack tells the contact it should reset the session (TCP RST).
    ackTotalSize = originalMessageData->totalSize;
    ackSessionID = originalMessageData->ackSessionID;
  }
  else if( ackType == P2PMessage::MSN_FLAG_NEGATIVE_ACK )
  {
#ifdef KMESSTEST
    ASSERT( ! fragmentTracker_.isEmpty() );
    ASSERT( fragmentTracker_.isInitialized( originalMessageData->messageID ) );
#endif
    // This ack tells the contact we haven't received the data properly (e.g. invalid offsets)
    // TODO: guess the ackDataSize is the the last offset which was received.
    ackSessionID = originalMessageData->messageID;
    ackDataSize  = fragmentTracker_.isInitialized( originalMessageData->messageID )
                 ? fragmentTracker_.getTransferredBytes() : 0;
  }
  else if( ackType == P2PMessage::MSN_FLAG_ABORTED_RECEIVING )
  {
    // This ack tells the contact we won't ack the data anymore, since it's aborted.
    ackSessionID = originalMessageData->messageID;
    ackUniqueID  = generateID();
    ackDataSize  = originalMessageData->totalSize;
  }
  else if( ackType == P2PMessage::MSN_FLAG_ABORTED_SENDING )
  {
#ifdef KMESSTEST
    ASSERT( fragmentMessageID_ != 0 );
    ASSERT( originalMessageData->messageType == P2P_MSG_DATA );
    ASSERT( hasUnAckedMessage( P2P_MSG_DATA) );
#endif
    // this ack tells the contact to terminate it's ack session.
    ackSessionID = originalMessageData->messageID;  // must reffer to our OWN message.
  }
  else if( ackType == P2PMessage::MSN_FLAG_WAITING )
  {
    // The ackSid seams to be random if it was waiting for an ack.
    ackSessionID = originalMessageData->ackSessionID;  // we give it a more precise value.
  }
  else
  {
#ifdef KMESSTEST
    ASSERT( ackType == P2PMessage::MSN_FLAG_ACK );
#endif
    // This is a normal ack which confirms the arrival.
    ackSessionID = originalMessageData->messageID;
    ackUniqueID  = originalMessageData->ackSessionID;
    ackDataSize  = originalMessageData->totalSize;
  }

  // Insert the bytes
  P2PMessage::insertBytes(header, originalMessageData->sessionID,   0); // SessionID
  P2PMessage::insertBytes(header, messageID,                        4); // BaseIdentifier
  // offset of 8 bytes is always 0  (position 8)
  P2PMessage::insertBytes(header, ackTotalSize,                    16); // Confirm total size set in previous message
  // size of 4 bytes is always 0    (position 24)
  P2PMessage::insertBytes(header, ackType,                         28); // Set the ACK flag.
  P2PMessage::insertBytes(header, ackSessionID,                    32); // ID referring to a previous message.
  P2PMessage::insertBytes(header, ackUniqueID,                     36); // ID referring to a specific fragment.
  P2PMessage::insertBytes(header, ackDataSize,                     40); // reference to the Size of that message.

  // Deliver the message over the correct link (switchboard or direct connection)
  applicationList_->sendMessage(this, header);
  shouldSendAck_ = false;

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendP2PAck() - ACK Transmitted "
            << "(flags=0x" << QString::number(ackType, 16) << ")." << endl;
#endif
}



/**
 * @brief Sends a P2P Message.
 *
 * This method constructs the P2P binary header fields.
 * The constructed message is delivered to ApplicationList::sendMessage().
 *
 * The following instance fields are updated automatically:
 * - nextMessageID_ is generated for every new P2P message.
 * - fragmentOffset_ is updated when a splitted message is sent.
 * - fragmentTotalSize_ and messageOffse_ are reset when the last message fragment is sent.
 * - outgoingMessages_ is updated to trace ACKs back later.
 *
 * The fragmentTotalSize_ should be set to the total size before sending message fragments.
 * This is done automatically by sendSlpMessage().
 *
 * @param messageData    The message payload to send. This can be binary file data or a SLP MIME data.
 * @param flagField      The value for the flag field in the P2P header.
 * @param footerCode     The message footer, which is appended when the message is sent over the switchboard.
 * @param messageType    The type of the message. This value is stored with in the unacked message queue.
 *                       This value is used later to respond to ACKs of certain special messages.
 * @return               Whether the message can be sent. If the socket would block, false is returned.
 *                       This only occurs for direct connections connections.
 */
04197 bool P2PApplication::sendP2PMessage(const QByteArray &messageData, int flagField, uint footerCode, P2PMessageType messageType, unsigned long messageID)
{
#ifdef KMESSTEST
  ASSERT( messageData.size() <= 1202 );
#endif

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


  // Determine the message ID, if none is given.
  if( messageID == 0 )
  {
    if( nextMessageID_ == 0)
    {
      // This is the first P2P message we send, initialize message ID
      nextMessageID_ = generateID();
    }
    else if(fragmentOffset_ == 0)
    {
      // Only increments the ID for the first message.
      // Don't increment it for the next fragments (if it's a splitted message).
      // Better approach: pass the desired messageID to this function.
      nextMessageID_++;
    }

    messageID = nextMessageID_;
  }


  // Verify we're not configured to send a splitted message.
  if( fragmentTotalSize_ > 0
  &&  flagField != 0
  &&  flagField != P2PMessage::MSN_FLAG_OBJECT_DATA
  &&  flagField != P2PMessage::MSN_FLAG_FILE_DATA )
  {
    kdWarning() << "P2PApplication::sendP2PMessage() - Attempting to send splitted ACK message "
                   "(flags=0x" << QString::number( flagField, 16 ) <<
                   " state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)!" << endl;
  }


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

  if( fragmentTotalSize_ == 0 )
  {
    // Normal message
    totalSize   = messageData.size();
    offsetField = 0;
  }
  else
  {
    // Splited message: total size is set.
    totalSize   = fragmentTotalSize_;
    offsetField = fragmentOffset_;
  }


  // Generate ack session identifier for this message
  unsigned long ackSessionID = generateID();

  // Set session ID to zero when SLP messages are sent.
  unsigned long sessionID = sessionID_;
  if( flagField == 0
  &&  messageType != P2P_MSG_DATA_PREPARATION )
  {
    sessionID = 0;
  }

  // Update the header
  P2PMessage::insertBytes(header, sessionID,      0);    // 1: Session ID (zero for SLP messages).
  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, ackSessionID,   32);   // 7: Used to link ack messages (ACK session 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)


  // Deliver the message over the correct link (switchboard or direct connection)
  bool writeSuccess = applicationList_->sendMessage( this, header, messageData, footerCode );
  if( ! writeSuccess )
  {
    // This only happens when a direct connection transfer is congested.
    // The same message will be re-sent later.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendP2PMessage() - Message sending was blocked, should try again later." << endl;
#endif
    return false;
  }


  // Update unacked message queue to handle incoming ACKs
  if( flagField == 0 || messageType != 0 )
  {
    if( offsetField == 0 )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::sendP2PMessage() - Storing message fields to track the ACK response." << endl;
#endif
      // First message, or single message, store new record for ACKs.
      QDateTime currentTime;
      UnAckedMessage *ackData = new UnAckedMessage;
      ackData->sessionID    = sessionID_;
      ackData->messageID    = messageID;
      ackData->dataSize     = messageSize;
      ackData->totalSize    = totalSize;
      ackData->ackSessionID = ackSessionID;
      ackData->messageType  = messageType;  // used later to handle certain ACKs.
      ackData->sentTime     = currentTime.toTime_t();
      outgoingMessages_.append(ackData);
    }
    else
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::sendP2PMessage() - Updating message fields to track the ACK response." << endl;
#endif
      // Follow-up part of a splitted/fragmented message.
      // Update the uniqueID aka ackSID field to handle new acks.
      // TODO: it appears MSN7 uses the same ackSID for some messages, not sure
      //       whether that is a feature (to capture acks in a short time) or a problem in their random function.
      QPtrListIterator<UnAckedMessage> it(outgoingMessages_);
      while( it.current() != 0 )
      {
        if( it.current()->messageID == messageID )
        {
          it.current()->ackSessionID = ackSessionID;
          break;
        }
        ++it;
      }
    }
  }

  // Update the offset fields for splitted messages
  if(fragmentTotalSize_ != 0)
  {
    // Update offset
    fragmentOffset_ += messageSize;

    // If we transmitted all parts, reset the fields for the next normal message
    if(fragmentOffset_ >= fragmentTotalSize_)
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::sendP2PMessage() - All message parts sent, resetting fragmentOffset_ and fragmentTotalSize_." << endl;
#endif

      fragmentOffset_    = 0;
      fragmentTotalSize_ = 0;
    }
  }


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendP2PMessage() - Message transmitted "
            << "(sid="     << sessionID_
            << " mid="     << messageID
            << " ackSid="  << ackSessionID
            << " flags=0x" << QString::number(flagField, 16)
            << " size="    << messageSize
            << " type="    << messageType
            << ")" << endl;
#endif

  return true;
}



/**
 * @brief Send a SLP BYE message to close the session.
 *
 * This should only be called if we sent the SLP INVITE too,
 * otherwise the contact should sent it.
 *
 * Once the BYE message is sent, it waits for the last ACK to arrive.
 * The session is terminated afterwards. If the last ACK doesn't arrive
 * before a timeout occurs, the application is terminated as well to avoid.
 *
 * See gotSlpBye() for an example of a SLP BYE message.
 */
04388 void P2PApplication::sendSlpBye()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendSlpBye() - Sending SLP BYE message." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_ != 0 );
  ASSERT( ! callID_.isEmpty() );
  ASSERT( ! branch_.isEmpty() );
#endif

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

  // Determine content.
  // In the last, the official client only had "\r\n\0" as content (length = 3).
  QString contentType   = "application/x-msnmsgr-sessionclosebody";
  QString content       = "SessionID: " + QString::number( sessionID_ ) + "\r\n\r\n\0";
  uint    contentLength = content.length();

  // Create the message
  QString slpMessage;
  slpMessage = "BYE MSNMSGR:" + getContactHandle() + " MSNSLP/1.0\r\n"
               "To: <msnmsgr:" + getContactHandle() + ">\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: " + QString::number(contentLength) + "\r\n"
               + (contentLength > 0 ? "\r\n" : "")
               + content;

  // Send the message
  sendSlpMessage(slpMessage, P2P_MSG_SESSION_BYE);
  setClosing(true);

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

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



/**
 * @brief Send an SLP error response.
 *
 * This can be done to decline a file tranfer or tell the other client it sent bad SLP data.
 *
 * 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
 * the invitation is cancelled.
 *
 * @param  statusLine          The status line, for example <tt>200 OK</tt>, or <tt>603 Decline</tt>.
 * @param  sessionID           The identifier of the current session (can be zero).
 * @param  messageContentType  Contact type of the message which is in error (can be empty)
 * @param  messageType         The message type. This is used later when an ACK is received.
 */
04451 void P2PApplication::sendSlpError(const QString &statusLine,
                                  const uint sessionID, const QString &messageContentType, P2PMessageType messageType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendSlpError() - Sending SLP error response: " << statusLine << endl;
#endif

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

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

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

  if( callID_.isEmpty() || branch_.isEmpty() )
  {
#ifdef KMWSSTEST
    ASSERT( ! shouldSendAck_ );   // if the caller already sent an ACK, the send sendP2PAck() call will be ignored by WLM8.
#endif
    kdWarning() << "P2PApplication::sendSlpError() - attempting to send SLP error back, "
                   "but Call-ID or Branch is unknown! "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=send0x08)" << endl;
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    return;
  }

  // It's safe now to send the normal ACK.
  // Send it if this was not done by the caller yet.
  if( shouldSendAck_ )
  {
    sendP2PAck();
  }

  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:" + getContactHandle() + ">\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, messageType);
  setClosing(true);

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

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



/**
 * @brief Utility function to send an SLP INVITE message.
 *
 * Derived classes use sendSlpSessionInvitation() and sendSlpTransferInvitation() instead.
 * Both functions use this method internally to send the invitation.
 *
 * This method is constructs the SLP headers for the INVITE message, and sends it using sendSlpMessage().
 * The Branch-ID is generated by this method, and the Call-ID is generated with the first request.
 * After sending the invitation, it's waiting for the contact to accept or decline the message.
 *
 * @param  message      The SLP body fields, typically 'SessionID', 'EUF-GUID', 'AppId', and 'Context'.
 * @param  contentType  Content-Type of the message body.
 * @param  messageType  The message type. This is used later when an ACK is received.
 */
04557 void P2PApplication::sendSlpInvitation(const MimeMessage &message, const QString &contentType, P2PMessageType messageType)
{
#ifdef KMESSTEST
  ASSERT(! contentType.isEmpty()    );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
#endif

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

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

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

  // Generate identifiers
  if( callID_.isNull() )
  {
    // The callID remains the same between multiple invites
    // It identifies the session.
    callID_ = generateGUID();
  }

  // BranchID identifies this INVITE request
  branch_ = generateGUID();

  // Create the message (space after CSeq is added to emulate msn exactly)
  QString slpMessage;
  slpMessage = "INVITE MSNMSGR:" + getContactHandle() + " MSNSLP/1.0\r\n"
               "To: <msnmsgr:" + getContactHandle() + ">\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
  fragmentOffset_    = 0;
  fragmentTotalSize_ = 0;

  sendSlpMessage(slpMessage, messageType);

  // Keep the used content type in the state variables.
  // For contact-started invitations, this is used to store their type.
  // In this case, it can be used to store ours.
  invitationContentType_ = contentType;

  // Wait for the contact to accept
  waitingState_     = P2P_WAIT_FOR_INVITE_ACK;
  waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACCEPT, true ); // MSN 6 had a timeout of 30 sec to accept, WLM seams to have no timeout.

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendSlpInvitation() - Waiting for contact to send INVITE ACK, and accept later..." << endl;
#endif
}



/**
 * @brief Utility function to send an SLP message.
 *
 * It splits the SLP message into chunks which fit in the P2P payload.
 * Each part will be sent with sendP2PMessage().
 * The messages always have a Session ID of zero.
 *
 * @param  slpMessage   The whole SLP message to send, including SLP headers.
 * @param  messageType  The type of the message, used to trace ACK messages back later.
 */
04632 void P2PApplication::sendSlpMessage(const QString &slpMessage, P2PMessageType messageType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendSlpMessage() - SLP message=" << slpMessage << endl;
#endif

  // Get the raw utf8 encoded bytearray
  // This converts 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.
  QByteArray utf8Message = slpMessage.utf8();
  QByteArray messagePart;

  // Make sure the message gets sent in chunks of 1202 bytes.
  // Only set fragmentTotalSize_ if there are chunks, for useless debug messages.
  uint remainingBytes = utf8Message.size();
  fragmentOffset_      = 0;
  fragmentTotalSize_   = ( remainingBytes <= 1202 ? 0 : remainingBytes );

  do
  {
    // Let another QByteArray encapsulate a part of the utf8Message
    char *dataPointer = utf8Message.data() + fragmentOffset_;
    uint  dataSize    = QMIN(remainingBytes, 1202);
    messagePart.setRawData( dataPointer, dataSize );

    // Send the message with the generic sendP2PMessage() method
    // fragmentOffset_ is updated automatically
    sendP2PMessage(messagePart, 0, 0, messageType);
    remainingBytes -= dataSize;

#ifdef KMESSTEST
    ASSERT( remainingBytes == fragmentTotalSize_ - fragmentOffset_ );
#endif

    // Release data
    messagePart.resetRawData( dataPointer, dataSize );
  }
  while(remainingBytes > 0);

  // Reset for normal operations
  fragmentOffset_    = 0;
  fragmentTotalSize_ = 0;
}



/**
 * @brief Send the 200 OK message to accept the invitation.
 *
 * The message should contain the 'SessionID' field. It's value can
 * be retreived with getInvitationSessionID().
 *
 * Once the confirmation message has been sent, this class will automatically
 * use the correct Session ID for following messages (since it was already read from the SLP INVITE message).
 *
 * See gotSlpOk() for a message example.
 * Note that the 'message' parameter only needs to supply the SLP body fields.
 * The SLP header will be added by this method.
 *
 * @param message  The SLP message body.
 */
04695 void P2PApplication::sendSlpOkMessage(const MimeMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::sendSlpOkMessage() - Sending MSNSLP/1.0 200 OK message." << endl;
#endif
#ifdef KMESSTEST
  ASSERT(! branch_.isEmpty()                );
  ASSERT(! invitationContentType_.isEmpty() );
  // Waiting state is P2P_WAIT_USER_ACCEPT if contactStarted2_.. is called directly from contactStarted1_..
  // TODO: handle WLM8 invitations sent before data preparation
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_USER_ACCEPT );
#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:" + getContactHandle() + ">\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, ( gotTransferInvitation ? P2P_MSG_TRANSFER_OK : P2P_MSG_SESSION_OK ) );


  // Make sure it's not overwritten when the other contact invites for a DC transfer.
  if( sessionID_ == 0 )
  {
    // Now that we've confirmed the session, use the sessionID
    // in the next messages:
    sessionID_ = invitationSessionID_;
  }



  if(gotTransferInvitation)
  {
    // Wait until our transfer OK message is ACK-ed
    waitingState_ = P2P_WAIT_FOR_TRANSFER_ACK;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACK, true );

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendSlpOkMessage() - Waiting for contact to ACK the 200 transfer OK message..." << endl;
#endif
  }
  else
  {
    // Wait until our normal OK message is ACK-ed
    waitingState_ = P2P_WAIT_FOR_SLP_OK_ACK;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACK, true );

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



/**
 * @brief Send the invitation for a normal session.
 *
 * It sends a normal <tt>application/x-msnmsgr-sessionreqbody</tt> message using sendSlpInvitation().
 * The session ID is stored to handle incoming messages. It can be requested with getSessionID().
 *
 * Internally the remaining message fields are appended using:
 * - sendSlpInvitation()
 * - sendSlpMessage()
 * - sendP2PMessage()
 *
 * See gotSlpSessionInvitation() for an example message.
 *
 * @param  sessionID  The generated session ID, can be generated with generateID().
 * @param  eufGuid    The EUF-GUID value identifying the application type, from <tt>getAppId()</tt>.
 * @param  appId      The numeric app-Id value, which also identifies the application type.
 * @param  context    The context field.
 */
04785 void P2PApplication::sendSlpSessionInvitation(uint sessionID, const QString &eufGuid, const int appId, const QString &context)
{
  // Create the message
  MimeMessage invitation;
  invitation.addField( "EUF-GUID",  eufGuid );
  invitation.addField( "SessionID", QString::number(sessionID) );
  invitation.addField( "AppID",     QString::number(appId)     );
  invitation.addField( "Context",   context                    );

  // Send the invitation
  sendSlpInvitation( invitation, "application/x-msnmsgr-sessionreqbody", P2P_MSG_SESSION_INVITATION);

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



/**
 * @brief Send the invitation for a direct connection.
 *
 * This sends a <tt>application/x-msnmsgr-transreqbody</tt> message using sendSlpInvitation().
 * The contact responds with the ipaddresses/ports we can connect to.
 *
 * When the direct connection attempt completed, initiateTransfer() will be called.
 * It's possible the direct connection attempt failed, the clients will
 * exchange file data over the switchboard instead.
 *
 * See gotSlpTransferInvitation() for message examples.
 */
04816 void P2PApplication::sendSlpTransferInvitation()
{
  // Check if there is already an connection
  if( applicationList_->hasAuthorizedDirectConnection() )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendSlpSessionInvitation() - Connection is already setup, "
              << "re-using the current connection." << endl;
#endif
    initiateTransfer();
    return;
  }

  // Prepare to transfer when a connection is made by this application, or another application.
  connect( applicationList_, SIGNAL(      connectionAuthorized() ),   // when authorized, send data
           this,             SLOT  (  slotConnectionAuthorized() ));
  connect( applicationList_, SIGNAL(          connectionFailed() ),   // when failed, revert to switchboard
           this,             SLOT  (      slotConnectionFailed() ));

  // Check if there are pending connections
  if( applicationList_->hasPendingConnections() || applicationList_->hasPendingConnectionInvitation() )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::sendSlpSessionInvitation() - Another application is already inviting "
              << "for a direct connection, waiting for that attempt to succeed or fail..." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_CONNECTION2;
    showTransferMessage( i18n("Waiting for connection") );
    return;
  }

  // Only connect last slot if we're really going to make the connection ourselves.
  connect( applicationList_, SIGNAL(     connectionEstablished() ),   // when connected, send nonce
           this,             SLOT  ( slotConnectionEstablished() ));

  // Invite the contact to create a connection.
  // This message tells the other client it can't connect to this end-host.
  // Either the data needs to be sent over the switchboard,
  // or the other client should initiate a direct connection server.
  MimeMessage invitation;
  invitation.addField("Bridges",      "TCPv1");
  invitation.addField("NetID",        QString::number(generateID()));
  invitation.addField("Conn-Type",    "IP-Restrict-NAT");
  invitation.addField("UPnPNat",      "false");
  invitation.addField("ICF",          "false");

  // Send the invitation
  sendSlpInvitation( invitation, "application/x-msnmsgr-transreqbody", P2P_MSG_TRANSFER_INVITATION );
  waitingState_ = P2P_WAIT_FOR_INVITE_TR_ACK;

  // Mark invitation as pending.
  // This will fire an event when it's manually reset to false.
  applicationList_->setPendingConnectionInvitation(true);


  // When we include a Hashed-Nonce field, the contact also
  // adds a Hashed-Nonce to it's 'transrespbody' response message.
  // This may resemble http-digest authentication, but is still not researched.

  // Supported Conn-Type values:
  // - Firewall
  // - Direct-Connect     (Same IP, same port)
  // - Port-Restrict-NAT  (Same IP, different port)
  // - IP-Restrict-NAT    (Different IP, same port)
  // - Symmetric-NAT      (Different IP, different port)
  // - Unknown-Connect    (Different IP, different port)
  // The connection type can be discovered with STUN.

  // NetID is zero for Firewall/Direct-Connect,
  // otherwise it's a random number.

  // Inform the user about the file transfer progress.
  showTransferMessage( i18n("Negotiating options to connect") );
}



/**
 * @brief Cleanup function, is used to handle timeout events.
 *
 * When a timeout occurs before the contact sent something we expect,
 * this function terminates the application automatically.
 *
 * Depending on the internal "waiting state", an error message will be outputted with <tt>kdWarning()</tt>.
 */
04902 void P2PApplication::slotCleanup()
{
  // See if the waiting time for new packets is over (like TCP's TIME_WAIT state).
  if( waitingState_ == P2P_WAIT_END_APPLICATION )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::slotCleanup() - timer of endApplicationLater() fired, finally destroying object." << endl;
#endif

    // Directly destroy ...finally.
    testUnAckedMessages( true );
    endApplication();
    return;
  }


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::slotCleanup() - timeout fired, cleaning up "
            << "(session=" << sessionID_ << " waitingState=" << waitingState_ << ")." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( waitingState_ != P2P_WAIT_DEFAULT );
#endif


  // Not waiting for ourselves to connect.
  // Not waiting for contact to accept.
  // Not waiting for contact to send BYE ack.
  if( waitingState_ == P2P_WAIT_FOR_CONNECTION2
  ||  waitingState_ == P2P_WAIT_CONTACT_ACCEPT
  ||  waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::slotCleanup() - Not sending timeout message for current waiting state." << endl;
#endif
  }
  else
  {
    if( aborting_ )
    {
      // Don't send messages when aborting. This could potentially crash KMess, or cause new windows to appear.
      // - suppose this is the last aborting application which requests removal.
      // - ApplicationList informs the MsnSwitchboardConnection it can remove itself.
      // - ChatMaster processes the putMsg() call, and attempts to deliver the message.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::slotCleanup() - Not sending error message because application is aborting." << endl;
#endif

      testUnAckedMessages( false );
    }
    else
    {
      // Tell the other client we were waiting for a message to receive!
      // TODO: this is still not as it should be, but a start.
      uint waitFlag = P2PMessage::MSN_FLAG_WAITING;
      switch(waitingState_)
      {
        /*
          if waiting for ack, wlm sends 0x04.

        case P2P_WAIT_FOR_SLP_OK_ACK:
        case P2P_WAIT_FOR_PREPARE_ACK:
        case P2P_WAIT_FOR_TRANSFER_ACK:
        case P2P_WAIT_FOR_DATA_ACK:
        //not P2P_WAIT_FOR_SLP_BYE_ACK:
        case P2P_WAIT_FOR_SLP_ERR_ACK:
          // we guess 0x06 is used as waiting-for-ack error.
          waitFlag = P2PMessage::MSN_FLAG_WAITING_FOR_ACK;
          break;
        */

        case P2P_WAIT_FOR_FILE_DATA:
          // Waiting for file data. Application is shutting down sender right now.
          waitFlag = P2PMessage::MSN_FLAG_ABORTED_RECEIVING;  // 0x80
          break;

        default: break;
      }

      // Send the message
      sendP2PMessage(0, waitFlag, 0);  // TODO: pass the original message which is unacked!!

      testUnAckedMessages( true );
    }
  }

  // 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_FOR_INVITE_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting SLP INVITE ack message "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_CONTACT_ACCEPT:
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication::slotCleanup() - Contact didn't accept the invitation, terminating manually." << endl;
#endif

      // NOTE: WLM8 has no timeout abort P2P (file transfer) invitations.
      // It keeps the invitation open even if the switchboard closes.
      showEventMessage( i18n("The invitation was cancelled. A timeout occured waiting for the contact to accept."), ChatMessage::CONTENT_APP_CANCELED, false );
      sendCancelMessage( CANCEL_ABORT );  // Should call sendSlpBye().
      // Can't send 0x04 timeout message because there is no session yet, and previous message is already ACKed.
      // NOTE: it seams Windows Live Messenger 8.0 doesn't send a BYE ACK for this message.
      return;
    }

    case P2P_WAIT_FOR_CONNECTION:
    {
      showEventMessage( i18n("The invitation was cancelled.  A timeout occured waiting for a connection to succeed or fail."), ChatMessage::CONTENT_APP_FAILED, ! isUserStartedApp() );
      endApplication();
      break;
    }

    case P2P_WAIT_FOR_CONNECTION2:
    {
      // Still keep waiting, DirectConnectionBase should trigger a timeout.
      kdWarning() << "P2PApplication:slotCleanup() - Time-out waiting to connect to the remote system "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=return)." << endl;
      return;
    }

    case P2P_WAIT_FOR_SLP_OK_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for SLP OK ACK "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_PREPARE:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for data-preparation message "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_PREPARE_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for data-preparation ACK "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_TRANSFER_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for SLP transfer OK ACK "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_FILE_DATA:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for file data "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;

      // The 0x80 mesage should be sent now.

      // Set "incoming" parameter to display messages like "the file you're sending.." in the future.
      showEventMessage( i18n("The invitation was cancelled.  A timeout occured waiting for data."), ChatMessage::CONTENT_APP_FAILED, ! isUserStartedApp() );
      sendCancelMessage( CANCEL_FAILED );  // Should call sendSlpBye().
      return;
    }

    case P2P_WAIT_FOR_DATA_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for file data received ack "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_HANDSHAKE:
    {
      // NOTE: we could start an initiateTransfer() here, but this is clearly a client bug.
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for DC handshake "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_HANDSHAKE_OK:
    {
      // NOTE: we could start an initiateTransfer() here, but this is clearly a client bug.
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for DC handshake response "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      break;
    }

    case P2P_WAIT_FOR_SLP_BYE:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for BYE message "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      // No message required, already got what we wanted
      endApplication();
      return;
    }

    case P2P_WAIT_FOR_SLP_BYE_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for BYE-ACK "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      // No message required, already got what we wanted
      endApplication();
      return;
    }

    case P2P_WAIT_FOR_SLP_ERR_ACK:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting for SLP Error-ACK "
                     "(contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=endapplication)." << endl;
      // No message required, already got what we wanted
      endApplication();
      return;
    }

    default:
    {
      kdWarning() << "P2PApplication::slotCleanup() - Timeout waiting "
                     "(state="   << waitingState_ <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " contact=" << getContactHandle() << ")" << endl;
    }
  }

  // Default implementation quits
  showEventMessage( i18n("The invitation was cancelled.  A timeout occured waiting for data."), ChatMessage::CONTENT_APP_FAILED, ! isUserStartedApp() );
  endApplication();
}



/**
 * @brief Called when the direct connection is authorized by one of the P2P applications.
 *
 * This initiates the transfer by calling initiateTransfer().
 */
05184 void P2PApplication::slotConnectionAuthorized()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::slotConnectionAuthorized() - Direct connection authorized, "
               "starting transfer (state=" << waitingState_ << ")." << endl;
#endif

  // Avoid posibility that this method is invoked again if
  // the direct connection is closed and re-connected later by another application.
  disconnect( applicationList_, 0, this, SLOT(slotConnectionAuthorized()));

  // Protect against situations which could break our internal state.
  if( ! fragmentTracker_.isEmpty() )
  {
    kdWarning() << "P2PApplication::slotConnectionAuthorized() - Direct connection authorized, "
                   "but data transfer was already started before. Won't notify derived classes "
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=return)." << endl;
    return;
  }

  // No longer wait for packets, got connection.
  waitingTimer_->stop();

  initiateTransfer();
}



/**
 * @brief Called when the direct connection is established.
 *
 * This sends the direct connection handshake with sendDirectConnectionHandshake(),
 * or waits for the contact to send one.
 */
05222 void P2PApplication::slotConnectionEstablished()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::slotConnectionEstablished() - Direct connection established." << endl;
#endif

  // Disconnect irrelevant signals.
  // Connection can still fail if the nonce is invalid.
  disconnect( applicationList_, 0, this, SLOT( slotConnectionEstablished() ));

  // Send the handshake if this is a new connection
  bool isClient = ( ! applicationList_->getDirectConnection()->isServer() );
  if( isClient )
  {
#ifdef KMESSTEST
    ASSERT( waitingState_ = P2P_WAIT_FOR_CONNECTION2 );
#endif

    // Send the handshake
    sendDirectConnectionHandshake();

    // Start the timeout timer again.
    waitingState_ = P2P_WAIT_FOR_HANDSHAKE_OK;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACK, true );

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::slotConnectionEstablished() - Waiting for direct connection handshake response..." << endl;
#endif
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::slotConnectionEstablished() - Waiting for contact to send connection handshake..." << endl;
#endif

    waitingState_ = P2P_WAIT_FOR_HANDSHAKE;
    waitingTimer_->start( P2PAPPLICATION_TIMEOUT_ACK, true );
  }
}



/**
 * @brief Called when a direct connection could not be made.
 *
 * It initiates the transfer with initiateTransfer().
 */
05269 void P2PApplication::slotConnectionFailed()
{
  // Make this a warning, so users can figure out stuff when running kmess from the console.
  kdWarning() << "P2PApplication::slotConnectionFailed() - A direct connection could not be made, "
                 "switching file transfer to slow indirect transfer "
                 "(state="   << waitingState_ <<
                 " contact=" << getContactHandle() <<
                 " session=" << sessionID_ <<
                 " class="   << className() <<
                 " action=continue)." << endl;

#ifdef KMESSTEST
  ASSERT( ! applicationList_->hasDirectConnection()   );
  ASSERT( ! applicationList_->hasPendingConnections() );
#endif

  // Disconnect all signals as they are not relevant anymore
  disconnect( applicationList_, 0, this, SLOT( slotConnectionAuthorized()  ));
  disconnect( applicationList_, 0, this, SLOT( slotConnectionEstablished() ));
  disconnect( applicationList_, 0, this, SLOT( slotConnectionFailed()      ));

  // Inform the user about the transfer progress
  showTransferMessage( i18n("Reverting to indirect file transfer (this could be slow).") );

  // start the transfer over the switchboard
  waitingTimer_->stop();
  initiateTransfer();
}



/**
 * @brief  Test if there are still unacked messages.
 * @param  sendError  Whether to send an error to the client back as well.
 */
05304 void P2PApplication::testUnAckedMessages( bool sendError )
{
  // Check if there are still unacked messages.
  if( ! outgoingMessages_.isEmpty() )
  {
    UnAckedMessage *unAcked = 0;
    uint unackedCount = outgoingMessages_.count();

    if( unackedCount == 1 )
    {
      unAcked = outgoingMessages_.at(0);

      // More simple debug statement for most used case.
      kdWarning() << "P2PApplication::testUnackedMessages() - "
                     "there is still " << unackedCount << " unacked message "
                     "(type="      << unAcked->messageType <<
                     " ackSid="    << unAcked->ackSessionID <<
                     " totalsize=" << unAcked->totalSize <<
                     " state="     << waitingState_ <<
                     " contact="   << getContactHandle() <<
                     " class="     << className() <<
                     " session="   << sessionID_ << ")" << endl;
    }
    else
    {
      kdWarning() << "P2PApplication::testUnackedMessages() - "
                     "there are still " << unackedCount << " unacked messages "
                     "(state="   << waitingState_ <<
                     " contact=" << getContactHandle() <<
                     " class="   << className() <<
                     " session=" << sessionID_ << ")" << endl;

      QPtrListIterator<UnAckedMessage> it( outgoingMessages_ );
      while( it.current() != 0 )
      {
        unAcked = it.current();
        kdWarning() << "Message "   << unAcked->messageID << " is unacked "
                      "(type="      << unAcked->messageType <<
                      " ackSid="    << unAcked->ackSessionID <<
                      " totalsize=" << unAcked->totalSize << ")." << endl;

        ++it;
      }
    }
  }
}



/**
 * @brief Called when the user aborted the application.
 *
 * Displays a message using getUserAbortMessage() and notifies the contact.
 * Unlike the base class, it won't terminate the application since the contact should return an ACK message first.
 *
 * This method may be overwritten to add additional event handling.
 */
05361 void P2PApplication::userAborted()
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "Application::userAborted() - user requests to abort the session. state=" << waitingState_ << endl;
#endif

  // Make sure a second message is not displayed here.
  if( isClosing() )
  {
    kdWarning() << "P2PApplication::userAborted: Attempted to close application twice "
                   "(state=" << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=endapplicationlater)" << endl;

    // Make sure the timer is started again.
    endApplicationLater();
    return;
  }
  else if( waitingState_ == P2P_WAIT_FOR_SLP_BYE )
  {
    // This is a problem when a client does not send a SLP BYE (as seen with amsn 0.97).
#ifdef KMESSDEBUG_APPLICATION
    kdDebug() << "Application::userAborted() - Ignoring request while waiting for SLP BYE." << endl;
#endif

    return;
  }

  bool isTransferComplete = fragmentTracker_.isComplete();
  if( isTransferComplete )
  {
    // Make sure the user won't get a "transfer complete" message
    // followed by a "contact aborted" message.
    //
    // Currently happens when a contact restarts a switchboard (aMsn 0.97 does).
    // The old switchboard is removed, and all applications abort.
    // TODO: The switchboard closeConnection() call assumes it's always user initiated.

    kdWarning() << "P2PApplication::userAborted() - not displaying message because data transfer is complete. "
                   "The switchboard was likely closed after receiving all data. "
                   "(state="   << waitingState_ <<
                   " contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=continue)" << endl;
  }
  else
  {
    // Display abort message
    showEventMessage( getUserAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, false );
  }

  // Send cancel message.
  // Prepares the application to abort.
  sendCancelMessage( CANCEL_ABORT );

  // Set the state to avoid crashes.
  aborting_ = true;
  setClosing( true );

  // Don't end the application, instead wait for contact's client to respond too
}



/**
 * @brief Called when the user rejected (declined) the application.
 *
 * Displays a message using getUserRejectMessage() and notifies the contact.
 * Unlike the base class, it won't terminate the application since the contact should return an ACK message first.
 */
05434 void P2PApplication::userRejected()
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "Application::userRejected() - user requests to reject the invitation." << endl;
#endif

  // Send abort messages
  showEventMessage( getUserRejectMessage(), ChatMessage::CONTENT_APP_CANCELED, false );
  sendCancelMessage( CANCEL_INVITATION );

  // Set the state to avoid crashes.
  aborting_ = true;
  setClosing( true );

  // Don't end the application, instead wait for contact's client to respond too
}



/**
 * @brief Utility function to write P2P data to a file, also deals with message offsets.
 *
 * When an error occurs, the required error message
 * will be sent back to the other client.
 *
 * While Windows Live Messenger does not allow messages to be out of order,
 * this method explicitly allows it because WLM itself seams to send messages out of order
 * in some cases when it's switching to the direct connection (likely error prone at slow systems).
 *
 * @param  message  The P2P message with a file part.
 * @param  file     The file to output the data to.
 * @returns  True if the data could be written to the file, false on error.
 */
05467 bool P2PApplication::writeP2PDataToFile( const P2PMessage &message, QIODevice *file )
{
  unsigned long offset = message.getDataOffset();

  // See if the offset should be changed,
  // and if so, update it.
  if( offset != file->at() && ! file->at( offset ) )
  {
#ifdef KMESSDEBUG_APPLICATION
    kdDebug() << "P2PApplication::writeP2PDataToFile() - data is received out of order, updating file offset." << endl;
#endif

    // File position was different and could not be changed..
    unsigned long fileSize = file->size();

    // See if the offset is too far ahead.. (e.g. don't make us allocate 1GB)
    if( offset > ( fileSize + 100000 ) ) // ~100 kB
    {
      kdWarning() << "P2PApplication::writeP2PDataToFile() - data offset is too far ahead of current end of file "
                     "(filepos=" << file->at() <<
                     " eofpos="  << file->size() <<
                     " offset="  << offset <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=send0x01)" << endl;
      sendP2PAck( P2PMessage::MSN_FLAG_NEGATIVE_ACK );
      sendCancelMessage( CANCEL_FAILED );   // sends slp bye
      return false;
    }
    else if( offset <= fileSize )
    {
      // at( offset ) should only fail if the offset is beyond the end of the file.
      // If this is not the case, moving within the file failed so abort.
      kdWarning() << "P2PApplication::writeP2PDataToFile() - file pointer could not be set to message offset "
                     "(filepos=" << file->at() <<
                     " eofpos="  << file->size() <<
                     " offset="  << offset <<
                     " contact=" << getContactHandle() <<
                     " session=" << sessionID_ <<
                     " class="   << className() <<
                     " action=sendcancel)" << endl;
      sendCancelMessage( CANCEL_FAILED );
      return false;
    }

    // Create padding to write at the end
    unsigned long remainingSize = offset - fileSize;
    QByteArray padding( 4096 );
    padding.fill(0x00);

    // Add padding at the end of the file.
    file->at( fileSize );
    while( remainingSize > 0 )
    {
      if( file->writeBlock( padding, QMIN( 4096, remainingSize ) ) <= 0 )
      {
        break;
      }
      remainingSize -= 4096;
    }

    // Make sure the file position is correct now.
    if( offset != file->at() )
    {
      sendCancelMessage( CANCEL_FAILED );
      return false;
    }
  }


  // Offsets are correct.
  // Write the data to the file,
  Q_LONG status = file->writeBlock( message.getData(), message.getDataSize() );
  if( status == -1 )
  {
    kdWarning() << "P2PApplication::writeP2PDataToFile() - Failed to write the data to the file "
                   "(contact=" << getContactHandle() <<
                   " session=" << sessionID_ <<
                   " class="   << className() <<
                   " action=sendcancel)" << endl;

    sendCancelMessage(CANCEL_FAILED);
    return false;
  }

  // Indicate success!
  return true;
}



#include "p2papplication.moc"

Generated by  Doxygen 1.6.0   Back to index