| /**************************************************************************** |
| ** |
| ** This file is part of the Qt Extended Opensource Package. |
| ** |
| ** Copyright (C) 2009 Trolltech ASA. |
| ** |
| ** Contact: Qt Extended Information ([email protected]) |
| ** |
| ** 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 <stdlib.h> |
| |
| #include <qsmsmessage.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 = 0; |
| 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 ( d->ref == 1 ) |
| 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 0; |
| } |
| |
| 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 = 0; |
| |
| // 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 = 0; |
| 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; |
| } |
| |
| |
| 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 = 0; |
| QString text = userData |
| ( (QSMSDataCodingScheme)scheme, codec, headers, false, true ); |
| len = text.length(); |
| while ( len > 0 && ( text[len - 1] == '\r' || text[len - 1] == '\n' || |
| text[len - 1] == '\0' ) ) { |
| --len; |
| } |
| m.setText( text.left( len ) ); |
| |
| // Return the completed message to the caller. |
| return m; |
| } |
| |
| void QCBSDeliverMessage::pack(const QCBSMessage &m, QSMSDataCodingScheme scheme) |
| { |
| // Clear the pdu before we start. |
| mBuffer = QByteArray(); |
| mPosn = 0; |
| mBits = 0; |
| |
| scheme = QSMS_DefaultAlphabet; |
| QByteArray data; |
| 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) ); |
| mBuffer.append( (char)(((scheme & 0x0F)<<4) | (m.language() & 0x0F)) ); |
| mBuffer.append( (char)((m.numPages() & 0x0F) | ((m.page() & 0x0F) << 4)) ); |
| |
| QTextCodec *codec = QAtUtils::codec( "gsm" ); |
| QByteArray header; |
| |
| QString paddedText = m.text(); |
| |
| int numPad = 93 - getEncodedLength(paddedText, paddedText.length()); |
| |
| for (int i = 0; i < numPad; i++) |
| paddedText.append(QChar(0x0D)); |
| |
| setUserData(paddedText, scheme, codec, header,true); |
| } |