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

p2papplicationbase.cpp
/***************************************************************************
                          p2papplicationbase.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 "../../kmessdebug.h"
#include "../../currentaccount.h"
#include "../../contact/msnobject.h"
#include "../../utils/kmessshared.h"
#include "../extra/msndirectconnection.h"
#include "../mimemessage.h"
#include "../p2pmessage.h"
#include "applicationlist.h"

#include <QBuffer>
#include <QDateTime>
#include <QIODevice>
#include <QTimer>

#include <KLocale>


#ifdef KMESSDEBUG_P2PAPPLICATION
#define KMESSDEBUG_P2PAPPLICATION_GENERAL
#endif

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

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



/**
 * @brief Constructor, initializes the P2PApplicationBase 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
 */
00064 P2PApplicationBase::P2PApplicationBase(ApplicationList *applicationList)
: Application( applicationList->getContactHandle() ),
  applicationList_(applicationList),
  buffer_(0),
  dataSource_(0),
  dataType_(P2P_TYPE_NEGOTIATION),
  fragmentMessageID_(0),
  fragmentOffset_(0),
  fragmentTotalSize_(0),
  nextMessageID_(0),
  shouldSendAck_(false),
  userAborted_(false),
  waitingState_(P2P_WAIT_DEFAULT)
{
  // For debugging
  setObjectName( QLatin1String( metaObject()->className() ) + "[0/" + getContactHandle() + "]" );

  // Reset other internal fields
  lastIncomingMessage_.dataSize     = 0;
  lastIncomingMessage_.messageID    = 0;
  lastIncomingMessage_.messageType  = P2P_MSG_UNKNOWN;
  lastIncomingMessage_.sentTime     = 0;
  lastIncomingMessage_.sessionID    = 0;
  lastIncomingMessage_.totalSize    = 0;
  lastIncomingMessage_.ackSessionID = 0;
  lastIncomingMessage_.footerCode   = 0;

  // Initiaize the timer
  waitingTimer_ = new QTimer(this);
  waitingTimer_->setSingleShot( true );
  connect( waitingTimer_, SIGNAL(timeout()), this, SLOT(slotCleanup()) );

  // Crash prevention.
  connect( applicationList_, SIGNAL(destroyed()), this, SLOT(slotApplicationListDeleted()) );
}



/**
 * @brief Class destructor.
 *
 * Cleans up buffers and timers.
 */
00107 P2PApplicationBase::~P2PApplicationBase()
{
#ifdef KMESSDEBUG_APPLICATION
  // Can't include getSessionID() here, is virtual method of destructed derived class.
  kmDebug() << " closing=" << isClosing() <<
              " timer=" << waitingTimer_->isActive() << 
              " state=" << waitingState_ <<
              " unackedCount=" << outgoingMessages_.count() << ".";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( outgoingMessages_.isEmpty() );
#endif

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

  // Delete pointers
  qDeleteAll( outgoingMessages_ );
  delete waitingTimer_;
  delete buffer_;
}



/**
 * @brief  Make sure no more data will be sent.
 *
 * Note this method does nothing special,
 * except for closing down the data source.
 * Typically you want to call a high-level method
 * instead which calls this method internally.
 */
00141 void P2PApplicationBase::abortDataSending()
{
  // Abort sending.
  if( dataSource_ != 0 )
  {
#ifdef KMESSDEBUG_APPLICATION
    kmDebug() << "application was still sending data, resetting.";
#endif

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



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

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

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

  // Set timeout to destroy
  setWaitingState( P2P_WAIT_END_APPLICATION, P2PAPPLICATION_TIMEOUT_DESTROY );

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



/**
 * @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.
 */
00196 unsigned long P2PApplicationBase::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.
 */
00210 const QString & P2PApplication::getNonce() const
{
  return nonce_;
}



/**
 * @brief Return the number of transferred bytes.
 */
00220 unsigned long P2PApplicationBase::getTransferredBytes() const
{
  return dataType_ != P2P_TYPE_NEGOTIATION
       ? fragmentOffset_                          // outgoing
       : fragmentTracker_.getTransferredBytes();  // incoming
}



/**
 * @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.
 */
00238 P2PApplicationBase::UnAckedMessage * P2PApplicationBase::getUnAckedMessage(const P2PMessage &ackMessage) const
{
#ifdef KMESSTEST
  KMESS_ASSERT( ackMessage.getFlags() != 0 && ackMessage.getDataSize() == 0 );
#endif

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

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


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

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

      // Found it, save reference
      return unAcked;
    }
  }

  return 0;
}



/**
 * @brief Find the unacked message which corresponds with given message type.
 *
 * @param  messageType  The P2P message type.
 * @return The message unacked message details.
 */
00336 P2PApplicationBase::UnAckedMessage * P2PApplicationBase::getUnAckedMessage(const P2PMessageType messageType) const
{
  foreach( UnAckedMessage *unAcked, outgoingMessages_ )
  {
    if( unAcked->messageType == messageType )
    {
      return unAcked;
    }
  }

  return 0;
}



/**
 * @brief Return the current waiting state.
 *
 * This function is intended to be used for debugging.
 * Use isWaitingState() to test for the current state.
 *
 * @return The current waiting state
 */
00359 P2PApplicationBase::P2PWaitingState P2PApplicationBase::getWaitingState() const
{
  return waitingState_;
}



/**
 * @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.
 */
00374 void P2PApplicationBase::gotDataFragment(const P2PMessage &message)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "Received file data, calling gotData().";
#endif
#ifdef KMESSDEBUG
  KMESS_ASSERT( waitingState_ == P2P_WAIT_FOR_FILE_DATA );
#endif

  unsigned long dataOffset = message.getDataOffset();

  // See if the class is trying to abort.
  // Avoid bothering the derived class with these issues.
  if( waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK )
  {
    kmWarning() << "Still receiving data while BYE is sent "
                  "(contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=send0x08).";
    sendP2PAbort();
    return;
  }
  else if( waitingState_ == P2P_WAIT_FOR_SLP_BYE )
  {
    // This actually happened when there were some delayed packets in the buffer with WLM.
    // When switching bridges (SB/DC) it actually cannot guarantee the messages will be sent in order.
    // This is visible at slow computers.
    kmWarning() << "Still receiving data while last fragment was received "
                  "(contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=send0x08).";
    sendP2PAbort();
    return;
  }
  else if( waitingState_ == P2P_WAIT_END_APPLICATION )
  {
    // This happend when the data preparation was rejected,
    // and the contact kept sending data.
    kmWarning() << "Still receiving data while the application wants to close "
                  "(contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=send0x08).";
    sendP2PAbort();
    return;
  }


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


  // Initialize at the first message
  if( dataOffset == 0 )
  {
    fragmentTracker_.initialize( message.getMessageID(), message.getTotalSize() );
  }
  else
  {
    // Check if the new data fragment belongs to the current one.
    if( ! fragmentTracker_.isInitialized( message.getMessageID() ) )
    {
      // Send negative ack message, like WLM does (0x01)
      // TODO: provide last failed position, allow contact to resume
      kmWarning() << "Received unexpected offset for message "
                    "(offset="    << dataOffset <<
                    " messageID=" << message.getMessageID() <<
                    " contact="   << getContactHandle() <<
                    " session="   << getSessionID() <<
                    " class="     << metaObject()->className() <<
                    " action=send0x01).";
      sendP2PAckImpl( P2PMessage::MSN_FLAG_NEGATIVE_ACK );
      sendCancelMessage( CANCEL_FAILED );   // sends slp bye
      return;
    }
  }


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

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


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

    if( message.isLastFragment() )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kmDebug() << "Got the last fragment, "
                  "but some messages are not received yet.";
      kmDebug() << "Current message is " << fragmentTracker_.getDebugMap();
#endif
      shouldSendAck_ = false;   // could be true if received out of order.
    }
  }
  else
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "Got the last fragment. Sending ack, calling gotDataComplete() and showTransferComplete().";
#endif

    // After all data is received, ACK it and send the BYE
    // TODO: if the messages were received out of order, should we ACK the final message instead?
    shouldSendAck_ = true;   // could be false if received out of order.
    sendP2PAckImpl();

    // Notify the derived class, so it can process it's buffered data.
    // The P2PApplication class also uses this to send the SLP BYE if needed.
    setWaitingState( P2P_WAIT_DEFAULT, 0 );
    gotDataComplete( message );

    // For datacast applications (ink), request termination immediately.
    if( getMode() == APP_MODE_DATACAST )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kmDebug() << "Application is a datacast handler, request termination immediately.";
#endif
      // FIXME we must decide if it's better implement in derived ink class the method
      // gotDataComplete called few lines above or keep showTransferComplete(). Also we check 
      // if it's possible to move this code fragment ( from if statement ) over the "sendP2PAckImp()"
      // line and figure out if the send Ack is necessary also for INK...
      showTransferComplete();
      endApplication();
      return;
    }

    // Warn if developers mess up with gotDataComplete()
    // e.g. forget to call the parent P2PApplication::gotDataComplete() method to send the BYE.
    if( waitingState_ == P2P_WAIT_DEFAULT )
    {
      kmWarning() << "The overwritten gotDataComplete() method did not send anything, nor changed the waiting state, "
                    "(contact=" << getContactHandle() <<
                    " session=" << getSessionID() <<
                    " class="   << metaObject()->className() <<
                    ").";
    }

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

    // Next, either the derived class:
    // - terminated this object, or
    // - sent the SLP BYE, or
    // - waits for the SLP BYE from the contact.
  }
}



/**
 * @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.
 */
00550 void P2PApplicationBase::gotErrorAck(const P2PMessage &message)
{
#ifdef KMESSTEST
  KMESS_ASSERT( message.getDataSize()     == 0 );

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

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

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

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

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

    // TODO: resume transfer at the indicated byte position.
    abortDataSending();

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "Waiting for contact to send a BYE message.";
#endif

    // Wait for BYE at the moment.
    setWaitingState( P2P_WAIT_FOR_SLP_BYE, P2PAPPLICATION_TIMEOUT_SLP );
  }
  else
  {
    // Error in our headers?
    kmWarning() << "Got an unexpected P2P error-response "
                  "(flag=0x"  << QString::number( message.getFlags(), 16 ) <<
                  " state="   << waitingState_ <<
                  " contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=contactaborted).";


    if( dataType_ == P2P_TYPE_NEGOTIATION && ! P2P_WAIT_FOR_DATA_ACK )
    {
      // We likely made an error in the invitation/bye reponse
      contactAborted( i18n("The contact rejected the invitation. An internal error occured.") );
    }
    else
    {
      // TODO: make this error custom.
      // We were sending or receiving a file/picture
      // Can also happen if the contact can't handle our data for a different reason.
      contactAborted( i18n("The transfer failed.") );
    }
  }
}



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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if( getMode() == APP_MODE_DATACAST )
  {
    // Special case, abuse of P2P system
    kmDebug() << "P2P message from " << getContactHandle() << " is handled by a datacast session.";
  }
  else if( nextMessageID_ == 0 )
  {
    // No messages sent, new application
    kmDebug() << "P2P message from " << getContactHandle() << " is handled by a newly created session.";
  }
  else
  {
    // s/found/received/ since ApplicationList::gotMessage() finds it, but this makes it easier to filter the logs.
    kmDebug() << "New P2P message from " << getContactHandle() << " is handled by session " << getSessionID() << ".";
  }
#endif

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

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


  // If this object was initialized to handle a Bad packet.
  if( getMode() == APP_MODE_ERROR_HANDLER )
  {
    // Make an exception for SLP packets
    bool isSlpMessage = ( p2pMessage.isSlpData() &&
                          p2pMessage.getDataSize() > 20  );   // for cvs produced data-preparation messages without a session-id
    if(! isSlpMessage)
    {
      // We received an unknown control message, this object was initialized to send the correct response.
      // Inform the user
      showEventMessage( i18n("The invitation was cancelled. The contact sent bad data, or KMess does not support it."), ChatMessage::CONTENT_APP_CANCELED, ! isUserStartedApp() );

      // Abort
      sendP2PAbort();
      endApplication();
      return;
    }
  }


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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kmDebug() << "Removed ack slot for message "
               << "(sid="     << unAcked->sessionID
               << " mid="     << unAcked->messageID
               << " ackSid="  << unAcked->ackSessionID
               << " size="    << unAcked->dataSize
               << " type="    << unAcked->messageType
               << ")";
#endif

      delete unAcked;
    }


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

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

    return;
  }


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

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


  // Special cases (abuse of the P2P system)
  if( getMode() == APP_MODE_DATACAST )
  {
    // Ink data, etc..
    gotDataFragment(p2pMessage);
    return;
  }


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

      // Don't know what to do with a negotiation message with flags set.
      sendP2PAbort();

      // Abort the application
      showEventMessage( i18n("The transfer failed. The contact sent bad data, or KMess does not support it."), ChatMessage::CONTENT_APP_CANCELED, true );
      endApplicationLater();
      return;
    }
  }
  // Session ID not zero? -> clients exchange data in the session
  else
  {
    // Check for data preparation messages
    if( p2pMessage.isDataPreparation() )
    {
      shouldSendAck_ = true;
      lastIncomingMessage_.messageType = P2P_MSG_DATA_PREPARATION;
      gotDataPreparation(p2pMessage);
    }
    else if( waitingState_ != P2P_WAIT_FOR_FILE_DATA
         &&  p2pMessage.isData()
         &&  p2pMessage.getDataSize()  == 4
         &&  p2pMessage.getTotalSize() == 4 )
    {
      // HACK: added for Encarta Instant Answers (has data flag set with preparation message).
      kmWarning() << "Expecting data-preparation message, "
                    "got message of 4 bytes with incorrect flags "
                    "(assuming data preparation,"
                    " contact=" << getContactHandle() <<
                    " session=" << getSessionID() <<
                    " class="   << metaObject()->className() << ")!";
      shouldSendAck_ = true;
      lastIncomingMessage_.messageType = P2P_MSG_DATA_PREPARATION;
      gotDataPreparation(p2pMessage);
    }
    // Check for normal data messages
    else if( p2pMessage.isData() )
    {
      lastIncomingMessage_.messageType = P2P_MSG_DATA;
      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).
      kmWarning() << "Expecting data message, "
                    "got fragmented message with no flags set "
                    "(assuming file data,"
                    " contact=" << getContactHandle() <<
                    " session=" << getSessionID() <<
                    " class="   << metaObject()->className() << ")!";
      lastIncomingMessage_.messageType = P2P_MSG_DATA;
      gotDataFragment(p2pMessage);
    }
    else if( p2pMessage.getFlags() == 0
         &&  p2pMessage.getDataSize() >= 18
         &&  ( p2pMessage.getData()[0] == '\x80'  // start of each webcam data message
               || fragmentTracker_.getMessageID() == p2pMessage.getMessageID() )  // continuation of setup message (<receiver>))    TODO: add native support for "data streams" in this base class.
           )
    {
      // Webcam setup. Even WLM sends this without flags.
      // At the switchboard the "footercode" is set to 4.
      // Pass it to the WebcamTransferP2P class to deal with it.
      lastIncomingMessage_.messageType = P2P_MSG_WEBCAM_SETUP;
      lastIncomingMessage_.footerCode  = 4;   // FIXME: direct assumption.
      gotDataFragment( p2pMessage );
    }
    else
    {
      // Unknown p2p message
      kmWarning() << "P2P message can't be handled, "
                    "unknown data message type "
                    "(flags=0x" << QString::number(p2pMessage.getFlags(), 16) <<
                    " size="    << p2pMessage.getDataSize() <<
                    " contact=" << getContactHandle() <<
                    " session=" << getSessionID() <<
                    " class="   << metaObject()->className() <<
                    " action=send0x08).";

      // Got an P2P binary message we can't handle. (some new kind of flag..?)
      sendP2PAbort();

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

#ifdef KMESSTEST
  // At the end of this method, we should have sent the ACK..!
  KMESS_ASSERT( isP2PAckSent() );
#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.
 */
00957 void P2PApplicationBase::gotNegotiationFragment(const P2PMessage &p2pMessage)
{
  if(! p2pMessage.isLastFragment())
  {
    // Not everything received yet, buffer it.
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "Got an SLP fragment, buffering it.";
#endif

    if(buffer_ == 0)
    {
      buffer_ = new QBuffer();
      buffer_->open(QIODevice::ReadWrite);
    }

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

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

      // Append the last MSNSLP fraqment and call the method.
      buffer_->write( 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(), (int)p2pMessage.getDataSize() );
    }

    // Remove the first preamble line, because it's not in MIME format.
    // Parse the remaining as MIME fields.
    int      preambleEnd  = slpData.indexOf("\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 a P2P message with timeout-flag was received.
 *
 * This method aborts the application with contactAborted().
 *
 * @param  message  The received P2P message.
 */
01024 void P2PApplicationBase::gotTimeoutAck(const P2PMessage &message)
{
  // Error in our headers?
  if(isWaitingForUser())
  {
    // Contact aborted this session because we didn't press "accept/cancel" within 120 secs.
    contactAborted( i18n("The transfer failed. Timeout while waiting for user to accept.") );
  }
  else
  {
    // Apparently the contact was waiting for something, likely some packet we had to send.
    kmWarning() << "Got an unexpected P2P timeout-error response "
                  "(flag="    << QString::number( message.getFlags(), 16 ) <<
                  " state="   << waitingState_ <<
                  " contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=contactaborted).";

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



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

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

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

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



/**
 * @brief  Returns whether the application can handle the given ACK message.
 *
 * It checks whether an unacked message exists for the given ACK message.
 *
 * @return Whether the application can handle the ACK message.
 */
01106 bool P2PApplicationBase::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.
 */
01119 bool P2PApplicationBase::hasUnAckedMessage(const P2PMessageType messageType)
{
  return (getUnAckedMessage(messageType) != 0);
}


/**
 * @brief Test whether the first message has been sent.
 */
01128 bool P2PApplicationBase::isFirstMessageSent() const
{
  return nextMessageID_ != 0;
}


/**
 * @brief Test whether a P2P ack was sent for the current message.
 * @return  Whether a P2P ack was sent or the current message.
 */
01138 bool P2PApplicationBase::isP2PAckSent() const
{
  return ! shouldSendAck_;
}


/**
 * @brief Test whether the transfer is active.
 * @return  Whether the transfer is active.
 */
01148 bool P2PApplicationBase::isTransferActive() const
{
  return dataType_ != P2P_TYPE_NEGOTIATION  // sending data
      || ! fragmentTracker_.isEmpty();      // receiving data
}



/**
 * @brief Verify whether the transfer was initiated and was completed.
 * @return  Whether the transfer is complete.
 * @todo  This only works for receiving data at the moment.
 */
01161 bool P2PApplicationBase::isTransferComplete() const
{
  return fragmentTracker_.isComplete();  // TODO: sending data.
}



/**
 * @brief Test whether a given waiting state is set.
 * @param  waitingState  The state to test for.
 * @return  Whether the waiting state is currently set.
 */
01173 bool P2PApplicationBase::isWaitingState( P2PWaitingState waitingState ) const
{
  // TODO: later perhaps make it a flag instead (to wait for both the DC response and data preparation).
  return waitingState_ == waitingState;
}



/**
 * @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.
 */
01190 void P2PApplicationBase::sendData(QIODevice *dataSource, P2PDataType dataType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "Begin of data transfer";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( getSessionID() != 0 );
  KMESS_ASSERT( dataSource != 0 );
  KMESS_ASSERT( nextMessageID_ != 0 );
#endif

  // Make sure we don't activate a slot without a data source.
  if(dataSource == 0)
  {
    kmWarning() << "Couldn't send data, data source is null "
                  "(contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=send500).";

    showEventMessage( i18n("The transfer failed. Could not open data source."), ChatMessage::CONTENT_APP_FAILED, false );

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

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

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

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

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



/**
 * @brief Send the data preparation message.
 *
 * This is a consists of a P2P message with <tt>0x00000000</tt> as message body.
 * The contact should return with a data-preparation ACK, which initiates the file transfer.
 * Note that Windows Live Messenger (v8) will start a direct connection setup first
 * before it sends the data preparation ACK.
 */
01248 void P2PApplicationBase::sendDataPreparation()
{
#ifdef KMESSTEST
  KMESS_ASSERT( getSessionID() != 0 );
  KMESS_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, 0x00 );
  sendP2PMessageImpl( p2pMessage, 0, P2P_TYPE_PICTURE, P2P_MSG_DATA_PREPARATION );

  // 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
  kmDebug() << "Waiting for data preparation ACK...";
#endif

  // Wait until our preparation message is ACK-ed
  setWaitingState( P2P_WAIT_FOR_PREPARE_ACK, P2PAPPLICATION_TIMEOUT_ACK );
}



/**
 * @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.
 */
01283 void P2PApplicationBase::sendDirectConnectionHandshake( const QString &nonce )
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "Sending direct connection handshake "
            << "(nonce=" << nonce << ").";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( applicationList_->hasDirectConnection() );
#endif
#ifdef KMESSTEST
  // NOTE: the handshake could happen while the file transfer was in progress, or even completed already.
  KMESS_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, 0x00 );
  nextMessageID_++;

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

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


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

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

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



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

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


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

    // Wrap in buffer, without copying.
    QByteArray data = QByteArray::fromRawData( buffer, (int)bytesRead );

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "Sending data:"
              << " offset=" << fragmentOffset_
              << " buffer=" << bytesRead;
#endif

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

    // Determine the flags:
    // Funny, MSN6 doesn't seam to care which flag was set
    // while transferring the file. KMess however, does.
    uint flag = 0;
    switch( dataType_ )
    {
      case P2P_TYPE_PICTURE: flag = P2PMessage::MSN_FLAG_OBJECT_DATA; break;
      case P2P_TYPE_FILE:    flag = P2PMessage::MSN_FLAG_FILE_DATA;   break;
      default:
        // Display error message for the first message only.
        if( fragmentOffset_ == 0 )
        {
          kmWarning() << "Unknown data transfer mode "
                        "(mode=" << dataType_ <<
                        " contact=" << getContactHandle() <<
                        " session=" << getSessionID() <<
                        " class="   << metaObject()->className() <<
                        " action=tryflag).";
        }
    }

    // Send the message
    bool writeSuccess = sendP2PMessageImpl( data, flag, dataType_, P2P_MSG_DATA, fragmentMessageID_ );

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

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


  // Rounds completed, make final assertions.
  // Be verbose if something internally goes wrong.
  if( dataSource_ != 0 && dataSource_->atEnd() && fragmentOffset_ < fragmentTotalSize_ )
  {
    kmWarning() << "data source is at the end, "
                  "but offset byte count is not "
                  "(fragmentoffset="    << fragmentOffset_ <<
                  " fragmenttotalsize=" << fragmentTotalSize_ <<
                  " sourceopen="        << dataSource_->isOpen() <<
                  " sourcesize="        << dataSource_->size() <<
                  " datatype="          << dataType_ <<
                  " contact="           << getContactHandle() <<
                  " session="           << getSessionID() <<
                  " class="             << metaObject()->className() <<
                  " action=resetstate)!";

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

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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "Waiting for all data received ACK...";
#endif

    // Now wait for the contact to ACK that it received all data.
    setWaitingState( P2P_WAIT_FOR_DATA_ACK, P2PAPPLICATION_TIMEOUT_ACK );

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

  return true;
}



/**
 * @brief Send a low-level control message to terminate the session.
 *
 * The session will automatically be scheduelled termination using a call to endApplicationLater().
 */
01502 void P2PApplicationBase::sendP2PAbort()
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if( ! shouldSendAck_ )
  {
    kmDebug() << "NOTICE: sending 0x08 control packet while the contact is not expecting an ack response.";
  }
#endif

  // Inform the client we want to kill the session right away.
  sendP2PAckImpl( P2PMessage::MSN_FLAG_ERROR );

#ifdef KMESSTEST
  KMESS_ASSERT( isP2PAckSent() );
#endif

  // Scheduelle termination of the session.
  endApplicationLater();
}



/**
 * @brief Send an ACK message if this is needed.
 *
 * This method called each time a P2P payload is fully received (such as an SLP 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.
 *
 * This function is mostly used by the derived P2PApplication class.
 * It calls sendP2PAck() when a SLP message is properly received.
 *
 * @return Whether an ack had to be sent.
 */
01536 bool P2PApplicationBase::sendP2PAck()
{
  // Only send an ACK is this is really needed.
  if( ! shouldSendAck_ )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "NOTICE: not sending 0x02 ack packet, something is already sent (or nothing needs to be sent).";
#endif

    return false;
  }

  // Send the ack
  sendP2PAckImpl();  // type = P2PMessage::MSN_FLAG_ACK.
  return true;
}



/**
 * @brief Internal function to send a P2P ACK message.
 *
 * This method is the internal implementation for sendP2PAck() and others.
 *
 * @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.
 *                              Defaults to the last received message.
 */
01564 void P2PApplicationBase::sendP2PAckImpl(int ackType, UnAckedMessage *originalMessageData)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  if(ackType == P2PMessage::MSN_FLAG_ACK)
  {
    if( nextMessageID_ == 0 && getMode() == APP_MODE_NORMAL )
    {
      kmDebug() << "Sending P2P ACK (INVITE-ACK)";
    }
    else
    {
      kmDebug() << "Sending P2P ACK";
    }
  }
  else if(ackType == P2PMessage::MSN_FLAG_ERROR)
  {
    kmDebug() << "Sending P2P ACK (Error-response)";
  }
  else if(ackType == P2PMessage::MSN_FLAG_WAITING)
  {
    kmDebug() << "Sending P2P ACK (Timeout-response)";
  }
  else if(ackType == P2PMessage::MSN_FLAG_WAITING_FOR_ACK)
  {
    kmDebug() << "Sending P2P ACK (ACK-timeout-response)";
  }
  else if(ackType == P2PMessage::MSN_FLAG_ABORTED_SENDING)
  {
    kmDebug() << "Sending P2P ACK (Aborted-myself-ACK)";
  }
  else if(ackType == P2PMessage::MSN_FLAG_ABORTED_RECEIVING)
  {
    kmDebug() << "Sending P2P ACK (Aborted-you-ACK)";
  }
  else
  {
    kmDebug() << "Sending P2P ACK (Unknown-type)";
  }
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( shouldSendAck_ );   // TODO: test for control messages, which do not have to obey this rule.
#endif

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


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

  QByteArray header( 48, 0x00 );

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

    nextMessageID_ = KMessShared::generateID();
    messageID      = nextMessageID_;

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

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

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

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

  // The Ack needs to have the same footer code (visible in webcam setup for example).
  uint footerCode = originalMessageData->footerCode;

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

#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "ACK Transmitted "
            << "(flags=0x" << QString::number(ackType, 16) << ").";
#endif
}



/**
 * @brief Sends a complete P2P message payload.
 *
 * It splits the data message into chunks which fit in the individual P2P messages.
 *
 * If you need to send data, use sendData() instead.
 * This method is stream based, and also takes care of state changes.
 *
 * @param messageData    The message payload to send.
 * @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 returned with gotAck().
 */
01740 void P2PApplicationBase::sendP2PMessage(const QByteArray &messageData, int flagField, P2PDataType footerCode, P2PMessageType messageType)
{
  // Make sure the message gets sent in chunks of 1202 bytes.
  // Only set fragmentTotalSize_ if there are chunks, for useless debug messages.
  int remainingBytes = messageData.size();
  fragmentOffset_    = 0;
  fragmentTotalSize_ = ( remainingBytes <= 1202 ? 0 : remainingBytes );

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

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

#ifdef KMESSTEST
    KMESS_ASSERT( (uint) remainingBytes == fragmentTotalSize_ - fragmentOffset_ );
#endif
  }
  while( remainingBytes > 0 );

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



/**
 * @brief Internal function to send a single P2P message.
 *
 * This method constructs the P2P binary header fields.
 * The constructed message is delivered to ApplicationList::sendMessage().
 *
 * The following instance fields are updated automatically:
 * - nextMessageID_ is generated for every new P2P message.
 * - fragmentOffset_ is updated when a splitted message is sent.
 * - fragmentTotalSize_ and messageOffse_ are reset when the last message fragment is sent.
 * - outgoingMessages_ is updated to trace ACKs back later.
 *
 * The fragmentTotalSize_ should be set to the total size before sending message fragments.
 * This is done automatically by sendP2PMessage() function, which is part of the public API.
 *
 * @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.
 * @param messageID      Optional message ID to enforce. If nothing is selected,
 *                       the next available message ID will be used automatically.
 *                       This parameter is used to send file data with a specific message ID.
 * @return               Whether the message can be sent. If the socket would block, false is returned.
 *                       This only occurs for direct connections connections.
 */
01799 bool P2PApplicationBase::sendP2PMessageImpl(const QByteArray &messageData, int flagField, P2PDataType footerCode, P2PMessageType messageType, quint32 messageID)
{
#ifdef KMESSTEST
  KMESS_ASSERT( messageData.size() <= 1202 );
#endif

  // Initialize the header
  QByteArray header( 48, 0x00 );

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

    messageID = nextMessageID_;
  }


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


  // Determine the message size:
  // Internal trick: fragmentTotalSize_ is set by the caller if this is a splitted message.
  quint32 messageSize = messageData.size();
  quint32 totalSize;
  quint32 offsetField;

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


  // Generate ack session identifier for this message
  quint32 ackSessionID = KMessShared::generateID();

  // Set session ID to zero when SLP messages are sent.
  quint32 sessionID = getSessionID();
  if( flagField  == 0
  &&  footerCode == 0      // fixes all data transfers, like msnobjects and webcam.
  &&  messageType != P2P_MSG_DATA_PREPARATION )
  {
    sessionID = 0;
  }

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


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


  // Update unacked message queue to handle incoming ACKs
  if( flagField == 0 || messageType != 0 )
  {
    if( offsetField == 0 )
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kmDebug() << "Storing message fields to track the ACK response.";
#endif
      // First message, or single message, store new record for ACKs.
      QDateTime currentTime;
      UnAckedMessage *ackData = new UnAckedMessage;
      ackData->sessionID    = getSessionID();
      ackData->messageID    = messageID;
      ackData->dataSize     = messageSize;
      ackData->totalSize    = totalSize;
      ackData->ackSessionID = ackSessionID;
      ackData->messageType  = messageType;  // used later to handle certain ACKs.
      ackData->sentTime     = currentTime.toTime_t();
      outgoingMessages_.append(ackData);
    }
    else
    {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
      kmDebug() << "Updating message fields to track the ACK response.";
#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.
      foreach( UnAckedMessage *unAcked, outgoingMessages_ )
      {
        if( unAcked->messageID == messageID )
        {
          unAcked->ackSessionID = ackSessionID;
          break;
        }
      }
    }
  }

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

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

      fragmentOffset_    = 0;
      fragmentTotalSize_ = 0;
    }
  }


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "Message transmitted "
            << "(sid="     << getSessionID()
            << " mid="     << messageID
            << " ackSid="  << ackSessionID
            << " flags=0x" << QString::number(flagField, 16)
            << " size="    << messageSize
            << " type="    << messageType
            << ")";
#endif

  return true;
}



/**
 * @brief Send a low-level error message that the application is waiting for a certain message.
 */
01979 void P2PApplicationBase::sendP2PWaitingError()
{
  sendP2PMessageImpl( 0, P2PMessage::MSN_FLAG_WAITING );
  shouldSendAck_ = false;
}



/**
 * @brief Send a complete SLP message in multiple P2P packets.
 *
 * This is a low-level function which is primary used by 
 * P2PApplication::sendSlpInvitation(), P2PApplication::sendSlpBye(), etc..
 *
 * It converts the SLP string to the payload data,
 * which could be splitted across multiple P2P messages.
 *
 * @param  slpMessage   The whole SLP message to send, including SLP headers.
 * @param  messageType  The type of the message. This value is returned with gotAck().
 */
01999 void P2PApplicationBase::sendSlpMessage(const QString &slpMessage, P2PMessageType messageType)
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "SLP message=" << slpMessage;
#endif

  // Get the raw utf8 encoded bytearray
  QByteArray utf8Message = slpMessage.toUtf8();
  QByteArray messagePart;

  // Make sure the extra padded '\0' character is there. MSN 6-WLM rely on this.
  utf8Message.append('\0');

  sendP2PMessage( utf8Message, 0, P2P_TYPE_NEGOTIATION, messageType );  // will split the message.
}



/**
 * @brief Set the current message type, for debugging.
 *
 * This method can be set from gotNegotiationMessage() to set the current message type.
 * It's used for debugging, and setting the proper footer code in ACK responses.
 */
02023 void P2PApplicationBase::setCurrentMessageType( P2PMessageType messageType )
{
  // Only send an ACK is this is really needed.
  if( ! shouldSendAck_ )
  {
    kmWarning() << "NOTICE: ack is likely sent already, setting setCurrentMessageType() has no use anymore.";
  }

  lastIncomingMessage_.messageType = messageType;
}



/**
 * @brief Notify this base class the user is aborting the session.
 *
 * This method sets a flag to avoid sending certain error messages while the user is aborting
 * (e.g. closing the chat window). It prevents crashes or spawning new chat sessions.
 *
 * @param  userAborted  Whether the user is aborting.
 */
02044 void P2PApplicationBase::setUserAborted( bool userAborted )
{
  userAborted_ = userAborted;
}



/**
 * @brief Indicate which packet is expected next.
 * @param  waitingState  The new waiting state.
 * @param  timeout       Number of milliseconds before a timer fires and showTimeoutMessage() is called.
 *                       The contact's client will be informed automatically that it forgot to send some data.
 *                       If this value is 0, it means the class temporary changes the state for debugging.
 */
02058 void P2PApplicationBase::setWaitingState( P2PWaitingState waitingState, int timeout )
{
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  unsigned long sessionID = getSessionID();

  if( timeout == 0 )
  {
#ifdef KMESSTEST
    KMESS_ASSERT( ! waitingTimer_->isActive() );
#endif

    if( waitingState_ != waitingState )
    {
      kmDebug() << "temporary switching waitingState of session" << sessionID << "from" << waitingState_ << "to" << waitingState;
    }
  }
  else
  {
    if( waitingState_ != waitingState )
    {
      kmDebug() << "switching waitingState of session" << sessionID << "from" << waitingState_ << "to" << waitingState;
    }
    else
    {
      kmDebug() << "resuming waitingState of session" << sessionID << "for" << waitingState_;
    }
  }
#endif

  // Update the state
  waitingState_ = waitingState;

  // Restart the timer
  if( timeout != 0 )
  {
    waitingTimer_->start( timeout );
  }
}



/**
 * @brief Crash prevention method.
 *
 * While it should never happen that the ApplicationList is deleted before the P2PApplication class,
 * this did happen in the past (see http://trac.kmess.org/ticket/263). This method is an extra
 * safety guard to prevent crashes after many things do wrong.
 */
02106 void P2PApplicationBase::slotApplicationListDeleted()
{
  kmWarning() << "ApplicationList is deleted before the P2PApplication class! This should not happen!"
                "(maybe an old session, "
                " contact=" << getContactHandle() <<
                " session=" << getSessionID() <<
                " class="   << metaObject()->className() <<
                " action=endapplication).";

  applicationList_ = 0;   // ignores the derived P2PApplication class for now.

  // No one can or needs to receive the deleteMe() signal anymore.
  // Forcefuly delete ourselves and hope for the best.
  deleteLater();
}



/**
 * @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>kmWarning()</tt>.
 */
02132 void P2PApplicationBase::slotCleanup()
{
  // See if the waiting time for new packets is over (like TCP's TIME_WAIT state).
  if( waitingState_ == P2P_WAIT_END_APPLICATION )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "timer of endApplicationLater() fired, finally destroying object.";
#endif

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


#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
  kmDebug() << "timeout fired, cleaning up "
              "(contact="      << getContactHandle() <<
              " session="      << getSessionID() <<
              " waitingState=" << waitingState_ << ").";
#endif
#ifdef KMESSTEST
  KMESS_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
    kmDebug() << "Not sending timeout message for current waiting state.";
#endif
  }
  else
  {
    if( userAborted_ )
    {
      // 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
      kmDebug() << "Not sending error message because application is aborting.";
#endif

      testUnAckedMessages( false );
    }
    else
    {

/*
      // Tell the other client we were waiting for a message to receive!
      // TODO: this is still not as it should be, but a start.
      uint waitFlag = P2PMessage::MSN_FLAG_WAITING;
      switch(waitingState_)
      {
        case P2P_WAIT_FOR_FILE_DATA:
          // Waiting for file data. Application is shutting down sender right now.
          waitFlag = P2PMessage::MSN_FLAG_ABORTED_RECEIVING;  // 0x80
          break;

          if waiting for ack, wlm also sends 0x04.

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

        default: break;
      }

      // Send the message
      sendP2PMessageImpl(0, waitFlag, 0);  // TODO: pass the original message which is unacked!!
*/
      testUnAckedMessages( true );  // TODO: argument is not used, and replaces sendP2PWaitingError().

      sendP2PWaitingError();
    }
  }


  // Pass to delivered class
  showTimeoutMessage( waitingState_ );
}



/**
 * @brief  Test if the contact aborted the sending of data.
 *
 * This method is intended to be used by the P2PApplication class.
 * When a transfer to the contact is aborted, this method sends a <code>0x40</code> message.
 * That control packet terminates the "ack slot" in the remote client.
 */
02236 void P2PApplicationBase::testDataSendingAborted()
{
#ifdef KMESSTEST
  KMESS_ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK );
#endif

  // See if the data was not acked.
  UnAckedMessage *unAckedDataMessage = getUnAckedMessage( P2P_MSG_DATA );
  if( unAckedDataMessage != 0 )
  {
#ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL
    kmDebug() << "Sent data was not acked, sending 0x40 to abort ack request.";
#endif

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

    // Remove from collection
    outgoingMessages_.removeAll( unAckedDataMessage );
    delete unAckedDataMessage;
  }
}



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

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

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

      foreach( UnAckedMessage *unAcked, outgoingMessages_ )
      {
        kmWarning() << "Message "    << unAcked->messageID << " is unacked "
                      "(type="      << unAcked->messageType <<
                      " ackSid="    << unAcked->ackSessionID <<
                      " totalsize=" << unAcked->totalSize << ").";

      }
    }
  }
}



/**
 * @brief Stop the waiting timer
 * @return  Whether the timer was still running, or not.
 */
02313 bool P2PApplicationBase::stopWaitingTimer()
{
  bool wasActive = waitingTimer_->isActive();
  waitingTimer_->stop();
  return wasActive;
}



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

  // See if the offset should be changed,
  // and if so, update it.
  if( offset != (ulong)file->pos() && ! file->seek( offset ) )
  {
#ifdef KMESSDEBUG_APPLICATION
    kmDebug() << "data is received out of order, updating file offset.";
#endif

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

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

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

    // Add padding at the end of the file.
    file->seek( fileSize );
    while( remainingSize > 0 )
    {
      if( file->write( padding, qMin( (unsigned long) 4096, remainingSize ) ) <= 0 )
      {
        break;
      }
      remainingSize -= 4096;
    }

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


  // Offsets are correct.
  // Write the data to the file,
  qint64 status = file->write( message.getData(), message.getDataSize() );
  if( status == -1 )
  {
    kmWarning() << "Failed to write the data to the file "
                  "(contact=" << getContactHandle() <<
                  " session=" << getSessionID() <<
                  " class="   << metaObject()->className() <<
                  " action=sendcancel)";

    sendCancelMessage(CANCEL_FAILED);
    return false;
  }

  // Indicate success!
  return true;
}



#include "p2papplicationbase.moc"

Generated by  Doxygen 1.6.0   Back to index