Logo Search packages:      
Sourcecode: kmess version File versions

p2papplication.cpp

/***************************************************************************
                          p2papplication.cpp -  description
                             -------------------
    begin                : Mon Nov 22 2004
    copyright            : (C) 2004 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "p2papplication.h"

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

#include <qregexp.h>
#include <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


/**
 * @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
 */
00053 P2PApplication::P2PApplication(ApplicationList *applicationList)
: Application( applicationList->getContactHandle() ),
  aborting_(false),
  applicationList_(applicationList),
  buffer_(0),
  dataSource_(0),
  dataType_(P2P_TYPE_NEGOTIATION),
  gotSlpMessage_(false),
  invitationCSeq_(0),
  invitationSessionID_(0),
  sessionID_(0),
  messageID_(0),
  messageOffset_(0),
  messageTotalSize_(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_.uniqueID     = 0;

  // Enable auto-deletion on ack queue properties.
  incomingMessages_.setAutoDelete(true);
  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.
 */
00096 P2PApplication::~P2PApplication()
{
  // Stop the timer
  waitingTimer_->stop();

  // Delete pointers
  delete waitingTimer_;
  delete buffer_;
}



/**
 * @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.
 */
00124 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 initiated a MSN6 feature KMess can't handle yet "
              << "(euf-guid=" << slpMimeBody.getValue("EUF-GUID") << " appid=" << slpMimeBody.getValue("AppID") << ")." << endl;

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


  // 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.
 */
00159 void P2PApplication::contactStarted4_ContactConfirmsPreparation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - contactStarted4_ContactConfirmsPreparation" << endl;
#endif
}



/**
 * @brief Private utility function to generate an Session and message/sequence ID.
 * @return A random value between 4 and 4294967295.
 */
00172 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.
 */
00183 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.
 */
00209 QString P2PApplication::getBranch() const
{
  return branch_;
}



/**
 * @brief Return the identifier of the SLP session.
 * @return The Call-ID value from the SLP INVITE message.
 */
00220 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>.
 */
00234 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.
 */
00249 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.
 */
00264 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.
 */
00278 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.
 */
00294 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.
 */
00310 P2PApplication::UnAckedMessage * P2PApplication::getUnAckedMessage(const P2PMessage &ackMessage) const
{
#ifdef KMESSTEST
  ASSERT( ackMessage.getFlags() != 0 && ackMessage.getDataSize() == 0 );
#endif

  unsigned long ackUniqueID  = ackMessage.getAckUniqueID();
  unsigned long ackMessageID = ackMessage.getAckMessageID();

  QPtrListIterator<UnAckedMessage> it(outgoingMessages_);
  while( it.current() != 0 )
  {
    UnAckedMessage *unAcked = it.current();
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Testing if P2P ACK matches"
              << "    (UniqueID "  << unAcked->uniqueID  << " == " << ackUniqueID  << " ?)"
              << " or (UniqueID "  << unAcked->uniqueID  << " == " << ackMessageID << " ?)"
              << " or (MessageID " << unAcked->messageID << " == " << ackMessageID << " ?)" << endl;
#endif

    if(  unAcked->uniqueID == ackUniqueID   // This works with MSN 6.2
    ||  (unAcked->uniqueID == ackMessageID && ackUniqueID == 0) )  // For type-8 error messages
    {
      // Found it, save reference
      return unAcked;
    }

    // Also attempt to match on the messageID alone if the UniqueID is not set.
    if( ackUniqueID == 0 && unAcked->messageID == ackMessageID )
    {
      // HACK: added for GAIM 1.5 (does not return UniqueID)
      // Calling ApplicationList displays a warning, so it does not appear twice.
      return unAcked;
    }

    ++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, this is ignored.
 * - ACK of a SLP OK message, invokes contactStarted3_ContactConfirmsAccept()
 * - ACK of a SLP tranfer OK message, invokes initiateTransfer() or waits for an other direct connection to complete.
 * - ACK of the data preparation message, invokes initiateTransfer()
 * - ACK of the sent file data, invokes showTransferComplete(). Waits for the SLP BYE or sends it.
 * - ACK of the SLP BYE message, invokes endApplication()
 * - ACK of a SLP decline message, invokes endApplication() or waits for the SLP BYE.
 * - ACK of a SLP error message, invokes endApplication  or waits for the SLP BYE.
 * - all other ACKs are not treated specially.
 *
 * @param  message      The ACK message.
 * @param  messageType  The type of the original message this ACK corresponds with.
 */
00373 void P2PApplication::gotAck(const P2PMessage & message, const P2PMessageType messageType)
{
  switch( messageType )
  {
    // Are we waiting for the contact to accept our invitation (e.g. send "200 OK")?
    case P2P_MSG_SESSION_INVITATION:

      // Don't change the waiting state yet
      waitingTimer_->start(60000, true); // 60 sec for user to accept/decline. WLM8 has no timeout here for file transfer.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Got an ACK message (INVITE ACK, still waiting for 200 OK)." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( waitingState_ == P2P_WAIT_CONTACT_ACCEPT );
#endif
      return;


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

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

      // 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: P2P message can't be handled, "
                    << "unexpected ACK response, waiting for 200 OK ACK (contact=" << getContactHandle() << ")." << 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);
        endApplication( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
        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: 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(30000, true); // 30 sec
      }

      return;


    case P2P_MSG_TRANSFER_OK:

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Got an ACK message (200 transfer OK ACK)." << endl;
#endif
      // 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 )
      {
        kdWarning() << "P2PApplication: Received unexpected ACK for 'transrespbody' message "
                    << "(state=" << waitingState_ << " contact=" << getContactHandle() << ")." << endl;
        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: 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: 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: Waiting state not changed, waiting for file data..." << endl;
#endif
        // For Files, the contact must initiate the data transfer now. (P2P_WAIT_DEFAULT)
        waitingState_ = P2P_WAIT_FOR_FILE_DATA;
        waitingTimer_->start(30000, true); // 30 sec
      }

      return;


    // Are we waiting for the ACK to confirm our data preparation message?
    case P2P_MSG_DATA_PREPARATION:
#ifdef KMESSTEST
      ASSERT( waitingState_ == P2P_WAIT_FOR_PREPARE_ACK );
      ASSERT( message.getAckDataSize() == 4 );
#endif

      waitingState_ = P2P_WAIT_DEFAULT;

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

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

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


      // Dispatch to derived implementation
      if( hasUnAckedMessage(P2P_MSG_TRANSFER_OK) )
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication: Got an ACK message (data-preparation ACK, still waiting for 200 transfer OK ACK)." << endl;
#endif
        waitingState_ = P2P_WAIT_FOR_TRANSFER_ACK;
      }
      else
      {
        initiateTransfer();
      }
      return;


    // Are we waiting for the ACK to confirm that all data was received?
    case P2P_MSG_DATA:
#ifdef KMESSTES
      ASSERT( waitingState_ == P2P_WAIT_FOR_DATA_ACK );
#endif
      waitingState_ = P2P_WAIT_DEFAULT;

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

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


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

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

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

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

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

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

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

      return;



    // Are we waiting for the BYE ACK to quit the session/application?
    case P2P_MSG_SESSION_BYE:
#ifdef KMESSTEST
      ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK );
#endif

      waitingState_ = P2P_WAIT_DEFAULT;

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

      // Session complete.
      endApplication();
      return;


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


    // Are we waiting for the SLP Error message to be ACK-ed?
    case P2P_MSG_SLP_ERROR:
#ifdef KMESSTEST
      ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_ERR_ACK );
#endif
      waitingState_ = P2P_WAIT_DEFAULT;

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

        // We wait for the BYE
        waitingState_ = P2P_WAIT_FOR_SLP_BYE;
        waitingTimer_->start(30000, true); // 30 sec
      }
      else
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication: Got an ACK message (Confirmed SLP Error, terminating)." << endl;
#endif
        // TODO: send BYE after the client confirmed our SLP error?
        endApplication();
      }

      return;


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


      // 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: Got an ACK message." << endl;
#endif
      }
      else if( waitingState_ != P2P_WAIT_DEFAULT || messageType != P2P_MSG_UNKNOWN )
      {
        // Unexpected, this should be a normal ACK message.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdWarning() << "P2PApplication: Unhandled state for ACK message:"
                    << " waitingState=" << waitingState_ << " messageType=" << messageType << "!" << 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(30000, true); // 30 sec
        return;
      }
      else
      {
        // Yes, this is truely a normal ack message
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication: Got an ACK message." << endl;
#endif
      }
  }
}



/**
 * @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.
 */
00686 void P2PApplication::gotDirectConnectionHandshake(const P2PMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got a direct connection handshake message." << endl;
#endif

  MsnDirectConnection *directConnection = applicationList_->getDirectConnection();

  // If the contact was sending a handshake when we dropped the connection,
  // it's possible this packet will be delivered over the switchboard.
  if( directConnection == 0 || ! directConnection->isConnected() )
  {
    // Ignore the packet
    return;
  }


  // 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_ )
  {
    // Mark connection as authorized
    directConnection->setAuthorized(true);

    // If we're the server, we still need to send a handshake back
    if(isServer)
    {
      // KMess is the server, it should authenticate the handshake message.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Contact sent correct nonce, sending direct connection handshake ACK." << endl;
#endif
#ifdef KMESSTEST
      ASSERT( waitingState_ == P2P_WAIT_FOR_HANDSHAKE );
#endif
      sendDirectConnectionHandshake();
      return;
    }
    else
    {
      // KMess is the client, it should verify the handshake response only.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Contact sent correct nonce, marked connection as authorized." << endl;
#endif

#ifdef KMESSTEST
      ASSERT( waitingState_ == P2P_WAIT_FOR_HANDSHAKE_OK );
#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: received nonce from contact, but it's running the server (contact=" << getContactHandle() << ")!" << endl;

        waitingState_ = P2P_WAIT_FOR_HANDSHAKE_OK;
        sendDirectConnectionHandshake();
      }
    }
  }
  else
  {
    // Invalid nonce! Drop connection
    kdWarning() << "P2PApplication: Contact sent invalid nonce (received " << nonce << " expected " << nonce_ << ")." << endl;
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: 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.
  }
}



/**
 * @brief Called when data is received.
 *
 * This method should be overwritten in the derived class
 * to store the data in a file, buffer, etc..
 *
 * @param  message  The P2P message containing the data.
 */
00774 void P2PApplication::gotData(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - gotData" << 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.
 */
00791 void P2PApplication::gotDataFragment(const P2PMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Received file data, calling gotData()." << endl;
#endif
#ifdef KMESSDEBUG
  ASSERT( waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  // TODO: do some verification on the size/offset fields??
  //       after all, the data can be sent over the public switchboard.

  uint dataMessageSize = message.getDataSize();
  uint dataOffset      = message.getDataOffset();


  // See if the class is trying to abort
  if( waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdWarning() << "P2PApplication::gotDataFragment: Still receiving data while BYE is sent, sending error back." << endl;
#endif
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    return;
  }


  // Dispatch the message to the derived class
  showTransferProgress( dataOffset + dataMessageSize );
  gotData(message);


  // If we received the last part, ACK it and send the BYE
  if( ! message.isLastFragment() )
  {
    // Wait for more data to arrive.
    waitingTimer_->start(30000, true); // 30 sec
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication::gotDataFragment: Got the last fragment, sending ACK." << endl;
#endif

    // All data received
    // TODO: For pictures, we can check whether this was really the picture data we expected.
    //       The file data needs to be hashed, and compared to the SHA1D field of the MsnObject.

    // Send the ACK
    sendP2PAck();

    // Signal derived class the transfer is complete
    showTransferComplete();

    // 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(30000, true); // 30 sec
    }
  }
}



/**
 * @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.
 */
00881 void P2PApplication::gotDataPreparation(const P2PMessage &/*message*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got the data preparation message" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( shouldSendAck_ );
  ASSERT( waitingState_ == P2P_WAIT_FOR_PREPARE );
#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_)
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Data preparation failed, sending reject" << endl;
#endif

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

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


  // 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.
    endApplication();
    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: Data preparation successful, sending ACK" << endl;
#endif

  sendP2PAck();


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

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



/**
 * @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.
 */
00959 void P2PApplication::gotErrorAck(const P2PMessage &/*message*/)
{
  // Error in our headers?
  kdWarning() << "P2PApplication: Got a P2P error-response (flag=error contact=" << getContactHandle() << ")." << endl;

  if(dataType_ == P2P_TYPE_NEGOTIATION)
  {
    // We likely made an error in the invitation/bye reponse
    contactAborted( /* defaults to getContactAbortMessage() */ );
  }
  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.
 */
00994 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
  kdDebug() << "P2PApplication: Message size=" << p2pMessage.getDataSize()
                           << " total="        << p2pMessage.getTotalSize()
                           << " offset="       << p2pMessage.getDataOffset() << endl;
#endif


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


  // Handle ACKs before everything else.
  if( p2pMessage.getDataSize() == 0 )  // HACK: for Bot2K3 4.1: no testing for flags
  {
    UnAckedMessage *unAcked    = getUnAckedMessage(p2pMessage);
    P2PMessageType messageType = P2P_MSG_UNKNOWN;
    if( unAcked == 0 )
    {
      if( ! p2pMessage.isConnectionHandshake() ) // exception, no original message set.
      {
        kdWarning() << "P2PApplication: Unable to handle ACK message, original message not found"
                    << " (flags=0x" << QString::number(p2pMessage.getFlags(), 16)
                    << " size=0 state=" << waitingState_ << " contact=" << getContactHandle() << ")." << endl;
        return;
      }
    }
    else
    {
      // Remove record, auto-deletes
      // Avoids that hasUnAckedMessage() returns the message we just received.
      messageType = unAcked->messageType;
      outgoingMessages_.remove(unAcked);
    }


    if( p2pMessage.isAck() )
    {
      gotAck( p2pMessage, messageType );
    }
    else if(p2pMessage.isConnectionHandshake())
    {
      gotDirectConnectionHandshake( p2pMessage );
    }
    else if(p2pMessage.isError())
    {
      gotErrorAck( p2pMessage );
    }
    else if(p2pMessage.isWaiting())
    {
      gotTimeoutAck( 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_.uniqueID    = p2pMessage.getAckMessageID();


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


      // Parse the fragment
      gotNegotiationFragment(p2pMessage);
    }
    else
    {
      kdWarning() << "P2PApplication: P2P message can't be handled, negotiation message type "
                  << "(flags=0x" << QString::number(p2pMessage.getFlags(), 16)
                  << " size="    << p2pMessage.getDataSize()
                  << " contact=" << getContactHandle() << ")." << endl;

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

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

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



/**
 * @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.
 */
01185 void P2PApplication::gotNegotiationFragment(const P2PMessage &p2pMessage)
{
  if(! p2pMessage.isLastFragment())
  {
    // Not everything received yet, buffer it.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Got an SLP fragment, buffering it." << endl;
#endif

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

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

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

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

      // Read the buffer
      bufferData = buffer_->readAll();
      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.
 */
01289 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.
    if( msgTo != "<msnmsgr:" + CurrentAccount::instance()->getHandle() + ">" )
    {
      if(shouldSendAck_) sendP2PAck();

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

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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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: P2P message can't be handled, unsuppored SLP negotiation message "
                << "(preamble=" << preamble << " contact=" << getContactHandle() << ")." << endl;

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



/**
 * @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: 112

IPv4InternalAddrsAndPorts: 192.168.0.101:1611
SessionID: 15751111\r\n
SChannelState: 0
Capabilities-Flags: 1
@endcode
 *
 * @param  slpMessage  The parsed SLP message.
 */
01403 void P2PApplication::gotSlpAck(const MimeMessage &/*slpMessage*/)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Got SLP ACK message." << endl;
#endif

  // This is some internal message of WLM8.
  // Accept it for now.
}



/**
 * @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.
 */
01440 void P2PApplication::gotSlpBye(const MimeMessage &/*slpMessage*/)
{
  // 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.


  if(shouldSendAck_)
  {
    sendP2PAck();
  }

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

  if( waitingState_ != P2P_WAIT_FOR_SLP_BYE && waitingState_ != P2P_WAIT_FOR_DATA_ACK )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: 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: send a 0x40 or 0x80 packet here.
    // When we started the invitation, send a 0x40 packet to indicate we closed our invite early.
    // When the contact started the invite, send a 0x80 packet to incide we closed their invite early.
  }
  else
  {
    // Normal end
    endApplication();
  }
}



/**
 * @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.
 */
01494 void P2PApplication::gotSlpInvite(const MimeMessage &slpMessage)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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: second invitation for picture transfer
        );
#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
    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: Received unexpected Content-Type "
                << "(type=" << invitationContentType_ << " contact=" << getContactHandle() << ")." << 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.
 */
01625 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: Received unexpected 200/OK message "
                << "(type=" << contentType << " contact=" << getContactHandle() << ")!" << endl;
    showMessage( i18n("The invitation was cancelled.  The contact sent bad data, or KMess doesn't support it.") );
    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: Waiting for contact to send some prepare message..." << endl;
#endif

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


/**
 * @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'.
 */
01706 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( messageID_ != 0 && isUserStartedApp() )
  {
    kdWarning() << "P2PApplication: Got an INVITE response, rejecting (contact=" << getContactHandle() << ")." << endl;
    sendCancelMessage( CANCEL_INVALID_SLP_CONTENT_TYPE );
    return;
  }

  // Tell the derived class we've got an invitation
  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>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).
 */
01739 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: Got SLP status message (" << preamble << ")" << endl;
#endif


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

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


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


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

  // Handle the status codes


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

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

    // Parse in separate function
    gotSlpOk( slpMessage );
    return;
  }


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

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


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

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

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


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

    // Contact Declined
    contactRejected();
    return;
  }


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

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

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



/**
 * @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".
 */
01919 void P2PApplication::gotSlpTransferInvitation(const MimeMessage &slpMimeBody)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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.
  if( sessionID_ == 0 )
  {
    kdWarning() << "P2PApplication: Received an invitation for a direct connection,"
                << " but no session is set up (maybe a msn7 feature, contact=" << getContactHandle() << ")." << endl;
  }


  // Abort if the connection already exists
  if(applicationList_->hasDirectConnection() )
  {
    kdWarning() << "P2PApplication: Received an invitation to start a direct connection, "
                << "but a direct connection is already available "
                << "(rejecting invitation, "
                << " authorized=" << applicationList_->getDirectConnection()->isAuthorized()
                << " contact=" << getContactHandle() << ")." << endl;

    // Decline the invitation forcefully with an error.
    // Other client should still continue with the file transfer
    // TODO: test the effect of this.
    sendCancelMessage(CANCEL_ABORT);
    return;
  }


  // 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: Received an invitation for a direct connection, but expecting BYE instead (file transfer just completed, contact=" << getContactHandle() << ")." << endl;
    sendCancelMessage( CANCEL_INVITATION );
    return;
  }


  // TODO: Quick fix for WLM8: don't allow direct connection invite for picture transfer
  if( isUserStartedApp() )
  {
    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.
  int listeningPort = applicationList_->addServerConnection();

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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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: Could not listen at local port, redirecting transfer to the switchboard." << 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: 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: 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: Waiting for contact to send file data..." << endl;
#endif
  }
  waitingTimer_->start(30000);  // 30 secs.
}



/**
 * @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. 
 */
02145 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_->getDirectConnection()->isAuthorized() )
    {
#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: Storing contact nonce value " << nonce_ << " to authenticate later." << endl;
#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

    QStringList ipAddresses;
    uint        port;

    // Connect to the internal IP address
    if( listenInternal )
    {
      // Create a direct connection for the internal IP-address(es).
      ipAddresses = QStringList::split( " ", slpMimeBody.getValue("IPv4Internal-Addrs") );
      port        = slpMimeBody.getValue("IPv4Internal-Port").toUInt();

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

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

    // Connect to the external IP address (unless a connection was made really quick)
    if( listenExternal && ! applicationList_->hasDirectConnection() )
    {
      // Create a direct connection for the external IP-address(es).
      ipAddresses  = QStringList::split( " ", slpMimeBody.getValue("IPv4External-Addrs") );
      port         = slpMimeBody.getValue("IPv4External-Port").toUInt();
      bool sameLan = ( listenInternal && ipAddresses.contains( CurrentAccount::instance()->getExternalIp() ) );

      // Add connection attempt for each external IP address
      for ( QStringList::Iterator it = ipAddresses.begin(); it != ipAddresses.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.
        if( applicationList_->addConnection( *it, port ) && ! sameLan )
        {
          showTransferMessage( i18n("Connecting to %1, port %2").arg(*it).arg(port) );
        }
      }
    }
  }


  // After passing the connections to addConnection() it's possible
  // some slots are already fired and the connection was established.

  // See if there aren't connections established already.
  if( ! applicationList_->hasDirectConnection() )
  {
    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(30000, true); // 30 sec
    }
    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.
 */
02322 void P2PApplication::gotTimeoutAck(const P2PMessage &/*message*/)
{
  // Error in our headers?
  if(isWaitingForUser())
  {
    // Contact aborted this session because we didn't press "accept/cancel" within 30 secs.
    contactAborted( i18n("The transfer failed.  Timeout while waiting for user to accept.") );
  }
  else
  {
    // Apparently the contact was waiting for something, likely some packet we had to send.
    kdDebug() << "P2PApplication: WARNING - Got a P2P error-response (flag=waiting"
              << " localstate=" << waitingState_ << " contact=" << getContactHandle() << ")." << endl;
    contactAborted( i18n("The transfer failed.  An internal error occured.") );
  }
}



/**
 * @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.
 */
02348 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.
 */
02361 bool P2PApplication::hasUnAckedMessage(const P2PMessageType messageType)
{
  QPtrListIterator<UnAckedMessage> it( outgoingMessages_ );
  while( it.current() != 0 )
  {
    if( it.current()->messageType == messageType )
    {
      return true;
    }
  }

  return false;
}



/**
 * @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.
 */
02383 void P2PApplication::initiateTransfer()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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() )
  {
    kdWarning() << "P2PApplication::initiateTransfer: timeout detection timer is still running "
                << "(state=" << waitingState_ << ")." << endl;
    waitingTimer_->stop();
  }


  // 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 The switchboard requested to resume the transfer.
 *
 * If there is an active data transfer, it's resumed by rescheduling the slotSendData() slot.
 */
02426 void P2PApplication::resumeTransfer()
{
  if( ! isTransferPaused() )
  {
    return;
  }

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Application is allowed to resume transfer." << endl;
#endif

  // Call parent, reset isTransferPaused()
  Application::resumeTransfer();

  if( dataSource_ != 0 && dataType_ != P2P_TYPE_NEGOTIATION)
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Data source was set, rescheduling transfer slot." << endl;
#endif

    QTimer::singleShot( 10, this, SLOT(slotSendData()) );
  }
}



/**
 * @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(),
 *     this sends an SLP <tt>500 Internal Error</tt> message, 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.
 */
02494 void P2PApplication::sendCancelMessage(const ApplicationCancelReason cancelReason)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication - sendCancelMessage (reason=" << cancelReason << ")." << 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() << ")." << endl;
    endApplication();
    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 "
                  << "(state=" << waitingState_ << " contact=" << getContactHandle() << ")" << endl;
      sendP2PMessage(0, P2PMessage::MSN_FLAG_WAITING, 0);
      shouldSendAck_ = false;
      endApplication();
      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
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdWarning() << "P2PApplication: unexpected cancelReason used: CANCEL_NOT_INSTALLED." << endl;
#endif
      }
      return;


    // User or application wants to abort
    case CANCEL_ABORT:
    case CANCEL_FAILED:  // for compatibility with the MimeApplication class.
      // If we're sending data, abort that
      if( dataSource_ != 0 && dataType_ != P2P_TYPE_NEGOTIATION )
      {
        dataSource_ = 0;
        dataType_   = P2P_TYPE_NEGOTIATION;
      }

      // Determine which error message to send back.
      if(gotSlpMessage_)
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication: Cancelling 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() )
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication: Cancelling an application we've started ourselves, sending BYE early." << endl;
#endif
        // Send bye early, contact should detect this.
        sendSlpBye();
      }
      else
      {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
        kdDebug() << "P2PApplication: 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:
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdWarning() << "P2PApplication: unknown cancelReason used." << endl;
#endif

      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.
 */
02636 void P2PApplication::sendData(QIODevice *dataSource, P2PDataType dataType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Begin of data transfer" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_ != 0 );
  ASSERT( dataSource != 0 );
#endif

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


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

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

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

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



/**
 * @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.
 */
02681 void P2PApplication::sendDataPreparation()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending data preparation message." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( sessionID_    != 0 );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  // 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(30000, true); // 30 sec

  // 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: 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.
 */
02723 void P2PApplication::sendDataPreparationAck()
{
  if(userShouldAcknowledge_)
  {
    // This value is checked again after the
    // userStarted3_UserPrepares() method returns.
    userShouldAcknowledge_ = false;
  }
  else
  {
#ifdef KMESSTEST
    kdWarning() << "P2PApplication: Call to sendDataPreparationAck() not expected!" << endl;
#endif
  }
}



/**
 * @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.
 */
02748 void P2PApplication::sendDirectConnectionHandshake()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending direct connection handshake (nonce=" << nonce_ << ")." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( applicationList_->hasDirectConnection() );
#endif
#ifdef KMESSTEST
  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);
  messageID_++;

  // Insert the bytes
  P2PMessage::insertBytes(header, 0,               0);   // 1: SessionID
  P2PMessage::insertBytes(header, messageID_,      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: copied nonce '" << nonce_ << "' to p2p message header as '" << hex << "'." << endl;
#endif

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

  if( waitingState_ != P2P_WAIT_FOR_HANDSHAKE_OK )
  {
    // Start the timeout timer again.
    waitingState_ = P2P_WAIT_FOR_HANDSHAKE_OK;
    waitingTimer_->start(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Waiting for direct connection handshake response..." << endl;
#endif
  }
}



/**
 * @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.
 */
02827 void P2PApplication::sendP2PAck(int ackType, UnAckedMessage *originalMessageData)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if(ackType == P2PMessage::MSN_FLAG_ACK)
  {
    if(messageID_ == 0)
    {
      kdDebug() << "P2PApplication: Sending P2P ACK (INVITE-ACK)" << endl;
    }
    else
    {
      kdDebug() << "P2PApplication: Sending P2P ACK" << endl;
    }
  }
  else if(ackType == P2PMessage::MSN_FLAG_ERROR)
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Error-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_WAITING)
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Timeout-response)" << endl;
  }
  else if(ackType == P2PMessage::MSN_FLAG_CLOSING_ACK)
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Closing-ACK)" << endl;
  }
  else
  {
    kdDebug() << "P2PApplication: Sending P2P ACK (Unknown-type)" << endl;
  }
#endif
#ifdef KMESSTEST
  ASSERT( shouldSendAck_ );
#endif

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

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

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


  // Insert the bytes
  P2PMessage::insertBytes(header, originalMessageData->sessionID,   0); // SessionID
  // Message ID was set earlier
  P2PMessage::insertBytes(header, originalMessageData->totalSize,  16); // Confirm total size set in previous message
  P2PMessage::insertBytes(header, ackType,                         28); // Set the ACK flag.
  P2PMessage::insertBytes(header, originalMessageData->messageID,  32); // Set the ACK message ID
  P2PMessage::insertBytes(header, originalMessageData->uniqueID,   36); // Set the ACK Unique ID field.
  P2PMessage::insertBytes(header, originalMessageData->dataSize,   40); // Set the ACK data size with the previous message size.


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


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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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:
 * - messageID_ is generated for every new P2P message.
 * - messageOffset_ is updated when a splitted message is sent.
 * - messageTotalSize_ and messageOffse_ are reset when the last message fragment is sent.
 * - outgoingMessages_ is updated to trace ACKs back later.
 *
 * The messageTotalSize_ 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.
 */
02949 void P2PApplication::sendP2PMessage(const QByteArray &messageData, int flagField, uint footerCode, P2PMessageType messageType)
{
#ifdef KMESSTEST
  ASSERT( messageData.size() <= 1202 );
#endif

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


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


  // Verify we're not configured to send a splitted message.
  if( messageTotalSize_ > 0
  &&  flagField != 0
  &&  flagField != P2PMessage::MSN_FLAG_DATA
  &&  flagField != P2PMessage::MSN_FLAG_FILETRANSFER )
  {
    kdWarning() << "P2PApplication: Attempt to send splitted ACK message "
                << "(flags="   << flagField
                << " state="   << waitingState_
                << " contact=" << getContactHandle() << ")!" << endl;
  }


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

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


  // Generate Unique ID for this message
  unsigned long uniqueID = 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, uniqueID,       32);   // 7: Field2 of previous for ACK-messages (base message ID)
//P2PMessage::insertBytes(header, 0,              36);   // 8: Field7 of previous for ACK-messages (unique ID)
//P2PMessage::insertBytes(header, 0,              40);   // 9: Field4 of previous for ACK-messages (size)


  // Update unacked message queue to handle incoming ACKs
  if( flagField == 0 || messageType != 0 )
  {
    if( offsetField == 0 )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Storing message fields in queue for ACK response." << endl;
#endif
      // First message, or single message, store new record for ACKs.
      QDateTime currentTime;
      UnAckedMessage *ackData = new UnAckedMessage;
      ackData->dataSize     = messageSize;
      ackData->messageID    = messageID_;
      ackData->messageType  = messageType;  // used later to handle certain ACKs.
      ackData->sentTime     = currentTime.toTime_t();
      ackData->sessionID    = sessionID_;
      ackData->totalSize    = totalSize;
      ackData->uniqueID     = uniqueID;
      outgoingMessages_.append(ackData);
    }
    else
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Updating message fields in queue for 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()->uniqueID = uniqueID;
          break;
        }
        ++it;
      }
    }
  }

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

    // If we transmitted all parts, reset the fields for the next normal message
    if(messageOffset_ >= messageTotalSize_)
    {
      messageOffset_    = 0;
      messageTotalSize_ = 0;
    }
  }


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


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Message transmitted "
            << "(SID="         << sessionID_
            << " MID="         << messageID_
            << " UID="         << uniqueID
            << " flags=0x"     << QString::number(flagField, 16)
            << " size="        << messageSize
            << " type="        << messageType
            << ")" << endl;
#endif
}



/**
 * @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.
 */
03115 void P2PApplication::sendSlpBye()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending SLP BYE message." << endl;
#endif
#ifdef KMESSTEST
  // Only send a BYE if we started it
  ASSERT( isUserStartedApp() );
#endif

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

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


  // 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: 3\r\n"
               "\r\n";

  // 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(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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.
 */
03175 void P2PApplication::sendSlpError(const QString &statusLine,
                                  const uint sessionID, const QString &messageContentType, P2PMessageType messageType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Sending SLP error response: " << statusLine << endl;
#endif

#ifdef KMESSTEST
  // Test, as other invocations likely result in undefined client behavour
  // (
  ASSERT( ! shouldSendAck_ );
  ASSERT(   gotSlpMessage_ );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
  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() )
  {
    kdWarning() << "P2PApplication: attempting to send SLP error back, but Call-ID or Branch is unknown! "
                << "Sending 0x08 error instead (contact=" << getContactHandle() << ")" << endl;
    sendP2PAck(P2PMessage::MSN_FLAG_ERROR);
    return;
  }

  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(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: 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.
 */
03267 void P2PApplication::sendSlpInvitation(const MimeMessage &message, const QString &contentType)
{
#ifdef KMESSTEST
  ASSERT(! contentType.isEmpty()    );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );
#endif

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

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

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

  // 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
  messageOffset_    = 0;
  messageTotalSize_ = 0;

  sendSlpMessage(slpMessage, P2P_MSG_SESSION_INVITATION);

  // 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_CONTACT_ACCEPT;
  waitingTimer_->start(30000, true); // 30 sec (MSN6 also has a 30 sec limit for this)

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



/**
 * @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.
 */
03342 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
  messageOffset_      = 0;
  messageTotalSize_   = utf8Message.size();
  uint remainingBytes = messageTotalSize_;

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

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

#ifdef KMESSTEST
    ASSERT( remainingBytes == messageTotalSize_ - messageOffset_ );
#endif

    // Release data
    messagePart.resetRawData( dataPointer, dataSize );
  }

  // Reset for normal operations
  messageOffset_    = 0;
  messageTotalSize_ = 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.
 */
03403 void P2PApplication::sendSlpOkMessage(const MimeMessage &message)
{
#ifdef KMESSTEST
  ASSERT(! branch_.isEmpty()                );
  ASSERT(! invitationContentType_.isEmpty() );
  ASSERT( waitingState_ == P2P_WAIT_DEFAULT );  // TODO: WLM8 invitations before data preparation
#endif

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

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

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

  // Send the SLP OK message to tell the other client you'd like to start the transfer.
  QString slpMessage;
  slpMessage = "MSNSLP/1.0 200 OK\r\n"
               "To: <msnmsgr:" + 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(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: 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(30000, true); // 30 sec

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: 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.
 */
03491 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");

  // 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.
 */
03522 void P2PApplication::sendSlpTransferInvitation()
{
  
  // Check if there is already an connection
  if( applicationList_->hasDirectConnection() )
  {
#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 a connection to succeed or fail." << endl;
#endif

    showTransferMessage( i18n("File transfer dialog message", "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" );
  waitingState_ = P2P_WAIT_TRANSFER_ACCEPT;

  // 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("File transfer dialog message", "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>.
 */
03607 void P2PApplication::slotCleanup()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication::slotCleanup: timeout fired, cleaning up "
            << "(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
    }
    else
    {
      // Tell the other client we were waiting for a message to receive!
      sendP2PMessage(0, P2PMessage::MSN_FLAG_WAITING, 0);
    }
  }

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

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

      // NOTE: WLM8 has no timeout abort P2P (file transfer) invitations.
      // It keeps the invitation open even if the switchboard closes.
      showMessage( i18n("The invitation was cancelled.  A timeout occured waiting for the contact to accept.") );
      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:
    {
      endApplication( i18n("The invitation was cancelled.  A timeout occured waiting for a connection to succeed or fail.") );
      break;
    }

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

    case P2P_WAIT_FOR_SLP_OK_ACK:
    {
      kdWarning() << "P2PApplication: Timeout waiting for SLP OK ACK (contact=" << getContactHandle() << ")." << endl;
      break;
    }

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

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

    case P2P_WAIT_FOR_TRANSFER_ACK:
    {
      kdWarning() << "P2PApplication: Timeout waiting for SLP transfer OK ACK (contact=" << getContactHandle() << ")." << endl;
      break;
    }

    case P2P_WAIT_FOR_FILE_DATA:
    {
      kdWarning() << "P2PApplication: Timeout waiting for file data transfer (contact=" << getContactHandle() << ")." << endl;
      break;
    }

    case P2P_WAIT_FOR_DATA_ACK:
    {
      kdWarning() << "P2PApplication: Timeout waiting for file data received ack (contact=" << getContactHandle() << ")." << endl;
      break;
    }

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

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

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

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

    default:
    {
      kdWarning() << "P2PApplication: Timeout waiting (state=" << waitingState_ << " contact=" << getContactHandle() << ")" << endl;
    }
  }

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



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

  // 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.
 */
03789 void P2PApplication::slotConnectionEstablished()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: Direct connection established." << endl;
#endif

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

  // Send the handshake if this is a new connection
  bool isClient = ( ! applicationList_->getDirectConnection()->isServer() );
  if( isClient )
  {
    sendDirectConnectionHandshake();
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kdDebug() << "P2PApplication: Waiting for contact to send connection handshake..." << endl;
#endif
    waitingState_ = P2P_WAIT_FOR_HANDSHAKE;
    waitingTimer_->start(30000, true); // 30 sec
  }
}



/**
 * @brief Called when a direct connection could not be made.
 *
 * It initiates the transfer with initiateTransfer().
 */
03822 void P2PApplication::slotConnectionFailed()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kdDebug() << "P2PApplication: The direct connection could not be made, transfer continues over the switchboard." << endl;
#endif
#ifdef KMESSTEST
  ASSERT( ! applicationList_->hasDirectConnection()   );
  ASSERT( ! applicationList_->hasPendingConnections() );
#endif

  // Disconnect all signals as they are not relevant anymore
  disconnect( this, SLOT( slotConnectionAuthorized()  ));
  disconnect( this, SLOT( slotConnectionEstablished() ));
  disconnect( 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 A slot to send file data periodically.
 *
 * It transfers the data provided to sendData().
 * The GUI won't be blocked because this slot is called from a timer event.
 * The timer will be stopped when the switchboard requests to pause the transfer.
 * A call to resumeTransfer() reschedules this slot again to resume the transfer.
 */
03855 void P2PApplication::slotSendData()
{
  // KMess would crash otherwise...
  if( dataSource_ == 0 )
  {
    kdDebug() << "P2PApplication: WARNING - Couldn't send more data, data source is null." << endl;

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

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

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

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

  // Dispatch the progress to the derived class
  // Note that the messageOffset_ field is reset by sendP2PMessage().
  showTransferProgress( messageOffset_ + bytesRead );

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

  // Finished?
  if(messageTotalSize_ > 0)
  {
    bool hasDirectConnection = applicationList_->hasDirectConnection();

    // Not finished
    if( ! hasDirectConnection && isTransferPaused() )
    {
      // The pause functionality is used when the switchboard has way too much messages unacked.
      // If we continue to queue messages, it will start to consume all memory with the file contents.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kdDebug() << "P2PApplication: Switchboard requested to pause the transfer." << endl;
#endif
    }
    else
    {
      // Run this slot again, transfer rate depends whether we're using a switchboard or not.
      // The MsnSwitchboardConnection/ChatMaster class queue messages when there are too many unacked ones.
      // This avoids flooding the client memory. The switchboard server won't accept more messages anyway.
      int timeout = ( hasDirectConnection ? 10 : 100 );   // 100 ms: 1200 bytes * (1000/50) = 14kb/s max.
      QTimer::singleShot( timeout, this, SLOT(slotSendData()) );
    }
  }
  else
  {
    // Transfer finished!
#ifdef KMESSTEST
    ASSERT( dataSource_->atEnd() );
#endif

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

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

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



/**
 * @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.
 */
03954 void P2PApplication::userAborted()
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "Application::userAborted" << endl;
#endif

  aborting_ = true;
  setClosing(true);
  showMessage( getUserAbortMessage() );
  sendCancelMessage( CANCEL_ABORT );

  // 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.
 */
03976 void P2PApplication::userRejected()
{
#ifdef KMESSDEBUG_APPLICATION
  kdDebug() << "Application::userRejected" << endl;
#endif

  aborting_ = true;
  setClosing(true);
  showMessage( getUserRejectMessage() );
  sendCancelMessage( CANCEL_INVITATION );

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


#include "p2papplication.moc"

Generated by  Doxygen 1.6.0   Back to index