/***************************************************************************
 *                                                                         *
 *   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.
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 );
}

