1 /****************************************************************************
3 ** Copyright (C) 2000-2008 TROLLTECH ASA. All rights reserved.
5 ** This file is part of the Opensource Edition of the Qtopia Toolkit.
7 ** This software is licensed under the terms of the GNU General Public
8 ** License (GPL) version 2.
10 ** See http://www.trolltech.com/gpl/ for GPL licensing information.
12 ** Contact info@trolltech.com if any conditions of this licensing are
17 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 ****************************************************************************/
24 #include <qsmsmessage.h>
26 #include "qsmsmessage_p.h"
28 #include <qtopiaphone/private/qsmsmessage_p.h>
31 #include <qgsmcodec.h>
33 #include <qtopialog.h>
36 #define qLog(dbgcat) if(1); else qDebug()
38 #include <qstringlist.h>
40 #include <qtextcodec.h>
44 class QSMSMessagePartPrivate
47 QSMSMessagePartPrivate( const QString& text )
53 QSMSMessagePartPrivate( const QString& mimeType, const QByteArray& data )
56 this->mimeType = mimeType;
60 QSMSMessagePartPrivate( const QString& mimeType, const QByteArray& data, uint position )
63 this->mimeType = mimeType;
65 this->position = position;
67 QSMSMessagePartPrivate( const QSMSMessagePartPrivate& part )
69 this->isText = part.isText;
70 this->text = part.text;
71 this->mimeType = part.mimeType;
72 this->data = part.data;
73 this->position = part.position;
83 class QSMSMessagePrivate
88 #if QT_VERSION < 0x040400
93 mValidity = 2 * 24 * 60; // 2 days
94 mReplyRequest = false;
95 mStatusReportRequested = false;
96 mMessageType = QSMSMessage::Normal;
99 mBestScheme = (QSMSDataCodingScheme)(-1);
100 mDataCodingScheme = -1;
105 ~QSMSMessagePrivate()
109 /* Convert from minutes to GSM 03.40 specification (section 9.2.3.12).
110 - 0-143 = 0 to 12 hours in 5 minute increments (0 = 5 minutes).
111 - 144-167 = 12hrs30min to 24hrs in 30 minute increments.
112 - 168-196 = 2days to 30days in 1 day increments.
113 - 197-255 = 5weeks to 63weeks in 1 week increments.
115 uint gsmValidityPeriod()
117 uint mins = mValidity;
122 if ( mins <= 12 * 60 )
123 return ((mins / 5) - 1);
125 if ( mins <= 24 * 60 )
126 return ((mins + 29 - (12 * 60 + 30)) / 30) + 144;
128 uint days = (mins + (24 * 60 - 1)) / (24 * 60);
131 return days - 2 + 168;
133 if ( days <= 63 * 7 )
134 return ((days + 6) / 7) - 5 + 197;
139 void setGsmValidityPeriod(uint value)
142 mValidity = (value + 1) * 5;
143 else if ( value <= 167 )
144 mValidity = (value - 143) * 30 + 12 * 60;
145 else if ( value <= 196 )
146 mValidity = (value - 166) * 24 * 60;
148 mValidity = (value - 192) * 7 * 24 * 60;
151 template <typename Stream> void readFromStream( Stream &s )
155 if ( codec.length() > 0)
156 mCodec = QTextCodec::codecForName( codec.toLatin1() );
166 mReplyRequest = (val != 0);
168 mStatusReportRequested = (val != 0);
170 mForceGsm = (val != 0);
172 mMessageType = (QSMSMessage::MessageType)val;
174 mBestScheme = (QSMSDataCodingScheme)val;
177 mCachedBody = QString();
178 s >> mDataCodingScheme;
183 template <typename Stream> void writeToStream( Stream &s )
186 s << QString( mCodec->name() );
194 s << (int)mReplyRequest;
195 s << (int)mStatusReportRequested;
197 s << (int)mMessageType;
198 s << (int)mBestScheme;
201 s << mDataCodingScheme;
206 void copy( QSMSMessagePrivate *from )
209 mHeaders = from->mHeaders;
212 #if QT_VERSION < 0x040400
218 QString mServiceCenter;
221 QDateTime mTimestamp;
224 bool mStatusReportRequested;
226 QSMSMessage::MessageType mMessageType;
227 QSMSDataCodingScheme mBestScheme;
229 QList<QSMSMessagePart> mParts;
231 int mDataCodingScheme;
237 \enum QSMSMessage::MessageType
238 Defines the type of an SMS message.
240 \value Normal The message is a normal SMS message.
241 \value CellBroadCast The message is a cell broadcast message.
242 \value StatusReport The message is an SMS status report message.
246 \enum QSMSDataCodingScheme
247 Define the data coding scheme to use to encode SMS message text.
249 \value QSMS_Compressed Flag that indicates that the data is compressed
250 (not used at present).
251 \value QSMS_MessageClass Flag that indicates the message class
252 (not used at present).
253 \value QSMS_DefaultAlphabet Use the default 7-bit GSM alphabet.
254 \value QSMS_8BitAlphabet Use the locale-specific 8-bit alphabet.
255 \value QSMS_UCS2Alphabet Use the UCS-2 16-bit alphabet.
256 \value QSMS_ReservedAlphabet Use the reserved alphabet
257 (not used at present).
261 \class QSMSMessagePart
263 \brief The QSMSMessagePart class specifies a part within an SMS message.
268 QSMSMessage objects contain zero or more "parts", which describe
269 the contents of the SMS message. A part may be plain text,
270 or a binary stream tagged by a MIME type.
274 Constructs an empty SMS message part.
276 QSMSMessagePart::QSMSMessagePart()
278 d = new QSMSMessagePartPrivate( QString() );
282 Constructs a new plain text SMS message part from the
285 QSMSMessagePart::QSMSMessagePart( const QString& text )
287 d = new QSMSMessagePartPrivate( text );
291 Constructs a new binary SMS message part with the specified
292 \a mimeType and \a data.
294 QSMSMessagePart::QSMSMessagePart( const QString& mimeType, const QByteArray& data )
296 d = new QSMSMessagePartPrivate( mimeType, data );
300 Constructs a new binary SMS message part with the specified
301 \a mimeType and \a data. The part is intended to be displayed
302 at \a position within a subsequent text part.
304 QSMSMessagePart::QSMSMessagePart( const QString& mimeType, const QByteArray& data, uint position )
306 d = new QSMSMessagePartPrivate( mimeType, data, position );
310 Constructs a copy of \a part.
312 QSMSMessagePart::QSMSMessagePart( const QSMSMessagePart& part )
314 d = new QSMSMessagePartPrivate( *(part.d) );
318 Destructs the QSMSMessagePart.
320 QSMSMessagePart::~QSMSMessagePart()
326 Assigns a copy of \a part to this object.
328 QSMSMessagePart& QSMSMessagePart::operator=( const QSMSMessagePart& part )
330 if ( &part != this ) {
332 d = new QSMSMessagePartPrivate( *(part.d) );
338 Returns true if this SMS message part is plain text; or false if binary.
340 bool QSMSMessagePart::isText() const
346 Returns the plain text contents of this SMS message part,
347 or QString() if it is not a text part.
349 QString QSMSMessagePart::text() const
355 Returns the MIME type associated with this SMS message part,
356 or QString() if it is not a binary part.
358 QString QSMSMessagePart::mimeType() const
364 Returns the binary data associated with this SMS message part.
365 Returns and empty QByteArray if it is not a binary part.
367 const QByteArray& QSMSMessagePart::data() const
373 Returns the text position to display this SMS message part at.
375 uint QSMSMessagePart::position() const
382 \fn void QSMSMessagePart::deserialize(Stream &stream)
384 template <typename Stream> void QSMSMessagePart::deserialize(Stream &stream)
388 d->isText = (flag != 0);
390 stream >> d->mimeType;
392 stream >> d->position;
397 \fn void QSMSMessagePart::serialize(Stream &stream) const
399 template <typename Stream> void QSMSMessagePart::serialize(Stream &stream) const
401 stream << (int)(d->isText);
403 stream << d->mimeType;
405 stream << d->position;
411 \brief The QSMSMessage class specifies the contents of an SMS message.
413 This class is intended for use with QSMSReader and QSMSSender to process
414 SMS messages according to 3GPP TS 03.40 and 23.040.
416 An incoming SMS message from QSMSReader will typically have text() and
417 sender() set. Other fields such as destinationPort() and
418 applicationData() may be set if the message is a WAP Push or SMS datagram
419 message rather than plain text.
421 An outgoing SMS message sent via QSMSSender will need to have at least
422 recipient() and text() set. If the message is a WAP Push or SMS datagram
423 message rather than plain text, then destinationPort() and applicationData()
426 Special header fields in SMS messages can be accessed with serviceCenter(),
427 replyRequest(), statusReportRequested(), validityPeriod(), timestamp(),
428 dataCodingScheme(), and protocol().
434 Constructs an empty QSMSMessage.
436 QSMSMessage::QSMSMessage()
438 d = new QSMSMessagePrivate;
442 Constructs an QSMSMessage that is a copy of \a msg.
444 QSMSMessage::QSMSMessage(const QSMSMessage &msg)
451 Destructs the QSMSMessage.
453 QSMSMessage::~QSMSMessage()
455 if ( !d->ref.deref() )
459 QSMSMessagePrivate *QSMSMessage::dwrite()
461 // If we are the only user of the private object, return it as-is.
465 // Create a new private object and copy the current contents into it.
466 QSMSMessagePrivate *newd = new QSMSMessagePrivate();
468 if ( !d->ref.deref() )
475 Assigns a copy of \a msg to this object.
477 QSMSMessage& QSMSMessage::operator=( const QSMSMessage &msg)
482 if ( !d->ref.deref() )
492 Sets the contents of this QSMSMessage to a single plain text
493 body containing \a str. This is for simple messages only.
494 Complex messages should be constructed part by part using
495 the addPart() method.
499 void QSMSMessage::setText(const QString &str)
502 addPart( QSMSMessagePart( str ) );
506 Returns the contents of this QSMSMessage as a single plain text string.
507 If the message contains binary parts, they will not appear
508 in the result. This is for simple message viewers only.
509 Complex message viewers should iterate over the list returned
514 QString QSMSMessage::text() const
516 // Handle the easy cases first.
517 if ( d->mParts.count() == 0 ) {
519 } else if ( d->mParts.count() == 1 && d->mParts[0].isText() ) {
520 return d->mParts[0].text();
521 } else if ( d->mCachedBody.length() > 0 ) {
522 return d->mCachedBody;
525 // We need the private structure to be writable to cache the body.
526 const_cast<QSMSMessage *>(this)->dwrite();
528 // Append all text parts to get the complete body.
529 QString body = QString();
530 for ( int posn = 0; posn < d->mParts.count(); ++posn ) {
531 if ( d->mParts[posn].isText() ) {
532 body += d->mParts[posn].text();
535 d->mCachedBody = body;
540 If the message consists solely of characters in the 7-bit GSM
541 encoding, then the message will be transmitted that way.
542 Otherwise \a codec is used to convert the characters into an
543 appropriate language-specific 8-bit encoding. If \a codec is
544 set to NULL, then the default UCS-2 encoding for GSM messages
549 void QSMSMessage::setTextCodec(QTextCodec *codec)
551 dwrite()->mCodec = codec;
555 Returns the current 8-bit text codec, or NULL if none has
560 QTextCodec *QSMSMessage::textCodec() const
566 If \a force is true, then the codec set by QSMSMessage::setTextCodec
567 is ignored and the 7-bit GSM encoding is always used. Setting this
568 flag increases the number of characters that can be sent in any
569 given SMS message, but may lose information.
573 void QSMSMessage::setForceGsm(bool force)
575 dwrite()->mForceGsm = force;
579 Returns true if the 7-bit GSM encoding has been forced.
583 bool QSMSMessage::forceGsm() const
589 Sets the SMS data coding \a scheme to use for this message.
590 Normally you won't need to call this unless the user has
591 somehow explicitly requested an override. By default,
592 the QSMSMessage class will choose the best scheme for you.
593 Should be set to one of \c QSMS_DefaultAlphabet,
594 \c QSMS_8BitAlphabet, or \c QSMS_UCS2Alphabet.
598 void QSMSMessage::setBestScheme(QSMSDataCodingScheme scheme)
600 dwrite()->mBestScheme = scheme;
605 Returns the best SMS data coding scheme to use for this
606 message, determined by an inspection of the plain text body parts.
610 QSMSDataCodingScheme QSMSMessage::bestScheme() const
612 QTextCodec *codec = QAtUtils::codec( "gsm-noloss" );
613 QString body = text();
614 uint len = body.length();
617 // Did the user provide a scheme override?
618 if ( d->mBestScheme != (QSMSDataCodingScheme)(-1) )
619 return d->mBestScheme;
621 // Encode zero-length bodies in the default alphabet.
623 return QSMS_DefaultAlphabet;
625 // Always use GSM if we are forced to do so.
627 return QSMS_DefaultAlphabet;
629 // Check the body for non-GSM characters.
630 gsmSafe = codec->canEncode( body );
632 // Use the default alphabet if everything is GSM-compatible.
634 return QSMS_DefaultAlphabet;
636 // See if we can convert to 8-bit using the codec
637 // without losing any information.
638 if ( d->mCodec && d->mCodec->canEncode( body ) )
639 return QSMS_8BitAlphabet;
641 // Default to the UCS-2 alphabet.
642 return QSMS_UCS2Alphabet;
646 Sets the recipient's telephone number to \a txt.
650 void QSMSMessage::setRecipient(const QString &txt)
652 dwrite()->mRecipient = txt;
656 Returns the recipient's telephone number. Normally QString()
657 for an incoming message.
661 QString QSMSMessage::recipient() const
663 return d->mRecipient;
667 Sets the sender's telephone number to \a txt.
671 void QSMSMessage::setSender(const QString &txt)
673 dwrite()->mSender = txt;
677 Returns the sender's telephone number. Normally QString()
678 for an outgoing message.
682 QString QSMSMessage::sender() const
688 Sets the service center to use for transmitting this SMS message,
689 or QString() for the default service center, to \a str.
693 void QSMSMessage::setServiceCenter(const QString &str)
695 dwrite()->mServiceCenter = str;
699 Returns the service center.
701 \sa setServiceCenter()
703 QString QSMSMessage::serviceCenter() const
705 return d->mServiceCenter;
709 Enable or disable the "reply request" flag for this SMS message,
710 according to the value of \a on.
714 void QSMSMessage::setReplyRequest(bool on )
716 dwrite()->mReplyRequest = on;
720 Returns the "reply request" flag.
722 \sa setReplyRequest()
724 bool QSMSMessage::replyRequest() const
726 return d->mReplyRequest;
730 Sets the status report requested flag to \a on.
732 \sa statusReportRequested()
734 void QSMSMessage::setStatusReportRequested(bool on)
736 dwrite()->mStatusReportRequested = on;
740 Returns true if status report requested flag is currently set;otherwise returns false.
742 \sa setStatusReportRequested()
744 bool QSMSMessage::statusReportRequested() const
746 return d->mStatusReportRequested;
750 Sets the validity period to \a minutes. The default is 2 days. If the value
751 is set to \c{(uint)(-1)}, it indicates that the message should have no
752 validity period specified.
754 \sa validityPeriod(), gsmValidityPeriod()
756 void QSMSMessage::setValidityPeriod(uint minutes)
758 dwrite()->mValidity = minutes;
762 Returns the validity period in minutes for this message. The default is 2 days.
763 If the value is \c{(uint)(-1)}, it indicates that the message should have no
764 validity period specified.
766 \sa setValidityPeriod(), setGsmValidityPeriod()
768 uint QSMSMessage::validityPeriod() const
774 Sets the GSM validity period to \a value, which must be between
775 0 and 255, inclusive. The setValidity() method
776 is a friendlier way to set the validity value.
778 0 to 143 indicates 0 to 12 hours in 5 minute increments (0 = 5 minutes).
779 144 to 167 indicates 12 hrs 30 min to 24 hrs in 30 minute increments.
780 168 to 196 indicates 2 days to 30 days in 1 day increments.
781 197 to 255 indicates 5 weeks to 63 weeks in 1 week increments.
783 \sa gsmValidityPeriod(), validityPeriod()
785 void QSMSMessage::setGsmValidityPeriod(uint value)
787 dwrite()->setGsmValidityPeriod(value);
791 Returns the GSM validity period value, between 0 and 255, inclusive.
793 \sa setGsmValidityPeriod(), setValidityPeriod()
795 uint QSMSMessage::gsmValidityPeriod() const
797 return d->gsmValidityPeriod();
801 Sets the SMS message's \a timestamp.
805 void QSMSMessage::setTimestamp(const QDateTime& timestamp)
807 dwrite()->mTimestamp = timestamp;
811 Returns the SMS message's timestamp, which will be null if the
812 message does not have a timestamp.
816 QDateTime QSMSMessage::timestamp() const
818 return d->mTimestamp;
822 Returns the SMS message type.
826 QSMSMessage::MessageType QSMSMessage::messageType() const
828 return d->mMessageType;
832 Sets the SMS message type to \a m. There is rarely any need to
833 set this to something other than QSMSMessage::Normal.
837 void QSMSMessage::setMessageType(MessageType m)
839 dwrite()->mMessageType = m;
843 Sets the SMS message's user data headers to \a value.
847 void QSMSMessage::setHeaders(const QByteArray& value)
849 dwrite()->mHeaders = value;
853 Returns the SMS message's user data headers.
857 const QByteArray& QSMSMessage::headers() const
863 Clear all body parts from this SMS message.
865 \sa addPart(), addParts(), parts()
867 void QSMSMessage::clearParts()
869 dwrite()->mParts.clear();
870 dwrite()->mCachedBody = QString();
874 Add a new body \a part to this SMS message.
876 \sa clearParts(), addParts(), parts()
878 void QSMSMessage::addPart( const QSMSMessagePart& part )
880 // We need a writable copy.
883 // Clear the part list if we have one empty text part.
884 if ( d->mParts.count() == 1 &&
885 d->mParts[0].isText() &&
886 d->mParts[0].text().length() == 0 ) {
890 // Append the new part and clear the cached text.
891 d->mParts.append( part );
892 d->mCachedBody = QString();
896 Add a list of body \a parts to this SMS message.
898 \sa clearParts(), addPart(), parts()
900 void QSMSMessage::addParts( const QList<QSMSMessagePart>& parts )
902 // We need a writable copy.
905 if ( d->mParts.count() == 1 &&
906 d->mParts[0].isText() &&
907 d->mParts[0].text().length() == 0 ) {
911 d->mCachedBody = QString();
915 Returns a list of all body parts in this SMS message.
917 \sa clearParts(), addParts(), addPart()
919 QList<QSMSMessagePart> QSMSMessage::parts() const
925 Compute an estimate for the number of messages that will need
926 to be used to send this SMS message (\a numMessages), and the
927 number of spare characters that are left in the last message
928 before it overflows (\a spaceLeftInLast).
930 This function may be useful in user interfaces to indicate to
931 the user that an SMS message needs to be sent in multiple pieces,
932 costing the user more money.
934 void QSMSMessage::computeSize( uint& numMessages, uint& spaceLeftInLast ) const
936 int part = findPart( "application/x-qtopia-wdp-ports" );
938 // This is an application datagram, which has its size
939 // computed using a different algorithm.
940 uint headerLen = 3 + d->mParts[(uint)part].data().size();
941 uint dataLen = applicationData().size();
942 if ( ( headerLen + dataLen ) <= 140 ) {
944 spaceLeftInLast = 134 - headerLen - dataLen;
946 uint partSize = ( 134 - headerLen );
947 numMessages = ( dataLen + partSize - 1 ) / partSize;
948 spaceLeftInLast = dataLen - numMessages * partSize;
953 QSMSDataCodingScheme scheme = bestScheme();
955 QString body = text();
957 if ( scheme == QSMS_DefaultAlphabet ) {
959 // Encode the message using 7-bit GSM.
963 spaceLeftInLast = 160 - len;
965 // 153 = 160 - fragment_header_size (7).
966 numMessages = ( len + 152 ) / 153;
969 spaceLeftInLast = 153 - len;
974 } else if ( scheme == QSMS_8BitAlphabet ) {
976 // Encode the message using an 8-bit character set.
977 QByteArray converted = d->mCodec->fromUnicode( body );
978 len = converted.length();
981 spaceLeftInLast = 140 - len;
983 // 134 = 140 - fragment_header_size (6).
984 numMessages = ( len + 133 ) / 134;
987 spaceLeftInLast = 134 - len;
994 // Encode the message with unicode.
998 spaceLeftInLast = 70 - len;
1000 // 67 = 70 - fragment_header_size (3).
1001 numMessages = ( len + 66 ) / 67;
1004 spaceLeftInLast = 67 - len;
1006 spaceLeftInLast = 0;
1012 Returns the destination port number if this SMS message contains
1013 an application datagram, or -1 if not an application datagram.
1015 When an SMS message is received that has a destination port
1016 number, Qtopia will attempt to find a QDS service that can handle it.
1017 Qtopia first looks for a QDS service named \c push for the MIME type
1018 \c T from the WAP push header. Next, it looks for a service named
1019 \c push for the MIME type \c{application/x-smsapp-N} where
1020 \c N is the port number in decimal.
1022 If a matching service is found, then the SMS message is
1023 sent to the corresponding application via QDS. The QCop
1024 message is that specified in the QDS service definition.
1025 Thus, applications can register to receive special SMS messages.
1027 The following QDS definition, in \c{etc/qds/ContactsPhone} will
1028 direct vcard's that are received via WAP to the \c ContactsPhone
1029 service. The \c ContactsPhone service is normally implemented
1030 by the \c addressbook program.
1035 Context=ContactsPhone
1037 RequestDataType=text/x-vcard
1040 Description[]=Receive a vcard via WAP push
1043 The \c Attributes must contain \c push and the \c RequestDataType
1044 must be the MIME type to be dispatched. The QCop message that is
1045 delivered to the application will have the name
1046 \c pushVCard(QDSActionRequest). The data in the action request
1047 will be the payload of the push message.
1049 The auxilary data in the action request will be a QByteArray
1050 containing the full QSMSMessage object, from which the application
1051 can extra header information if it needs it. Use the QSMSMessage
1052 datastream operators to extract the QSMSMessage object.
1054 If the application fails to process an SMS message that is sent to
1055 it via QCop, then it will be lost. It is important that such
1056 applications take steps to save the message if necessary.
1058 If a matching service is not found, then the SMS message
1059 will be delivered to the \c qtmail application normally,
1060 and then saved by that application.
1062 \sa setDestinationPort(), sourcePort()
1064 int QSMSMessage::destinationPort() const
1066 int part = findPart( "application/x-qtopia-wdp-ports" );
1070 QByteArray data = d->mParts[(uint)part].data();
1071 if ( data.size() == 4 ) {
1073 return ((((int)(data[0])) & 0xFF) << 8) |
1074 (((int)(data[1])) & 0xFF);
1076 } else if ( data.size() == 2 ) {
1078 return (((int)(data[0])) & 0xFF);
1085 Sets the destination port number for an SMS message that contains
1086 an application datagram to \a value.
1088 \sa destinationPort(), sourcePort()
1090 void QSMSMessage::setDestinationPort(int value)
1094 int part = findPart( "application/x-qtopia-wdp-ports" );
1097 data[0] = (char)(value >> 8);
1098 data[1] = (char)value;
1102 source = sourcePort();
1104 data[0] = (char)(value >> 8);
1105 data[1] = (char)value;
1106 data[2] = (char)(source >> 8);
1107 data[3] = (char)source;
1109 removeParts( "application/x-qtopia-wdp-ports" );
1110 addPart( QSMSMessagePart( "application/x-qtopia-wdp-ports", data ) );
1114 Returns the source port number if this SMS message contains
1115 an application datagram, or -1 if not an application datagram.
1117 \sa setSourcePort(), destinationPort()
1119 int QSMSMessage::sourcePort() const
1121 int part = findPart( "application/x-qtopia-wdp-ports" );
1125 QByteArray data = d->mParts[(uint)part].data();
1126 if ( data.size() == 4 ) {
1128 return ((((int)(data[2])) & 0xFF) << 8) |
1129 (((int)(data[3])) & 0xFF);
1131 } else if ( data.size() == 2 ) {
1133 return (((int)(data[1])) & 0xFF);
1140 Sets the source port number for an SMS message that contains
1141 an application datagram to \a value.
1143 \sa sourcePort(), destinationPort()
1145 void QSMSMessage::setSourcePort(int value)
1149 int part = findPart( "application/x-qtopia-wdp-ports" );
1154 data[2] = (char)(value >> 8);
1155 data[3] = (char)value;
1157 dest = destinationPort();
1159 data[0] = (char)(dest >> 8);
1160 data[1] = (char)dest;
1161 data[2] = (char)(value >> 8);
1162 data[3] = (char)value;
1164 removeParts( "application/x-qtopia-wdp-ports" );
1165 addPart( QSMSMessagePart( "application/x-qtopia-wdp-ports", data ) );
1169 Returns the data if this SMS message contains an application datagram.
1170 Returns an empty byte array if this message is not a datagram.
1172 \sa setApplicationData()
1174 QByteArray QSMSMessage::applicationData() const
1177 QList<QSMSMessagePart>::ConstIterator iter;
1179 for ( iter = d->mParts.begin(); iter != d->mParts.end(); ++iter ) {
1180 if ( (*iter).mimeType() == "application/x-qtopia-wdp" ) {
1182 data.resize( size + (*iter).data().size() );
1183 memcpy( data.data() + size, (*iter).data().data(),
1184 (*iter).data().size() );
1191 Sets the data within an SMS message that contains an application
1192 datagram to \a value.
1194 \sa applicationData()
1196 void QSMSMessage::setApplicationData(const QByteArray& value)
1198 removeParts( "application/x-qtopia-wdp" );
1199 addPart( QSMSMessagePart( "application/x-qtopia-wdp", value ) );
1203 Sets the data coding scheme to use within an SMS message to \a value.
1204 If \a value is -1, then the system chooses the best data coding scheme
1205 based on the content.
1207 This method is mainly of use with application datagrams, not text
1210 \sa dataCodingScheme()
1212 void QSMSMessage::setDataCodingScheme(int value)
1214 dwrite()->mDataCodingScheme = value;
1218 Returns the data coding scheme to use within an SMS message.
1219 If the value is -1, then the system chooses the best data coding scheme
1220 based on the content.
1222 This method is mainly of use with application datagrams, not text
1225 \sa setDataCodingScheme()
1227 int QSMSMessage::dataCodingScheme() const
1229 return d->mDataCodingScheme;
1233 Sets the message class for this message to \a value. The \value should
1234 be -1 if the system should choose a default message class. The message
1235 class should otherwise be 0, 1, 2, or 3, according to 3GPP TS 03.38.
1237 void QSMSMessage::setMessageClass(int value)
1239 dwrite()->mMessageClass = value;
1243 Get the message class for this message, or -1 if the system should
1244 choose a default message class. The message class should otherwise
1245 be 0, 1, 2, or 3, according to 3GPP TS 03.38.
1247 int QSMSMessage::messageClass() const
1249 return d->mMessageClass;
1253 Sets the SMS message's protocol field to \a value.
1257 void QSMSMessage::setProtocol(int value)
1259 dwrite()->mProtocol = value;
1263 Returns the SMS message's protocol field.
1267 int QSMSMessage::protocol() const
1269 return d->mProtocol;
1273 Returns true if this message needs to be split into multiple messages
1274 before being transmitted over a GSM network; otherwise returns false.
1278 bool QSMSMessage::shouldSplit() const
1280 uint numMessages, spaceLeftInLast;
1281 this->computeSize( numMessages, spaceLeftInLast );
1282 return ( numMessages <=1 ? false : true );
1286 Split this message into several messages of smaller size for
1287 transmission over a GSM network.
1291 QList<QSMSMessage> QSMSMessage::split() const
1293 QList<QSMSMessage> list;
1294 uint numMessages, spaceLeftInLast;
1295 static uint fragmentCounter =0;
1297 computeSize( numMessages, spaceLeftInLast );
1298 if ( numMessages <= 1 ) {
1299 // Splitting is not necessary, so return a list with one message.
1304 // Get the number of characters to transmit in each fragment.
1306 QSMSDataCodingScheme scheme = bestScheme();
1308 case QSMS_DefaultAlphabet: split = 153; break;
1309 case QSMS_8BitAlphabet: split = 134; break;
1310 default: split = 67; break;
1313 // Split the message to create sub-messages and transmit them.
1319 if ( destinationPort() == -1 ) {
1320 // Splitting a simple text message.
1321 QString txt = text();
1322 while ( posn < txt.length() ) {
1324 len = txt.length() - posn;
1325 if ( len > split ) {
1328 tmp.setText( txt.mid( posn, len ) );
1329 tmp.setFragmentHeader( fragmentCounter, number++,
1330 numMessages, scheme );
1335 // Splitting a datagram message.
1336 QByteArray data = applicationData();
1338 uint partSize = 134 - 6;
1339 while ( posn < data.size() ) {
1341 if ( ( posn + partSize ) <= (uint)data.size() ) {
1342 part.resize(partSize);
1343 memcpy(part.data(), data.data() + posn, partSize );
1345 part.resize(data.size() - posn);
1346 memcpy(part.data(), data.data() + posn, data.size() - posn);
1348 tmp.setDestinationPort( destinationPort() );// Force 16-bit ports.
1349 tmp.setFragmentHeader( fragmentCounter, number++,
1350 numMessages, QSMS_8BitAlphabet );
1351 tmp.setApplicationData( part );
1357 // Increase the fragment counter for the next multi-part SMS message.
1358 fragmentCounter = ( fragmentCounter + 1 ) & 0xFF;
1364 Convert this SMS message into its binary PDU form, according to
1365 3GPP TS 03.40 and 3GPP TS 23.040. If the message has a recipient,
1366 then a SUBMIT message will be constructed. If the message does not
1367 have a recipient, then a DELIVER message will be constructed.
1371 QByteArray QSMSMessage::toPdu() const
1373 QSMSSubmitMessage submit( *this, recipient().isEmpty() );
1374 return submit.toByteArray();
1378 Convert a binary \a pdu into an SMS message, according to
1379 3GPP TS 03.40 and 3GPP TS 23.040.
1383 QSMSMessage QSMSMessage::fromPdu( const QByteArray& pdu )
1385 QSMSDeliverMessage pdumsg( pdu );
1386 return pdumsg.unpack();
1390 Returns the length of the service center address on the start of \a pdu.
1391 This is typically used with AT-based GSM modems that need to transmit
1392 the length of the pdu, excluding the service center address,
1393 along with the \c{AT+CMGS} command.
1395 int QSMSMessage::pduAddressLength( const QByteArray& pdu )
1397 if( pdu.length() > 0 )
1398 return (pdu[0] & 0xFF) + 1;
1403 int QSMSMessage::findPart( const QString& mimeType ) const
1405 QList<QSMSMessagePart>::ConstIterator iter;
1407 for ( iter = d->mParts.begin(); iter != d->mParts.end(); ++iter ) {
1408 if ( (*iter).mimeType() == mimeType )
1415 void QSMSMessage::removeParts( const QString& mimeType )
1418 QList<QSMSMessagePart>::Iterator iter;
1419 for ( iter = d->mParts.begin(); iter != d->mParts.end(); ) {
1420 if ( (*iter).mimeType() == mimeType ) {
1421 iter = d->mParts.erase( iter );
1428 void QSMSMessage::setFragmentHeader( uint refNum, uint part, uint numParts,
1429 QSMSDataCodingScheme scheme )
1431 dwrite()->mBestScheme = scheme;
1432 uint len = d->mHeaders.size();
1433 d->mHeaders.resize( len + 5 );
1434 d->mHeaders[len++] = 0; // Type for concatenated short messages.
1435 d->mHeaders[len++] = 3; // Length of header information.
1436 d->mHeaders[len++] = (char)refNum;
1437 d->mHeaders[len++] = (char)numParts;
1438 d->mHeaders[len++] = (char)part;
1442 void QSMSMessage::unpackHeaderParts()
1444 QByteArray headers = dwrite()->mHeaders;
1449 while ( ( posn + 2 ) <= (uint)(headers.size()) ) {
1450 tag = (unsigned char)(headers[posn]);
1451 len = (unsigned char)(headers[posn + 1]);
1452 if ( ( posn + len + 2 ) > (uint)(headers.size()) )
1454 switch ( (SMSHeaderKind)tag ) {
1456 case SMS_HK_Predefined_Sound:
1458 // Predefined sound type.
1460 QByteArray data( len - 1, 0 );
1461 memcpy( data.data(), headers.data() + posn + 3, len - 1 );
1462 addPart( QSMSMessagePart
1463 ( "application/x-qtopia-predefined-sound", data,
1464 headers[posn + 2] & 0xFF ) );
1469 case SMS_HK_User_Defined_Sound:
1471 // User defined sound type.
1473 QByteArray data( len - 1, 0 );
1474 memcpy( data.data(), headers.data() + posn + 3, len - 1 );
1475 addPart( QSMSMessagePart
1476 ( "audio/imelody", data, headers[posn + 2] & 0xFF ) );
1481 case SMS_HK_Predefined_Animation:
1483 // Predefined animation type.
1485 QByteArray data( len - 1, 0 );
1486 memcpy( data.data(), headers.data() + posn + 3, len - 1 );
1487 addPart( QSMSMessagePart
1488 ( "application/x-qtopia-predefined-animation", data,
1489 headers[posn + 2] & 0xFF ) );
1494 case SMS_HK_Large_Animation:
1495 case SMS_HK_Large_Picture:
1497 // 32x32 monochrome animation or image - turn it into a WBMP.
1499 QByteArray data( len + 3, 0 );
1504 for ( temp = 0; temp < (len - 1); ++temp ) {
1505 data[4 + temp] = (char)(~(headers[posn + 3 + temp]));
1507 addPart( QSMSMessagePart
1508 ( "image/vnd.wap.wbmp", data,
1509 headers[posn + 2] & 0xFF ) );
1514 case SMS_HK_Small_Animation:
1515 case SMS_HK_Small_Picture:
1517 // 16x16 monochrome animation or image - turn it into a WBMP.
1519 QByteArray data( len + 3, 0 );
1524 for ( temp = 0; temp < (len - 1); ++temp ) {
1525 data[4 + temp] = (char)(~(headers[posn + 3 + temp]));
1527 addPart( QSMSMessagePart
1528 ( "image/vnd.wap.wbmp", data,
1529 headers[posn + 2] & 0xFF ) );
1534 case SMS_HK_Variable_Picture:
1536 // Variable-sized monochrome image - turn it into a WBMP.
1538 QByteArray data( len - 1, 0 );
1541 data[2] = headers[posn + 3];
1542 data[3] = headers[posn + 4];
1543 for ( temp = 2; temp < (len - 1); ++temp ) {
1544 data[2 + temp] = (char)(~(headers[posn + 3 + temp]));
1546 addPart( QSMSMessagePart
1547 ( "image/vnd.wap.wbmp", data,
1548 headers[posn + 2] & 0xFF ) );
1553 case SMS_HK_Concat_8Bit: break;
1554 case SMS_HK_Concat_16Bit: break;
1556 case SMS_HK_AppPort_8Bit:
1557 case SMS_HK_AppPort_16Bit:
1559 QByteArray data( len, 0 );
1560 memcpy( data.data(), headers.data() + posn + 2, len );
1561 addPart( QSMSMessagePart
1562 ( "application/x-qtopia-wdp-ports", data ) );
1568 // Add the unknown part as "application/x-qtopia-sms-N".
1569 // Maybe qtmail will know what to do with it.
1570 QByteArray data( len, 0 );
1571 memcpy( data.data(), headers.data() + posn + 2, len );
1572 addPart( QSMSMessagePart( "application/x-qtopia-sms-" +
1573 QString::number( tag ), data ) );
1583 \fn void QSMSMessage::deserialize(Stream &stream)
1585 template <typename Stream> void QSMSMessage::deserialize(Stream &stream)
1587 dwrite()->readFromStream( stream );
1592 \fn void QSMSMessage::serialize(Stream &stream) const
1594 template <typename Stream> void QSMSMessage::serialize(Stream &stream) const
1596 d->writeToStream( stream );
1599 QPDUMessage::QPDUMessage()
1605 QPDUMessage::QPDUMessage(const QByteArray &data)
1612 QPDUMessage::~QPDUMessage()
1616 QPDUMessage::QPDUMessage(const QPDUMessage &msg)
1618 mBuffer = msg.mBuffer;
1623 void QPDUMessage::skipOctet()
1625 if ( mPosn < mBuffer.size() )
1629 QByteArray QPDUMessage::getOctets( uint len )
1632 if ( ( mBuffer.size() - mPosn ) < (int)len ) {
1633 result = mBuffer.mid( mPosn );
1636 result = mBuffer.mid( mPosn, (int)len );
1642 void QPDUMessage::setBit(int b, bool on)
1645 mBits |= (char)(Unit << b);
1647 mBits &= (char)(~(Unit << b));
1650 void QPDUMessage::setBits(int offset, int len, int val)
1652 uint mask = ((Unit << len) - 1) << offset;
1653 mBits = (char)((mBits & ~mask) | ((val << offset) & mask));
1656 void QPDUMessage::commitBits()
1662 bool QPDUMessage::bit(int b)
1664 if ( needOctets(1) )
1665 return (mBuffer[mPosn] & (Unit << b));
1670 unsigned char QPDUMessage::bits(int offset, int len)
1672 if ( needOctets(1) )
1673 return (unsigned char)
1674 ((mBuffer[mPosn] >> offset) & ((Unit << len) - 1));
1679 unsigned char QPDUMessage::getOctet()
1681 if ( needOctets(1) )
1682 return (unsigned char)(mBuffer[mPosn++]);
1687 unsigned char QPDUMessage::peekOctet() const
1689 if ( needOctets(1) )
1690 return (unsigned char)(mBuffer[mPosn]);
1695 // Collapse 8-bit-aligned GSM data to its 7-bit form.
1696 static QByteArray collapse7Bit( const QByteArray& in )
1701 for ( int posn = 0; posn < in.length(); ++posn ) {
1702 for ( int bit = 0; bit < 7; ++bit ) {
1703 if ( ( in[posn] & (1 << bit) ) != 0 )
1704 byte |= (1 << size);
1719 void QPDUMessage::setAddress(const QString &strin, bool SCAddress)
1722 int len, digit, octet;
1724 const int maxPhoneNumberLen = 15;
1725 QString str( strin );
1726 str.truncate( maxPhoneNumberLen );
1728 // Determine the address type and length.
1729 at = SMS_Address_Unknown;
1731 for ( posn = 0; posn < str.length(); ++posn ) {
1732 switch ( str[posn].unicode() ) {
1734 at = SMS_Address_International;
1737 case '0': case '1': case '2': case '3':
1738 case '4': case '5': case '6': case '7':
1739 case '8': case '9': case '*': case '#':
1740 case 'A': case 'B': case 'C': case 'a':
1746 // Probably an alpha-numeric address.
1748 at = SMS_Address_AlphaNumeric;
1753 len = (len + 1) / 2;
1755 // Bail out early if the address is zero-length.
1761 // Handle alphanumeric address fields.
1762 if ( at == SMS_Address_AlphaNumeric ) {
1763 // Convert the address and calculate its encoded length.
1764 QTextCodec *codec = QAtUtils::codec( "gsm-noloss" );
1765 QByteArray bytes = collapse7Bit( codec->fromUnicode( strin ) );
1768 // Need an extra byte for SCAddress fields.
1772 // Output the length of the encoded address.
1775 // Output the type of number information.
1776 setBits(0, 4, SMS_NumberId_Unknown);
1781 // Output the encoded address and exit.
1786 // SCAddress len = octets + type specifier
1792 setBits(0, 4, SMS_Phone);
1797 bool upper4 = false;
1799 for ( posn = 0; posn < str.length(); posn++ ) {
1800 switch ( str[posn].unicode() ) {
1801 case '0': digit = 0; break;
1802 case '1': digit = 1; break;
1803 case '2': digit = 2; break;
1804 case '3': digit = 3; break;
1805 case '4': digit = 4; break;
1806 case '5': digit = 5; break;
1807 case '6': digit = 6; break;
1808 case '7': digit = 7; break;
1809 case '8': digit = 8; break;
1810 case '9': digit = 9; break;
1811 case '*': digit = 10; break;
1812 case '#': digit = 11; break;
1813 case 'A': case 'a': digit = 12; break;
1814 case 'B': case 'b': digit = 13; break;
1815 case 'C': case 'c': digit = 14; break;
1816 default: digit = -1; break;
1818 if ( digit != -1 ) {
1822 octet |= (digit << 4);
1823 appendOctet( octet );
1829 appendOctet( octet | 0xF0 );
1833 // Expand the 7-bit GSM data to 8-bit-aligned characters.
1834 static QByteArray expand7Bit( const QByteArray& in )
1839 for ( int posn = 0; posn < in.length(); ++posn ) {
1840 for ( int bit = 0; bit < 8; ++bit ) {
1841 if ( ( in[posn] & (1 << bit) ) != 0 )
1842 byte |= (1 << size);
1854 QString QPDUMessage::address(bool SCAddress)
1858 // Get the address length and validate it.
1859 if ( !needOctets(1) )
1861 uint len = (((uint)(getOctet())) & 0xFF);
1862 if ( !needOctets(len) ) {
1868 SMSAddressType at = (SMSAddressType) bits(4, 3);
1870 if ( at == SMS_Address_International )
1875 if ( at != SMS_Address_AlphaNumeric ) {
1876 len = len / 2 + (len % 2);
1882 if ( at != SMS_Address_AlphaNumeric ) {
1884 for (int i = 0; i < (int)len; i++) {
1885 c = peekOctet() & 0x0F;
1886 str += (char) ('0' + c);
1887 c = (peekOctet() & 0xF0) >> 4;
1889 str += (char) ('0' + c);
1893 // Recognize an alphanumeric address in the 7-bit GSM encoding.
1894 QTextCodec *codec = QAtUtils::codec( "gsm" );
1895 QByteArray bytes = expand7Bit( getOctets( len ) );
1896 str += codec->toUnicode( bytes );
1903 // Return the length of the service centre address.
1904 uint QPDUMessage::addressLength() const
1906 if( mPosn < mBuffer.size() )
1907 return (((uint)peekOctet()) & 0xFF) + 1;
1912 void QPDUMessage::setTimeStamp(const QDateTime &dt)
1914 QDate date = dt.date();
1916 tArr[0] = date.year();
1917 tArr[1] = date.month();
1918 tArr[2] = date.day();
1920 QTime t = dt.time();
1922 tArr[4] = t.minute();
1923 tArr[5] = t.second();
1925 for ( int i = 0; i < 6; i++) {
1926 appendOctet((unsigned char) ( (((tArr[i]/10)%10) & 0x0F) | (((tArr[i]%10) & 0x0F)<<4) ) );
1929 appendOctet(0x04); //arbitrary random timezone
1932 QDateTime QPDUMessage::timeStamp()
1935 unsigned char c, c4, date[7];
1937 if ( !needOctets(7) ) {
1941 for (int i = 0; i < 7; i++) {
1942 c = (peekOctet() & 0x0F);
1943 c4 = (peekOctet() & 0xF0) >> 4;
1944 date[i] = c*10 + c4;
1948 if ( date[0] < 80 ) {
1949 d.setDate( QDate(2000 + date[0], date[1], date[2]) );
1951 d.setDate( QDate(1900 + date[0], date[1], date[2]) );
1953 d.setTime( QTime(date[3], date[4], date[5]) );
1958 // Get the length of a string when encoded in the GSM 7-bit alphabet.
1959 static uint getEncodedLength( const QString& txt, uint size )
1962 for ( int u = 0; u < (int)size; u++ ) {
1963 if ( QGsmCodec::twoByteFromUnicode( txt[u].unicode() ) >= 256 )
1971 void QPDUMessage::setUserData(const QString &txt, QSMSDataCodingScheme scheme, QTextCodec *codec, const QByteArray& headers, bool implicitLength)
1973 uint len = txt.length();
1976 uint headerLen = (uint)(headers.size());
1980 // Strip off everything except the alphabet bits.
1981 scheme = (QSMSDataCodingScheme)(scheme & 0x0C);
1983 if ( scheme == QSMS_DefaultAlphabet ) {
1985 // Encode the text using the 7-bit GSM alphabet.
1988 encodedLen = getEncodedLength( txt, len );
1989 while ( encodedLen > 160 ) {
1990 // Chop off some more characters until it is <= 160.
1992 encodedLen = getEncodedLength( txt, len );
1994 if (!implicitLength)
1995 appendOctet( encodedLen + ( headerLen * 8 + 6 ) / 7 );
1998 if ( headerLen > 0 ) {
1999 // Output the header and align on a septet boundary.
2000 bitCount = headerLen * 8;
2001 if ((bitCount % 7) != 0)
2002 bitCount = 7 - (bitCount % 7);
2005 appendOctet( headerLen - 1 );
2006 for ( u = 0; u < headerLen - 1; u++ ) {
2007 appendOctet( headers[u] );
2010 for ( u = 0; u < len; u++ ) {
2011 c = QGsmCodec::twoByteFromUnicode( txt[u].unicode() );
2013 // Encode a two-byte sequence.
2014 for ( int i = 0; i < 7; i++ ) {
2015 if ( bitCount == 8 ) {
2019 setBit( bitCount++, (Unit << (i+8)) & c );
2022 for ( int i = 0; i < 7; i++ ) {
2023 if ( bitCount == 8 ) {
2027 setBit( bitCount++, (Unit << i) & c );
2030 if ( bitCount != 0 ) {
2034 } else if ( scheme == QSMS_8BitAlphabet ) {
2035 // Encode the text using the codec's 8-bit alphabet.
2037 codec = QAtUtils::codec( "iso-8859-1" );
2038 QByteArray converted = codec->fromUnicode( txt );
2039 len = converted.length();
2042 if (!implicitLength)
2043 appendOctet( len + headerLen );
2044 if ( headerLen > 0 ) {
2045 appendOctet( headerLen - 1 );
2046 for ( u = 0; u < headerLen - 1; u++ ) {
2047 appendOctet( headers[u] );
2050 const char *s = (const char *)converted;
2051 for ( u = 0; u < len; u++ ) {
2052 appendOctet( (unsigned char)(s[u]) );
2057 // Encode the text using the 16-bit UCS2 alphabet.
2060 if (!implicitLength)
2061 appendOctet( len * 2 + headerLen );
2062 if ( headerLen > 0 ) {
2063 appendOctet( headerLen - 1 );
2064 for ( u = 0; u < headerLen - 1; u++ ) {
2065 appendOctet( headers[u] );
2068 for ( u = 0; u < len; u++ ) {
2069 appendOctet( (unsigned char)(txt[u].unicode() >> 8) );
2070 appendOctet( (unsigned char)(txt[u].unicode() & 0xFF) );
2076 // Determine if the headers in an SMS frame indicate that it
2077 // is an application datagram destinated for a particular port.
2078 static bool isSMSDatagram( const QByteArray& headers )
2082 while ( ( posn + 2 ) <= (uint)(headers.size()) ) {
2083 tag = (unsigned char)(headers[posn]);
2084 len = (unsigned char)(headers[posn + 1]);
2085 if ( ( posn + len + 2 ) > (uint)(headers.size()) )
2087 if ( tag == (uint)SMS_HK_AppPort_8Bit ||
2088 tag == (uint)SMS_HK_AppPort_16Bit ) {
2096 QString QPDUMessage::userData(QSMSDataCodingScheme scheme, QTextCodec *codec, QByteArray *& headers, bool hasHeaders, bool implicitLength)
2099 uint len, headerLen;
2103 // Reset the header return.
2106 // Get the length of the user data payload.
2107 if ( implicitLength ) {
2108 len = mBuffer.size() - mPosn;
2110 if ( !needOctets(1) )
2112 len = (uint)getOctet();
2115 // Strip off everything except the alphabet bits.
2116 scheme = (QSMSDataCodingScheme)(scheme & 0x0C);
2118 if ( scheme == QSMS_DefaultAlphabet ) {
2120 // Process a sequence in the default 7-bit GSM character set.
2124 if ( implicitLength )
2125 len = len * 8 / 7; // Convert 8-bit bytes to 7-bit characters.
2127 u = ( len * 7 + 7 ) / 8;
2128 if ( !u || !needOctets(u) )
2130 headerLen = getOctet();
2131 if ( headerLen >= u )
2133 headers = new QByteArray( getOctets( headerLen ) );
2134 if ( isSMSDatagram( *headers ) )
2136 u = ((headerLen + 1) * 8 + 6) / 7;
2138 startBit = (headerLen + 1) * 8;
2139 if ((startBit % 7) != 0)
2140 startBit = 7 - (startBit % 7);
2144 bool prefixed = false;
2146 if ( !needOctets(1) )
2148 for ( int i = startBit; len > 0 && i < 8; i++ ) {
2149 // test the bit and shift it down again, as i doesn't mark
2150 // where we are in the current char, but bitCount does
2151 ch |= ( ( peekOctet() & (Unit << i) ) >> i) << bitCount;
2153 if ( bitCount == 7 ) {
2155 if ( ch == 0x1B ) { // Start of a two-byte encoding.
2157 } else if ( prefixed ) {
2159 ( QGsmCodec::twoByteToUnicode( 0x1B00 | ch ) );
2162 str += QGsmCodec::singleToUnicode( (unsigned char)ch );
2172 } else if ( scheme == QSMS_8BitAlphabet && codec ) {
2174 // Process an 8-bit sequence using the supplied codec.
2175 if ( !needOctets(len) ) {
2180 if ( !len || len < (uint)( (peekOctet() & 0xFF) + 1 ) ) {
2184 headerLen = getOctet();
2185 headers = new QByteArray( getOctets( headerLen ) );
2186 if ( isSMSDatagram( *headers ) )
2188 len -= headerLen + 1;
2190 str = codec->toUnicode( getOctets( len ) );
2192 } else if ( scheme == QSMS_UCS2Alphabet ) {
2194 // Process a UCS2 sequence.
2195 if ( !needOctets(len) ) {
2200 if ( !len || len < (uint)( (peekOctet() & 0xFF) + 1 ) ) {
2204 headerLen = getOctet();
2205 headers = new QByteArray( getOctets( headerLen ) );
2206 len -= headerLen + 1;
2209 for ( u = 0; u < len; ++u ) {
2210 ch = (((uint)(getOctet() & 0xFF)) << 8);
2211 ch |= ((uint)(getOctet() & 0xFF));
2217 // Assume 8-bit for any other coding scheme value.
2219 // Process an 8-bit sequence using the default Latin1 codec.
2220 if ( len > (uint)(mBuffer.size() - mPosn) ) {
2221 // The length field is invalid - use the actual size.
2222 len = mBuffer.size() - mPosn;
2225 if ( !len || len < (uint)( (peekOctet() & 0xFF) + 1 ) ) {
2229 headerLen = getOctet();
2230 headers = new QByteArray( getOctets( headerLen ) );
2231 if ( isSMSDatagram( *headers ) )
2233 len -= headerLen + 1;
2235 for ( u = 0; u < len; ++u ) {
2236 str += (QChar)(getOctet() & 0xFF);
2244 /* Note that the meaning of messageType is also dependant on
2245 the direction of the message (orig. location) */
2246 SMSMessageType QPDUMessage::messageType()
2248 if ( mBuffer.size() >= 1 &&
2249 mBuffer.size() >= ((mBuffer[0] & 0xFF) + 2) ) {
2251 const char *ptr = mBuffer.constData();
2252 ptr += (*ptr & 0xFF) + 1;
2253 unsigned char c = *ptr & 3;
2254 return (SMSMessageType) c;
2257 return (SMSMessageType)0;
2260 QSMSSubmitMessage::QSMSSubmitMessage(const QSMSMessage &m, bool isDeliver)
2263 // Clear the pdu before we start.
2264 mBuffer = QByteArray();
2268 setAddress( m.serviceCenter(), true );
2270 // If there is port and application data information, then
2271 // create header parts for them. This is for sending datagram
2273 QByteArray headers = m.headers();
2274 QSMSDataCodingScheme scheme = m.bestScheme();
2275 int part = m.findPart( "application/x-qtopia-wdp-ports" );
2277 QByteArray portData = m.parts()[(uint)part].data();
2278 uint size = headers.size();
2279 headers.resize(size + portData.size() + 2);
2280 if ( portData.size() == 4 ) {
2281 headers[size++] = (char)SMS_HK_AppPort_16Bit;
2283 headers[size++] = (char)SMS_HK_AppPort_8Bit;
2285 headers[size++] = (char)(portData.size());
2286 memcpy(headers.data() + size, portData.data(), portData.size());
2287 int dataScheme = m.dataCodingScheme(); // User-supplied override.
2288 if ( dataScheme == -1 )
2289 dataScheme = 0xF5; // Special value for datagrams.
2290 scheme = (QSMSDataCodingScheme)dataScheme;
2291 } else if ( m.messageClass() != -1 ) {
2292 scheme = (QSMSDataCodingScheme)
2293 (scheme | QSMS_MessageClass | m.messageClass());
2294 int dataScheme = m.dataCodingScheme(); // User-supplied override.
2295 if ( dataScheme != -1 )
2296 scheme = (QSMSDataCodingScheme)dataScheme;
2298 int dataScheme = m.dataCodingScheme(); // User-supplied override.
2299 if ( dataScheme != -1 )
2300 scheme = (QSMSDataCodingScheme)dataScheme;
2304 setBits(0, 2, SMS_Submit);
2306 setBits(0, 2, SMS_Deliver);
2308 setBit(2, false); // TP-Reject-Duplicates
2309 if ( !isDeliver && m.validityPeriod() == (uint)(-1) )
2310 setBits(3, 2, SMS_VF_NoPresent);
2312 setBits(3, 2, SMS_VF_Relative);
2313 setBit(5, m.statusReportRequested());// TP-Status-Report-Requested;
2314 setBit(6, headers.size() != 0); // TP-User-Data Header
2315 setBit(7, m.replyRequest()); // TP-Reply-Path
2317 commitBits(); // first octet done
2319 //second octet TP-MR (Message reference
2324 //third octet TP-DA (Destination Address)
2325 //len must be done later
2326 setAddress( m.recipient(), false );
2328 setAddress( m.sender(), false);
2332 //nth octet, TP-PID (protocol identifier)
2334 appendOctet(m.protocol());
2336 appendOctet(1); //arbitrary protocol for deliver
2338 // TP-DCS ( Data coding scheme )
2339 appendOctet(scheme);
2342 // TP-VP ( Validity Period )
2343 if ( m.validityPeriod() != (uint)(-1) )
2344 appendOctet(m.gsmValidityPeriod());
2346 setTimeStamp(m.timestamp());
2349 // Set the user data field.
2351 setUserData(m.text(), scheme, m.textCodec(), headers);
2353 // The applicationData() is the text to be sent in the datagram.
2354 QByteArray appData = m.applicationData();
2355 uint len = appData.size();
2356 if ( ( len + headers.size() + 1 ) > 140 )
2357 len = 140 - headers.size() - 1;
2358 appendOctet( len + headers.size() + 1 );
2359 appendOctet( headers.size() );
2361 for ( u = 0; u < (uint)headers.size(); u++ ) {
2362 appendOctet( headers[u] );
2364 for ( u = 0; u < len; u++ ) {
2365 appendOctet( appData[u] );
2370 QSMSDeliverMessage::QSMSDeliverMessage(const QByteArray &pdu)
2375 // Unpack a message that has a "//SCKL" header in its text body.
2376 // This is a standard to interoperate with networks and phones
2377 // that do not support user data headers properly.
2378 static void unpackSckl( QSMSMessage& m, const QString& text )
2380 // Split into header and body, separated by a space.
2381 int index = text.indexOf( QChar(' ') );
2382 if ( index == -1 ) {
2383 m.addPart( QSMSMessagePart( text ) );
2386 QString head = text.mid( 6, index - 6 );
2387 QString body = text.mid( index + 1 );
2389 // Convert the header from hex into raw binary and then
2390 // copy its details into the real message header fields.
2391 QByteArray header = QAtUtils::fromHex( head );
2393 if ( header.size() < 4 ) {
2394 len = header.size();
2398 QByteArray ports( len, '\0' );
2399 memcpy( ports.data(), header.data(), len );
2400 m.addPart( QSMSMessagePart( "application/x-qtopia-wdp-ports", ports ) );
2401 if ( header.size() > 4 ) {
2402 QByteArray fragments( header.size() - 4 + 2, '\0' );
2403 fragments[0] = (char)( SMS_HK_Concat_8Bit );
2404 fragments[1] = (char)( header.size() - 4 );
2405 memcpy( fragments.data() + 2, header.data() + 4, header.size() - 4 );
2406 m.setHeaders( fragments );
2409 // Is the body text or binary?
2410 int port = m.destinationPort();
2411 if ( port == 226 || port == 9204 || // vCard port numbers.
2412 port == 228 || port == 9205 ) { // vCalendar port numbers.
2413 m.setApplicationData( body.toLatin1() );
2415 m.setApplicationData( QAtUtils::fromHex( body ) );
2419 QSMSMessage QSMSDeliverMessage::unpack(QTextCodec *codec)
2424 bool userDataHeader;
2426 bool rejectDuplicates;
2427 unsigned char protocol;
2428 unsigned char scheme;
2430 uint validityFormat;
2432 // Start from the beginning of the PDU.
2435 // Extract the service center address.
2436 m.setServiceCenter( address(true) );
2438 // Pull apart the message header. We handle both deliver and
2439 // submit messages, because we may have pulled a submit out
2440 // of the phone's outgoing SMS queue.
2441 if ( !needOctets(1) )
2443 msgType = bits(0, 2);
2444 if ( msgType == SMS_Deliver ) {
2445 moreMessages = bit(2);
2446 // Bits 3 and 4 are unused for deliver messages.
2447 statusReport = bit(5);
2448 userDataHeader = bit(6);
2450 rejectDuplicates = false;
2451 validityFormat = SMS_VF_NoPresent;
2452 } else if ( msgType == SMS_Submit ) {
2453 moreMessages = false;
2454 rejectDuplicates = bit(2);
2455 validityFormat = bits(3, 2);
2456 statusReport = bit(5);
2457 userDataHeader = bit(6);
2460 // Probably a delivery report, which we don't process.
2465 // Remember the status and reply bits.
2466 m.setStatusReportRequested(statusReport);
2467 m.setReplyRequest(replyPath);
2469 // Skip the message reference (submit messages only).
2470 if ( msgType == SMS_Submit ) {
2471 if ( !needOctets(1) )
2476 // Get the address of the sender (delivery) or recipient (submit).
2477 if ( msgType == SMS_Deliver ) {
2478 m.setSender( address(false) );
2480 m.setRecipient( address(false) );
2483 // Get the protocol identifier and data coding scheme.
2484 if ( !needOctets(2) )
2486 protocol = getOctet();
2487 scheme = getOctet();
2488 m.setProtocol( protocol );
2489 m.setDataCodingScheme( scheme );
2490 if ( ( scheme & QSMS_MessageClass ) != 0 )
2491 m.setMessageClass( scheme & 0x03 );
2493 m.setMessageClass( -1 );
2495 // Get the timestamp (deliver) or validity period (submit) information.
2496 if ( msgType == SMS_Deliver ) {
2497 m.setTimestamp( timeStamp() );
2499 if ( validityFormat == SMS_VF_Relative ) {
2500 if ( !needOctets(1) )
2502 m.setGsmValidityPeriod( bits(0, 8) );
2504 } else if (validityFormat == SMS_VF_Absolute ) {
2505 m.setTimestamp( timeStamp() );
2506 } else if (validityFormat == SMS_VF_Enhanced ) {
2507 // Not supported yet - skip the octets.
2508 if ( !needOctets(7) )
2512 m.setValidityPeriod( (uint)(-1) );
2516 // Read the user data field.
2517 QByteArray *headers = 0;
2519 text = userData( (QSMSDataCodingScheme)scheme, codec,
2520 headers, userDataHeader, false );
2521 if ( !headers && text.startsWith( "//SCKL" ) ) {
2522 unpackSckl( m, text );
2526 m.setHeaders( *headers );
2528 m.unpackHeaderParts();
2529 if ( isSMSDatagram( m.d->mHeaders ) && mPosn <= mBuffer.size() ) {
2530 // The rest of the PDU is assumed to be the application payload.
2531 QByteArray array = mBuffer.mid( mPosn );
2532 m.addPart( QSMSMessagePart( "application/x-qtopia-wdp", array ) );
2535 if ( text.length() > 0 ) {
2536 m.addPart( QSMSMessagePart( text ) );
2539 // Return the completed message to the caller.
2544 QCBSDeliverMessage::QCBSDeliverMessage()
2547 // Nothing to do here.
2551 QCBSDeliverMessage::QCBSDeliverMessage(const QByteArray &pdu)
2554 // Nothing to do here.
2558 QCBSMessage QCBSDeliverMessage::unpack(QTextCodec *codec)
2561 unsigned char scheme;
2564 // Start from the beginning of the PDU.
2567 // Extract the header fields.
2568 if ( !needOctets(6) )
2571 const char *mOffset = mBuffer.constData() + mPosn;
2572 m.setMessageCode( ((mOffset[0] & 0xFC) << 2) | (mOffset[1] & 0x0F) );
2573 m.setScope( (QCBSMessage::GeographicalScope)(mOffset[0] & 0x03) );
2574 m.setUpdateNumber( (mOffset[1] >> 4) & 0x0F );
2575 m.setChannel( ((mOffset[2] & 0xFF) << 8) | (mOffset[3] & 0xFF) );
2576 scheme = (unsigned char)((mOffset[4] >> 4) & 0x0F);
2577 m.setLanguage( (QCBSMessage::Language)(mOffset[4] & 0x0F) );
2578 m.setNumPages( (uint)((mOffset[5] >> 4) & 0x0F) );
2579 m.setPage( (uint)(mOffset[5] & 0x0F) );
2582 // Read the user data field and strip CR's, LF's, and NUL's from the end.
2583 QByteArray *headers = 0;
2584 QString text = userData
2585 ( (QSMSDataCodingScheme)scheme, codec, headers, false, true );
2586 len = text.length();
2587 while ( len > 0 && ( text[len - 1] == '\r' || text[len - 1] == '\n' ||
2588 text[len - 1] == '\0' ) ) {
2591 m.setText( text.left( len ) );
2593 // Return the completed message to the caller.
2597 void QCBSDeliverMessage::pack(const QCBSMessage &m, QSMSDataCodingScheme scheme)
2599 // Clear the pdu before we start.
2600 mBuffer = QByteArray();
2604 scheme = QSMS_8BitAlphabet; // Only 8-bit works at present.
2606 mBuffer.append( (char)( ((m.messageCode() & 0x000003F0)>>2) | (m.scope() & 0x03)) );
2607 mBuffer.append( (char)((m.messageCode() & 0x0000000F) | (m.updateNumber() & 0x0000000F)<<4) );
2608 mBuffer.append( (char)((m.channel() & 0x0000FF00) >> 8) );
2609 mBuffer.append( (char)(m.channel() & 0x000000FF) );
2610 mBuffer.append( (char)( ((scheme & 0x0F)<<4) | (m.language() & 0x0F)) );
2611 mBuffer.append( (char)( ((m.numPages() & 0x0F)<<4) | (m.page() & 0x0F)) );
2613 QTextCodec *codec = QAtUtils::codec( "gsm" );
2615 setUserData(m.text(), scheme, codec, header,true);
2616 int numPad = 88 - (m.text().length() + data.size());
2618 for ( int i=0; i<numPad; i++ )
2622 Q_IMPLEMENT_USER_METATYPE(QSMSMessage)
2623 Q_IMPLEMENT_USER_METATYPE(QSMSMessagePart)