/*************************************************************************** p2papplication.cpp - description ------------------- begin : Mon Nov 22 2004 copyright : (C) 2004 by Diederik van der Boor email : vdboor --at-- codingdomain.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "p2papplication.h" #include <stdlib.h> #include <string.h> // for memcpy #include <qregexp.h> #include <qiodevice.h> #include <qtimer.h> #include <qbuffer.h> #include <klocale.h> #include <kdebug.h> #include "../mimemessage.h" #include "../p2pmessage.h" #include "../../msnobject.h" #include "../../kmessdebug.h" #include "../../currentaccount.h" #ifdef KMESSDEBUG_P2PAPPLICATION #define KMESSDEBUG_P2PAPPLICATION_GENERAL #endif // TODO: Reset global messageTotalSize_ for error messages/ack messages. /** * Constructor, initializes the P2PApplication instance fields. * * @param localIP IP-Address of the switchboard socket. * @param contactHandle Handle of the other contact */ 00049 P2PApplication::P2PApplication(const QString &localIP, const QString &contactHandle) : Application(localIP), buffer_(0), contactHandle_(contactHandle), dataSource_(0), dataType_(P2P_TYPE_NEGOTIATION), gotSlpMessage_(false), lastUniqueID_(0), invitationCSeq_(0), invitationSessionID_(0), originalAckMessageID_(0), originalDataSize_(0), originalMessageID_(0), originalSessionID_(0), originalTotalSize_(0), sentCancelMessage_(false), sessionID_(0), messageID_(0), messageOffset_(0), messageTotalSize_(0), shouldSendAck_(false), userShouldAcknowledge_(false), waitingState_(P2P_WAIT_DEFAULT) { // Initiaize the timer waitingTimer_ = new QTimer(this); connect( waitingTimer_, SIGNAL(timeout()), this, SLOT(slotCleanup()) ); } /** * Class destructor. */ 00083 P2PApplication::~P2PApplication() { waitingTimer_->stop(); delete waitingTimer_; delete buffer_; } /** * Step one of a contact-started chat: the contact invites the user * By default the invitation will be declined unless you override this method. */ 00096 void P2PApplication::contactStarted1_ContactInvitesUser(const MimeMessage &message) { #ifdef KMESSDEBUG_APPLICATION kdDebug() << "P2PApplication - contactStarted1_ContactInvitesUser (rejects invitation)" << endl; #endif // Cancel the invitation sendCancelMessage( CANCEL_NOT_INSTALLED ); // We can assume the switchboard already displayed a better error message #ifdef KMESTEST kdWarning() << "The Application subclass didn't implement contactStarted1_ContactInvitesUser!" << endl; #endif // Send some debug info to the console MimeMessage slpContent(message.getBody()); kdWarning() << "The contact initiated a MSN6 feature KMess can't handle yet " << "(euf-guid=" << slpContent.getValue("EUF-GUID") << " appid=" << slpContent.getValue("AppID") << ")." << endl; // Unlike Application::contactStarted1_ContactInvitesUser() // we don't quit here!! We should wait for the BYE message from the other contact! } /** * Step four of a contact-started chat: the contact confirms the data-preparation message. */ 00124 void P2PApplication::contactStarted4_ContactConfirmsPreparation() { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication - contactStarted4_ContactConfirmsPreparation" << endl; #endif } /** * Private utility function to generate an Session and message/sequence ID. * The message/sequence ID starts random, between [4, 4294967295]. */ 00137 unsigned long P2PApplication::generateID() { return ((rand() & 0xFFFFFF00) + 4); } /** * Private utility function to generate a Call ID or Branch ID. * This ID has the format of a GUID, but it can in fact by any random string. */ 00148 QString P2PApplication::generateGUID() { // This code is based on Kopete, but much shorter. QString guid = "{" + QString::number((rand() & 0xAAFF) + 0x1111, 16) + QString::number((rand() & 0xAAFF) + 0x1111, 16) + "-" + QString::number((rand() & 0xAAFF) + 0x1111, 16) + "-" + QString::number((rand() & 0xAAFF) + 0x1111, 16) + "-" + QString::number((rand() & 0xAAFF) + 0x1111, 16) + "-" + QString::number((rand() & 0xAAFF) + 0x1111, 16) + QString::number((rand() & 0xAAFF) + 0x1111, 16) + QString::number((rand() & 0xAAFF) + 0x1111, 16) + "}"; return guid.upper(); } /** * Return the branch ID, this identifies the SLP INVITE-message. */ 00173 QString P2PApplication::getBranch() const { return branch_; } /** * Return the call ID, this identifies the SLP session. */ 00183 QString P2PApplication::getCallID() const { return callID_; } /** * Return the handle of the other contact. */ 00192 QString P2PApplication::getContactHandle() const { return contactHandle_; } /** * Return the content type read from the invitation message * This method is useful for some contactStarted1_ContactInvitesUser() implementations. */ 00203 const QString& P2PApplication::getInvitationContentType() const { return invitationContentType_; } /** * Return the Session ID this class read from the INVITE message. * This method is useful for some contactStarted2_UserAccepts() implementations. */ 00214 unsigned long P2PApplication::getInvitationSessionID() const { return invitationSessionID_; } /** * Return the MessageID the other contact sent with his/hers last P2P message. */ 00224 unsigned long P2PApplication::getLastContactMessageID() const { return originalMessageID_; } /** * Return the UniqueID the other contact sent with his/hers last P2P message. */ 00234 unsigned long P2PApplication::getLastContactAckMessageID() const { return originalAckMessageID_; } /** * Return the message ID we sent with the last P2P message. */ 00244 unsigned long P2PApplication::getLastUserMessageID() const { return messageID_; } /** * Return the session ID, this identifies the P2P session. */ 00254 unsigned long P2PApplication::getSessionID() const { return sessionID_; } /** * Return the Unique ID we sent with the last P2P message. */ 00264 unsigned long P2PApplication::getLastUserUniqueID() const { return lastUniqueID_; } /** * Parse the ACK message. In certain situations we need to activate some methods. */ 00275 void P2PApplication::gotAck(const P2PMessage &message) { // Are we waiting for the contact to accept our invitation (e.g. send "200 OK")? if(waitingState_ == P2P_WAIT_CONTACT_ACCEPT) { // Don't change the waiting state yet waitingTimer_->start(30000, true); // 30 sec #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (INVITE ACK, still waiting for 200 OK)." << endl; #endif return; } // Are we waiting for the contact to send the BYE message? if(waitingState_ == P2P_WAIT_FOR_SLP_BYE) { // Don't change the waiting state yet waitingTimer_->start(30000, true); // 30 sec #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (still waiting for BYE)." << endl; #endif return; } // Are we waiting for the ACK to confirm our "200 OK" message? if(waitingState_ == P2P_WAIT_FOR_SLP_OK_ACK) { waitingState_ = P2P_WAIT_DEFAULT; #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (200 OK ACK)." << endl; #endif // This should happen after the "200 OK" message is sent. // If the user started the inviation, // we send the SLP OK ack, not receive it if(isUserStartedApp()) { // We received an 200 OK ACK but didn't expect it. kdWarning() << "P2PApplication: P2P message can't be handled, " << "unexpected ACK response, waiting for 200 OK ACK (contact=" << contactHandle_ << ")." << endl; shouldSendAck_ = true; // ASSERT in sendP2PAck would fail otherwise sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); return; } // App started by contact, // this is truely the right message. // Dispatch to derived implementation contactStarted3_ContactConfirmsAccept( (const MimeMessage&) message ); // The message is empty, but pass it anyway. #ifdef KMESSTEST ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_PREPARE_ACK ); #endif // If the contactStarted3() didn't sent anything, we'll wait for incoming file data. if(waitingState_ == P2P_WAIT_DEFAULT) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting state not changed, waiting for file data..." << endl; #endif // For Pictures, the contact must send the data preparation ACK at this point (P2P_WAIT_FOR_PREPARE_ACK) // For Files, the contact must initiate the data transfer now. (P2P_WAIT_DEFAULT) // TODO: trillian does not initiate the file transfer here, why?!?) waitingState_ = P2P_WAIT_FOR_FILE_DATA; waitingTimer_->start(30000, true); // 30 sec } return; } // Are we waiting for the ACK to confirm our data preparation message? if(waitingState_ == P2P_WAIT_FOR_PREPARE_ACK) { waitingState_ = P2P_WAIT_DEFAULT; #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (data-preparation ACK)." << endl; #endif // This should happen after the data preparation message is sent. // If the user sent the invitation, // we shouldn't receive the prepare-ack, but sent it. if(isUserStartedApp()) { // We received a data preparation ACK but didn't expect it. kdWarning() << "P2PApplication: P2P message can't be handled, " << "unexpected ACK response, waiting for data-preparation-ACK (contact=" << contactHandle_ << ")." << endl; shouldSendAck_ = true; // ASSERT in sendP2PAck would fail otherwise sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); return; } // Dispatch to derived implementation contactStarted4_ContactConfirmsPreparation(); return; } // Are we waiting for the ACK to confirm that all data was received? if(waitingState_ == P2P_WAIT_FOR_DATA_ACK) { waitingState_ = P2P_WAIT_DEFAULT; #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (all-received ACK)." << endl; #endif // This should happen after all file/picture data was sent. if(isUserStartedApp()) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: All data sent and confirmed, sending BYE message..." << endl; #endif // We sent the INVITE, now we send the BYE. waitingState_ = P2P_WAIT_DEFAULT; sendSlpBye(); // Disabled in backport // // All data was acked // // Telling derived class the transfer is complete. // showTransferComplete(); // Don't terminate yet, that message needs to be ACK-ed as well. } else { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: All data sent and confirmed, waiting for BYE message..." << endl; #endif // Disabled in backport // // All data was acked, this is good enough. // // The remaining teardown happens behind the scenes. // showTransferComplete(); // The other contact sent the INVITE, so we also wait for the BYE message. // This happens for picture transfers for example. waitingState_ = P2P_WAIT_FOR_SLP_BYE; waitingTimer_->start(30000, true); // 30 sec } return; } // Are we waiting for the BYE ACK to send our closing-ACK? if(waitingState_ == P2P_WAIT_FOR_SLP_BYE_ACK) { waitingState_ = P2P_WAIT_DEFAULT; #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (BYE ACK, terminating)." << endl; #endif // Transfer complete. endApplication(); return; } // Are we waiting for the SLP Error message to be ACK-ed? if(waitingState_ == P2P_WAIT_FOR_SLP_ERR_ACK) { waitingState_ = P2P_WAIT_DEFAULT; if(! isUserStartedApp()) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (Confirmed SLP Error, waiting for BYE)." << endl; #endif // We wait for the BYE waitingState_ = P2P_WAIT_FOR_SLP_BYE; waitingTimer_->start(30000, true); // 30 sec return; } else { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message (Confirmed SLP Error, terminating)." << endl; #endif // TODO: send BYE here? endApplication(); return; } } // Last option: This should be a normal ACK message. #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an ACK message." << endl; #endif #ifdef KMESSTEST // If we were waiting: the timer is stopped and you forgot an if-block in this method ASSERT( waitingState_ == P2P_WAIT_DEFAULT ); if(waitingState_ != P2P_WAIT_DEFAULT) { kdDebug() << "P2PApplication: Waiting state is " << waitingState_ << "!" << endl; } #endif if(waitingState_ != P2P_WAIT_DEFAULT) { // Make sure the object will be killed eventually. waitingTimer_->start(30000, true); // 30 sec } } /** * Got an closing ack (received when our BYE-ACK is transmitted). * This method aborts the application. */ 00510 void P2PApplication::gotClosingAck(const P2PMessage &/*message*/) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an Closing-ACK message, terminating session." << endl; #endif #ifdef KMESSTEST ASSERT( waitingState_ == P2P_WAIT_FOR_CLOSING_ACK ); #endif // Unfortunately we can't rely on this message because many // third-party clients don't send this message yet. // In that case, we send an "type-4 waiting" message before we quit. endApplication(); } /** * Called when data is received * * @param message The P2P message containing the data. */ 00532 void P2PApplication::gotData(const P2PMessage &/*message*/) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication - gotData" << endl; #endif } /** * Called internally when data is received, this eventually calls gotData() */ 00544 void P2PApplication::gotDataFragment(const P2PMessage &message) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Received file data" << endl; #endif #ifdef KMESSDEBUG ASSERT( waitingState_ == P2P_WAIT_FOR_FILE_DATA ); #endif // TODO: do some verification on the size/offset fields?? // Dispatch the message to the derived class gotData(message); // If we received the last part, ACK it and send the BYE if( message.isLastFragment() ) { // TODO: We can check (by encrypting, encoding & comparing it to // the SHA1D field) if this was really the data we expected. // Send the ACK and BYE sendP2PAck(); // Strange, MSN6.2 doesn't send this message..?!? if(isUserStartedApp()) { // We sent the INVITE, now we send the BYE. waitingState_ = P2P_WAIT_DEFAULT; sendSlpBye(); // Don't terminate yet, that message needs to be ACK-ed as well. } else { // Otherwise we wait for the BYE waitingState_ = P2P_WAIT_FOR_SLP_BYE; waitingTimer_->start(30000, true); // 30 sec } } else { // Wait for more data to arrive. waitingTimer_->start(30000, true); // 30 sec } } /** * Called when the data preparation message is received */ 00595 void P2PApplication::gotDataPreparation(const P2PMessage &/*message*/) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got the data preparation message" << endl; #endif // Only if the user starts the invitation, // the other contact should send a data preparation message if(! isUserStartedApp()) { kdWarning() << "P2PApplication: P2P message can't be handled, " << "unexpected data preparation message (contact=" << contactHandle_ << ")." << endl; // Got an data-preparation message, but didn't expect it. shouldSendAck_ = true; // ASSERT in sendP2PAck would fail otherwise sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The transfer failed. The contact sent bad data, or KMess doesn't support it.") ); return; } #ifdef KMESSTEST ASSERT( shouldSendAck_ ); ASSERT( waitingState_ == P2P_WAIT_FOR_PREPARE ); #endif // The userStarted3_UserPrepares() method should call // sendDataPreparationAck() or sendCancelMessage() // to reset "userShouldAcknowledge_" shouldSendAck_ = true; userShouldAcknowledge_ = true; // Dispatch to the derived class userStarted3_UserPrepares(); // ACK still wasn't sent yet, determine now what to send: if(userShouldAcknowledge_) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Data preparation failed, sending reject" << endl; #endif // Derived class still didn't acknowledge or cancel at all // (both disable userShouldAcknowledge_). This shouldn't occur! // Reject the data-preparation message sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The transfer failed. Data preparation failed.") ); return; } // Little API trick: // If the user didn't acknowledge, // but we no longer have to send an ack any more (sendDataPreparationAck() doesn't send it!), // it must have been sendCancelMessage(CANCEL_SESSION) calling sendP2PAck(P2PMessage::MSN_FLAG_ERROR). if(! shouldSendAck_) { // No error message here, assume your derived class already displayed something. endApplication(); return; } // User did acknowledge, we can send the *actual* ACK now. // sendDataPreparationAck() only sets a boolean, so we can do the actual stuff here #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Data preparation successful, sending ACK" << endl; #endif sendP2PAck(); // If data preparation was successful, wait for the data to arrive #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for file data to arrive..." << endl; #endif waitingState_ = P2P_WAIT_FOR_FILE_DATA; waitingTimer_->start(30000, true); // 30 sec } /** * Received a P2P message with the error-flag set. * This method aborts the application. */ 00687 void P2PApplication::gotErrorAck(const P2PMessage &/*message*/) { // Error in our headers? kdWarning() << "P2PApplication: Got a P2P error-response (flag=error contact=" << contactHandle_ << ")." << endl; if(dataType_ == P2P_TYPE_NEGOTIATION) { // We likely made an error in the invitation/bye reponse contactAborted( i18n("The contact cancelled the session.") ); } else { // We were sending or receiving a file/picture // Can also happen if the contact can't handle our data for a different reason. contactAborted( i18n("The transfer failed.") ); } } /** * Parse an incoming message * * @param message A reference to a P2PMessage object. */ 00712 void P2PApplication::gotMessage(const MimeMessage &message) { #ifdef KMESSTEST ASSERT( message.isA("P2PMessage") ); // QObject function #endif // If we were explicitly waiting for a packet, stop the timer waitingTimer_->stop(); // Convert the message const P2PMessage &p2pMessage = static_cast<const P2PMessage>(message); // Store some message parameters for Acknowledgement later originalAckMessageID_ = p2pMessage.getAckMessageID(); originalDataSize_ = p2pMessage.getDataSize(); originalMessageID_ = p2pMessage.getMessageID(); originalSessionID_ = p2pMessage.getSessionID(); originalTotalSize_ = p2pMessage.getTotalSize(); #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Message size: " << p2pMessage.getDataSize() << " Total size: " << p2pMessage.getTotalSize() << " Offset: " << p2pMessage.getDataOffset() << endl; #endif // If this object was initialized to handle a Bad packet. if(getMode() == APP_MODE_ERROR_HANDLER) { // Make an exception for SLP packets bool isSlpMessage = (p2pMessage.getSessionID() == 0 && p2pMessage.getFlags() == 0); if(! isSlpMessage) { // We received an unknown message, this object was initialized to send the correct response. shouldSendAck_ = true; // ASSERT in sendP2PAck would fail otherwise sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); return; } } // Handle ACKs before everything else. // The application can be terminated here. if(p2pMessage.isAck()) { gotAck(p2pMessage); return; } else if(p2pMessage.isClosingAck()) { gotClosingAck(p2pMessage); return; } else if(p2pMessage.isError()) { gotErrorAck(p2pMessage); return; } else if(p2pMessage.isWaiting()) { gotTimeoutAck(p2pMessage); return; } // Send the ack if needed (also applies to INVITE messages) shouldSendAck_ = p2pMessage.isLastFragment(); // Session ID is 0? -> the clients negotiate for session. if(p2pMessage.getSessionID() == 0) { if(p2pMessage.getFlags() == 0) { // If this object was initialized to handle a Bad packet. if(getMode() == APP_MODE_ERROR_HANDLER) { // This object was initialized to send an error response for an unknown packet (at SLP level). emit appInitMessage( i18n("The transfer failed. The contact sent bad data, or KMess doesn't support it.") ); sendSlpError("500 Internal Error"); // Waits for ACK and terminates return; } // Parse the fragment gotNegotiationFragment(p2pMessage); } else { kdWarning() << "P2PApplication: P2P message can't be handled, " << "unknown negotiation message type (flags=" << p2pMessage.getFlags() << " contact=" << contactHandle_ << ")." << endl; // Don't know what to do with a negotiation message with flags set. sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The transfer failed. The contact sent bad data, or KMess doesn't support it.") ); return; } } // Session ID not zero? -> clients exchange data in the session else { // Check for data preparation messages if(p2pMessage.getFlags() == 0 && p2pMessage.getDataSize() == 4 && p2pMessage.getTotalSize() == 4) { gotDataPreparation(p2pMessage); } else if( waitingState_ != P2P_WAIT_FOR_FILE_DATA && p2pMessage.isPictureData() && p2pMessage.getDataSize() == 4 && p2pMessage.getTotalSize() == 4 ) { // HACK: added for Encarta Instant Answers (has data flag set with preparation message). kdWarning() << "P2PApplication: Expecting data-preparation message, got message of 4 bytes with incorrect flags (assuming data preparation, contact=" << contactHandle_ << ")!" << endl; gotDataPreparation(p2pMessage); } // Check for data messages else if(p2pMessage.isPictureData() || p2pMessage.isFileData() || p2pMessage.isFragment()) { gotDataFragment(p2pMessage); } else if(waitingState_ == P2P_WAIT_FOR_FILE_DATA && p2pMessage.getFlags() == 0 && p2pMessage.isFragment()) { // HACK: added for Kopete 0.9.2 code (has no flag set with data messages). kdWarning() << "P2PApplication: Expecting data message, got fragmented message with no flags set (assuming file data, contact=" << contactHandle_ << ")!" << endl; gotDataFragment(p2pMessage); } else { // Unknown p2p message kdWarning() << "P2PApplication: P2P message can't be handled, " << " unknown data message type (flags=" << p2pMessage.getFlags() << " contact=" << contactHandle_ << ")." << endl; // Got an P2P binary message we can't handle. (some new kind of flag..?) shouldSendAck_ = true; // ASSERT in sendP2PAck would fail otherwise sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The transfer failed. The contact sent bad data, or KMess doesn't support it.") ); return; } } #ifdef KMESSTEST // At the end of this method, we should have sent the ACK..! ASSERT( ! shouldSendAck_ ); #endif } /** * Got a message fragment with SessionID 0 * All fragments are buffered and * gotNegotiationMessage() is called when all parts are received * * @param p2pMessage The received P2P message */ 00879 void P2PApplication::gotNegotiationFragment(const P2PMessage &p2pMessage) { if(! p2pMessage.isLastFragment()) { // Not everything received yet, buffer it. #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an SLP fragment, buffering it." << endl; #endif if(buffer_ == 0) { buffer_ = new QBuffer(); buffer_->open(IO_ReadWrite); } buffer_->writeBlock( p2pMessage.getData(), p2pMessage.getDataSize() ); } else { // Read the message or buffer QString slpMessage; QByteArray bufferData; if(buffer_ != 0) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got the last SLP fragment." << endl; #endif // Append the last MSNSLP fraqment and call the method. buffer_->writeBlock( p2pMessage.getData(), p2pMessage.getDataSize() ); buffer_->reset(); // Read the buffer bufferData = buffer_->readAll(); slpMessage = QString::fromUtf8( bufferData.data(), bufferData.size() ); delete buffer_; buffer_ = 0; } else { // Read the data directly from the message slpMessage = QString::fromUtf8( p2pMessage.getData(), p2pMessage.getDataSize() ); } // Parse the negotiation message gotNegotiationMessage( slpMessage ); } } /** * Parse SLP "session negotiation messages". * Those messages don't have a sessionID set, and have * MSNSLP Mime fields in the body. * * @param slpMessage The MSNSLP message to parse. */ 00940 void P2PApplication::gotNegotiationMessage(const QString &slpMessage) { // Parse the "negotiation message" (P2PMessage with sessionID 0) // Parse the MIME-fields (skip the first line, because it's not in MIME format) int preambleEnd = slpMessage.find("\r\n"); QString preamble = slpMessage.left( (preambleEnd == -1) ? 20 : preambleEnd ); QString mimeFields = slpMessage.mid( (preambleEnd == -1) ? 0 : preambleEnd + 2 ); MimeMessage slpMimeMessage( mimeFields ); // Verify it is indeed directed to us.. QString msgTo = slpMimeMessage.getValue("To"); if(! msgTo.isEmpty()) { // Only test if the field is available. If it's not, either something // is wrong in this application, or the client sent a bad message and // we should respond with an 500 error instead. if( msgTo != "<msnmsgr:" + CurrentAccount::instance()->getHandle() + ">" ) { if(shouldSendAck_) sendP2PAck(); // In the future, the <msnmsgr: > string might be replaced with // something to support MSN to AOL chats, etc.. kdWarning() << "P2PApplication: P2P message can't be handled, " << "addressed to someone else (contact=" << contactHandle_ << ")." << endl; emit appInitMessage( i18n("The invitation was cancelled. Message was not directed to us.") ); sendSlpError("404 Not Found"); // Waits for ACK and terminates return; } } // Find out what kind of message this is: if(preamble.startsWith("INVITE MSNMSGR")) { // Don't accept invites if we sent one. if(messageID_ != 0 && isUserStartedApp()) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got an INVITE response, rejecting (contact is likely using MSN7)." << endl; #endif unsigned long tempSessionID = sessionID_; sessionID_ = 0; sendP2PAck(P2PMessage::MSN_FLAG_ERROR); sessionID_ = tempSessionID; return; } if(shouldSendAck_) { sendP2PAck(); } // Got an invitation! gotSlpInvite(slpMimeMessage); } else if(preamble.startsWith("MSNSLP/")) { // Error message, or "MSNSLP/1.0 200 OK" message gotSlpStatus(slpMimeMessage, preamble); } else if(preamble.startsWith("BYE MSNMSGR")) { // Got a BYE message gotSlpBye(slpMimeMessage); } else { // Unknown SLP message kdWarning() << "P2PApplication: P2P message can't be handled, " << "unsuppored SLP negotiation message (preamble=" << preamble << " contact=" << contactHandle_ << ")." << endl; // Got an unknown SLP preamble sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); return; } } /** * Got an MSNSLP INVITE message */ 01028 void P2PApplication::gotSlpBye(const MimeMessage &/*slpMimeMessage*/) { // Don't accept BYE messages if we sent the INVITE. if(isUserStartedApp()) { kdDebug() << "P2PApplication: WARNING - P2P message can't be handled, " << "unexpected SLP BYE message (contact=" << contactHandle_ << ")." << endl; // We should be sending the BYE.. sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); return; } // If the BYE was received early, it may be an indication we // didn't sent something and the other client decided to quit. // // There appears to be a small bug in MSN 7.0. It doesn't sent the final "all data received" ACK, // but sends the BYE directly. After we ACK it, we get a "BYE sent" message back. // This issue appears to be fixed in MSN 7.5. if( waitingState_ != P2P_WAIT_FOR_SLP_BYE && waitingState_ != P2P_WAIT_FOR_DATA_ACK ) { kdWarning() << "P2PApplication: Unexpectedly received a SLP BYE message (contact=" << contactHandle_ << ")." << endl; } if(shouldSendAck_) { sendP2PAck(); } #ifdef KMESSTEST ASSERT( waitingState_ == P2P_WAIT_FOR_SLP_BYE || waitingState_ == P2P_WAIT_FOR_DATA_ACK ); #endif #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got SLP BYE message, closing application." << endl; #endif endApplication(); return; } /** * Got an MSNSLP INVITE message */ 01076 void P2PApplication::gotSlpInvite(const MimeMessage &slpMimeMessage) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got SLP INVITE message" << endl; #endif #ifdef KMESSTEST ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_FILE_DATA ); #endif // Reset the waiting state if this is the second INVITE for a file transfer if(waitingState_ == P2P_WAIT_FOR_FILE_DATA) { waitingState_ = P2P_WAIT_DEFAULT; } // Session invitation // Extract the fields of the message. This is required for userRejected() MimeMessage slpMimeContent(slpMimeMessage.getBody()); // Set global fields from INVITE message. QString slpVia = slpMimeMessage.getValue("Via"); callID_ = slpMimeMessage.getValue("Call-ID"); invitationCSeq_ = slpMimeMessage.getValue("CSeq").toInt(); invitationContentType_ = slpMimeMessage.getValue("Content-Type"); if(invitationContentType_ == "application/x-msnmsgr-sessionreqbody") { invitationSessionID_ = slpMimeContent.getValue("SessionID").toULong(); } // Extract branch from the "Via" parameter QRegExp callRE(";branch=(.+)"); // don't use a guid-pattern here, msn6 seams to accept random strings. callRE.search(slpVia); branch_ = callRE.cap(1); // Don't forget the initialize the base class startByInvite(generateCookie()); // Indicate this is an SLP message (sendCancelMessage() uses this) // This value is reset once a message is sent. gotSlpMessage_ = true; // Validate the content type if(invitationContentType_ != "application/x-msnmsgr-sessionreqbody" && invitationContentType_ != "application/x-msnmsgr-transreqbody" && // HACK: added for KMess 1.4.2 (sent incorrect value) invitationContentType_ != "application/x-msnmsgr-transrespbody" ) { kdWarning() << "P2PApplication: Received unexpected Content-Type: " << invitationContentType_ << "." << endl; // Indicate we don't like that content-type: sendCancelMessage(CANCEL_INVALID_SLP_CONTENT_TYPE); // Don't QUIT, the error will be ACK-ed. return; } // Extract the body of the SLP message MimeMessage slpContent(slpMimeMessage.getBody()); // Tell the derived class we've got an invitation contactStarted1_ContactInvitesUser(slpContent); } /** * Got an MSNSLP Status header */ 01148 void P2PApplication::gotSlpStatus(const MimeMessage &slpMimeMessage, const QString &preamble) { // An Error message, or "MSNSLP/1.0 200 OK" message #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Got SLP status message (" << preamble << ")" << endl; #endif // Should only be received if we started it. if(! isUserStartedApp()) { kdWarning() << "P2PApplication: P2P message can't be handled, " << "unexpected SLP status response (contact=" << contactHandle_ << ")." << endl; sessionID_ = 0; sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); return; } // Parse the preamble QRegExp re("MSNSLP/\\d+\\.\\d+ (\\d+) ([^\r\n]+)"); re.search(preamble); int code = re.cap(1).toUInt(); QString detail = re.cap(2); // Reset the waiting state. if(waitingState_ == P2P_WAIT_CONTACT_ACCEPT) { waitingState_ = P2P_WAIT_DEFAULT; } // Handle the status codes // If the invitation was accepted if(code == 200) { // We don't need to do much here: // The message will be ACK-ed automatically with the correct session ID // because that ID was given in the sendSlpInvitation() method. // This the first ACK to be sent with the SessionID set. if(shouldSendAck_) { sendP2PAck(); } // Dispatch to derived class, // It's invitation was accepted userStarted2_ContactAccepts(slpMimeMessage); // Waiting for prepare message #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for contact to send some prepare message..." << endl; #endif // TODO: I guess this will be different for file transfers.. waitingState_ = P2P_WAIT_FOR_PREPARE; waitingTimer_->start(30000, true); // 30 sec return; } // We sent a bad address in the to: header if(code == 404) { if(shouldSendAck_) { sendP2PAck(); } // Inform the user KMess has an error kdWarning() << "P2PApplication: KMess sent a bad address in the 'to:' header." << endl; contactAborted( i18n("The contact rejected the invitation. An internal error occured.") ); return; } // Something was not supported if(code == 500) { if(shouldSendAck_) { sendP2PAck(); } if(slpMimeMessage.getValue("Content-Type") == "null") { // The Content-Type was not supported kdWarning() << "P2PApplication: Content-Type of our invitation was not understood" << endl; } else { // Bad header sent in SLP fields, or invitation type was not supported kdWarning() << "P2PApplication: Our invitation was not supported" << endl; } contactAborted( i18n("The contact rejected the invitation. An internal error occured.") ); return; } // The invitation was declined if(code == 603) { if(shouldSendAck_) { sendP2PAck(); } // Contact Declined contactRejected(); return; } // Last option: tell the contact it sent an unknown message kdWarning() << "P2PApplication: P2P message can't be handled, " << "unsupported SLP response (preamble=" << preamble << " contact=" << contactHandle_ << ")." << endl; // Got an unknown SLP status code sendP2PAck(P2PMessage::MSN_FLAG_ERROR); endApplication( i18n("The invitation was cancelled. The contact sent bad data, or KMess doesn't support it.") ); } /** * Received a message with the timeout-flag set. * This method aborts the application. */ 01283 void P2PApplication::gotTimeoutAck(const P2PMessage &/*message*/) { // Error in our headers? if(isWaitingForUser()) { // Contact aborted this session because we didn't press "accept/cancel" within 30 secs. contactAborted( i18n("The transfer failed. Timeout while waiting for user to accept.") ); } else { // Apparently the contact was waiting for something, likely some packet we had to send. kdDebug() << "P2PApplication: WARNING - Got a P2P error-response (flag=waiting contact=" << contactHandle_ << ")." << endl; contactAborted( i18n("The transfer failed. An internal error occured.") ); } } /** * Send a cancel message. Some errors also terminate the application eventually. * * @param cancelReason The reason why the application is cancelled. * * MSNSLP related notes: * Error reasons sending an MSNSLP error messages should only be * used in the contactStarted1 / contactStarted2 methods.. * Once the MSNSLP error message is sent, P2PApplication waits for the * contact to ACK, and terminates the application afterwards. * Hence, you don't have to call endApplication() from contactStarted1 / contactStarted2. * * When an MSNSLP error is sent at a different moment, this will likely result in * undefined responses from the other client (or the error message might be ignored). * If KMess is compiled in debug mode, an assertion in sendSlpError() will detect this. * * * The following cancelReason values are supported: * - CANCEL_INVITATION * The user declined the invitation. * * This sends an "MSNSLP 603 Decline" message, waits for * the contact to ACK and terminates this application afterwards. * * This reason will be used automatically. * * - CANCEL_SESSION / CANCEL_FAILED * The user aborted the session. * * If called from contactStarted1_ContactInvitesUser(), * this sends an "MSNSLP 603 Decline" message, waits for * the contact to ACK and terminates this application afterwards. * * From other methods an P2P ACK message is sent with the "error flag" set (type 8). * * Within the userStarted3_UserPrepares() method, CANCEL_SESSION is the only * valid response. If KMess is compiled in debug mode, an assertion will be triggered otherwise. * * - CANCEL_NOT_INSTALLED * The requested service/application is not installed. * * This sends an "MSNSLP 500 Internal Error" message, waits for * the contact to ACK and terminates this application afterwards. * * This reason should only be used in contactStarted1_ContactInvitesUser(). * The default implementation in Application::contactStarted1_ContactInvitesUser() * uses this reason automatically to cancel the invitation. Hence the base class * is used to reject unknown services automatically. * * - CANCEL_TIMEOUT * There was a timeout waiting for the contact to accept or send certain P2P data. * Usually this reason is used internally by certain timeout functions. * This sends an P2P message with the "waiting flag" set (type 4). * * - CANCEL_INVALID_SLP_CONTENT_TYPE * Sends an "MSNSLP 500 Internal Error" message back, but without an Content-Type set. * * This reason should only be used from contactStarted1_ContactInvitesUser() * or userStarted2_ContactAccepts() to indicate the given Content-Type is not supported. * Other invocations result in undefined responses from the other client (it will likely be ignored). * */ 01363 void P2PApplication::sendCancelMessage(const ApplicationCancelReason cancelReason) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication - sendCancelMessage (reason=" << cancelReason << ")." << endl; #endif // We'll send the cancel message! sentCancelMessage_ = true; // I don't care what the reason is if you had // to send the data-preparation ACK if(userShouldAcknowledge_) { #ifdef KMESSTEST ASSERT( cancelReason == CANCEL_SESSION ); #endif // Cancelled the data preparation message sendP2PAck(P2PMessage::MSN_FLAG_ERROR); userShouldAcknowledge_ = false; shouldSendAck_ = false; return; } // Declined the invitation if(cancelReason == CANCEL_INVITATION) { // Use the cancel-reason to send the correct message: sendSlpError("603 Decline", invitationSessionID_, invitationContentType_); return; } // Timeout waiting for some P2P data // ...or timeout waiting for contact to accept. if(cancelReason == CANCEL_TIMEOUT) { sendP2PMessage(0, P2PMessage::MSN_FLAG_WAITING, 0); shouldSendAck_ = false; return; } // The application type is not installed if(cancelReason == CANCEL_NOT_INSTALLED && gotSlpMessage_) { // Usually this particular error is invoked from Application::contactStarted1_ContactInvitesUser(). sendSlpError("500 Internal Error", invitationSessionID_, invitationContentType_); return; } // User or application wants to abort if(cancelReason == CANCEL_SESSION || cancelReason == CANCEL_FAILED) { if(gotSlpMessage_) { // Cancelled while in SLP negotiate mode. // Include the original content-type to tell the other // client that part of the message was not in error. sendSlpError("500 Internal Error", invitationSessionID_, invitationContentType_); return; } else { // TODO: these error messages are somehow rejected with // another "error in binary fields" error-message response. // One way of testing this code is causing the file-open code // in P2PPictureTransfer::userStarted3_UserPrepares() to fail. // Cancelled while receiving data sendP2PAck(P2PMessage::MSN_FLAG_ERROR); return; } } // Invalid content type received if(cancelReason == CANCEL_INVALID_SLP_CONTENT_TYPE) { // Send an 500 error message without content-type set sendSlpError("500 Internal Error"); return; } // Last option, unknown cancel message #ifdef KMESSDEBUG_MIMEAPPLICATION kdWarning() << "MimeApplication: unknown cancelReason used." << endl; #endif sendP2PAck(P2PMessage::MSN_FLAG_ERROR); } /** * Send data packets from an data source. A timer/thread starts that transmits * all data packets automatically. It also uses the dataSource object * to determine the data size. * * @param dataSource Source of the data to send * @param dataType Type of the data, a member of the P2PDataType enum. */ 01469 void P2PApplication::sendData(QIODevice *dataSource, P2PDataType dataType) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Begin of data transfer" << endl; #endif #ifdef KMESSTEST ASSERT( sessionID_ != 0 ); ASSERT( dataSource != 0 ); #endif // Make sure we don't activate a slot without a data source. if(dataSource == 0) { kdWarning() << "P2PApplication: Couldn't send data, data source is null." << endl; emit appInitMessage( i18n("The transfer failed. Couldn't open data source.") ); // TODO: Not sure this will work though.. gotSlpMessage_ = true; // Make sure the right cancel message is sent. sendCancelMessage(CANCEL_FAILED); // MSNSLP messages terminate the application eventually. return; } // Set the global data fields dataSource_ = dataSource; dataType_ = dataType; // Set the total size field, this causes sendP2PMessage() // to handle fragmented messages correctly. messageTotalSize_ = dataSource->size(); // Run the slot after 10ms QTimer::singleShot( 10, this, SLOT(slotSendData()) ); } /** * Send the data preparation message. * * The data message consists of a P2P message with 0x00000000 as message body. */ 01512 void P2PApplication::sendDataPreparation() { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Sending data preparation message." << endl; #endif #ifdef KMESSTEST ASSERT( sessionID_ != 0 ); ASSERT( waitingState_ == P2P_WAIT_DEFAULT || waitingState_ == P2P_WAIT_FOR_FILE_DATA ); #endif QByteArray p2pMessage(4); p2pMessage.fill(0x00); sendP2PMessage(p2pMessage, 0, 1); // Wait until our preparation message is ACK-ed waitingState_ = P2P_WAIT_FOR_PREPARE_ACK; waitingTimer_->start(30000, true); // 30 sec #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for data preparation ACK..." << endl; #endif } /** * A special method to handle certain acknowledgements. * * You need to call this method from userStarted3_UserPrepares() * to accept the data-preparation message. */ 01543 void P2PApplication::sendDataPreparationAck() { if(userShouldAcknowledge_) { // This value is checked again after the // userStarted3_UserPrepares() method returns. userShouldAcknowledge_ = false; } else { #ifdef KMESSTEST kdWarning() << "P2PApplication: call to sendDataPreparationAck() not expected!" << endl; #endif } } /** * Send a P2P ACK message. * MSN sends an P2P ACK for each received P2P message, which * turns the P2P communication into a stop-and-wait protocol. * * @param ackType The ACK type (flag) to use, defaults to MSN_FLAG_ACK. */ 01568 void P2PApplication::sendP2PAck(int ackType) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL if(ackType == P2PMessage::MSN_FLAG_ACK) { if(messageID_ == 0) { kdDebug() << "P2PApplication: Sending P2P ACK (INVITE-ACK)" << endl; } else { kdDebug() << "P2PApplication: Sending P2P ACK" << endl; } } else if(ackType == P2PMessage::MSN_FLAG_ERROR) { kdDebug() << "P2PApplication: Sending P2P ACK (Error-response)" << endl; } else if(ackType == P2PMessage::MSN_FLAG_WAITING) { kdDebug() << "P2PApplication: Sending P2P ACK (Timeout-response)" << endl; } else if(ackType == P2PMessage::MSN_FLAG_CLOSING_ACK) { kdDebug() << "P2PApplication: Sending P2P ACK (Closing-ACK)" << endl; } else { kdDebug() << "P2PApplication: Sending P2P ACK (Unknown-type)" << endl; } #endif #ifdef KMESSTEST ASSERT( shouldSendAck_ ); #endif MimeMessage message; // Fill the header: // // 0 4 8 16 24 28 32 36 40 48 // |....|....|....|....|....|....|....|....|....|....|....|....| // |sid |mid |offset |totalsize|size|flag|asid|auid|a-datasz | // QByteArray header(48); header.fill(0x00); // Determine the message ID if(messageID_ == 0) { // We don't have a message ID set yet, we're acknowledging // the first INVITE message. Generate an ID messageID_ = generateID(); P2PMessage::insertBytes(header, messageID_, 4); // Somehow, the next message we send (200 OK) should contain a messageID - 3: // We decrement the messageID with 4 because it will be incremented in the next call. messageID_ -= 4; } else { messageID_++; P2PMessage::insertBytes(header, messageID_, 4); } // Insert the bytes P2PMessage::insertBytes(header, originalSessionID_, 0); // SessionID // Message ID was set earlier P2PMessage::insertBytes(header, originalTotalSize_, 16); // Confirm total size set in previous message P2PMessage::insertBytes(header, ackType, 28); // Set the ACK flag. P2PMessage::insertBytes(header, originalMessageID_, 32); // Set the ACK message ID P2PMessage::insertBytes(header, originalAckMessageID_, 36); // Set the ACK Unique ID field. P2PMessage::insertBytes(header, originalDataSize_, 40); // Set the ACK data size with the previous message size. // Footer char footer[4]; footer[0] = 0x00; footer[1] = 0x00; footer[2] = 0x00; footer[3] = 0x00; // Assign the message fields message.addField("MIME-Version", "1.0"); message.addField("Content-Type", "application/x-msnmsgrp2p"); message.addField("P2P-Dest", contactHandle_); message.setBinaryData(header.data(), 0, footer, 0); // Send the message emit putMsg(message); shouldSendAck_ = false; #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: ACK Transmitted (flags=" << ackType << ")." << endl; #endif } /** * Constructs and sends an P2P Message. * * This method includes some of the global fields in the message body. * * @param messageData The message to send. This could be a binary file data block. * @param flagField The flag-field content. * @param footerCode The message footer. */ 01682 void P2PApplication::sendP2PMessage(const QByteArray &messageData, int flagField, uint footerCode) { #ifdef KMESSTEST ASSERT( messageData.size() <= 1202 ); #endif MimeMessage message; // Reset this flag to make sure no other // "500 Internal Error" will be sent afterwards. gotSlpMessage_ = false; // Initialize the header QByteArray header(48); header.fill(0x00); // Determine the message ID: if(messageID_ == 0) { // This is the first P2P message we send, initialize message ID messageID_ = generateID(); } else if(messageOffset_ == 0) { // Only increments the ID if this is not a "fragmented message". messageID_++; } // Determine the message size: uint messageSize = messageData.size(); uint totalSize; uint offsetField; if(messageTotalSize_ == 0) { // Normal message: totalSize = messageData.size(); offsetField = 0; } else { // Splitted message: totalSize = messageTotalSize_; offsetField = messageOffset_; } // Generate Unique ID for this message lastUniqueID_ = generateID(); // Update the header P2PMessage::insertBytes(header, sessionID_, 0); // 1: Session ID P2PMessage::insertBytes(header, messageID_, 4); // 2: BaseIdentifier (starts random [4, 4294967295]) P2PMessage::insertBytes(header, offsetField, 8); // 3: Offset (if more then 1202 bytes to send) P2PMessage::insertBytes(header, totalSize, 16); // 4: Total size of the message P2PMessage::insertBytes(header, messageSize, 24); // 5: This message size, size of previous if no data P2PMessage::insertBytes(header, flagField, 28); // 6: Message type (flags) P2PMessage::insertBytes(header, lastUniqueID_, 32); // 7: Field2 of previous for ACK-messages (base message ID) //P2PMessage::insertBytes(header, 0, 36); // 8: Field7 of previous for ACK-messages (unique ID) //P2PMessage::insertBytes(header, 0, 40); // 9: Field4 of previous for ACK-messages (size) // Update the offset fields for splitted messages if(messageTotalSize_ != 0) { messageOffset_ += messageSize; // If we transmitted all parts, reset the fields for the next normal message if(messageOffset_ >= messageTotalSize_) { messageOffset_ = 0; messageTotalSize_ = 0; } } // At the end of the message we need to add an footer of 4 bytes. // This is little endian format, so we can't use P2PMessage::insertBytes() // // Possible values: // 0x00 session // 0x01 for display/emoticon // 0x02 filetransfer, or start menu code char footer[4]; footer[0] = (char) ((footerCode >> 24) & 0xFF); footer[1] = (char) ((footerCode >> 16) & 0xFF); footer[2] = (char) ((footerCode >> 8) & 0xFF); footer[3] = (char) ( footerCode & 0xFF); // Assign the message fields message.addField("MIME-Version", "1.0"); message.addField("Content-Type", "application/x-msnmsgrp2p"); message.addField("P2P-Dest", contactHandle_); message.setBinaryData(header.data(), messageData.data(), footer, messageData.size()); // Send the message emit putMsg(message); #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Message transmitted " << "(SID=" << sessionID_ << " MID=" << messageID_ << " UID=" << lastUniqueID_ << " flags=" << flagField << " size=" << messageSize << ")" << endl; #endif } /** * Close the session by sending a SLP BYE message. * This should only be called if we sent the INVITE as well. * * Once the BYE message is sent, it waits for the last ACK to arrive, * ACKs that message and terminates the application instance afterwards. * * If the last ACK doesn't arrive in a certain timeout, the application is also terminated. */ 01809 void P2PApplication::sendSlpBye() { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Sending SLP BYE message." << endl; #endif #ifdef KMESSTEST // Only send a BYE if we started it ASSERT( isUserStartedApp() ); ASSERT( waitingState_ == P2P_WAIT_DEFAULT ); #endif // Content-Type: QString contentType; contentType = "application/x-msnmsgr-sessionclosebody"; // Handle QString myHandle = CurrentAccount::instance()->getHandle(); // Create the message QString slpMessage; slpMessage = "BYE MSNMSGR:" + contactHandle_ + " MSNSLP/1.0\r\n" "To: <msnmsgr:" + contactHandle_ + ">\r\n" "From: <msnmsgr:" + myHandle + ">\r\n" "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n" // from our INVITE "CSeq: 0\r\n" "Call-ID: " + callID_ + "\r\n" // from our INVITE "Max-Forwards: 0\r\n" "Content-Type: " + contentType + "\r\n" "Content-Length: 3\r\n" "\r\n"; // Send the message sendSlpMessage(slpMessage); // Don't run endApplication() yet, there is one ACK we wait for... waitingState_ = P2P_WAIT_FOR_SLP_BYE_ACK; waitingTimer_->start(30000, true); // 30 sec #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for BYE ACK..." << endl; #endif } /** * Send an error response at SLP level. * This can be done to decline a file tranfer or tell the other client it sent bad SLP data. * * @param statusLine The status line, for example "200 OK", or "603 Decline" * @param sessionID ID of the current session (can be empty) * @param messageContentType Contact type of the message which is in error (can be empty) * * The session ID can be 0 to indicate there was an error in the Content-Type. Setting the * session ID and Content-Type to the values used in the INVITE message indicates * you like to cancel that invitation. * * Once this method returns the SessionID is set to 0 (which shouldn't be a problem * since you use this method to end a session). This is because SLP messages are always * sent without an SessionID. */ 01871 void P2PApplication::sendSlpError(const QString &statusLine, const uint sessionID, const QString &messageContentType) { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Sending SLP error response: " << statusLine << endl; #endif #ifdef KMESSTEST // Test, as other invocations likely result in undefined client behavour // ( ASSERT( ! shouldSendAck_ ); ASSERT( gotSlpMessage_ ); ASSERT( waitingState_ == P2P_WAIT_DEFAULT ); #endif QString myHandle = CurrentAccount::instance()->getHandle(); // Determine Content-Type and Content-Length fields QString content; QString contentType; uint contentLength; if(sessionID == 0) { // Used to indicate a general error content = QString::null; contentType = "null"; contentLength = 0; } else { #ifdef KMESSTEST ASSERT(messageContentType != 0 && ! messageContentType.isEmpty()); #endif // Used to indicate an error in the Invite, or decline a session content = "SessionID: " + QString::number(sessionID) + "\r\n\r\n\0"; contentLength = content.length(); contentType = messageContentType; } // Create the message QString slpMessage; slpMessage = "MSNSLP/1.0 " + statusLine + "\r\n" "To: <msnmsgr:" + contactHandle_ + ">\r\n" "From: <msnmsgr:" + myHandle + ">\r\n" "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n" // Indicates which INVITE was in error. "CSeq: 0\r\n" "Call-ID: " + callID_ + "\r\n" "Max-Forwards: 0\r\n" "Content-Type: " + contentType + "\r\n" "Content-Length: " + QString::number(contentLength) + "\r\n" + (contentLength > 0 ? "\r\n" : "") + content; // Send the message sendSlpMessage(slpMessage); // Wait for the message to be ACK-ed. waitingState_ = P2P_WAIT_FOR_SLP_ERR_ACK; waitingTimer_->start(30000, true); // 30 sec #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for SLP ACK..." << endl; #endif } /** * Start a new MSNSLP session * * @param sessionID Identifies the Session ID you've chosen. * @param contentType Content-Type of the message body. * @param message Invitation message body. */ 01946 void P2PApplication::sendSlpInvitation(uint sessionID, const QString &contentType, const MimeMessage &message) { #ifdef KMESSTEST ASSERT(! contentType.isEmpty() ); ASSERT(! contactHandle_.isEmpty() ); ASSERT( waitingState_ == P2P_WAIT_DEFAULT ); #endif #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Sending INVITE message" << endl; #endif // Mime message conversion QString mimeBody = message.getFields(); // Content handles QString myHandle = CurrentAccount::instance()->getHandle(); // Identifiers callID_ = generateGUID(); // Identifies the session branch_ = generateGUID(); // Identifies the INVITE request // TODO: this is a QCString now but it must finish with a \0. // Create the message QString slpMessage; slpMessage = "INVITE MSNMSGR:" + contactHandle_ + " MSNSLP/1.0\r\n" "To: <msnmsgr:" + contactHandle_ + ">\r\n" "From: <msnmsgr:" + myHandle + ">\r\n" "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n" "CSeq: 0\r\n" "Call-ID: " + callID_ + "\r\n" "Max-Forwards: 0\r\n" "Content-Type: " + contentType + "\r\n" // Starts MSNSLP Session. "Content-Length: " + QString::number(mimeBody.length() + 5) + "\r\n" "\r\n" + mimeBody + "\r\n"; // Send the message sessionID_ = 0; // Session ID is zero for negoriations. messageOffset_ = 0; messageTotalSize_ = 0; sendSlpMessage(slpMessage); // If the client accepts our invitation, we // use the session ID in the next ACK message. sessionID_ = sessionID; // Wait for the contact to accept waitingState_ = P2P_WAIT_CONTACT_ACCEPT; waitingTimer_->start(30000, true); // 30 sec (MSN6 also has a 30 sec limit for this) #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for contact to accept..." << endl; #endif } /** * Send a given string using sendP2PMessage(). * The SLP message is always sent with a sessionid of zero. */ 02012 void P2PApplication::sendSlpMessage(const QString &slpMessage) { // Make sure the message is sent with sessionid=0 (for BYE messages) unsigned long oldSessionID = sessionID_; sessionID_ = 0; // Get the raw utf8 encoded bytearray QCString utf8Message = slpMessage.utf8(); // Convert the QCString to QByteArray directly, // don't try to remove the extra padded '\0' character // (like MimeMessage::getMessage() does) // because MSN6/7 actually relies on this. // Send the message with the generic sendP2PMessage() method sendP2PMessage(utf8Message); // Restore the session ID sessionID_ = oldSessionID; } /** * Send the 200 OK message to accept the invitation. * Most likely the content message should contain the SessionID sent in the invitation. * Once the confirmation message has been sent, this class will automatically * use the correct Session ID (because it was read when the invite was received). * * @param message Body of the SLP message */ 02043 void P2PApplication::sendSlpOkMessage(const MimeMessage &message) { #ifdef KMESSTEST ASSERT(! branch_.isEmpty() ); ASSERT(! contactHandle_.isEmpty() ); ASSERT(! invitationContentType_.isEmpty() ); ASSERT( waitingState_ == P2P_WAIT_DEFAULT ); #endif #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Sending MSNSLP/1.0 200 OK message." << endl; #endif // Get message content and Content-Type bool gotTransferInvitation = (invitationContentType_ == "application/x-msnmsgr-transreqbody"); QString content = message.getFields(); QString contentType = (gotTransferInvitation ? "application/x-msnmsgr-transrespbody" : invitationContentType_); QString myHandle = CurrentAccount::instance()->getHandle(); // Send the SLP OK message to tell the other client you'd like to start the transfer. QString slpMessage; slpMessage = "MSNSLP/1.0 200 OK\r\n" "To: <msnmsgr:" + contactHandle_ + ">\r\n" "From: <msnmsgr:" + myHandle + ">\r\n" "Via: MSNSLP/1.0/TLP ;branch=" + branch_ + "\r\n" "CSeq: " + QString::number(invitationCSeq_ + 1) + "\r\n" "Call-ID: " + callID_ + "\r\n" "Max-Forwards: 0\r\n" "Content-Type: " + contentType + "\r\n" "Content-Length: " + QString::number(content.length() + 3) + "\r\n" "\r\n" + content + "\r\n"; sendSlpMessage(slpMessage); // Now that we've confirmed the session, use the sessionID // in the next messages: sessionID_ = invitationSessionID_; // Wait until our OK message is ACK-ed waitingState_ = P2P_WAIT_FOR_SLP_OK_ACK; waitingTimer_->start(30000, true); // 30 sec #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Waiting for contact to ACK the 200 OK message..." << endl; #endif } /** * Cleanup function, is called if we never received the data we were waiting for. */ 02097 void P2PApplication::slotCleanup() { #ifdef KMESSTEST ASSERT( waitingState_ != P2P_WAIT_DEFAULT ); #endif // Tell the other client we were waiting for a message to receive! sendCancelMessage(CANCEL_TIMEOUT); // MSN_FLAG_WAITING message // Important note: // Each case calling endApplication() contains a "return;" // All debug cases use "break;" so the final endApplication() is called. // Put a message at the console switch(waitingState_) { case P2P_WAIT_CONTACT_ACCEPT: { #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Contact didn't accept the invitation, terminating manually." << endl; #endif endApplication( i18n("The invitation was cancelled. A timeout occured waiting for the contact to accept.") ); return; } case P2P_WAIT_FOR_SLP_OK_ACK: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for SLP OK ACK (contact=" << contactHandle_ << ")." << endl; break; } case P2P_WAIT_FOR_PREPARE: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for data-preparation message (contact=" << contactHandle_ << ")." << endl; break; } case P2P_WAIT_FOR_PREPARE_ACK: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for data-preparation ACK (contact=" << contactHandle_ << ")." << endl; break; } case P2P_WAIT_FOR_FILE_DATA: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for file data transfer (contact=" << contactHandle_ << ")." << endl; break; } case P2P_WAIT_FOR_SLP_BYE: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for BYE message (contact=" << contactHandle_ << ")." << endl; endApplication(); // No message required, already got what we wanted return; } case P2P_WAIT_FOR_SLP_BYE_ACK: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for BYE-ACK (contact=" << contactHandle_ << ")." << endl; endApplication(); // No message required, already got what we wanted return; } case P2P_WAIT_FOR_SLP_ERR_ACK: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for SLP Error-ACK (contact=" << contactHandle_ << ")." << endl; endApplication(); // No message required, already got what we wanted return; } case P2P_WAIT_FOR_CLOSING_ACK: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for closing-ACK (contact=" << contactHandle_ << ")." << endl; endApplication(); // No message required, already got what we wanted return; } default: { kdDebug() << "P2PApplication: WARNING - Timeout waiting for ...? (contact=" << contactHandle_ << ")" << endl; } } endApplication( i18n("The invitation was cancelled. A timeout occured waiting for data.") ); } /** * A slot which sends the file data. * This slot is called from a timer event. */ 02190 void P2PApplication::slotSendData() { // KMess would crash otherwise... if( dataSource_ == 0 ) { kdDebug() << "P2PApplication: WARNING - Couldn't send more data, data source is null." << endl; // Don't send an error message, already happened before. return; } // Read the data from the file. char buffer[1202]; // 1202 is the max P2P data size. int bytesRead = dataSource_->readBlock( buffer, 1202 ); QByteArray data; data.duplicate( buffer, bytesRead ); #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Sending data." << " offset = " << messageOffset_ << " buffer = " << bytesRead << endl; #endif // Determine the flags: // Funny, MSN6 didn't seam to care which flag was set // while transferring the file. KMess however, does. if( dataType_ == P2P_TYPE_PICTURE ) { sendP2PMessage(data, P2PMessage::MSN_FLAG_DATA, 1); } else if( dataType_ == P2P_TYPE_FILE ) { sendP2PMessage(data, P2PMessage::MSN_FLAG_FILETRANSFER, 2); } else { if(messageOffset_ == 0) kdDebug() << "P2PApplication: Unknown data transfer mode." << endl; sendP2PMessage(data, 0, 1); // The best solution in this situation } // Disabled in backport // // Dispatch the progress to the derived class // showTransferProgress( messageOffset_ + bytesRead ); // Finished? if(messageTotalSize_ > 0) { // Run this slot again after 10 ms. QTimer::singleShot( 10, this, SLOT(slotSendData()) ); } else { // Transfer finished! #ifdef KMESSTEST ASSERT( dataSource_->atEnd() ); #endif // Reset data source dataSource_ = 0; dataType_ = P2P_TYPE_NEGOTIATION; #ifdef KMESSDEBUG_P2PAPPLICATION_GENERAL kdDebug() << "P2PApplication: Finished sending data, waiting for ACK..." << endl; #endif waitingState_ = P2P_WAIT_FOR_DATA_ACK; waitingTimer_->start(30000, true); // 30 sec } } #include "p2papplication.moc"