/*************************************************************************** * * * 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 "p2pmessage.h" #include "../kmessdebug.h" /* // Constructor to create an empty P2P message P2PMessage::P2PMessage() : sessionID_(0) , messageID_(0) , dataOffset_(0) , dataSize_(0) , flags_(0) , totalSize_(0) , ackSessionID_(0) , ackUniqueID_(0) , ackDataSize_(0) { } */ // Build a P2P message based on the received binary data. 00033 P2PMessage::P2PMessage(const char *data, uint size) { // Copy the binary data to our own managed copy message_ = QByteArray( data, size ); parseP2PHeader(); } // Build a P2P message based on the received binary data. P2PMessage::P2PMessage(const QByteArray &message) : message_(message) { parseP2PHeader(); } // Retreives the current session ID. quint32 P2PMessage::getSessionID() const { return sessionID_; } // Retreives the current message ID. */ quint32 P2PMessage::getMessageID() const { return messageID_; } // Retreives the current message offset. quint32 P2PMessage::getDataOffset() const { return dataOffset_; } // Retreives the current message size. quint32 P2PMessage::getDataSize() const { return dataSize_; } // Retreives the total message size. quint32 P2PMessage::getTotalSize() const { return totalSize_; } // Retreives the current message size. quint32 P2PMessage::getFlags() const { return flags_; } // Retrieves the ack session ID field. quint32 P2PMessage::getAckSessionID() const { return ackSessionID_; } // Retreives the ack unique ID field. quint32 P2PMessage::getAckUniqueID() const { return ackUniqueID_; } // Retreives the ack data size field. quint32 P2PMessage::getAckDataSize() const { return ackDataSize_; } // Retreives the nonce field (for direct connection handshake. QString P2PMessage::getNonce() const { #ifdef KMESSTEST KMESS_ASSERT( isConnectionHandshake() ); #endif return extractNonce(message_.data()); } // Return a pointer to the data. const char *P2PMessage::getData() const { return (message_.data() + 48); } // Negative ack message (0x01) bool P2PMessage::isNegativeAck() const { return (flags_ & MSN_FLAG_NEGATIVE_ACK) == MSN_FLAG_NEGATIVE_ACK; } // The message is an ACK (0x02) bool P2PMessage::isAck() const { return (flags_ & MSN_FLAG_ACK) == MSN_FLAG_ACK; } // Waiting for ack (0x06) bool P2PMessage::isWaitingForAck() const { return (flags_ & MSN_FLAG_WAITING_FOR_ACK) == MSN_FLAG_WAITING_FOR_ACK; } // Waiting for reply (0x04) bool P2PMessage::isWaitingForReply() const { return (flags_ & MSN_FLAG_WAITING) == MSN_FLAG_WAITING; } // Return true if the error flag was set (0x80) bool P2PMessage::isError() const { return (flags_ & MSN_FLAG_ERROR) == MSN_FLAG_ERROR; } // Return true if this is MsnObject data (e.g. pictures) or file data. bool P2PMessage::isData() const { // Possible values: // - 0x20 picture data till WLM 2009. // - 0x1000020 picture data in WLM 2009. // - 0x1000030 file data in WLM 2009. // They all have 0x20 in common. return ( ( flags_ & 0x20 ) == 0x20 ); } // Return true if the message is the data preparation message. bool P2PMessage::isDataPreparation() const { return sessionID_ != 0 && ( flags_ == 0 || flags_ == MSN_FLAG_DATA_2009 ) && dataSize_ == 4 && totalSize_ == 4; } // Returns true if the sender aborted it's own data transfer (0x40) bool P2PMessage::isAbortedSendingAck() const { return (flags_ & MSN_FLAG_ABORTED_SENDING) == MSN_FLAG_ABORTED_SENDING; } // Returns true if the receiver aborted the data transfer (0x80) bool P2PMessage::isAbortedReceivingAck() const { return (flags_ & MSN_FLAG_ABORTED_RECEIVING) == MSN_FLAG_ABORTED_RECEIVING; } // Returns true if the message is a direct-connection handshake message (0x100) bool P2PMessage::isConnectionHandshake() const { return (flags_ & MSN_FLAG_DC_HANDSHAKE) == MSN_FLAG_DC_HANDSHAKE; } // Return true if this is part of an SLP message. bool P2PMessage::isSlpData() const { return ( sessionID_ == 0 ) && ( flags_ == 0 || flags_ == MSN_FLAG_DATA_2009 ) && ( dataSize_ > 0 ); // HACK: added size constraint for Bot2k3 4.1 (sends an empty message with a zero flag) } // Return true if this is an fragment (splitted message packet). bool P2PMessage::isFragment() const { return (dataSize_ < totalSize_); } // Returns true if the message size exceeds the total size bool P2PMessage::isLastFragment() const { // offset + this message size >= the total size. return (dataOffset_ + dataSize_) >= totalSize_; } // Parse the binary P2P header bytes void P2PMessage::parseP2PHeader() { char *messageData = message_.data(); if(! messageData || message_.size() < 48) { // This clearly indicates an internal parsing error (in MimeMessage) // bodyData may be NULL kWarning() << "no data found to parse!"; sessionID_ = 0; messageID_ = 0; dataOffset_ = 0; totalSize_ = 0; dataSize_ = 0; flags_ = 0; ackSessionID_ = 0; ackUniqueID_ = 0; ackDataSize_ = 0; return; } // Header: // // 0 4 8 16 24 28 32 36 40 48 // |....|....|....|....|....|....|....|....|....|....|....|....| // |sid |mid |offset |totalsize|size|flag|asid|auid|a-datasz | // // More info can be found at http://siebe.bot2k3.net/docs/ sessionID_ = extractBytes ( messageData, 0); messageID_ = extractBytes ( messageData, 4); dataOffset_ = extractLongBytes ( messageData, 8); totalSize_ = extractLongBytes ( messageData, 16); dataSize_ = extractBytes ( messageData, 24); flags_ = extractBytes ( messageData, 28); ackSessionID_ = extractBytes ( messageData, 32); ackUniqueID_ = extractBytes ( messageData, 36); ackDataSize_ = extractLongBytes ( messageData, 40); // Verify data size! if( (uint) message_.size() < ( 48 + dataSize_ ) ) // message size could also contain the footer code. { kWarning().nospace() << "corrupt data size header detected " "(header=48" " datasize=" << dataSize_ << " total=" << message_.size() << ")"; dataSize_ = message_.size() - 48; } } // ------------------------------------------------------ // A utility to fill the byte data block void P2PMessage::insertBytes(QByteArray &buffer, const unsigned int value, const int offset) { // Also based on Kopete code. I started with a nice struct, // but not all c++ compilers use the same data size for the fields: /* * "The only restrictions that the language spec places are that * (1) a char is one byte, * (2) char <= short int <= int <= long int <= long long int. * The exact size is completely compiler dependent." * * gcc on i686-pc-linux-gnu gives these results: * - sizeof(int) = 4 * - sizeof(long int) = 4 * - sizeof(long long int) = 8 * this beats me. * * In other words, I'll use bitwise shifts to set bytes manually. */ buffer[offset + 0] = (char) ( value & 0xFF); buffer[offset + 1] = (char) ((value >> 8) & 0xFF); buffer[offset + 2] = (char) ((value >> 16) & 0xFF); buffer[offset + 3] = (char) ((value >> 24) & 0xFF); // I think it's obvious we're copying every byte individually here.. // However, note the bytes are placed in network order. (big endian) } void P2PMessage::insertNonce(QByteArray &buffer, const QString &nonce, const int offset) { // Remove the separators // The QString(..) call makes a copy because QString::remove isn't const. QString fixedNonce( QString(nonce).remove('-').remove('{').remove('}') ); #ifdef KMESSTEST KMESS_ASSERT( fixedNonce.length() == 32 ); // 32 hex codes will be saved as 16 bytes. KMESS_ASSERT( buffer.size() >= (offset + 16) ); // buffer needs to have 16 bytes of space left. #endif // fields 7-9 (ack-msgid/uniqueid/ack-size) will be overwritten with the nonce. // When the nonce was {4299E384-8248-4AF6-90E4-5B26A040AC34} as string // it will be sent as bytes like: 84E39942-4882-F64A-90E4-5B26A040AC34 // The order of the first 3 fields is reversed. const int noncePos[] = { 3,2,1,0 // Field 1 reversed , 5,4 // Field 2 reversed , 7,6 // Field 3 reversed , 8,9 // field 4 , 10,11,12,13,14,15 // field 5 }; for(int i = 0; i < 16; i++) { buffer[offset + i] = (char)( fixedNonce.mid(noncePos[i] * 2, 2).toUInt(0, 16) ); } } // Copy sort integers into the binary header. void P2PMessage::insertUtf16String(QByteArray &buffer, const QString &value, int offset) { const unsigned short *utf16Value = value.utf16(); uint valueLength = value.length(); for(uint i = 0; i < valueLength; ++i) { const short utf16Char = utf16Value[i]; buffer[offset + 0] = (char) ( utf16Char & 0xFF); buffer[offset + 1] = (char) ((utf16Char >> 8) & 0xFF); offset += 2; } } unsigned int P2PMessage::extractBytes(const char *data, const int offset) { // Convert the bytes from network order to a normal int. return (((unsigned char) data[offset + 0] ) | ((unsigned char) data[offset + 1] << 8) | ((unsigned char) data[offset + 2] << 16) | ((unsigned char) data[offset + 3] << 24)); } quint32 P2PMessage::extractLongBytes(const char *data, const int offset) { // Convert the bytes from network order to a long int. return (((unsigned char) data[offset + 0] ) | ((unsigned char) data[offset + 1] << 8) | ((unsigned char) data[offset + 2] << 16) | ((unsigned char) data[offset + 3] << 24)); // FIXME: gcc complains that the shift width is larger then the actual data type supports.. // as long as the msn servers don't send huge messages, we don't have much to worry.. // otherwise cast the data to qint64 first. // | ((unsigned char) data[offset + 4] << 32) // | ((unsigned char) data[offset + 5] << 40) // | ((unsigned char) data[offset + 6] << 48) // | ((unsigned char) data[offset + 7] << 56)); } QString P2PMessage::extractNonce(const char *data, const int offset) { const int noncePos[] = { 3,2,1,0 // Field 1 reversed , 5,4 // Field 2 reversed , 7,6 // Field 3 reversed , 8,9 // field 4 , 10,11,12,13,14,15 // field 5 }; const char hexMap[] = "0123456789ABCDEF"; QString hex; for(int i = 0; i < 16; i++) { if( i == 4 || i == 6 || i == 8 || i == 10 ) { hex += "-"; } int upper = (data[offset + noncePos[i]] & 0xf0) >> 4; int lower = (data[offset + noncePos[i]] & 0x0f); hex += hexMap[upper]; hex += hexMap[lower]; } return "{" + hex + "}"; } QString P2PMessage::extractUtf16String( const char *data, const int offset, int size ) { // Avoid copying the null char if( data[ offset + size - 1 ] == '\0' && data[ offset + size - 2 ] == '\0' ) { #ifdef KMESSDEBUG_P2PMESSAGE kDebug() << "avoid copying null character to utf16 string"; #endif size--; } return QString::fromUtf16( reinterpret_cast<const ushort *>( data + offset ), size / 2 ); }