Logo Search packages:      
Sourcecode: kmess version File versions

p2papplication.h

/***************************************************************************
                          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.                                   *
 *                                                                         *
 ***************************************************************************/

#ifndef P2PAPPLICATION_H
#define P2PAPPLICATION_H

#include "application.h"

#include <qstring.h>
#include <qcstring.h>
#include "../p2pmessage.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
@endcode
@code
MIME-Version: 1.0
Content-Type: application/x-msnmsgrp2p
P2P-Dest: user@kmessdemo.org
     (blank line)
@endcode
@code
00000000 0d6ff300 00000000 00000000
52010000 00000000 52010000 00000000
c08d4900 00000000 00000000 00000000
@endcode
@code
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
00
@endcode
@code
00000000
@endcode
 *
 * 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 |
@endcode
 * The fields are:
 * - <tt>sid</tt>: Session-ID, will always be zero for SLP messages.
 * - <tt>mid</tt>: Message-ID, 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 messages 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. 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.
 *
 * 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
 */
00213 class P2PApplication : public Application
{
  Q_OBJECT

  public: // public methods

    // The constructor
                           P2PApplication(ApplicationList *applicationList);

    // The destructor
    virtual               ~P2PApplication();

    // 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);
    // The switchboard requested to resume the transfer.
    void                   resumeTransfer();
    // 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.
     */
00252     enum P2PDataType
    {
00254       P2P_TYPE_NEGOTIATION = 0,  ///< Default value, the session is negotiating.
00255       P2P_TYPE_PICTURE     = 1,  ///< Packet contains MsnObject data.
00256       P2P_TYPE_FILE        = 2,  ///< Packet contains file data.
00257       P2P_TYPE_INK         = 3   ///< Packet contains an Ink message.
    };

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


  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().
     */
00298     enum P2PMessageType
    {
00300       P2P_MSG_UNKNOWN,             ///< Default value, message type is not relevant.
00301       P2P_MSG_SESSION_INVITATION,  ///< The SLP INVITE message.
00302       P2P_MSG_SESSION_OK,          ///< The SLP OK message.
00303       P2P_MSG_TRANSFER_OK,         ///< The SLP OK message for the transfer invitation.
00304       P2P_MSG_DATA_PREPARATION,    ///< The data preparation message.
00305       P2P_MSG_DATA,                ///< The actual data message.
00306       P2P_MSG_SESSION_BYE,         ///< The SLP BYE message.
00307       P2P_MSG_TRANSFER_DECLINE,    ///< The SLP 603 Decline message.
00308       P2P_MSG_SLP_ERROR            ///< One of the possible SLP error messages.
    };

    /**
     * @brief Meta data on an unacked message.
     */
00314     struct UnAckedMessage
    {
      // Data for ACK message:
00317       unsigned long  dataSize;     ///< The sent data size.
00318       unsigned long  messageID;    ///< The sent message ID.
00319       unsigned long  totalSize;    ///< The sent total size.
00320       unsigned long  sessionID;    ///< The sent session ID.
00321       unsigned long  uniqueID;     ///< The sent unique ID field.
00322       uint           sentTime;     ///< The time the message was sent.
00323       P2PMessageType messageType;  ///< The meta type of the message.
    };

    // 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;
    // Parse a ACK messsage (in certain situations we need to active some methods)
    void                   gotAck(const P2PMessage &message, const P2PMessageType messageType);
    // 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);
    // 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
    void                   sendP2PMessage(const QByteArray &messageData, int flagField = 0, uint footerCode = 0, P2PMessageType messageType = P2P_MSG_UNKNOWN);
    // Send an SLP BYE message to close the session
    void                   sendSlpBye();
    // Send an SLP invitation message
    void                   sendSlpInvitation(const MimeMessage &message, const QString &contentType);
    // 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);


  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();
    // Send the file data
    void                   slotSendData();


  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.
     */
00408     enum P2PWaitingState
    {
      // It's recommended to keep these values, as they're also printed by kdWarning() statements!

00412       P2P_WAIT_DEFAULT          = 0,  ///< Not waiting at all.
00413       P2P_WAIT_FOR_SLP_OK_ACK   = 1,  ///< Waiting for contact to ack the the SLP OK message.
00414       P2P_WAIT_FOR_FILE_DATA    = 2,  ///< Waiting for contact to send file data.
00415       P2P_WAIT_FOR_PREPARE      = 3,  ///< Waiting for contact to send some prepare message.
00416       P2P_WAIT_FOR_PREPARE_ACK  = 4,  ///< Waiting for contact to ack the data preparation.
00417       P2P_WAIT_FOR_SLP_BYE      = 5,  ///< Waiting for contact to send the SLP BYE.
00418       P2P_WAIT_FOR_SLP_BYE_ACK  = 6,  ///< Waiting for contact to ack the SLP BYE message.
00419       P2P_WAIT_FOR_SLP_ERR_ACK  = 7,  ///< Waiting for contact to ack the SLP error message.

00421       P2P_WAIT_CONTACT_ACCEPT   = 8,  ///< Waiting for the contact to accept out SLP INVITE.
00422       P2P_WAIT_TRANSFER_ACCEPT  = 9,  ///< Waiting for the transfer message to be accepted.

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

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

00431       P2P_WAIT_FOR_CONNECTION2  = 15  ///< Waiting until a connection is made to the remote contact.
    };


    // 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_;
    // True if we got an SLP message (requires a different error handling)
    bool                   gotSlpMessage_;
    // The last incoming message.
    UnAckedMessage         lastIncomingMessage_;
    // The list of expected incoming ACKs
    QPtrList<UnAckedMessage> incomingMessages_;
    // 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          messageID_;
    // Message offset, if we're sending "fragmented" content
    unsigned long          messageOffset_;
    // Size of the message, if we're sending "fragmented" content
    unsigned long          messageTotalSize_;
    // 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_;
};

#endif

Generated by  Doxygen 1.6.0   Back to index