blob: 1e2a317330ddade9a1288358fe84716b5084236b [file] [log] [blame]
/****************************************************************************
**
** 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);
}
OSZAR »