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


                          p2papplication.h -  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 "application.h"

#include <qstring.h>
#include <qcstring.h>
#include "../p2pmessage.h"

#include "../extra/p2pfragmenttracker.h"

class QIODevice;
class MimeMessage;
class QTimer;
class QBuffer;
class ApplicationList;

 * @brief An Application subclass implementing MSN6 P2P-style conversations.
 * This base class aims to hide all complex details of the P2P communication.
 * To implement a P2P service, create a derived class which
 * implements the userStarted*, contactStarted* and getAppId() methods.
 * P2P messages can be received from switchboard or direct connections.
 * The message flow looks like:
 * - the MsnSwitchboardConnection receives the message.
 * - the ChatMaster relays it to the correct contact.
 * - an ApplicationList relays it to the correct P2P session.
 * - an instance of P2PApplication processes it.
 * The incoming message is received at the gotMessage() method.
 * User-started invitations are handled this way:
 * - The start() method is called externally to start the application.
 *   This method calls initializes the application and calls
 *   userStarted1_UserInvitesContact()
 * - The userStarted1_UserInvitesContact() allows you to create the
 *   invitation message and use sendSlpInvitation() to send it.
 * - The userStarted2_ContactAccepts() method is called when the
 *   contact accepts the invitation (sending a 200/OK message).
 *   It's possible to call sendSlpTransferInvitation() here.
 * - The userStarted3_UserPrepares() method is called when the session is ready to transfer data.
 *   The  data-preparation message was received, a direct connection is ready,
 *   or data can be sent over the switchboard.
 *   From this method you can call start an external application,
 *   call sendDataPreparationAck() or sendData().
 * - The gotData() method is called for every received data packet (e.g. file or picture data).
 *   You can store the data in a file, buffer it, etc..
 *   The session will terminate automatically when all data is received.
 * Contacted-started invitations are handled this way:
 * - The gotMessage() method is called externally to handle an incoming P2P message.
 * - The contactStarted1_ContactInvitesUser() method is called when the invitation message is fully received.
 *   This allows you to extract the details from the invitation message.
 *   When needed, call offerAcceptOrReject() to show an accept/cancel link in the chat window.
 *   Otherwise, call contactStarted2_UserAccepts() directly to "automatically accept" the invitation.
 * - The contactStarted2_UserAccepts() method is called when the user hits the 'accept' link
 *   in the chat window. The accept message should be prepared and sent with sendSlpOkMessage().
 * - The contactStarted3_ContactConfirmsAccept() method is called when the contact ACKs the "SLP OK" message.
 *   From here, you can call sendDataPreparation() to send the data preparation message,
 *   or simply wait for the data transfer to start (contactStarted4_..).
 * - The contactStarted4_ContactConfirmsPreparation() is called called when the session is ready to tranfer data.
 *   Either the data-preparation message was ACKed, or the direct connection is available.
 *   You can call sendData() from here to start the data transfer.
 *   The session will terminate automatically if all data has been sent.
 * To cancel a running session, call userAborted() or use sendCancelMessage().
 * In most cases the application will terminate automatically after the contact ACK-ed the cancel message.
 * Otherwise, endApplication() needs to be called manually.
 * When a data transfer is active, the methods showTransferMessage(), showTransferProgress(), showTransferComplete()
 * are called to inform the derived class of the status. It can update the GUI from these methods.
 * --- internals ----
 * Internally, this code ensures valid P2P messages are sent.
 * A P2P message consists of a a 48 byte binary header followed by an optional pyload.
 * The payload which can be binary data or plain text MIME data (the MSNSLP messages).
 * The messages can be sent over the switchboard, or direct connection.
 * When the P2P messages are sent over the switchboard, they will be wrapped
 * in a standard MIME message and a footer code will be appended.
 * This is a P2P message with SLP payload sent over the switchboard:
 * @code
MSG contact@kmessdemo.org Test%20user 465
MIME-Version: 1.0
Content-Type: application/x-msnmsgrp2p
P2P-Dest: user@kmessdemo.org
     (blank line)
00000000 0d6ff300 00000000 00000000
52010000 00000000 52010000 00000000
c08d4900 00000000 00000000 00000000
MSNSLP/1.0 200 OK
To: <msnmsgr:user@kmessdemo.org>
From: <msnmsgr:contact@kmessdemo.org>
Via: MSNSLP/1.0/TLP ;branch={EA820F90-802C-48A3-AE53-F660111220FF}
CSeq: 1
Call-ID: {44AAC3F3-30D7-49B7-9732-AF7D32BD36B3}
Max-Forwards: 0
Content-Type: application/x-msnmsgr-sessionreqbody
Content-Length: 22

SessionID: 114164
 * The message is built of the following parts:
 * - The Switchboad payload command with total payload length
 * - The MIME header, with a blank line as separator.
 * - The 48 byte P2P header (written in hex notation here).
 * - The P2P payload (SLP preamble, header and body).
 * - A footer code, only appended when the message is sent over the switchboard.
 * There is a limit of 1202 bytes for the P2P payload, longer payloads are splitted into multiple P2P messages.
 * In practise, that only happens with the SLP INVITE and file data messages.
 * The binary P2P header has the following layout:
 * @code
0    4    8        16        24   28   32   36   40        48
|sid |mid |offset   |totalsize|size|flag|uid |auid|a-datasz |
 * The fields are:
 * - <tt>sid</tt>: Session-ID, true identifier of the session. It will always be zero for SLP messages.
 * - <tt>mid</tt>: Message-ID, identifier of a payload, used to match splitted payload parts.
 * - <tt>offset</tt>: current byte position of the part.
 * - <tt>totalsize</tt>: total size the payload. When the payload only needs one part, it's equal to 'size'.
 * - <tt>size</tt>: size of this message part.
 * - <tt>flag</tt>: the message type.
 * - <tt>uid</tt>: unique ID of the message, is used to trace ACK responses back.
 * - <tt>auid</tt>: zero for normal messages.
 * - <tt>a-datasz</tt>: zero for normal messages.
 * For ACK messages some the fields are used differently:
 * - <tt>sid</tt>: contains the Session-ID of the previous message.
 * - <tt>mid</tt>: has a new generated value.
 * - <tt>totalsize</tt>: contains the total size of the ACKed message.
 * - <tt>uid</tt>: contains the Message-ID of the ACKed message.
 * - <tt>auid</tt>: contains the Unique-ID of the ACKed message.
 * - <tt>a-datasz</tt>: ACK Data size, contains the 'size' field of the ACKed message.
 * For direct connection handshake messages, the last 3 fields will be replaced with the Nonce value.
 * The flag field can be one of these values:
 * - <tt>0x02</tt>: A normal ACK message.
 * - <tt>0x04</tt> and <tt>0x06</tt>: A timeout ACK message, the contact was waiting for something!
 * - <tt>0x08</tt>: An error message, aborts the session instantly (much like TCP RST).
 * - <tt>0x20</tt>: MsnObject data.
 * - <tt>0x40</tt>: some BYE error ack.
 * - <tt>0x80</tt>: some BYE error ack.
 * - <tt>0x100</tt>: Direct connection handshake.
 * - <tt>0x1000030</tt>: File data.
 * These fields and flags appear to make it utterly complex.
 * However, P2P messages can be divided into three groups:
 * - Messages without flags but with payload. These messages have a plain text MIME payload (the MSNSLP message),
 *   used to negotiate the session parameters. The Session-ID field is always zero for these messages.
 * - Messages with flags and payload. These messages are used to transfer the actual data.
 * - Messages with flags but without payload. These messages are used as "control messages".
 * Each message expects to get some ACK message back, clients actually wait to send more
 * data until an ACK or error message is received. KMess uses timers to detect whether
 * a contact did not sent a message back. The application will be aborted automatically
 * to avoid stale application in the memory.
 * To match a incoming P2P message with a P2PApplication instance, the following identifiers are used:
 * - <tt>sid</tt> or session-id: this is the true Session-ID of the complete session.
 *   If the session-id is zero however and there is a payload, different rules apply.
 *   In that case, the payload contains an SLP message (header + body) with the following idenfiers in the SLP header:
 *   - <tt>Call-ID</tt>: a guid identifier of the "SLP call". It spans the entire session, so other SLP messages (like 200 OK, or BYE) can be linked to the session which got the INVITE in the first place.
 *   - <tt>branch</tt>: a guid identifier the the single SLP message, it's used by SLP responses to reffer to the specific INVITE message in the session.
 * - <tt>mid</tt> or message-id: the identifier of a complete payload. If a payload is too large, it's splitted across multiple P2P messages. Each fragment has the same message-id.
 * - <tt>uid</tt> or unique-id: a real unique id for a single P2P message.
 *   P2P responses (messages with flags but without payload) reffer to this ID to point out which message is in error.
 * The ApplicationList::gotMessage() method uses these identifiers to find the correct P2PApplication
 * instance for a message. Note that the session-id is also transferred in the body of an SLP INVITE message.
 * The P2P packets are only routed by the ApplicationList using the binary header fields and SLP header.
 * Some third party clients (including KMess 1.4.x) seam to send incorrect message fields.
 * This class is able to handle those not-entirely valid messages as well.
 * The situations where this happens are marked as "HACK" in the code.
 * @author Diederik van der Boor
 * @ingroup Applications
00229 class P2PApplication : public Application

  public: // public methods

    // The constructor
                           P2PApplication(ApplicationList *applicationList);

    // The destructor
    virtual               ~P2PApplication();

    // The contact cancelled the session
    virtual void           contactAborted(const QString &message = 0);
    // Returns the branch.
    QString                getBranch() const;
    // Returns the call ID (identifies the invitation).
    QString                getCallID() const;
    // Return the message ID used in the previous message received from the contact.
    unsigned long          getLastContactMessageID() const;
    // Returns the nonce that's being expected.
    const QString &        getNonce() const;
    // Returns the session ID.
    unsigned long          getSessionID() const;
    // Parse a received message... (implements pure virtual method from base class)
    void                   gotMessage(const P2PMessage &p2pMessage);
    // Verify whether the application can handle the given ACK message.
    bool                   hasUnAckedMessage(const P2PMessage &p2pMessage);
    // Called when the connection is ready to send more file data.
    bool                   sendNextDataParts( int preferredFragments );
    // The user cancelled the session
    virtual void           userAborted();

  protected: // protected mehods

     * @brief  The data type being sent.
     * This value is used to set the footer code of P2P messages which are sent over the switchboard.
00270     enum P2PDataType
00272       P2P_TYPE_NEGOTIATION = 0,  ///< Default value, the session is negotiating.
00273       P2P_TYPE_PICTURE     = 1,  ///< Packet contains MsnObject data.
00274       P2P_TYPE_FILE        = 2,  ///< Packet contains file data.
00275       P2P_TYPE_INK         = 3   ///< Packet contains an Ink message.

    // Scheduelle termination, but wait for last incoming packets
    void                   endApplicationLater();
    // Step one of a contact-started chat: the contact invites the user
    virtual void           contactStarted1_ContactInvitesUser(const MimeMessage& message);
    // Step four of a contact-started chat: the contact confirms the data-preparation message.
    virtual void           contactStarted4_ContactConfirmsPreparation();
    // Generate the random numbers for the session-id and message-id.
    static unsigned long   generateID();
    // Return the content type read from the invitation message
    const QString&         getInvitationContentType() const;
    // Return the session id read from the invitation message
    unsigned long          getInvitationSessionID() const;
    // Called when data is received
    virtual void           gotData(const P2PMessage &message);
    // Send a cancel message
    void                   sendCancelMessage(const ApplicationCancelReason cancelReason);
    // Send the data
    void                   sendData(QIODevice *dataSource, const P2PDataType dataType);
    // Sends the data preparation message.
    void                   sendDataPreparation();
    // Method to be called from userStarted3_UserPrepares();
    void                   sendDataPreparationAck();
    // Send the invitation for a normal session
    void                   sendSlpSessionInvitation(uint sessionID, const QString &eufGuid, const int appId, const QString &context);
    // Send the invitation for a direct connection.
    void                   sendSlpTransferInvitation();
    // Send an SLP 200/OK message
    void                   sendSlpOkMessage(const MimeMessage &message);
    // The user rejected the invitation
    virtual void           userRejected();
    // Utility function to write P2P data to a file, also deals with message offsets.
    bool                   writeP2PDataToFile( const P2PMessage &message, QIODevice *file );

  private: // private methods

     * @brief The meta type of a P2P message.
     * These types are used by gotAck() to link the ACK message
     * to the original the sent message, sent by sendP2PMessage().
00320     enum P2PMessageType
00322       P2P_MSG_UNKNOWN             = 0,  ///< Default value, message type is not relevant.
00323       P2P_MSG_SESSION_INVITATION  = 1,  ///< The SLP INVITE message.
00324       P2P_MSG_SESSION_OK          = 2,  ///< The SLP OK message.
00325       P2P_MSG_TRANSFER_INVITATION = 9,  ///< The SLP INVITE message for the transfer.
00326       P2P_MSG_TRANSFER_OK         = 3,  ///< The SLP OK message for the transfer invitation.
00327       P2P_MSG_DATA_PREPARATION    = 4,  ///< The data preparation message.
00328       P2P_MSG_DATA                = 5,  ///< The actual data message.
00329       P2P_MSG_SESSION_BYE         = 6,  ///< The SLP BYE message.
00330       P2P_MSG_TRANSFER_DECLINE    = 7,  ///< The SLP 603 Decline message.
00331       P2P_MSG_SLP_ERROR           = 8   ///< One of the possible SLP error messages.

     * @brief Meta data on an unacked message.
00337     struct UnAckedMessage
      // Data for ACK message:
00340       unsigned long  dataSize;     ///< The sent data size.
00341       unsigned long  messageID;    ///< The sent message ID.
00342       unsigned long  totalSize;    ///< The sent total size.
00343       unsigned long  sessionID;    ///< The sent session ID.
00344       unsigned long  ackSessionID; ///< The sent unique ID field.
00345       uint           sentTime;     ///< The time the message was sent.
00346       P2PMessageType messageType;  ///< The meta type of the message.

    // Make sure no more data will be sent
    void                   abortDataSending();
    // Generate the strings for the BranchID and call-ID.
    static QString         generateGUID();
    // Find the unacked message record associated with the received P2P message.
    UnAckedMessage *       getUnAckedMessage(const P2PMessage &ackMessage) const;
    // Find the unacked message which corresponds with given message type.
    UnAckedMessage *       getUnAckedMessage(const P2PMessageType messageType) const;
    // Parse a ACK messsage (in certain situations we need to active some methods)
    void                   gotAck(const P2PMessage &message, const P2PMessageType messageType);
    // Called when the ACK for the data preparation was received.
    void                   gotAck_dataPreparation();
    // Called when the ACK for the sent file data was received.
    void                   gotAck_dataReceived();
    // Called when the ACK for the SLP BYE message was received.
    void                   gotAck_slpBye();
    // Called when the ACK for a SLP Error was received.
    void                   gotAck_slpError();
    // Called when the ACK for the first SLP INVITE message was received.
    void                   gotAck_slpSessionInvitation();
    // Called when the ACK of the SLP OK message was received.
    void                   gotAck_slpSessionOk();
    // Called when the ACK for the SLP transfer decline message was received.
    void                   gotAck_slpTransferDecline();
    // Called when the ACK for the SLP transfer INVITE message was received.
    void                   gotAck_slpTransferInvitation();
    // Called when the ACK for the SLP transfer OK mesages was received.
    void                   gotAck_slpTransferOk();
    // Got an direct connection handshake.
    void                   gotDirectConnectionHandshake(const P2PMessage &message);
    // Called internally when data is received, this eventually calls gotData()
    void                   gotDataFragment(const P2PMessage &message);
    // Called when the data preparation message is received
    void                   gotDataPreparation(const P2PMessage &message);
    // Received a P2P message with the error-flag set. This method aborts the application.
    void                   gotErrorAck(const P2PMessage &message);
    // Got a message fragment with SessionID 0
    void                   gotNegotiationFragment(const P2PMessage &message);
    // Got a message with SessionID 0
    void                   gotNegotiationMessage(const MimeMessage &slpMessage, const QString &preamble);
    // Got an MSNSLP ACK message
    void                   gotSlpAck(const MimeMessage &slpMessage);
    // Got an MSNSLP BYE message
    void                   gotSlpBye(const MimeMessage &slpMessage);
    // Got an MSNSLP INVITE message
    void                   gotSlpInvite(const MimeMessage &slpMessage);
    // Got an MSNSLP 200 OK header
    void                   gotSlpOk(const MimeMessage &slpMessage);
    // Got an MSNSLP session invitation
    void                   gotSlpSessionInvitation(const MimeMessage &slpMimeBody);
    // Got an MSNSLP status header
    void                   gotSlpStatus(const MimeMessage &slpMessage, const QString &preamble);
    // Got an MSNSLP transfer invitation
    void                   gotSlpTransferInvitation(const MimeMessage &slpMimeBody);
    // Got an MSNSLP transfer invitation response
    void                   gotSlpTransferResponse(const MimeMessage &slpMimeBody, bool secondInvite = false);
    // Received a message with the timeout-flag set. This method aborts the application.
    void                   gotTimeoutAck(const P2PMessage &message);
    // Received a P2P message with the aborted-flag set. This method aborts the application.
    void                   gotTransferAbortedAck(const P2PMessage &message);
    // Verify whether a given message is still unacked.
    bool                   hasUnAckedMessage(const P2PMessageType messageType);
    // Signal the derived class it can initiate the file transfer
    void                   initiateTransfer();
    // Send the direct connection handshake
    void                   sendDirectConnectionHandshake();
    // Send a P2P ACK message
    void                   sendP2PAck(int ackType = P2PMessage::MSN_FLAG_ACK, UnAckedMessage *originalMessageData = 0);
    // Send a P2P message
    bool                   sendP2PMessage(const QByteArray &messageData, int flagField = 0, uint footerCode = 0, P2PMessageType messageType = P2P_MSG_UNKNOWN, unsigned long messageID = 0);
    // Send an SLP BYE message to close the session
    void                   sendSlpBye();
    // Send an SLP invitation message
    void                   sendSlpInvitation(const MimeMessage &message, const QString &contentType, P2PMessageType messageType);
    // Send an SLP error message (to decline an invitation for example)
    void                   sendSlpError(const QString &statusLine, const uint sessionID = 0,
                                        const QString &messageContentType = 0,
                                        P2PMessageType messageType = P2P_MSG_SLP_ERROR);
    // Send a given string using sendP2PMessage()
    void                   sendSlpMessage(const QString &slpMessage, P2PMessageType messageType);
    // Test if there are still unacked messages.
    void                   testUnAckedMessages(bool sendError);

  private slots:
    // Cleanup function, called if the last ACK isn't received at all
    void                   slotCleanup();
    // The direct connection is authorized.
    void                   slotConnectionAuthorized();
    // The direct connection is established.
    void                   slotConnectionEstablished();
    // The direct connection could not be made.
    void                   slotConnectionFailed();

  private: // private fields

     * @brief An indication which message is expected from the contact.
     * This value is mostly used for debugging.
     * Incoming acks are nowadays handled by gotAck() using a #P2PMessageType value.
     * For some situations the waiting state is used to detect errors.
     * The slotCleanup() method uses this value to output a descriptive error message.
00455     enum P2PWaitingState
      // It's recommended to keep these values, as they're also printed by kdWarning() statements!

00459       P2P_WAIT_DEFAULT             = 0,  ///< Not waiting at all.
00460       P2P_WAIT_FOR_SLP_OK_ACK      = 1,  ///< Waiting for remote client to ack the the SLP OK message.
00461       P2P_WAIT_FOR_FILE_DATA       = 2,  ///< Waiting for remote client to send file data.
00462       P2P_WAIT_FOR_PREPARE         = 3,  ///< Waiting for remote client to send some prepare message.
00463       P2P_WAIT_FOR_PREPARE_ACK     = 4,  ///< Waiting for remote client to ack the data preparation.
00464       P2P_WAIT_FOR_SLP_BYE         = 5,  ///< Waiting for remote client to send the SLP BYE.
00465       P2P_WAIT_FOR_SLP_BYE_ACK     = 6,  ///< Waiting for remote client to ack the SLP BYE message.
00466       P2P_WAIT_FOR_SLP_ERR_ACK     = 7,  ///< Waiting for remote client to ack the SLP error message.

00468       P2P_WAIT_FOR_INVITE_ACK      = 18, ///< Waiting for remote client to ack the SLP INVITE.
00469       P2P_WAIT_CONTACT_ACCEPT      = 8,  ///< Waiting for the contact to accept out SLP INVITE.
00470       P2P_WAIT_FOR_TRANSFER_ACCEPT = 9,  ///< Waiting for the transfer message to be accepted.
00471       P2P_WAIT_FOR_INVITE_TR_ACK   = 19, ///< Waiting for the transfer INVITE message to be acked.

00473       P2P_WAIT_FOR_CONNECTION      = 10, ///< Waiting until a direct connection is made or all failed
00474       P2P_WAIT_FOR_DATA_ACK        = 11, ///< Waiting for the ack message of the data we sent.
00475       P2P_WAIT_FOR_TRANSFER_ACK    = 12, ///< Waiting for contact to ack the SLP OK transfer message

00477       P2P_WAIT_FOR_HANDSHAKE       = 13, ///< Direct connection is established, waiting for handshake.
00478       P2P_WAIT_FOR_HANDSHAKE_OK    = 14, ///< Direct connection handshake is sent, waiting for response.

00480       P2P_WAIT_FOR_CONNECTION2     = 15, ///< Waiting until a connection is made to the remote contact.
00481       P2P_WAIT_USER_ACCEPT         = 16, ///< Waiting for the user to accept out SLP INVITE. Complimentary to Application::isWaitingForUser().

00483       P2P_WAIT_END_APPLICATION     = 17  ///< Waiting for a short while before destroying this object (like TCP TIME_WAIT state)

    // The application is aborting
    bool                   aborting_;
    // The application list, parent of all application objects.
    ApplicationList       *applicationList_;
    // Message buffer, required to deal with splitted MSNSLP messages
    QBuffer               *buffer_;
    // Branch ID, identifies the INVITE-message
    QString                branch_;
    // Call ID, identifies the session at MSNSLP level.
    QString                callID_;
    // The data source
    QIODevice             *dataSource_;
    // The type of data to send.
    P2PDataType            dataType_;
    // Message ID, if we're sending "fragmented" content
    unsigned long          fragmentMessageID_;
    // Message offset, if we're sending "fragmented" content
    unsigned long          fragmentOffset_;
    // Size of the message, if we're sending "fragmented" content
    unsigned long          fragmentTotalSize_;
    // Track the status of a fragmented message (incoming currently)
    P2PFragmentTracker     fragmentTracker_;
    // True if we got an SLP message (requires a different error handling)
    bool                   gotSlpMessage_;
    // The last incoming message.
    UnAckedMessage         lastIncomingMessage_;
    // Content type from the invitation message
    QString                invitationContentType_;
    // CSeq field from the invitation message
    int                    invitationCSeq_;
    // SessionID field from the invitaiton message
    unsigned long          invitationSessionID_;
    // Session ID, identifies the session at MSNP2P level.
    unsigned long          sessionID_;
    // Message ID, updated each time a message has been sent
    unsigned long          nextMessageID_;
    // The nonce for direct connections
    QString                nonce_;
    // The list of outgoing messages to be acked
    QPtrList<UnAckedMessage> outgoingMessages_;
    // True if an ACK was not sent yet.
    bool                   shouldSendAck_;
    // True if the user needs to acknowledge the message. (doesn't send an ACK automatically)
    bool                   userShouldAcknowledge_;
    // The current waiting state
    P2PWaitingState        waitingState_;
    // The waiting timer (for timeout events)
    QTimer                *waitingTimer_;


Generated by  Doxygen 1.6.0   Back to index