/****************************************************************************
**
** This file is part of the Qt Extended Opensource Package.
**
** Copyright (C) 2009 Trolltech ASA.
**
** Contact: Qt Extended Information (info@qtextended.org)
**
** This file may be used under the terms of the GNU General Public License
** version 2.0 as published by the Free Software Foundation and appearing
** in the file LICENSE.GPL included in the packaging of this file.
**
** Please review the following information to ensure GNU General Public
** Licensing requirements will be met:
**     http://www.fsf.org/licensing/licenses/info/GPLv2.html.
**
**
****************************************************************************/

#include <cstdlib>

#include "qsmsmessage.h"
#include "qcbsmessage.h"
#include "qsmsmessage_p.h"
#include "qatutils.h"
#include "qgsmcodec.h"
#ifdef Q_WS_QWS
#include <qtopialog.h>
#else
#include <qdebug.h>
#define qLog(dbgcat) if(1); else qDebug()
#endif
#include <qstringlist.h>

#include <qtextcodec.h>

const int Unit = 1;

class QSMSMessagePartPrivate
{
public:
    QSMSMessagePartPrivate( const QString& text )
    {
        this->isText = true;
        this->text = text;
        this->position = 0;
    }
    QSMSMessagePartPrivate( const QString& mimeType, const QByteArray& data )
    {
        this->isText = false;
        this->mimeType = mimeType;
        this->data = data;
        this->position = 0;
    }
    QSMSMessagePartPrivate( const QString& mimeType, const QByteArray& data, uint position )
    {
        this->isText = false;
        this->mimeType = mimeType;
        this->data = data;
        this->position = position;
    }
    QSMSMessagePartPrivate( const QSMSMessagePartPrivate& part )
    {
        this->isText = part.isText;
        this->text = part.text;
        this->mimeType = part.mimeType;
        this->data = part.data;
        this->position = part.position;
    }

    bool isText;
    QString text;
    QString mimeType;
    QByteArray data;
    uint position;
};

class QSMSMessagePrivate
{
public:
    QSMSMessagePrivate()
    {
        ref = 1;
        mValidity = 2 * 24 * 60;        // 2 days
        mReplyRequest = false;
        mStatusReportRequested = false;
        mMessageType = QSMSMessage::Normal;
        mCodec = nullptr;
        mForceGsm = false;
        mBestScheme = (QSMSDataCodingScheme)(-1);
        mDataCodingScheme = -1;
        mMessageClass = -1;
        mProtocol = 0;
    }

    ~QSMSMessagePrivate()
    {
    }

    /*  Convert from minutes to GSM 03.40 specification (section 9.2.3.12).
         - 0-143   = 0 to 12 hours in 5 minute increments (0 = 5 minutes).
         - 144-167 = 12hrs30min to 24hrs in 30 minute increments.
         - 168-196 = 2days to 30days in 1 day increments.
         - 197-255 = 5weeks to 63weeks in 1 week increments.
    */
    uint gsmValidityPeriod()
    {
        uint mins = mValidity;

        if ( mins < 5 )
            return 0;

        if ( mins <= 12 * 60 )
            return ((mins / 5) - 1);

        if ( mins <= 24 * 60 )
            return ((mins + 29 - (12 * 60 + 30)) / 30) + 144;

        uint days = (mins + (24 * 60 - 1)) / (24 * 60);

        if ( days <= 30 )
            return days - 2 + 168;

        if ( days <= 63 * 7 )
            return ((days + 6) / 7) - 5 + 197;

        return 255;
    }

    void setGsmValidityPeriod(uint value)
    {
        if ( value <= 143 )
            mValidity = (value + 1) * 5;
        else if ( value <= 167 )
            mValidity = (value - 143) * 30 + 12 * 60;
        else if ( value <= 196 )
            mValidity = (value - 166) * 24 * 60;
        else
            mValidity = (value - 192) * 7 * 24 * 60;
    }

    void copy( QSMSMessagePrivate *from )
    {
        *this = *from;
        mHeaders = from->mHeaders;
    }

    QAtomicInt ref;
    QTextCodec *mCodec;
    QString mServiceCenter;
    QString mRecipient;
    QString mSender;
    QDateTime mTimestamp;
    uint mValidity;
    bool mReplyRequest;
    bool mStatusReportRequested;
    bool mForceGsm;
    QSMSMessage::MessageType mMessageType;
    QSMSDataCodingScheme mBestScheme;
    QByteArray mHeaders;
    QList<QSMSMessagePart> mParts;
    QString mCachedBody;
    int mDataCodingScheme;
    int mMessageClass;
    int mProtocol;
};

/*!
    \enum QSMSMessage::MessageType
    Defines the type of an SMS message.

    \value Normal The message is a normal SMS message.
    \value CellBroadCast The message is a cell broadcast message.
    \value StatusReport The message is an SMS status report message.
*/

/*!
    \enum QSMSDataCodingScheme
    Define the data coding scheme to use to encode SMS message text.

    \value QSMS_Compressed Flag that indicates that the data is compressed
           (not used at present).
    \value QSMS_MessageClass Flag that indicates the message class
           (not used at present).
    \value QSMS_DefaultAlphabet Use the default 7-bit GSM alphabet.
    \value QSMS_8BitAlphabet Use the locale-specific 8-bit alphabet.
    \value QSMS_UCS2Alphabet Use the UCS-2 16-bit alphabet.
    \value QSMS_ReservedAlphabet Use the reserved alphabet
           (not used at present).
*/

/*!
    \class QSMSMessagePart
    \inpublicgroup QtTelephonyModule

    \brief The QSMSMessagePart class specifies a part within an SMS message.

    \ingroup telephony
    \sa QSMSMessage

    QSMSMessage objects contain zero or more "parts", which describe
    the contents of the SMS message.  A part may be plain text,
    or a binary stream tagged by a MIME type.
*/

/*!
    Constructs an empty SMS message part.
*/
QSMSMessagePart::QSMSMessagePart()
{
    d = new QSMSMessagePartPrivate( QString() );
}

/*!
    Constructs a new plain text SMS message part from the
    string \a text.
*/
QSMSMessagePart::QSMSMessagePart( const QString& text )
{
    d = new QSMSMessagePartPrivate( text );
}

/*!
    Constructs a new binary SMS message part with the specified
    \a mimeType and \a data.
*/
QSMSMessagePart::QSMSMessagePart( const QString& mimeType, const QByteArray& data )
{
    d = new QSMSMessagePartPrivate( mimeType, data );
}

/*!
    Constructs a new binary SMS message part with the specified
    \a mimeType and \a data.  The part is intended to be displayed
    at \a position within a subsequent text part.
*/
QSMSMessagePart::QSMSMessagePart( const QString& mimeType, const QByteArray& data, uint position )
{
    d = new QSMSMessagePartPrivate( mimeType, data, position );
}

/*!
    Constructs a copy of \a part.
*/
QSMSMessagePart::QSMSMessagePart( const QSMSMessagePart& part )
{
    d = new QSMSMessagePartPrivate( *(part.d) );
}

/*!
    Destructs the QSMSMessagePart.
*/
QSMSMessagePart::~QSMSMessagePart()
{
    delete d;
}

/*!
    Assigns a copy of \a part to this object.
*/
QSMSMessagePart& QSMSMessagePart::operator=( const QSMSMessagePart& part )
{
    if ( &part != this ) {
        delete d;
        d = new QSMSMessagePartPrivate( *(part.d) );
    }
    return *this;
}

/*!
    Returns true if this SMS message part is plain text; or false if binary.
*/
bool QSMSMessagePart::isText() const
{
    return d->isText;
}

/*!
    Returns the plain text contents of this SMS message part,
    or QString() if it is not a text part.
*/
QString QSMSMessagePart::text() const
{
    return d->text;
}

/*!
    Returns the MIME type associated with this SMS message part,
    or QString() if it is not a binary part.
*/
QString QSMSMessagePart::mimeType() const
{
    return d->mimeType;
}

/*!
    Returns the binary data associated with this SMS message part.
    Returns and empty QByteArray if it is not a binary part.
*/
const QByteArray& QSMSMessagePart::data() const
{
    return d->data;
}

/*!
    Returns the text position to display this SMS message part at.
*/
uint QSMSMessagePart::position() const
{
    return d->position;
}

/*!
    \class QSMSMessage
    \inpublicgroup QtTelephonyModule

    \brief The QSMSMessage class specifies the contents of an SMS message.

    This class is intended for use with QSMSReader and QSMSSender to process
    SMS messages according to 3GPP TS 03.40 and 23.040.

    An incoming SMS message from QSMSReader will typically have text() and
    sender() set.  Other fields such as destinationPort() and
    applicationData() may be set if the message is a WAP Push or SMS datagram
    message rather than plain text.

    An outgoing SMS message sent via QSMSSender will need to have at least
    recipient() and text() set.  If the message is a WAP Push or SMS datagram
    message rather than plain text, then destinationPort() and applicationData()
    should also be set.

    Special header fields in SMS messages can be accessed with serviceCenter(),
    replyRequest(), statusReportRequested(), validityPeriod(), timestamp(),
    dataCodingScheme(), and protocol().

    \ingroup telephony
*/

/*!
    Constructs an empty QSMSMessage.
*/
QSMSMessage::QSMSMessage()
{
    d = new QSMSMessagePrivate;
}

/*!
    Constructs an QSMSMessage that is a copy of \a msg.
*/
QSMSMessage::QSMSMessage(const QSMSMessage &msg)
{
    d = msg.d;
    d->ref.ref();
}

/*!
    Destructs the QSMSMessage.
*/
QSMSMessage::~QSMSMessage()
{
    if ( !d->ref.deref() )
        delete d;
}

QSMSMessagePrivate *QSMSMessage::dwrite()
{
    // If we are the only user of the private object, return it as-is.
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
    if ( d->ref.loadRelaxed() == 1 )
#else
    if ( d->ref.load() == 1 )
#endif
        return d;

    // Create a new private object and copy the current contents into it.
    QSMSMessagePrivate *newd = new QSMSMessagePrivate();
    newd->copy( d );
    if ( !d->ref.deref() )
        delete d;
    d = newd;
    return newd;
}

/*!
    Assigns a copy of \a msg to this object.
*/
QSMSMessage& QSMSMessage::operator=( const QSMSMessage &msg)
{
    if ( d == msg.d )
        return *this;

    if ( !d->ref.deref() )
        delete d;

    d = msg.d;
    d->ref.ref();

    return *this;
}

/*!
    Sets the contents of this QSMSMessage to a single plain text
    body containing \a str.  This is for simple messages only.
    Complex messages should be constructed part by part using
    the addPart() method.

    \sa text()
*/
void QSMSMessage::setText(const QString &str)
{
    clearParts();
    addPart( QSMSMessagePart( str ) );
}

/*!
    Returns the contents of this QSMSMessage as a single plain text string.
    If the message contains binary parts, they will not appear
    in the result.  This is for simple message viewers only.
    Complex message viewers should iterate over the list returned
    by parts().

    \sa setText()
*/
QString QSMSMessage::text() const
{
    // Handle the easy cases first.
    if ( d->mParts.count() == 0 ) {
        return QString();
    } else if ( d->mParts.count() == 1 && d->mParts[0].isText() ) {
        return d->mParts[0].text();
    } else if ( d->mCachedBody.length() > 0 ) {
        return d->mCachedBody;
    }

    // We need the private structure to be writable to cache the body.
    const_cast<QSMSMessage *>(this)->dwrite();

    // Append all text parts to get the complete body.
    QString body = QString();
    for ( int posn = 0; posn < d->mParts.count(); ++posn ) {
        if ( d->mParts[posn].isText() ) {
            body += d->mParts[posn].text();
        }
    }
    d->mCachedBody = body;
    return body;
}

/*!
    If the message consists solely of characters in the 7-bit GSM
    encoding, then the message will be transmitted that way.
    Otherwise \a codec is used to convert the characters into an
    appropriate language-specific 8-bit encoding.  If \a codec is
    set to NULL, then the default UCS-2 encoding for GSM messages
    is used.

    \sa textCodec()
*/
void QSMSMessage::setTextCodec(QTextCodec *codec)
{
    dwrite()->mCodec = codec;
}

/*!
    Returns the current 8-bit text codec, or NULL if none has
    been set.

    \sa setTextCodec()
*/
QTextCodec *QSMSMessage::textCodec() const
{
    return d->mCodec;
}

/*!
    If \a force is true, then the codec set by QSMSMessage::setTextCodec
    is ignored and the 7-bit GSM encoding is always used.  Setting this
    flag increases the number of characters that can be sent in any
    given SMS message, but may lose information.

    \sa forceGsm()
*/
void QSMSMessage::setForceGsm(bool force)
{
    dwrite()->mForceGsm = force;
}

/*!
    Returns true if the 7-bit GSM encoding has been forced.

    \sa setForceGsm()
*/
bool QSMSMessage::forceGsm() const
{
    return d->mForceGsm;
}

/*!
    Sets the SMS data coding \a scheme to use for this message.
    Normally you won't need to call this unless the user has
    somehow explicitly requested an override.  By default,
    the QSMSMessage class will choose the best scheme for you.
    Should be set to one of \c QSMS_DefaultAlphabet,
    \c QSMS_8BitAlphabet, or \c QSMS_UCS2Alphabet.

    \sa bestScheme()
*/
void QSMSMessage::setBestScheme(QSMSDataCodingScheme scheme)
{
    dwrite()->mBestScheme = scheme;
}


/*!
    Returns the best SMS data coding scheme to use for this
    message, determined by an inspection of the plain text body parts.

    \sa setBestScheme()
*/
QSMSDataCodingScheme QSMSMessage::bestScheme() const
{
    QTextCodec *codec = QAtUtils::codec( "gsm-noloss" );
    QString body = text();
    uint len = body.length();
    bool gsmSafe;

    // Did the user provide a scheme override?
    if ( d->mBestScheme != (QSMSDataCodingScheme)(-1) )
        return d->mBestScheme;

    // Encode zero-length bodies in the default alphabet.
    if ( len == 0 )
        return QSMS_DefaultAlphabet;

    // Always use GSM if we are forced to do so.
    if ( d->mForceGsm )
        return QSMS_DefaultAlphabet;

    // Check the body for non-GSM characters.
    gsmSafe = codec->canEncode( body );

    // Use the default alphabet if everything is GSM-compatible.
    if ( gsmSafe )
        return QSMS_DefaultAlphabet;

    // See if we can convert to 8-bit using the codec
    // without losing any information.
    if ( d->mCodec && d->mCodec->canEncode( body ) )
        return QSMS_8BitAlphabet;

    // Default to the UCS-2 alphabet.
    return QSMS_UCS2Alphabet;
}

/*!
    Sets the recipient's telephone number to \a txt.

    \sa recipient()
*/
void QSMSMessage::setRecipient(const QString &txt)
{
    dwrite()->mRecipient = txt;
}

/*!
    Returns the recipient's telephone number.  Normally QString()
    for an incoming message.

    \sa setRecipient()
*/
QString QSMSMessage::recipient() const
{
    return d->mRecipient;
}

/*!
    Sets the sender's telephone number to \a txt.

    \sa sender()
*/
void QSMSMessage::setSender(const QString &txt)
{
    dwrite()->mSender = txt;
}

/*!
    Returns the sender's telephone number.  Normally QString()
    for an outgoing message.

    \sa setSender()
*/
QString QSMSMessage::sender() const
{
    return d->mSender;
}

/*!
    Sets the service center to use for transmitting this SMS message,
    or QString() for the default service center, to \a str.

    \sa serviceCenter()
*/
void QSMSMessage::setServiceCenter(const QString &str)
{
    dwrite()->mServiceCenter = str;
}

/*!
    Returns the service center.

    \sa setServiceCenter()
*/
QString QSMSMessage::serviceCenter() const
{
    return d->mServiceCenter;
}

/*!
    Enable or disable the "reply request" flag for this SMS message,
    according to the value of \a on.

    \sa replyRequest()
*/
void QSMSMessage::setReplyRequest(bool on )
{
    dwrite()->mReplyRequest = on;
}

/*!
    Returns the "reply request" flag.

    \sa setReplyRequest()
*/
bool QSMSMessage::replyRequest() const
{
    return d->mReplyRequest;
}

/*!
    Sets the status report requested flag to \a on.

    \sa statusReportRequested()
*/
void QSMSMessage::setStatusReportRequested(bool on)
{
    dwrite()->mStatusReportRequested = on;
}

/*!
    Returns true if status report requested flag is currently set;otherwise returns false.

    \sa setStatusReportRequested()
*/
bool QSMSMessage::statusReportRequested() const
{
    return d->mStatusReportRequested;
}

/*!
    Sets the validity period to \a minutes.  The default is 2 days.  If the value
    is set to \c{(uint)(-1)}, it indicates that the message should have no
    validity period specified.

    \sa validityPeriod(), gsmValidityPeriod()
*/
void QSMSMessage::setValidityPeriod(uint minutes)
{
    dwrite()->mValidity = minutes;
}

/*!
    Returns the validity period in minutes for this message.  The default is 2 days.
    If the value is \c{(uint)(-1)}, it indicates that the message should have no
    validity period specified.

    \sa setValidityPeriod(), setGsmValidityPeriod()
*/
uint QSMSMessage::validityPeriod() const
{
    return d->mValidity;
}

/*!
    Sets the GSM validity period to \a value, which must be between
    0 and 255, inclusive.  The setValidity() method
    is a friendlier way to set the validity value.

    0 to 143 indicates 0 to 12 hours in 5 minute increments (0 = 5 minutes).
    144 to 167 indicates 12 hrs 30 min to 24 hrs in 30 minute increments.
    168 to 196 indicates 2 days to 30 days in 1 day increments.
    197 to 255 indicates 5 weeks to 63 weeks in 1 week increments.

    \sa gsmValidityPeriod(), validityPeriod()
*/
void QSMSMessage::setGsmValidityPeriod(uint value)
{
    dwrite()->setGsmValidityPeriod(value);
}

/*!
    Returns the GSM validity period value, between 0 and 255, inclusive.

    \sa setGsmValidityPeriod(), setValidityPeriod()
*/
uint QSMSMessage::gsmValidityPeriod() const
{
    return d->gsmValidityPeriod();
}

/*!
    Sets the SMS message's \a timestamp.

    \sa timestamp()
*/
void QSMSMessage::setTimestamp(const QDateTime& timestamp)
{
    dwrite()->mTimestamp = timestamp;
}

/*!
    Returns the SMS message's timestamp, which will be null if the
    message does not have a timestamp.

    \sa setTimestamp()
*/
QDateTime QSMSMessage::timestamp() const
{
    return d->mTimestamp;
}

/*!
    Returns the SMS message type.

    \sa setMessageType()
*/
QSMSMessage::MessageType QSMSMessage::messageType() const
{
    return d->mMessageType;
}

/*!
    Sets the SMS message type to \a m.  There is rarely any need to
    set this to something other than QSMSMessage::Normal.

    \sa messageType()
*/
void QSMSMessage::setMessageType(MessageType m)
{
    dwrite()->mMessageType  = m;
}

/*!
    Sets the SMS message's user data headers to \a value.

    \sa headers()
*/
void QSMSMessage::setHeaders(const QByteArray& value)
{
    dwrite()->mHeaders = value;
}

/*!
    Returns the SMS message's user data headers.

    \sa setHeaders()
*/
const QByteArray& QSMSMessage::headers() const
{
    return d->mHeaders;
}

/*!
    Clear all body parts from this SMS message.

    \sa addPart(), addParts(), parts()
*/
void QSMSMessage::clearParts()
{
    dwrite()->mParts.clear();
    dwrite()->mCachedBody = QString();
}

/*!
    Add a new body \a part to this SMS message.

    \sa clearParts(), addParts(), parts()
*/
void QSMSMessage::addPart( const QSMSMessagePart& part )
{
    // We need a writable copy.
    dwrite();

    // Clear the part list if we have one empty text part.
    if ( d->mParts.count() == 1 &&
         d->mParts[0].isText() &&
         d->mParts[0].text().length() == 0 ) {
        d->mParts.clear();
    }

    // Append the new part and clear the cached text.
    d->mParts.append( part );
    d->mCachedBody = QString();
}

/*!
    Add a list of body \a parts to this SMS message.

    \sa clearParts(), addPart(), parts()
*/
void QSMSMessage::addParts( const QList<QSMSMessagePart>& parts )
{
    // We need a writable copy.
    dwrite();

    if ( d->mParts.count() == 1 &&
         d->mParts[0].isText() &&
         d->mParts[0].text().length() == 0 ) {
        d->mParts.clear();
    }
    d->mParts += parts;
    d->mCachedBody = QString();
}

/*!
    Returns a list of all body parts in this SMS message.

    \sa clearParts(), addParts(), addPart()
*/
QList<QSMSMessagePart> QSMSMessage::parts() const
{
    return d->mParts;
}

/*!
    Compute an estimate for the number of messages that will need
    to be used to send this SMS message (\a numMessages), and the
    number of spare characters that are left in the last message
    before it overflows (\a spaceLeftInLast).

    This function may be useful in user interfaces to indicate to
    the user that an SMS message needs to be sent in multiple pieces,
    costing the user more money.
*/
void QSMSMessage::computeSize( uint& numMessages, uint& spaceLeftInLast ) const
{
    int part = findPart( "application/x-qtopia-wdp-ports" );
    if ( part != -1 ) {
        // This is an application datagram, which has its size
        // computed using a different algorithm.
        uint headerLen = 3 + d->mParts[(uint)part].data().size();
        uint dataLen = applicationData().size();
        if ( ( headerLen + dataLen ) <= 140 ) {
            numMessages = 1;
            spaceLeftInLast = 134 - headerLen - dataLen;
        } else {
            uint partSize = ( 134 - headerLen );
            numMessages = ( dataLen + partSize - 1 ) / partSize;
            spaceLeftInLast = dataLen - numMessages * partSize;
        }
        return;
    }

    QSMSDataCodingScheme scheme = bestScheme();
    uint len;
    QString body = text();

    if ( scheme == QSMS_DefaultAlphabet ) {

        // Encode the message using 7-bit GSM.
        len = body.length();
        if ( len <= 160 ) {
            numMessages = 1;
            spaceLeftInLast = 160 - len;
        } else {
            // 153 = 160 - fragment_header_size (7).
            numMessages = ( len + 152 ) / 153;
            len %= 153;
            if ( len != 0 )
                spaceLeftInLast = 153 - len;
            else
                spaceLeftInLast = 0;
        }

    } else if ( scheme == QSMS_8BitAlphabet ) {

        // Encode the message using an 8-bit character set.
        QByteArray converted = d->mCodec->fromUnicode( body );
        len = converted.length();
        if ( len <= 140 ) {
            numMessages = 1;
            spaceLeftInLast = 140 - len;
        } else {
            // 134 = 140 - fragment_header_size (6).
            numMessages = ( len + 133 ) / 134;
            len %= 134;
            if ( len != 0 )
                spaceLeftInLast = 134 - len;
            else
                spaceLeftInLast = 0;
        }

    } else {

        // Encode the message with unicode.
        len = body.length();
        if ( len <= 70 ) {
            numMessages = 1;
            spaceLeftInLast = 70 - len;
        } else {
            // 67 = 70 - fragment_header_size (3).
            numMessages = ( len + 66 ) / 67;
            len %= 67;
            if ( len != 0 )
                spaceLeftInLast = 67 - len;
            else
                spaceLeftInLast = 0;
        }
    }
}

/*!
    Returns the destination port number if this SMS message contains
    an application datagram, or -1 if not an application datagram.

    When an SMS message is received that has a destination port
    number, Qt Extended will attempt to find a QDS service that can handle it.
    Qt Extended first looks for a QDS service named \c push for the MIME type
    \c T from the WAP push header.  Next, it looks for a service named
    \c push for the MIME type \c{application/x-smsapp-N} where
    \c N is the port number in decimal.

    If a matching service is found, then the SMS message is
    sent to the corresponding application via QDS.  The QCop
    message is that specified in the QDS service definition.
    Thus, applications can register to receive special SMS messages.

    The following QDS definition, in \c{etc/qds/ContactsPhone} will
    direct vcard's that are received via WAP to the \c ContactsPhone
    service.  The \c ContactsPhone service is normally implemented
    by the \c addressbook program.

    \code
    [Translation]
    File=QtopiaServices
    Context=ContactsPhone
    [pushVCard]
    RequestDataType=text/x-vcard
    ResponseDataType=
    Attributes="push"
    Description[]=Receive a vcard via WAP push
    \endcode

    The \c Attributes must contain \c push and the \c RequestDataType
    must be the MIME type to be dispatched.  The QCop message that is
    delivered to the application will have the name
    \c pushVCard(QDSActionRequest).  The data in the action request
    will be the payload of the push message.

    The auxilary data in the action request will be a QByteArray
    containing the full QSMSMessage object, from which the application
    can extra header information if it needs it.  Use the QSMSMessage
    datastream operators to extract the QSMSMessage object.

    If the application fails to process an SMS message that is sent to
    it via QCop, then it will be lost.  It is important that such
    applications take steps to save the message if necessary.

    If a matching service is not found, then the SMS message
    will be delivered to the \c qtmail application normally,
    and then saved by that application.

    \sa setDestinationPort(), sourcePort()
*/
int QSMSMessage::destinationPort() const
{
    int part = findPart( "application/x-qtopia-wdp-ports" );
    if ( part == -1 )
        return -1;

    QByteArray data = d->mParts[(uint)part].data();
    if ( data.size() == 4 ) {

        return ((((int)(data[0])) & 0xFF) << 8) |
                (((int)(data[1])) & 0xFF);

    } else if ( data.size() == 2 ) {

        return (((int)(data[0])) & 0xFF);
    }

    return -1;
}

/*!
    Sets the destination port number for an SMS message that contains
    an application datagram to \a value.

    \sa destinationPort(), sourcePort()
*/
void QSMSMessage::setDestinationPort(int value)
{
    QByteArray data;
    int source;
    int part = findPart( "application/x-qtopia-wdp-ports" );
    if ( part == -1 ) {
        data.resize(4);
        data[0] = (char)(value >> 8);
        data[1] = (char)value;
        data[2] = (char)0;
        data[3] = (char)0;
    } else {
        source = sourcePort();
        data.resize(4);
        data[0] = (char)(value >> 8);
        data[1] = (char)value;
        data[2] = (char)(source >> 8);
        data[3] = (char)source;
    }
    removeParts( "application/x-qtopia-wdp-ports" );
    addPart( QSMSMessagePart( "application/x-qtopia-wdp-ports", data ) );
}

/*!
    Returns the source port number if this SMS message contains
    an application datagram, or -1 if not an application datagram.

    \sa setSourcePort(), destinationPort()
*/
int QSMSMessage::sourcePort() const
{
    int part = findPart( "application/x-qtopia-wdp-ports" );
    if ( part == -1 )
        return -1;

    QByteArray data = d->mParts[(uint)part].data();
    if ( data.size() == 4 ) {

        return ((((int)(data[2])) & 0xFF) << 8) |
                (((int)(data[3])) & 0xFF);

    } else if ( data.size() == 2 ) {

        return (((int)(data[1])) & 0xFF);
    }

    return -1;
}

/*!
    Sets the source port number for an SMS message that contains
    an application datagram to \a value.

    \sa sourcePort(), destinationPort()
*/
void QSMSMessage::setSourcePort(int value)
{
    QByteArray data;
    int dest;
    int part = findPart( "application/x-qtopia-wdp-ports" );
    if ( part == -1 ) {
        data.resize(4);
        data[0] = (char)0;
        data[1] = (char)0;
        data[2] = (char)(value >> 8);
        data[3] = (char)value;
    } else {
        dest = destinationPort();
        data.resize(4);
        data[0] = (char)(dest >> 8);
        data[1] = (char)dest;
        data[2] = (char)(value >> 8);
        data[3] = (char)value;
    }
    removeParts( "application/x-qtopia-wdp-ports" );
    addPart( QSMSMessagePart( "application/x-qtopia-wdp-ports", data ) );
}

/*!
    Returns the data if this SMS message contains an application datagram.
    Returns an empty byte array if this message is not a datagram.

    \sa setApplicationData()
*/
QByteArray QSMSMessage::applicationData() const
{
    QByteArray data;
    QList<QSMSMessagePart>::ConstIterator iter;
    uint size;
    for ( iter = d->mParts.begin(); iter != d->mParts.end(); ++iter ) {
        if ( (*iter).mimeType() == "application/x-qtopia-wdp" ) {
            size = data.size();
            data.resize( size + (*iter).data().size() );
            memcpy( data.data() + size, (*iter).data().data(),
                    (*iter).data().size() );
        }
    }
    return data;
}

/*!
    Sets the data within an SMS message that contains an application
    datagram to \a value.

    \sa applicationData()
*/
void QSMSMessage::setApplicationData(const QByteArray& value)
{
    removeParts( "application/x-qtopia-wdp" );
    addPart( QSMSMessagePart( "application/x-qtopia-wdp", value ) );
}

/*!
    Sets the data coding scheme to use within an SMS message to \a value.
    If \a value is -1, then the system chooses the best data coding scheme
    based on the content.

    This method is mainly of use with application datagrams, not text
    SMS messages.

    \sa dataCodingScheme()
*/
void QSMSMessage::setDataCodingScheme(int value)
{
    dwrite()->mDataCodingScheme = value;
}

/*!
    Returns the data coding scheme to use within an SMS message.
    If the value is -1, then the system chooses the best data coding scheme
    based on the content.

    This method is mainly of use with application datagrams, not text
    SMS messages.

    \sa setDataCodingScheme()
*/
int QSMSMessage::dataCodingScheme() const
{
    return d->mDataCodingScheme;
}

/*!
    Sets the message class for this message to \a value.  The \value should
    be -1 if the system should choose a default message class.  The message
    class should otherwise be 0, 1, 2, or 3, according to 3GPP TS 03.38.
*/
void QSMSMessage::setMessageClass(int value)
{
    dwrite()->mMessageClass = value;
}

/*!
    Get the message class for this message, or -1 if the system should
    choose a default message class.  The message class should otherwise
    be 0, 1, 2, or 3, according to 3GPP TS 03.38.
*/
int QSMSMessage::messageClass() const
{
    return d->mMessageClass;
}

/*!
    Sets the SMS message's protocol field to \a value.

    \sa protocol()
*/
void QSMSMessage::setProtocol(int value)
{
    dwrite()->mProtocol = value;
}

/*!
    Returns the SMS message's protocol field.

    \sa setProtocol()
*/
int QSMSMessage::protocol() const
{
    return d->mProtocol;
}

/*!
    Returns true if this message needs to be split into multiple messages
    before being transmitted over a GSM network; otherwise returns false.

    \sa split()
*/
bool QSMSMessage::shouldSplit() const
{
    uint numMessages, spaceLeftInLast;
    this->computeSize( numMessages, spaceLeftInLast );
    return ( numMessages <=1 ? false : true );
}

/*!
    Split this message into several messages of smaller size for
    transmission over a GSM network.

    \sa shouldSplit()
*/
QList<QSMSMessage> QSMSMessage::split() const
{
    QList<QSMSMessage> list;
    uint numMessages, spaceLeftInLast;
    static uint fragmentCounter =0;

    computeSize( numMessages, spaceLeftInLast );
    if ( numMessages <= 1 ) {
        // Splitting is not necessary, so return a list with one message.
        list += *this;
        return list;
    }

    // Get the number of characters to transmit in each fragment.
    int split;
    QSMSDataCodingScheme scheme = bestScheme();
    switch ( scheme ) {
        case QSMS_DefaultAlphabet:  split = 153; break;
        case QSMS_8BitAlphabet:     split = 134; break;
        default:                    split = 67; break;
    }

    // Split the message to create sub-messages and transmit them.
    int posn = 0;
    int len;
    uint number;
    QSMSMessage tmp;
    number = 1;
    if ( destinationPort() == -1 ) {
        // Splitting a simple text message.
        QString txt = text();
        while ( posn < txt.length() ) {
            tmp = *this;
            len = txt.length() - posn;
            if ( len > split ) {
                len = split;
            }
            tmp.setText( txt.mid( posn, len ) );
            tmp.setFragmentHeader( fragmentCounter, number++,
                                   numMessages, scheme );
            posn += len;
            list.append(tmp);
        }
    } else {
        // Splitting a datagram message.
        QByteArray data = applicationData();
        QByteArray part;
        uint partSize = 134 - 6;
        while ( posn < data.size() ) {
            tmp = *this;
            if ( ( posn + partSize ) <= (uint)data.size() ) {
                part.resize(partSize);
                memcpy(part.data(), data.data() + posn, partSize );
            } else {
                part.resize(data.size() - posn);
                memcpy(part.data(), data.data() + posn, data.size() - posn);
            }
            tmp.setDestinationPort( destinationPort() );// Force 16-bit ports.
            tmp.setFragmentHeader( fragmentCounter, number++,
                                   numMessages, QSMS_8BitAlphabet );
            tmp.setApplicationData( part );
            list.append(tmp);
            posn += partSize;
        }
    }

    // Increase the fragment counter for the next multi-part SMS message.
    fragmentCounter = ( fragmentCounter + 1 ) & 0xFF;

    return list;
}

/*!
    Convert this SMS message into its binary PDU form, according to
    3GPP TS 03.40 and 3GPP TS 23.040.  If the message has a recipient,
    then a SUBMIT message will be constructed.  If the message does not
    have a recipient, then a DELIVER message will be constructed.

    \sa fromPdu()
*/
QByteArray QSMSMessage::toPdu() const
{
    QSMSSubmitMessage submit( *this, recipient().isEmpty() );
    return submit.toByteArray();
}

/*!
    Convert a binary \a pdu into an SMS message, according to
    3GPP TS 03.40 and 3GPP TS 23.040.

    \sa toPdu()
*/
QSMSMessage QSMSMessage::fromPdu( const QByteArray& pdu )
{
    QSMSDeliverMessage pdumsg( pdu );
    return pdumsg.unpack();
}

/*!
    Returns the length of the service center address on the start of \a pdu.
    This is typically used with AT-based GSM modems that need to transmit
    the length of the pdu, excluding the service center address,
    along with the \c{AT+CMGS} command.
*/
int QSMSMessage::pduAddressLength( const QByteArray& pdu )
{
    if( pdu.length() > 0 )
        return (pdu[0] & 0xFF) + 1;
    else
        return 0;
}

int QSMSMessage::findPart( const QString& mimeType ) const
{
    QList<QSMSMessagePart>::ConstIterator iter;
    int posn = 0;
    for ( iter = d->mParts.begin(); iter != d->mParts.end(); ++iter ) {
        if ( (*iter).mimeType() == mimeType )
            return posn;
        ++posn;
    }
    return -1;
}

void QSMSMessage::removeParts( const QString& mimeType )
{
    dwrite();
    QList<QSMSMessagePart>::Iterator iter;
    for ( iter = d->mParts.begin(); iter != d->mParts.end(); ) {
        if ( (*iter).mimeType() == mimeType ) {
            iter = d->mParts.erase( iter );
        } else {
            ++iter;
        }
    }
}

void QSMSMessage::setFragmentHeader( uint refNum, uint part, uint numParts,
                                    QSMSDataCodingScheme scheme )
{
    dwrite()->mBestScheme = scheme;
    uint len = d->mHeaders.size();
    d->mHeaders.resize( len + 5 );
    d->mHeaders[len++] = 0;         // Type for concatenated short messages.
    d->mHeaders[len++] = 3;         // Length of header information.
    d->mHeaders[len++] = (char)refNum;
    d->mHeaders[len++] = (char)numParts;
    d->mHeaders[len++] = (char)part;
}


void QSMSMessage::unpackHeaderParts()
{
    QByteArray headers = dwrite()->mHeaders;
    uint posn = 0;
    uint tag, len;
    uint temp;
    QString type;
    while ( ( posn + 2 ) <= (uint)(headers.size()) ) {
        tag = (unsigned char)(headers[posn]);
        len = (unsigned char)(headers[posn + 1]);
        if ( ( posn + len + 2 ) > (uint)(headers.size()) )
            break;
        switch ( (SMSHeaderKind)tag ) {

            case SMS_HK_Predefined_Sound:
            {
                // Predefined sound type.
                if ( len >= 2 ) {
                    QByteArray data( len - 1, 0 );
                    memcpy( data.data(), headers.data() + posn + 3, len - 1 );
                    addPart( QSMSMessagePart
                        ( "application/x-qtopia-predefined-sound", data,
                          headers[posn + 2] & 0xFF ) );
                }
            }
            break;

            case SMS_HK_User_Defined_Sound:
            {
                // User defined sound type.
                if ( len >= 2 ) {
                    QByteArray data( len - 1, 0 );
                    memcpy( data.data(), headers.data() + posn + 3, len - 1 );
                    addPart( QSMSMessagePart
                        ( "audio/imelody", data, headers[posn + 2] & 0xFF ) );
                }
            }
            break;

            case SMS_HK_Predefined_Animation:
            {
                // Predefined animation type.
                if ( len >= 2 ) {
                    QByteArray data( len - 1, 0 );
                    memcpy( data.data(), headers.data() + posn + 3, len - 1 );
                    addPart( QSMSMessagePart
                        ( "application/x-qtopia-predefined-animation", data,
                          headers[posn + 2] & 0xFF ) );
                }
            }
            break;

            case SMS_HK_Large_Animation:
            case SMS_HK_Large_Picture:
            {
                // 32x32 monochrome animation or image - turn it into a WBMP.
                if ( len >= 1 ) {
                    QByteArray data( len + 3, 0 );
                    data[0] = 0x00;
                    data[1] = 0x00;
                    data[2] = 0x20;
                    data[3] = 0x20;
                    for ( temp = 0; temp < (len - 1); ++temp ) {
                        data[4 + temp] = (char)(~(headers[posn + 3 + temp]));
                    }
                    addPart( QSMSMessagePart
                        ( "image/vnd.wap.wbmp", data,
                          headers[posn + 2] & 0xFF ) );
                }
            }
            break;

            case SMS_HK_Small_Animation:
            case SMS_HK_Small_Picture:
            {
                // 16x16 monochrome animation or image - turn it into a WBMP.
                if ( len >= 1 ) {
                    QByteArray data( len + 3, 0 );
                    data[0] = 0x00;
                    data[1] = 0x00;
                    data[2] = 0x10;
                    data[3] = 0x10;
                    for ( temp = 0; temp < (len - 1); ++temp ) {
                        data[4 + temp] = (char)(~(headers[posn + 3 + temp]));
                    }
                    addPart( QSMSMessagePart
                        ( "image/vnd.wap.wbmp", data,
                          headers[posn + 2] & 0xFF ) );
                }
            }
            break;

            case SMS_HK_Variable_Picture:
            {
                // Variable-sized monochrome image - turn it into a WBMP.
                if ( len >= 3 ) {
                    QByteArray data( len - 1, 0 );
                    data[0] = 0x00;
                    data[1] = 0x00;
                    data[2] = headers[posn + 3];
                    data[3] = headers[posn + 4];
                    for ( temp = 2; temp < (len - 1); ++temp ) {
                        data[2 + temp] = (char)(~(headers[posn + 3 + temp]));
                    }
                    addPart( QSMSMessagePart
                        ( "image/vnd.wap.wbmp", data,
                          headers[posn + 2] & 0xFF ) );
                }
            }
            break;

            case SMS_HK_Concat_8Bit:    break;
            case SMS_HK_Concat_16Bit:   break;

            case SMS_HK_AppPort_8Bit:
            case SMS_HK_AppPort_16Bit:
            {
                QByteArray data( len, 0 );
                memcpy( data.data(), headers.data() + posn + 2, len );
                addPart( QSMSMessagePart
                    ( "application/x-qtopia-wdp-ports", data ) );
            }
            break;

            default:
            {
                // Add the unknown part as "application/x-qtopia-sms-N".
                // Maybe qtmail will know what to do with it.
                QByteArray data( len, 0 );
                memcpy( data.data(), headers.data() + posn + 2, len );
                addPart( QSMSMessagePart( "application/x-qtopia-sms-" +
                                         QString::number( tag ), data ) );
            }
            break;
        }
        posn += 2 + len;
    }
}

QPDUMessage::QPDUMessage()
{
    mPosn = 0;
    mBits = 0;
}

QPDUMessage::QPDUMessage(const QByteArray &data)
{
    mBuffer = data;
    mPosn = 0;
    mBits = 0;
}

QPDUMessage::~QPDUMessage()
{
}

QPDUMessage::QPDUMessage(const QPDUMessage &msg)
{
    mBuffer = msg.mBuffer;
    mPosn = msg.mPosn;
    mBits = msg.mBits;
}

void QPDUMessage::skipOctet()
{
    if ( mPosn < mBuffer.size() )
        ++mPosn;
}

QByteArray QPDUMessage::getOctets( uint len )
{
    QByteArray result;
    if ( ( mBuffer.size() - mPosn ) < (int)len ) {
        result = mBuffer.mid( mPosn );
        abort();
    } else {
        result = mBuffer.mid( mPosn, (int)len );
        mPosn += len;
    }
    return result;
}

int QPDUMessage::mPosn;
char QPDUMessage::mBits;

void QPDUMessage::setBit(int b, bool on)
{
    if ( on )
        mBits |= (char)(Unit << b);
    else
        mBits &= (char)(~(Unit << b));
}

void QPDUMessage::setBits(int offset, int len, int val)
{
    uint mask = ((Unit << len) - 1) << offset;
    mBits = (char)((mBits & ~mask) | ((val << offset) & mask));
}

void QPDUMessage::commitBits()
{
    mBuffer += mBits;
    mBits = 0;
}

void QPDUMessage::commitBits( QByteArray &buffer )
{
    buffer += mBits;
    mBits = 0;
}

bool QPDUMessage::bit(int b)
{
    if ( needOctets(1) )
        return (mBuffer[mPosn] & (Unit << b));
    else
        return false;
}

unsigned char QPDUMessage::bits(int offset, int len)
{
    if ( needOctets(1) )
        return (unsigned char)
            ((mBuffer[mPosn] >> offset) & ((Unit << len) - 1));
    else
        return 0;
}

unsigned char QPDUMessage::getOctet()
{
    if ( needOctets(1) )
        return (unsigned char)(mBuffer[mPosn++]);
    else
        return 0;
}

unsigned char QPDUMessage::peekOctet() const
{
    if ( needOctets(1) )
        return (unsigned char)(mBuffer[mPosn]);
    else
        return 0;
}

// Collapse 8-bit-aligned GSM data to its 7-bit form.
static QByteArray collapse7Bit( const QByteArray& in )
{
    QByteArray out;
    int byte = 0;
    int size = 0;
    for ( int posn = 0; posn < in.length(); ++posn ) {
        for ( int bit = 0; bit < 7; ++bit ) {
            if ( ( in[posn] & (1 << bit) ) != 0 )
                byte |= (1 << size);
            ++size;
            if ( size >= 8 ) {
                out += (char)byte;
                byte = 0;
                size = 0;
            }
        }
    }
    if ( size != 0 ) {
        out += (char)byte;
    }
    return out;
}

void QPDUMessage::appendAddress( QByteArray &buffer, const QString &strin, bool SCAddress )
{
    SMSAddressType at;
    int len, digit, octet;
    int posn;
    const int maxPhoneNumberLen = 15;
    QString str( strin );
    str.truncate( maxPhoneNumberLen );

    // Determine the address type and length.
    at = SMS_Address_Unknown;
    len = 0;
    for ( posn = 0; posn < str.length(); ++posn ) {
        switch ( str[posn].unicode() ) {
            case '+':
                at = SMS_Address_International;
                break;

            case '0': case '1': case '2': case '3':
            case '4': case '5': case '6': case '7':
            case '8': case '9': case '*': case '#':
            case 'A': case 'B': case 'C': case 'a':
            case 'b': case 'c':
                ++len;
                break;

            default:
                // Probably an alpha-numeric address.
                ++len;
                at = SMS_Address_AlphaNumeric;
                break;
        }
    }
    if ( SCAddress )
        len = (len + 1) / 2;

    // Bail out early if the address is zero-length.
    if ( !len ) {
        buffer.append( (char) 0 );
        return;
    }

    // Handle alphanumeric address fields.
    if ( at == SMS_Address_AlphaNumeric ) {
        // Convert the address and calculate its encoded length.
        QTextCodec *codec = QAtUtils::codec( "gsm-noloss" );
        QByteArray bytes = collapse7Bit( codec->fromUnicode( strin ) );
        len = bytes.size();

        // Need an extra byte for SCAddress fields.
        if ( SCAddress )
            len++;
        else
            len *= 2;

        // Output the length of the encoded address.
        buffer.append( len );

        // Output the type of number information.
        setBits(0, 4, SMS_NumberId_Unknown);
        setBits(4, 3, at);
        setBit(7, true);
        commitBits( buffer );

        // Output the encoded address and exit.
        buffer += bytes;
        return;
    }

    // SCAddress len = octets + type specifier
    if ( SCAddress )
        len++;

    buffer.append( len );

    setBits(0, 4, SMS_Phone);
    setBits(4, 3, at);
    setBit(7, true);
    commitBits( buffer );

    bool upper4 = false;
    octet = 0;
    for ( posn = 0; posn < str.length(); posn++ ) {
        switch ( str[posn].unicode() ) {
            case '0':           digit = 0; break;
            case '1':           digit = 1; break;
            case '2':           digit = 2; break;
            case '3':           digit = 3; break;
            case '4':           digit = 4; break;
            case '5':           digit = 5; break;
            case '6':           digit = 6; break;
            case '7':           digit = 7; break;
            case '8':           digit = 8; break;
            case '9':           digit = 9; break;
            case '*':           digit = 10; break;
            case '#':           digit = 11; break;
            case 'A': case 'a': digit = 12; break;
            case 'B': case 'b': digit = 13; break;
            case 'C': case 'c': digit = 14; break;
            default:            digit = -1; break;
        }
        if ( digit != -1 ) {
            if ( !upper4 ) {
                octet = digit;
            } else {
                octet |= (digit << 4);
                buffer.append( octet );
            }
            upper4 = !upper4;
        }
    }
    if ( upper4 ) {
        buffer.append( octet | 0xF0 );
    }
}

void QPDUMessage::setAddress(const QString &strin, bool SCAddress)
{
    appendAddress( mBuffer, strin, SCAddress );
}

void QSMSMessage::appendAddress( QByteArray &buffer, const QString &strin, bool SCAddress )
{
    return QPDUMessage::appendAddress( buffer, strin, SCAddress );
}

// Expand the 7-bit GSM data to 8-bit-aligned characters.
static QByteArray expand7Bit( const QByteArray& in )
{
    QByteArray out;
    int byte = 0;
    int size = 0;
    for ( int posn = 0; posn < in.length(); ++posn ) {
        for ( int bit = 0; bit < 8; ++bit ) {
            if ( ( in[posn] & (1 << bit) ) != 0 )
                byte |= (1 << size);
            ++size;
            if ( size >= 7 ) {
                out += (char)byte;
                byte = 0;
                size = 0;
            }
        }
    }
    return out;
}

QString QPDUMessage::address(bool SCAddress)
{
    QString str = "";

    // Get the address length and validate it.
    if ( !needOctets(1) )
        return str;
    uint len = (((uint)(getOctet())) & 0xFF);
    if ( !needOctets(len) ) {
        abort();
        return str;
    }

    if ( len ) {
        SMSAddressType at = (SMSAddressType) bits(4, 3);

        if ( at == SMS_Address_International )
            str += "+";

        skipOctet();
        if ( !SCAddress ) {
                len = len / 2 + (len % 2);
        } else {
            len--;
        }

        if ( at != SMS_Address_AlphaNumeric ) {
            unsigned char c;
            for (int i = 0; i < (int)len; i++) {
                c = peekOctet() & 0x0F;
                str += (char) ('0' + c);
                c = (peekOctet() & 0xF0) >> 4;
                if ( c != 0xf )
                    str += (char) ('0' + c);
                skipOctet();
            }
        } else {
            // Recognize an alphanumeric address in the 7-bit GSM encoding.
            QTextCodec *codec = QAtUtils::codec( "gsm" );
            QByteArray bytes = expand7Bit( getOctets( len ) );
            str += codec->toUnicode( bytes );
        }
    }

    return str;
}

// Return the length of the service centre address.
uint QPDUMessage::addressLength() const
{
    if( mPosn < mBuffer.size() )
        return (((uint)peekOctet()) & 0xFF) + 1;
    else
        return 0;
}

void QPDUMessage::setTimeStamp(const QDateTime &dt)
{
    QDate date = dt.date();
    int tArr[6];
    tArr[0] = date.year();
    tArr[1] = date.month();
    tArr[2] = date.day();

    QTime t = dt.time();
    tArr[3] = t.hour();
    tArr[4] = t.minute();
    tArr[5] = t.second();

    for ( int i = 0; i < 6; i++) {
        appendOctet((unsigned char) ( (((tArr[i]/10)%10) & 0x0F) | (((tArr[i]%10) & 0x0F)<<4) ) );
    }

    appendOctet(0x04); //arbitrary random timezone
}

QDateTime QPDUMessage::timeStamp()
{
    QDateTime d;
    unsigned char c, c4, date[7];

    if ( !needOctets(7) ) {
        abort();
        return d;
    }
    for (int i = 0; i < 7; i++) {
        c = (peekOctet() & 0x0F);
        c4 = (peekOctet() & 0xF0) >> 4;
        date[i] = c*10 + c4;
        skipOctet();
    }

    if ( date[0] < 80 ) {
        d.setDate( QDate(2000 + date[0], date[1], date[2]) );
    } else {
        d.setDate( QDate(1900 + date[0], date[1], date[2]) );
    }
    d.setTime( QTime(date[3], date[4], date[5]) );

    return d;
}

// Get the length of a string when encoded in the GSM 7-bit alphabet.
static uint getEncodedLength( const QString& txt, uint size )
{
    uint len = 0;
    for ( int u = 0; u < (int)size; u++ ) {
        if ( QGsmCodec::twoByteFromUnicode( txt[u].unicode() ) >= 256 )
            len += 2;
        else
            ++len;
    }
    return len;
}

void QPDUMessage::setUserData(const QString &txt, QSMSDataCodingScheme scheme, QTextCodec *codec, const QByteArray& headers, bool implicitLength)
{
    uint len = txt.length();
    uint u;
    uint encodedLen;
    uint headerLen = (uint)(headers.size());
    if ( headerLen )
        ++headerLen;

    // Strip off everything except the alphabet bits.
    switch (scheme >> 6) {
    case 0:
    default:
        scheme = (QSMSDataCodingScheme)(scheme & 0x0C);
        break;
    case 3:
        switch ((scheme & 0x30) >> 4) {
        case 0:
        case 1:
            scheme = QSMS_DefaultAlphabet;
            break;
        case 2:
            scheme = QSMS_UCS2Alphabet;
            break;
        case 3:
            if (scheme & 0x4)
                scheme = QSMS_8BitAlphabet;
            else
                scheme = QSMS_DefaultAlphabet;
            break;
        }
        break;
    }

    if ( scheme == QSMS_DefaultAlphabet ) {

        // Encode the text using the 7-bit GSM alphabet.
        if ( len > 160 )
            len = 160;
        encodedLen = getEncodedLength( txt, len );
        while ( encodedLen > 160 ) {
            // Chop off some more characters until it is <= 160.
            --len;
            encodedLen = getEncodedLength( txt, len );
        }
        if (!implicitLength)
            appendOctet( encodedLen + ( headerLen * 8 + 6 ) / 7 );
        int bitCount = 0;
        unsigned short c;
        if ( headerLen > 0 ) {
            // Output the header and align on a septet boundary.
            bitCount = headerLen * 8;
            if ((bitCount % 7) != 0)
                bitCount = 7 - (bitCount % 7);
            else
                bitCount = 0;
            appendOctet( headerLen - 1 );
            for ( u = 0; u < headerLen - 1; u++ ) {
                appendOctet( headers[u] );
            }
        }
        for ( u = 0; u < len; u++ ) {
            c = QGsmCodec::twoByteFromUnicode( txt[u].unicode() );
            if ( c >= 256 ) {
                // Encode a two-byte sequence.
                for ( int i = 0; i < 7; i++ ) {
                    if ( bitCount == 8 ) {
                        bitCount = 0;
                        commitBits();
                    }
                    setBit( bitCount++, (Unit << (i+8)) & c );
                }
            }
            for ( int i = 0; i < 7; i++ ) {
                if ( bitCount == 8 ) {
                    bitCount = 0;
                    commitBits();
                }
                setBit( bitCount++, (Unit << i) & c );
            }
        }
        if ( bitCount != 0 ) {
            commitBits();
        }

    } else if ( scheme == QSMS_8BitAlphabet ) {
        // Encode the text using the codec's 8-bit alphabet.
        if ( !codec )
            codec = QAtUtils::codec( "iso-8859-1" );
        QByteArray converted = codec->fromUnicode( txt );
        len = converted.length();
        if ( len > 140 )
            len = 140;
        if (!implicitLength)
            appendOctet( len + headerLen );
        if ( headerLen > 0 ) {
            appendOctet( headerLen - 1 );
            for ( u = 0; u < headerLen - 1; u++ ) {
                appendOctet( headers[u] );
            }
        }
        const char *s = (const char *)converted;
        for ( u = 0; u < len; u++ ) {
            appendOctet( (unsigned char)(s[u]) );
        }

    } else {

        // Encode the text using the 16-bit UCS2 alphabet.
        if ( len > 70 )
            len = 70;
        if (!implicitLength)
            appendOctet( len * 2 + headerLen );
        if ( headerLen > 0 ) {
            appendOctet( headerLen - 1 );
            for ( u = 0; u < headerLen - 1; u++ ) {
                appendOctet( headers[u] );
            }
        }
        for ( u = 0; u < len; u++ ) {
            appendOctet( (unsigned char)(txt[u].unicode() >> 8) );
            appendOctet( (unsigned char)(txt[u].unicode() & 0xFF) );
        }

    }
}

// Determine if the headers in an SMS frame indicate that it
// is an application datagram destinated for a particular port.
static bool isSMSDatagram( const QByteArray& headers )
{
    uint posn = 0;
    uint tag, len;
    while ( ( posn + 2 ) <= (uint)(headers.size()) ) {
        tag = (unsigned char)(headers[posn]);
        len = (unsigned char)(headers[posn + 1]);
        if ( ( posn + len + 2 ) > (uint)(headers.size()) )
            break;
        if ( tag == (uint)SMS_HK_AppPort_8Bit ||
             tag == (uint)SMS_HK_AppPort_16Bit ) {
            return true;
        }
        posn += len + 2;
    }
    return false;
}

QString QPDUMessage::userData(QSMSDataCodingScheme scheme, QTextCodec *codec, QByteArray *& headers, bool hasHeaders, bool implicitLength)
{
    QString str = "";
    uint len, headerLen;
    uint u;
    uint ch;

    // Reset the header return.
    headers = nullptr;

    // Get the length of the user data payload.
    if ( implicitLength ) {
        len = mBuffer.size() - mPosn;
    } else {
        if ( !needOctets(1) )
            return str;
        len = (uint)getOctet();
    }

    // Strip off everything except the alphabet bits.
    scheme = (QSMSDataCodingScheme)(scheme & 0x0C);

    if ( scheme == QSMS_DefaultAlphabet ) {

        // Process a sequence in the default 7-bit GSM character set.
        int bitCount = 0;
        int startBit = 0;
        ch = 0;
        if ( implicitLength )
            len = len * 8 / 7;      // Convert 8-bit bytes to 7-bit characters.
        if ( hasHeaders ) {
            u = ( len * 7 + 7 ) / 8;
            if ( !u || !needOctets(u) )
                return str;
            headerLen = getOctet();
            if ( headerLen >= u )
                return str;
            headers = new QByteArray( getOctets( headerLen ) );
            if ( isSMSDatagram( *headers ) )
                return str;
            u = ((headerLen + 1) * 8 + 6) / 7;
            len -= u;
            startBit = (headerLen + 1) * 8;
            if ((startBit % 7) != 0)
                startBit = 7 - (startBit % 7);
            else
                startBit = 0;
        }
        bool prefixed = false;
        while ( len > 0 ) {
            if ( !needOctets(1) )
                return str;
            for ( int i = startBit; len > 0 && i < 8; i++ ) {
                // test the bit and shift it down again, as i doesn't mark
                // where we are in the current char, but bitCount does
                ch |= ( ( peekOctet() & (Unit << i) ) >> i) << bitCount;
                bitCount++;
                if ( bitCount == 7 ) {
                    bitCount = 0;
                    if ( ch == 0x1B ) {     // Start of a two-byte encoding.
                        prefixed = true;
                    } else if ( prefixed ) {
                        str += QChar
                            ( QGsmCodec::twoByteToUnicode( 0x1B00 | ch ) );
                        prefixed = false;
                    } else {
                        str += QGsmCodec::singleToUnicode( (unsigned char)ch );
                    }
                    ch = 0;
                    --len;
                }
            }
            startBit = 0;
            skipOctet();
        }

    } else if ( scheme == QSMS_8BitAlphabet && codec ) {

        // Process an 8-bit sequence using the supplied codec.
        if ( !needOctets(len) ) {
            abort();
            return str;
        }
        if ( hasHeaders ) {
            if ( !len || len < (uint)( (peekOctet() & 0xFF) + 1 ) ) {
                abort();
                return str;
            }
            headerLen = getOctet();
            headers = new QByteArray( getOctets( headerLen ) );
            if ( isSMSDatagram( *headers ) )
                return str;
            len -= headerLen + 1;
        }
        str = codec->toUnicode( getOctets( len ) );

    } else if ( scheme == QSMS_UCS2Alphabet ) {

        // Process a UCS2 sequence.
        if ( !needOctets(len) ) {
            abort();
            return str;
        }
        if ( hasHeaders ) {
            if ( !len || len < (uint)( (peekOctet() & 0xFF) + 1 ) ) {
                abort();
                return str;
            }
            headerLen = getOctet();
            headers = new QByteArray( getOctets( headerLen ) );
            len -= headerLen + 1;
        }
        len /= 2;
        for ( u = 0; u < len; ++u ) {
            ch = (((uint)(getOctet() & 0xFF)) << 8);
            ch |= ((uint)(getOctet() & 0xFF));
            str += (QChar)ch;
        }

    } else {

        // Assume 8-bit for any other coding scheme value.

        // Process an 8-bit sequence using the default Latin1 codec.
        if ( len > (uint)(mBuffer.size() - mPosn) ) {
            // The length field is invalid - use the actual size.
            len = mBuffer.size() - mPosn;
        }
        if ( hasHeaders ) {
            if ( !len || len < (uint)( (peekOctet() & 0xFF) + 1 ) ) {
                abort();
                return str;
            }
            headerLen = getOctet();
            headers = new QByteArray( getOctets( headerLen ) );
            if ( isSMSDatagram( *headers ) )
                return str;
            len -= headerLen + 1;
        }
        for ( u = 0; u < len; ++u ) {
            str += (QChar)(getOctet() & 0xFF);
        }

    }

    return str;
}

/*  Note that the meaning of messageType is also dependant on
    the direction of the message (orig. location) */
SMSMessageType QPDUMessage::messageType()
{
    if ( mBuffer.size() >= 1 &&
         mBuffer.size() >= ((mBuffer[0] & 0xFF) + 2) ) {

        const char *ptr = mBuffer.constData();
        ptr += (*ptr & 0xFF) + 1;
        unsigned char c = *ptr & 3;
        return (SMSMessageType) c;

    }
    return (SMSMessageType)0;
}

QSMSSubmitMessage::QSMSSubmitMessage(const QSMSMessage &m, bool isDeliver)
    : QPDUMessage()
{
    // Clear the pdu before we start.
    mBuffer = QByteArray();
    mPosn = 0;
    mBits = 0;

    setAddress( m.serviceCenter(), true );

    // If there is port and application data information, then
    // create header parts for them.  This is for sending datagram
    // based messages.
    QByteArray headers = m.headers();
    QSMSDataCodingScheme scheme = m.bestScheme();
    int part = m.findPart( "application/x-qtopia-wdp-ports" );
    if ( part != -1 ) {
        QByteArray portData = m.parts()[(uint)part].data();
        uint size = headers.size();
        headers.resize(size + portData.size() + 2);
        if ( portData.size() == 4 ) {
            headers[size++] = (char)SMS_HK_AppPort_16Bit;
        } else {
            headers[size++] = (char)SMS_HK_AppPort_8Bit;
        }
        headers[size++] = (char)(portData.size());
        memcpy(headers.data() + size, portData.data(), portData.size());
        int dataScheme = m.dataCodingScheme(); // User-supplied override.
        if ( dataScheme == -1 )
            dataScheme = 0xF5;      // Special value for datagrams.
        scheme = (QSMSDataCodingScheme)dataScheme;
    } else if ( m.messageClass() != -1 ) {
        scheme = (QSMSDataCodingScheme)
                    (scheme | QSMS_MessageClass | m.messageClass());
        int dataScheme = m.dataCodingScheme(); // User-supplied override.
        if ( dataScheme != -1 )
            scheme = (QSMSDataCodingScheme)dataScheme;
    } else {
        int dataScheme = m.dataCodingScheme(); // User-supplied override.
        if ( dataScheme != -1 )
            scheme = (QSMSDataCodingScheme)dataScheme;
    }

    if ( !isDeliver )
        setBits(0, 2, SMS_Submit);
    else
        setBits(0, 2, SMS_Deliver);

    setBit(2, false);                   // TP-Reject-Duplicates
    if ( !isDeliver && m.validityPeriod() == (uint)(-1) )
        setBits(3, 2, SMS_VF_NoPresent);
    else
        setBits(3, 2, SMS_VF_Relative);
    setBit(5, m.statusReportRequested());// TP-Status-Report-Requested;
    setBit(6, headers.size() != 0);     // TP-User-Data Header
    setBit(7, m.replyRequest());        // TP-Reply-Path

    commitBits();       // first octet done

    //second octet TP-MR (Message reference
    if( !isDeliver )
        appendOctet(0);

    if ( !isDeliver ) {
        //third octet TP-DA (Destination Address)
        //len must be done later
        setAddress( m.recipient(), false );
    } else {
        setAddress( m.sender(), false);
    }


    //nth octet, TP-PID (protocol identifier)
    if ( !isDeliver )
        appendOctet(m.protocol());
    else
        appendOctet(1); //arbitrary protocol for deliver

    // TP-DCS ( Data coding scheme )
    appendOctet(scheme);

    if ( !isDeliver ) {
        // TP-VP ( Validity Period )
        if ( m.validityPeriod() != (uint)(-1) )
            appendOctet(m.gsmValidityPeriod());
    }  else {
        setTimeStamp(m.timestamp());
    }

    // Set the user data field.
    if ( part == -1 ) {
        setUserData(m.text(), scheme, m.textCodec(), headers);
    } else {
        // The applicationData() is the text to be sent in the datagram.
        QByteArray appData = m.applicationData();
        uint len = appData.size();
        if ( ( len + headers.size() + 1 ) > 140 )
            len = 140 - headers.size() - 1;
        appendOctet( len + headers.size() + 1 );
        appendOctet( headers.size() );
        uint u;
        for ( u = 0; u < (uint)headers.size(); u++ ) {
            appendOctet( headers[u] );
        }
        for ( u = 0; u < len; u++ ) {
            appendOctet( appData[u] );
        }
    }
}

QSMSDeliverMessage::QSMSDeliverMessage(const QByteArray &pdu)
    : QPDUMessage(pdu)
{
}

// Unpack a message that has a "//SCKL" header in its text body.
// This is a standard to interoperate with networks and phones
// that do not support user data headers properly.
static void unpackSckl( QSMSMessage& m, const QString& text )
{
    // Split into header and body, separated by a space.
    int index = text.indexOf( QChar(' ') );
    if ( index == -1 ) {
        m.addPart( QSMSMessagePart( text ) );
        return;
    }
    QString head = text.mid( 6, index - 6 );
    QString body = text.mid( index + 1 );

    // Convert the header from hex into raw binary and then
    // copy its details into the real message header fields.
    QByteArray header = QAtUtils::fromHex( head );
    int len;
    if ( header.size() < 4 ) {
        len = header.size();
    } else {
        len = 4;
    }
    QByteArray ports( len, '\0' );
    memcpy( ports.data(), header.data(), len );
    m.addPart( QSMSMessagePart( "application/x-qtopia-wdp-ports", ports ) );
    if ( header.size() > 4 ) {
        QByteArray fragments( header.size() - 4 + 2, '\0' );
        fragments[0] = (char)( SMS_HK_Concat_8Bit );
        fragments[1] = (char)( header.size() - 4 );
        memcpy( fragments.data() + 2, header.data() + 4, header.size() - 4 );
        m.setHeaders( fragments );
    }

    // Is the body text or binary?
    int port = m.destinationPort();
    if ( port == 226 || port == 9204 ||     // vCard port numbers.
         port == 228 || port == 9205 ) {    // vCalendar port numbers.
        m.setApplicationData( body.toLatin1() );
    } else {
        m.setApplicationData( QAtUtils::fromHex( body ) );
    }
}

QSMSMessage QSMSDeliverMessage::unpack(QTextCodec *codec)
{
    QSMSMessage m;
    bool statusReport;
    bool userDataHeader;
    bool replyPath;
    unsigned char protocol;
    unsigned char scheme;
    uint msgType;
    uint validityFormat;

    // Start from the beginning of the PDU.
    reset();

    // Extract the service center address.
    m.setServiceCenter( address(true) );

    // Pull apart the message header.  We handle both deliver and
    // submit messages, because we may have pulled a submit out
    // of the phone's outgoing SMS queue.
    if ( !needOctets(1) )
        return m;
    msgType = bits(0, 2);
    if ( msgType == SMS_Deliver ) {
        // Bits 3 and 4 are unused for deliver messages.
        statusReport = bit(5);
        userDataHeader = bit(6);
        replyPath = bit(7);
        validityFormat = SMS_VF_NoPresent;
    } else if ( msgType == SMS_Submit ) {
        validityFormat = bits(3, 2);
        statusReport = bit(5);
        userDataHeader = bit(6);
        replyPath = bit(7);
    } else {
        // Probably a delivery report, which we don't process.
        return m;
    }
    skipOctet();

    // Remember the status and reply bits.
    m.setStatusReportRequested(statusReport);
    m.setReplyRequest(replyPath);

    // Skip the message reference (submit messages only).
    if ( msgType == SMS_Submit ) {
        if ( !needOctets(1) )
            return m;
        skipOctet();
    }

    // Get the address of the sender (delivery) or recipient (submit).
    if ( msgType == SMS_Deliver ) {
        m.setSender( address(false) );
    } else {
        m.setRecipient( address(false) );
    }

    // Get the protocol identifier and data coding scheme.
    if ( !needOctets(2) )
        return m;
    protocol = getOctet();
    scheme = getOctet();
    m.setProtocol( protocol );
    m.setDataCodingScheme( scheme );
    if ( ( scheme & QSMS_MessageClass ) != 0 )
        m.setMessageClass( scheme & 0x03 );
    else
        m.setMessageClass( -1 );

    // Get the timestamp (deliver) or validity period (submit) information.
    if ( msgType == SMS_Deliver ) {
        m.setTimestamp( timeStamp() );
    } else {
        if ( validityFormat == SMS_VF_Relative ) {
            if ( !needOctets(1) )
                return m;
            m.setGsmValidityPeriod( bits(0, 8) );
            skipOctet();
        } else if (validityFormat == SMS_VF_Absolute ) {
            m.setTimestamp( timeStamp() );
        } else if (validityFormat == SMS_VF_Enhanced ) {
            // Not supported yet - skip the octets.
            if ( !needOctets(7) )
                return m;
            mPosn += 7;
        } else {
            m.setValidityPeriod( (uint)(-1) );
        }
    }

    // Read the user data field.
    QByteArray *headers = nullptr;
    QString text;
    text = userData( (QSMSDataCodingScheme)scheme, codec,
                     headers, userDataHeader, false );
    if ( !headers && text.startsWith( "//SCKL" ) ) {
        unpackSckl( m, text );
        return m;
    }
    if ( headers ) {
        m.setHeaders( *headers );
        delete headers;
        m.unpackHeaderParts();
        if ( isSMSDatagram( m.d->mHeaders ) && mPosn <= mBuffer.size() ) {
            // The rest of the PDU is assumed to be the application payload.
            QByteArray array = mBuffer.mid( mPosn );
            m.addPart( QSMSMessagePart( "application/x-qtopia-wdp", array ) );
        }
    }
    if ( text.length() > 0 ) {
        m.addPart( QSMSMessagePart( text ) );
    }

    // Return the completed message to the caller.
    return m;
}

static void iso639_2_from_language(QCBSMessage::Language lang, char *iso639)
{
    switch (lang) {
    case QCBSMessage::German:
        iso639[0] = 'd';
        iso639[1] = 'e';
        iso639[2] = '\0';
        break;
    case QCBSMessage::English:
        iso639[0] = 'e';
        iso639[1] = 'n';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Italian:
        iso639[0] = 'i';
        iso639[1] = 't';
        iso639[2] = '\0';
        break;
    case QCBSMessage::French:
        iso639[0] = 'f';
        iso639[1] = 'r';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Spanish:
        iso639[0] = 'e';
        iso639[1] = 's';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Dutch:
        iso639[0] = 'n';
        iso639[1] = 'l';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Swedish:
        iso639[0] = 's';
        iso639[1] = 'v';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Danish:
        iso639[0] = 'd';
        iso639[1] = 'a';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Portuguese:
        iso639[0] = 'p';
        iso639[1] = 't';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Finnish:
        iso639[0] = 'f';
        iso639[1] = 'i';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Norwegian:
        iso639[0] = 'n';
        iso639[1] = 'o';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Greek:
        iso639[0] = 'e';
        iso639[1] = 'l';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Turkish:
        iso639[0] = 't';
        iso639[1] = 'r';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Hungarian:
        iso639[0] = 'h';
        iso639[1] = 'u';
        iso639[2] = '\0';
        break;
    case QCBSMessage::Polish:
        iso639[0] = 'p';
        iso639[1] = 'l';
        iso639[2] = '\0';
        break;
    default:
        iso639[0] = '\0';
        iso639[1] = '\0';
        iso639[2] = '\0';
        break;
    }

    return;
}

QCBSDeliverMessage::QCBSDeliverMessage()
    : QPDUMessage()
{
    // Nothing to do here.
}


QCBSDeliverMessage::QCBSDeliverMessage(const QByteArray &pdu)
    : QPDUMessage(pdu)
{
    // Nothing to do here.
}


QCBSMessage QCBSDeliverMessage::unpack(QTextCodec *codec)
{
    QCBSMessage m;
    unsigned char scheme;
    uint len;

    // Start from the beginning of the PDU.
    reset();

    // Extract the header fields.
    if ( !needOctets(6) )
        return m;

    const char *mOffset = mBuffer.constData() + mPosn;
    m.setMessageCode( ((mOffset[0] & 0xFC) << 2) | (mOffset[1] & 0x0F) );
    m.setScope( (QCBSMessage::GeographicalScope)(mOffset[0] & 0x03) );
    m.setUpdateNumber( (mOffset[1] >> 4) & 0x0F );
    m.setChannel( ((mOffset[2] & 0xFF) << 8) | (mOffset[3] & 0xFF) );
    scheme = (unsigned char)((mOffset[4] >> 4) & 0x0F);
    m.setLanguage( (QCBSMessage::Language)(mOffset[4] & 0x0F) );
    m.setNumPages( (uint)((mOffset[5] >> 4) & 0x0F) );
    m.setPage( (uint)(mOffset[5] & 0x0F) );
    mPosn += 6;

    // Read the user data field and strip CR's, LF's, and NUL's from the end.
    QByteArray *headers = nullptr;
    QString text = userData
        ( (QSMSDataCodingScheme)scheme, codec, headers, false, true );
    len = text.length();
    while ( len > 0 && ( text.at(len - 1) == QChar ('\r') || text.at(len - 1) == QChar ('\n') ||
                         text.at(len - 1) == QChar ('\0') ) ) {
        --len;
    }
    m.setText( text.left( len ) );

    // Return the completed message to the caller.
    return m;
}

void QCBSDeliverMessage::pack(const QCBSMessage &m, QSMSDataCodingScheme scheme)
{
    int numPad;
    QTextCodec *codec = QAtUtils::codec( "gsm" );
    QByteArray header;

    // Clear the pdu before we start.
    mBuffer = QByteArray();
    mPosn = 0;
    mBits = 0;

    mBuffer.append( (char) (((m.messageCode() >> 4) & 0x3F) | (m.scope() << 6)) );
    mBuffer.append( (char)(((m.messageCode() & 0xF) << 4) | (m.updateNumber() & 0xF)) );
    mBuffer.append( (char)((m.channel() & 0x0000FF00) >> 8) );
    mBuffer.append( (char)(m.channel() & 0x000000FF) );

    if (scheme == QSMS_UCS2Alphabet)
        mBuffer.append( 0x11 );
    else
        mBuffer.append( (char)(((scheme & 0x0F)<<4) | (m.language() & 0x0F)) );

    mBuffer.append( (char)((m.numPages() & 0x0F) | ((m.page() & 0x0F) << 4)) );

    QString paddedText = m.text();

    if (scheme == QSMS_UCS2Alphabet) {
        char iso639[3];
        unsigned short c;

        iso639_2_from_language(m.language(), iso639);
        c = ((iso639[1] & 0x7F)<<7) | (iso639[0] & 0x7F);
        mBuffer.append( (char)(c & 0xFF) );
        mBuffer.append( (char)(c >> 8) );
        numPad = 40 - (paddedText.length());
    }
    else
        numPad = 93 - getEncodedLength(paddedText, paddedText.length());

    for (int i = 0; i < numPad; i++)
        paddedText.append(QChar(0x0D));

    setUserData(paddedText, scheme, codec, header,true);
}
