1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.gsm;
18
19import android.telephony.PhoneNumberUtils;
20import android.text.format.Time;
21import android.util.Log;
22
23import com.android.internal.telephony.EncodeException;
24import com.android.internal.telephony.GsmAlphabet;
25import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
26import com.android.internal.telephony.IccUtils;
27import com.android.internal.telephony.SmsHeader;
28import com.android.internal.telephony.SmsMessageBase;
29
30import java.io.ByteArrayOutputStream;
31import java.io.UnsupportedEncodingException;
32import java.text.ParseException;
33
34import static com.android.internal.telephony.SmsConstants.MessageClass;
35import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
36import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
37import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
38import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
39import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
40import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
41import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
42import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
43
44/**
45 * A Short Message Service message.
46 *
47 */
48public class SmsMessage extends SmsMessageBase {
49    static final String LOG_TAG = "GSM";
50
51    private MessageClass messageClass;
52
53    /**
54     * TP-Message-Type-Indicator
55     * 9.2.3
56     */
57    private int mti;
58
59    /** TP-Protocol-Identifier (TP-PID) */
60    private int protocolIdentifier;
61
62    // TP-Data-Coding-Scheme
63    // see TS 23.038
64    private int dataCodingScheme;
65
66    // TP-Reply-Path
67    // e.g. 23.040 9.2.2.1
68    private boolean replyPathPresent = false;
69
70    // "Message Marked for Automatic Deletion Group"
71    // 23.038 Section 4
72    private boolean automaticDeletion;
73
74    /** True if Status Report is for SMS-SUBMIT; false for SMS-COMMAND. */
75    private boolean forSubmit;
76
77    /** The address of the receiver. */
78    private GsmSmsAddress recipientAddress;
79
80    /** Time when SMS-SUBMIT was delivered from SC to MSE. */
81    private long dischargeTimeMillis;
82
83    /**
84     *  TP-Status - status of a previously submitted SMS.
85     *  This field applies to SMS-STATUS-REPORT messages.  0 indicates success;
86     *  see TS 23.040, 9.2.3.15 for description of other possible values.
87     */
88    private int status;
89
90    /**
91     *  TP-Status - status of a previously submitted SMS.
92     *  This field is true iff the message is a SMS-STATUS-REPORT message.
93     */
94    private boolean isStatusReportMessage = false;
95
96    public static class SubmitPdu extends SubmitPduBase {
97    }
98
99    /**
100     * Create an SmsMessage from a raw PDU.
101     */
102    public static SmsMessage createFromPdu(byte[] pdu) {
103        try {
104            SmsMessage msg = new SmsMessage();
105            msg.parsePdu(pdu);
106            return msg;
107        } catch (RuntimeException ex) {
108            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
109            return null;
110        }
111    }
112
113    /**
114     * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
115     * by TP_PID field set to value 0x40
116     */
117    public boolean isTypeZero() {
118        return (protocolIdentifier == 0x40);
119    }
120
121    /**
122     * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
123     * +CMT unsolicited response (PDU mode, of course)
124     *  +CMT: [&lt;alpha>],<length><CR><LF><pdu>
125     *
126     * Only public for debugging
127     *
128     * {@hide}
129     */
130    public static SmsMessage newFromCMT(String[] lines) {
131        try {
132            SmsMessage msg = new SmsMessage();
133            msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
134            return msg;
135        } catch (RuntimeException ex) {
136            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
137            return null;
138        }
139    }
140
141    /** @hide */
142    public static SmsMessage newFromCDS(String line) {
143        try {
144            SmsMessage msg = new SmsMessage();
145            msg.parsePdu(IccUtils.hexStringToBytes(line));
146            return msg;
147        } catch (RuntimeException ex) {
148            Log.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
149            return null;
150        }
151    }
152
153    /**
154     * Create an SmsMessage from an SMS EF record.
155     *
156     * @param index Index of SMS record. This should be index in ArrayList
157     *              returned by SmsManager.getAllMessagesFromSim + 1.
158     * @param data Record data.
159     * @return An SmsMessage representing the record.
160     *
161     * @hide
162     */
163    public static SmsMessage createFromEfRecord(int index, byte[] data) {
164        try {
165            SmsMessage msg = new SmsMessage();
166
167            msg.indexOnIcc = index;
168
169            // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
170            // or STORED_UNSENT
171            // See TS 51.011 10.5.3
172            if ((data[0] & 1) == 0) {
173                Log.w(LOG_TAG,
174                        "SMS parsing failed: Trying to parse a free record");
175                return null;
176            } else {
177                msg.statusOnIcc = data[0] & 0x07;
178            }
179
180            int size = data.length - 1;
181
182            // Note: Data may include trailing FF's.  That's OK; message
183            // should still parse correctly.
184            byte[] pdu = new byte[size];
185            System.arraycopy(data, 1, pdu, 0, size);
186            msg.parsePdu(pdu);
187            return msg;
188        } catch (RuntimeException ex) {
189            Log.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
190            return null;
191        }
192    }
193
194    /**
195     * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
196     * length in bytes (not hex chars) less the SMSC header
197     */
198    public static int getTPLayerLengthForPDU(String pdu) {
199        int len = pdu.length() / 2;
200        int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
201
202        return len - smscLen - 1;
203    }
204
205    /**
206     * Get an SMS-SUBMIT PDU for a destination address and a message
207     *
208     * @param scAddress Service Centre address.  Null means use default.
209     * @return a <code>SubmitPdu</code> containing the encoded SC
210     *         address, if applicable, and the encoded message.
211     *         Returns null on encode error.
212     * @hide
213     */
214    public static SubmitPdu getSubmitPdu(String scAddress,
215            String destinationAddress, String message,
216            boolean statusReportRequested, byte[] header) {
217        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
218                ENCODING_UNKNOWN, 0, 0);
219    }
220
221
222    /**
223     * Get an SMS-SUBMIT PDU for a destination address and a message using the
224     * specified encoding.
225     *
226     * @param scAddress Service Centre address.  Null means use default.
227     * @param encoding Encoding defined by constants in
228     *        com.android.internal.telephony.SmsConstants.ENCODING_*
229     * @param languageTable
230     * @param languageShiftTable
231     * @return a <code>SubmitPdu</code> containing the encoded SC
232     *         address, if applicable, and the encoded message.
233     *         Returns null on encode error.
234     * @hide
235     */
236    public static SubmitPdu getSubmitPdu(String scAddress,
237            String destinationAddress, String message,
238            boolean statusReportRequested, byte[] header, int encoding,
239            int languageTable, int languageShiftTable) {
240
241        // Perform null parameter checks.
242        if (message == null || destinationAddress == null) {
243            return null;
244        }
245
246        if (encoding == ENCODING_UNKNOWN) {
247            // Find the best encoding to use
248            TextEncodingDetails ted = calculateLength(message, false);
249            encoding = ted.codeUnitSize;
250            languageTable = ted.languageTable;
251            languageShiftTable = ted.languageShiftTable;
252
253            if (encoding == ENCODING_7BIT &&
254                    (languageTable != 0 || languageShiftTable != 0)) {
255                if (header != null) {
256                    SmsHeader smsHeader = SmsHeader.fromByteArray(header);
257                    if (smsHeader.languageTable != languageTable
258                            || smsHeader.languageShiftTable != languageShiftTable) {
259                        Log.w(LOG_TAG, "Updating language table in SMS header: "
260                                + smsHeader.languageTable + " -> " + languageTable + ", "
261                                + smsHeader.languageShiftTable + " -> " + languageShiftTable);
262                        smsHeader.languageTable = languageTable;
263                        smsHeader.languageShiftTable = languageShiftTable;
264                        header = SmsHeader.toByteArray(smsHeader);
265                    }
266                } else {
267                    SmsHeader smsHeader = new SmsHeader();
268                    smsHeader.languageTable = languageTable;
269                    smsHeader.languageShiftTable = languageShiftTable;
270                    header = SmsHeader.toByteArray(smsHeader);
271                }
272            }
273        }
274
275        SubmitPdu ret = new SubmitPdu();
276        // MTI = SMS-SUBMIT, UDHI = header != null
277        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
278        ByteArrayOutputStream bo = getSubmitPduHead(
279                scAddress, destinationAddress, mtiByte,
280                statusReportRequested, ret);
281
282        // User Data (and length)
283        byte[] userData;
284        try {
285            if (encoding == ENCODING_7BIT) {
286                userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
287                        languageTable, languageShiftTable);
288            } else { //assume UCS-2
289                try {
290                    userData = encodeUCS2(message, header);
291                } catch(UnsupportedEncodingException uex) {
292                    Log.e(LOG_TAG,
293                            "Implausible UnsupportedEncodingException ",
294                            uex);
295                    return null;
296                }
297            }
298        } catch (EncodeException ex) {
299            // Encoding to the 7-bit alphabet failed. Let's see if we can
300            // send it as a UCS-2 encoded message
301            try {
302                userData = encodeUCS2(message, header);
303                encoding = ENCODING_16BIT;
304            } catch(UnsupportedEncodingException uex) {
305                Log.e(LOG_TAG,
306                        "Implausible UnsupportedEncodingException ",
307                        uex);
308                return null;
309            }
310        }
311
312        if (encoding == ENCODING_7BIT) {
313            if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
314                // Message too long
315                Log.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
316                return null;
317            }
318            // TP-Data-Coding-Scheme
319            // Default encoding, uncompressed
320            // To test writing messages to the SIM card, change this value 0x00
321            // to 0x12, which means "bits 1 and 0 contain message class, and the
322            // class is 2". Note that this takes effect for the sender. In other
323            // words, messages sent by the phone with this change will end up on
324            // the receiver's SIM card. You can then send messages to yourself
325            // (on a phone with this change) and they'll end up on the SIM card.
326            bo.write(0x00);
327        } else { // assume UCS-2
328            if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
329                // Message too long
330                Log.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
331                return null;
332            }
333            // TP-Data-Coding-Scheme
334            // UCS-2 encoding, uncompressed
335            bo.write(0x08);
336        }
337
338        // (no TP-Validity-Period)
339        bo.write(userData, 0, userData.length);
340        ret.encodedMessage = bo.toByteArray();
341        return ret;
342    }
343
344    /**
345     * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
346     *
347     * @return
348     * @throws UnsupportedEncodingException
349     */
350    private static byte[] encodeUCS2(String message, byte[] header)
351        throws UnsupportedEncodingException {
352        byte[] userData, textPart;
353        textPart = message.getBytes("utf-16be");
354
355        if (header != null) {
356            // Need 1 byte for UDHL
357            userData = new byte[header.length + textPart.length + 1];
358
359            userData[0] = (byte)header.length;
360            System.arraycopy(header, 0, userData, 1, header.length);
361            System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
362        }
363        else {
364            userData = textPart;
365        }
366        byte[] ret = new byte[userData.length+1];
367        ret[0] = (byte) (userData.length & 0xff );
368        System.arraycopy(userData, 0, ret, 1, userData.length);
369        return ret;
370    }
371
372    /**
373     * Get an SMS-SUBMIT PDU for a destination address and a message
374     *
375     * @param scAddress Service Centre address.  Null means use default.
376     * @return a <code>SubmitPdu</code> containing the encoded SC
377     *         address, if applicable, and the encoded message.
378     *         Returns null on encode error.
379     */
380    public static SubmitPdu getSubmitPdu(String scAddress,
381            String destinationAddress, String message,
382            boolean statusReportRequested) {
383
384        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
385    }
386
387    /**
388     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
389     *
390     * @param scAddress Service Centre address. null == use default
391     * @param destinationAddress the address of the destination for the message
392     * @param destinationPort the port to deliver the message to at the
393     *        destination
394     * @param data the data for the message
395     * @return a <code>SubmitPdu</code> containing the encoded SC
396     *         address, if applicable, and the encoded message.
397     *         Returns null on encode error.
398     */
399    public static SubmitPdu getSubmitPdu(String scAddress,
400            String destinationAddress, int destinationPort, byte[] data,
401            boolean statusReportRequested) {
402
403        SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
404        portAddrs.destPort = destinationPort;
405        portAddrs.origPort = 0;
406        portAddrs.areEightBits = false;
407
408        SmsHeader smsHeader = new SmsHeader();
409        smsHeader.portAddrs = portAddrs;
410
411        byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
412
413        if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
414            Log.e(LOG_TAG, "SMS data message may only contain "
415                    + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
416            return null;
417        }
418
419        SubmitPdu ret = new SubmitPdu();
420        ByteArrayOutputStream bo = getSubmitPduHead(
421                scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
422                                                            // TP-UDHI = true
423                statusReportRequested, ret);
424
425        // TP-Data-Coding-Scheme
426        // No class, 8 bit data
427        bo.write(0x04);
428
429        // (no TP-Validity-Period)
430
431        // Total size
432        bo.write(data.length + smsHeaderData.length + 1);
433
434        // User data header
435        bo.write(smsHeaderData.length);
436        bo.write(smsHeaderData, 0, smsHeaderData.length);
437
438        // User data
439        bo.write(data, 0, data.length);
440
441        ret.encodedMessage = bo.toByteArray();
442        return ret;
443    }
444
445    /**
446     * Create the beginning of a SUBMIT PDU.  This is the part of the
447     * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
448     * one of which takes a byte array and the other of which takes a
449     * <code>String</code>.
450     *
451     * @param scAddress Service Centre address. null == use default
452     * @param destinationAddress the address of the destination for the message
453     * @param mtiByte
454     * @param ret <code>SubmitPdu</code> containing the encoded SC
455     *        address, if applicable, and the encoded message
456     */
457    private static ByteArrayOutputStream getSubmitPduHead(
458            String scAddress, String destinationAddress, byte mtiByte,
459            boolean statusReportRequested, SubmitPdu ret) {
460        ByteArrayOutputStream bo = new ByteArrayOutputStream(
461                MAX_USER_DATA_BYTES + 40);
462
463        // SMSC address with length octet, or 0
464        if (scAddress == null) {
465            ret.encodedScAddress = null;
466        } else {
467            ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
468                    scAddress);
469        }
470
471        // TP-Message-Type-Indicator (and friends)
472        if (statusReportRequested) {
473            // Set TP-Status-Report-Request bit.
474            mtiByte |= 0x20;
475            if (false) Log.d(LOG_TAG, "SMS status report requested");
476        }
477        bo.write(mtiByte);
478
479        // space for TP-Message-Reference
480        bo.write(0);
481
482        byte[] daBytes;
483
484        daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
485
486        // destination address length in BCD digits, ignoring TON byte and pad
487        // TODO Should be better.
488        bo.write((daBytes.length - 1) * 2
489                - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
490
491        // destination address
492        bo.write(daBytes, 0, daBytes.length);
493
494        // TP-Protocol-Identifier
495        bo.write(0);
496        return bo;
497    }
498
499    private static class PduParser {
500        byte pdu[];
501        int cur;
502        SmsHeader userDataHeader;
503        byte[] userData;
504        int mUserDataSeptetPadding;
505        int mUserDataSize;
506
507        PduParser(byte[] pdu) {
508            this.pdu = pdu;
509            cur = 0;
510            mUserDataSeptetPadding = 0;
511        }
512
513        /**
514         * Parse and return the SC address prepended to SMS messages coming via
515         * the TS 27.005 / AT interface.  Returns null on invalid address
516         */
517        String getSCAddress() {
518            int len;
519            String ret;
520
521            // length of SC Address
522            len = getByte();
523
524            if (len == 0) {
525                // no SC address
526                ret = null;
527            } else {
528                // SC address
529                try {
530                    ret = PhoneNumberUtils
531                            .calledPartyBCDToString(pdu, cur, len);
532                } catch (RuntimeException tr) {
533                    Log.d(LOG_TAG, "invalid SC address: ", tr);
534                    ret = null;
535                }
536            }
537
538            cur += len;
539
540            return ret;
541        }
542
543        /**
544         * returns non-sign-extended byte value
545         */
546        int getByte() {
547            return pdu[cur++] & 0xff;
548        }
549
550        /**
551         * Any address except the SC address (eg, originating address) See TS
552         * 23.040 9.1.2.5
553         */
554        GsmSmsAddress getAddress() {
555            GsmSmsAddress ret;
556
557            // "The Address-Length field is an integer representation of
558            // the number field, i.e. excludes any semi-octet containing only
559            // fill bits."
560            // The TOA field is not included as part of this
561            int addressLength = pdu[cur] & 0xff;
562            int lengthBytes = 2 + (addressLength + 1) / 2;
563
564            try {
565                ret = new GsmSmsAddress(pdu, cur, lengthBytes);
566            } catch (ParseException e) {
567                Log.e(LOG_TAG, e.getMessage());
568                ret = null;
569            }
570
571            cur += lengthBytes;
572
573            return ret;
574        }
575
576        /**
577         * Parses an SC timestamp and returns a currentTimeMillis()-style
578         * timestamp
579         */
580
581        long getSCTimestampMillis() {
582            // TP-Service-Centre-Time-Stamp
583            int year = IccUtils.gsmBcdByteToInt(pdu[cur++]);
584            int month = IccUtils.gsmBcdByteToInt(pdu[cur++]);
585            int day = IccUtils.gsmBcdByteToInt(pdu[cur++]);
586            int hour = IccUtils.gsmBcdByteToInt(pdu[cur++]);
587            int minute = IccUtils.gsmBcdByteToInt(pdu[cur++]);
588            int second = IccUtils.gsmBcdByteToInt(pdu[cur++]);
589
590            // For the timezone, the most significant bit of the
591            // least significant nibble is the sign byte
592            // (meaning the max range of this field is 79 quarter-hours,
593            // which is more than enough)
594
595            byte tzByte = pdu[cur++];
596
597            // Mask out sign bit.
598            int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
599
600            timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
601
602            Time time = new Time(Time.TIMEZONE_UTC);
603
604            // It's 2006.  Should I really support years < 2000?
605            time.year = year >= 90 ? year + 1900 : year + 2000;
606            time.month = month - 1;
607            time.monthDay = day;
608            time.hour = hour;
609            time.minute = minute;
610            time.second = second;
611
612            // Timezone offset is in quarter hours.
613            return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
614        }
615
616        /**
617         * Pulls the user data out of the PDU, and separates the payload from
618         * the header if there is one.
619         *
620         * @param hasUserDataHeader true if there is a user data header
621         * @param dataInSeptets true if the data payload is in septets instead
622         *  of octets
623         * @return the number of septets or octets in the user data payload
624         */
625        int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
626            int offset = cur;
627            int userDataLength = pdu[offset++] & 0xff;
628            int headerSeptets = 0;
629            int userDataHeaderLength = 0;
630
631            if (hasUserDataHeader) {
632                userDataHeaderLength = pdu[offset++] & 0xff;
633
634                byte[] udh = new byte[userDataHeaderLength];
635                System.arraycopy(pdu, offset, udh, 0, userDataHeaderLength);
636                userDataHeader = SmsHeader.fromByteArray(udh);
637                offset += userDataHeaderLength;
638
639                int headerBits = (userDataHeaderLength + 1) * 8;
640                headerSeptets = headerBits / 7;
641                headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
642                mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
643            }
644
645            int bufferLen;
646            if (dataInSeptets) {
647                /*
648                 * Here we just create the user data length to be the remainder of
649                 * the pdu minus the user data header, since userDataLength means
650                 * the number of uncompressed septets.
651                 */
652                bufferLen = pdu.length - offset;
653            } else {
654                /*
655                 * userDataLength is the count of octets, so just subtract the
656                 * user data header.
657                 */
658                bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
659                if (bufferLen < 0) {
660                    bufferLen = 0;
661                }
662            }
663
664            userData = new byte[bufferLen];
665            System.arraycopy(pdu, offset, userData, 0, userData.length);
666            cur = offset;
667
668            if (dataInSeptets) {
669                // Return the number of septets
670                int count = userDataLength - headerSeptets;
671                // If count < 0, return 0 (means UDL was probably incorrect)
672                return count < 0 ? 0 : count;
673            } else {
674                // Return the number of octets
675                return userData.length;
676            }
677        }
678
679        /**
680         * Returns the user data payload, not including the headers
681         *
682         * @return the user data payload, not including the headers
683         */
684        byte[] getUserData() {
685            return userData;
686        }
687
688        /**
689         * Returns the number of padding bits at the beginning of the user data
690         * array before the start of the septets.
691         *
692         * @return the number of padding bits at the beginning of the user data
693         * array before the start of the septets
694         */
695        int getUserDataSeptetPadding() {
696            return mUserDataSeptetPadding;
697        }
698
699        /**
700         * Returns an object representing the user data headers
701         *
702         * {@hide}
703         */
704        SmsHeader getUserDataHeader() {
705            return userDataHeader;
706        }
707
708        /**
709         * Interprets the user data payload as packed GSM 7bit characters, and
710         * decodes them into a String.
711         *
712         * @param septetCount the number of septets in the user data payload
713         * @return a String with the decoded characters
714         */
715        String getUserDataGSM7Bit(int septetCount, int languageTable,
716                int languageShiftTable) {
717            String ret;
718
719            ret = GsmAlphabet.gsm7BitPackedToString(pdu, cur, septetCount,
720                    mUserDataSeptetPadding, languageTable, languageShiftTable);
721
722            cur += (septetCount * 7) / 8;
723
724            return ret;
725        }
726
727        /**
728         * Interprets the user data payload as UCS2 characters, and
729         * decodes them into a String.
730         *
731         * @param byteCount the number of bytes in the user data payload
732         * @return a String with the decoded characters
733         */
734        String getUserDataUCS2(int byteCount) {
735            String ret;
736
737            try {
738                ret = new String(pdu, cur, byteCount, "utf-16");
739            } catch (UnsupportedEncodingException ex) {
740                ret = "";
741                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
742            }
743
744            cur += byteCount;
745            return ret;
746        }
747
748        /**
749         * Interprets the user data payload as KSC-5601 characters, and
750         * decodes them into a String.
751         *
752         * @param byteCount the number of bytes in the user data payload
753         * @return a String with the decoded characters
754         */
755        String getUserDataKSC5601(int byteCount) {
756            String ret;
757
758            try {
759                ret = new String(pdu, cur, byteCount, "KSC5601");
760            } catch (UnsupportedEncodingException ex) {
761                ret = "";
762                Log.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
763            }
764
765            cur += byteCount;
766            return ret;
767        }
768
769        boolean moreDataPresent() {
770            return (pdu.length > cur);
771        }
772    }
773
774    /**
775     * Calculate the number of septets needed to encode the message.
776     *
777     * @param msgBody the message to encode
778     * @param use7bitOnly ignore (but still count) illegal characters if true
779     * @return TextEncodingDetails
780     */
781    public static TextEncodingDetails calculateLength(CharSequence msgBody,
782            boolean use7bitOnly) {
783        TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(msgBody, use7bitOnly);
784        if (ted == null) {
785            ted = new TextEncodingDetails();
786            int octets = msgBody.length() * 2;
787            ted.codeUnitCount = msgBody.length();
788            if (octets > MAX_USER_DATA_BYTES) {
789                ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) /
790                        MAX_USER_DATA_BYTES_WITH_HEADER;
791                ted.codeUnitsRemaining = ((ted.msgCount *
792                        MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2;
793            } else {
794                ted.msgCount = 1;
795                ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2;
796            }
797            ted.codeUnitSize = ENCODING_16BIT;
798        }
799        return ted;
800    }
801
802    /** {@inheritDoc} */
803    @Override
804    public int getProtocolIdentifier() {
805        return protocolIdentifier;
806    }
807
808    /**
809     * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
810     * @return the TP-DCS field of the SMS header
811     */
812    int getDataCodingScheme() {
813        return dataCodingScheme;
814    }
815
816    /** {@inheritDoc} */
817    @Override
818    public boolean isReplace() {
819        return (protocolIdentifier & 0xc0) == 0x40
820                && (protocolIdentifier & 0x3f) > 0
821                && (protocolIdentifier & 0x3f) < 8;
822    }
823
824    /** {@inheritDoc} */
825    @Override
826    public boolean isCphsMwiMessage() {
827        return ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear()
828                || ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
829    }
830
831    /** {@inheritDoc} */
832    @Override
833    public boolean isMWIClearMessage() {
834        if (isMwi && !mwiSense) {
835            return true;
836        }
837
838        return originatingAddress != null
839                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageClear();
840    }
841
842    /** {@inheritDoc} */
843    @Override
844    public boolean isMWISetMessage() {
845        if (isMwi && mwiSense) {
846            return true;
847        }
848
849        return originatingAddress != null
850                && ((GsmSmsAddress) originatingAddress).isCphsVoiceMessageSet();
851    }
852
853    /** {@inheritDoc} */
854    @Override
855    public boolean isMwiDontStore() {
856        if (isMwi && mwiDontStore) {
857            return true;
858        }
859
860        if (isCphsMwiMessage()) {
861            // See CPHS 4.2 Section B.4.2.1
862            // If the user data is a single space char, do not store
863            // the message. Otherwise, store and display as usual
864            if (" ".equals(getMessageBody())) {
865                ;
866            }
867            return true;
868        }
869
870        return false;
871    }
872
873    /** {@inheritDoc} */
874    @Override
875    public int getStatus() {
876        return status;
877    }
878
879    /** {@inheritDoc} */
880    @Override
881    public boolean isStatusReportMessage() {
882        return isStatusReportMessage;
883    }
884
885    /** {@inheritDoc} */
886    @Override
887    public boolean isReplyPathPresent() {
888        return replyPathPresent;
889    }
890
891    /**
892     * TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
893     * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
894     * ME/TA converts each octet of TP data unit into two IRA character long
895     * hex number (e.g. octet with integer value 42 is presented to TE as two
896     * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
897     * something else...
898     */
899    private void parsePdu(byte[] pdu) {
900        mPdu = pdu;
901        // Log.d(LOG_TAG, "raw sms message:");
902        // Log.d(LOG_TAG, s);
903
904        PduParser p = new PduParser(pdu);
905
906        scAddress = p.getSCAddress();
907
908        if (scAddress != null) {
909            if (false) Log.d(LOG_TAG, "SMS SC address: " + scAddress);
910        }
911
912        // TODO(mkf) support reply path, user data header indicator
913
914        // TP-Message-Type-Indicator
915        // 9.2.3
916        int firstByte = p.getByte();
917
918        mti = firstByte & 0x3;
919        switch (mti) {
920        // TP-Message-Type-Indicator
921        // 9.2.3
922        case 0:
923        case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
924                //This should be processed in the same way as MTI == 0 (Deliver)
925            parseSmsDeliver(p, firstByte);
926            break;
927        case 2:
928            parseSmsStatusReport(p, firstByte);
929            break;
930        default:
931            // TODO(mkf) the rest of these
932            throw new RuntimeException("Unsupported message type");
933        }
934    }
935
936    /**
937     * Parses a SMS-STATUS-REPORT message.
938     *
939     * @param p A PduParser, cued past the first byte.
940     * @param firstByte The first byte of the PDU, which contains MTI, etc.
941     */
942    private void parseSmsStatusReport(PduParser p, int firstByte) {
943        isStatusReportMessage = true;
944
945        // TP-Status-Report-Qualifier bit == 0 for SUBMIT
946        forSubmit = (firstByte & 0x20) == 0x00;
947        // TP-Message-Reference
948        messageRef = p.getByte();
949        // TP-Recipient-Address
950        recipientAddress = p.getAddress();
951        // TP-Service-Centre-Time-Stamp
952        scTimeMillis = p.getSCTimestampMillis();
953        // TP-Discharge-Time
954        dischargeTimeMillis = p.getSCTimestampMillis();
955        // TP-Status
956        status = p.getByte();
957
958        // The following are optional fields that may or may not be present.
959        if (p.moreDataPresent()) {
960            // TP-Parameter-Indicator
961            int extraParams = p.getByte();
962            int moreExtraParams = extraParams;
963            while ((moreExtraParams & 0x80) != 0) {
964                // We only know how to parse a few extra parameters, all
965                // indicated in the first TP-PI octet, so skip over any
966                // additional TP-PI octets.
967                moreExtraParams = p.getByte();
968            }
969            // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
970            // only process the byte if the reserved bits (bits3 to 6) are zero.
971            if ((extraParams & 0x78) == 0) {
972                // TP-Protocol-Identifier
973                if ((extraParams & 0x01) != 0) {
974                    protocolIdentifier = p.getByte();
975                }
976                // TP-Data-Coding-Scheme
977                if ((extraParams & 0x02) != 0) {
978                    dataCodingScheme = p.getByte();
979                }
980                // TP-User-Data-Length (implies existence of TP-User-Data)
981                if ((extraParams & 0x04) != 0) {
982                    boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
983                    parseUserData(p, hasUserDataHeader);
984                }
985            }
986        }
987    }
988
989    private void parseSmsDeliver(PduParser p, int firstByte) {
990        replyPathPresent = (firstByte & 0x80) == 0x80;
991
992        originatingAddress = p.getAddress();
993
994        if (originatingAddress != null) {
995            if (false) Log.v(LOG_TAG, "SMS originating address: "
996                    + originatingAddress.address);
997        }
998
999        // TP-Protocol-Identifier (TP-PID)
1000        // TS 23.040 9.2.3.9
1001        protocolIdentifier = p.getByte();
1002
1003        // TP-Data-Coding-Scheme
1004        // see TS 23.038
1005        dataCodingScheme = p.getByte();
1006
1007        if (false) {
1008            Log.v(LOG_TAG, "SMS TP-PID:" + protocolIdentifier
1009                    + " data coding scheme: " + dataCodingScheme);
1010        }
1011
1012        scTimeMillis = p.getSCTimestampMillis();
1013
1014        if (false) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis);
1015
1016        boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
1017
1018        parseUserData(p, hasUserDataHeader);
1019    }
1020
1021    /**
1022     * Parses the User Data of an SMS.
1023     *
1024     * @param p The current PduParser.
1025     * @param hasUserDataHeader Indicates whether a header is present in the
1026     *                          User Data.
1027     */
1028    private void parseUserData(PduParser p, boolean hasUserDataHeader) {
1029        boolean hasMessageClass = false;
1030        boolean userDataCompressed = false;
1031
1032        int encodingType = ENCODING_UNKNOWN;
1033
1034        // Look up the data encoding scheme
1035        if ((dataCodingScheme & 0x80) == 0) {
1036            // Bits 7..4 == 0xxx
1037            automaticDeletion = (0 != (dataCodingScheme & 0x40));
1038            userDataCompressed = (0 != (dataCodingScheme & 0x20));
1039            hasMessageClass = (0 != (dataCodingScheme & 0x10));
1040
1041            if (userDataCompressed) {
1042                Log.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
1043                        + "(compression) " + (dataCodingScheme & 0xff));
1044            } else {
1045                switch ((dataCodingScheme >> 2) & 0x3) {
1046                case 0: // GSM 7 bit default alphabet
1047                    encodingType = ENCODING_7BIT;
1048                    break;
1049
1050                case 2: // UCS 2 (16bit)
1051                    encodingType = ENCODING_16BIT;
1052                    break;
1053
1054                case 1: // 8 bit data
1055                case 3: // reserved
1056                    Log.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
1057                            + (dataCodingScheme & 0xff));
1058                    encodingType = ENCODING_8BIT;
1059                    break;
1060                }
1061            }
1062        } else if ((dataCodingScheme & 0xf0) == 0xf0) {
1063            automaticDeletion = false;
1064            hasMessageClass = true;
1065            userDataCompressed = false;
1066
1067            if (0 == (dataCodingScheme & 0x04)) {
1068                // GSM 7 bit default alphabet
1069                encodingType = ENCODING_7BIT;
1070            } else {
1071                // 8 bit data
1072                encodingType = ENCODING_8BIT;
1073            }
1074        } else if ((dataCodingScheme & 0xF0) == 0xC0
1075                || (dataCodingScheme & 0xF0) == 0xD0
1076                || (dataCodingScheme & 0xF0) == 0xE0) {
1077            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1078
1079            // 0xC0 == 7 bit, don't store
1080            // 0xD0 == 7 bit, store
1081            // 0xE0 == UCS-2, store
1082
1083            if ((dataCodingScheme & 0xF0) == 0xE0) {
1084                encodingType = ENCODING_16BIT;
1085            } else {
1086                encodingType = ENCODING_7BIT;
1087            }
1088
1089            userDataCompressed = false;
1090            boolean active = ((dataCodingScheme & 0x08) == 0x08);
1091
1092            // bit 0x04 reserved
1093
1094            if ((dataCodingScheme & 0x03) == 0x00) {
1095                isMwi = true;
1096                mwiSense = active;
1097                mwiDontStore = ((dataCodingScheme & 0xF0) == 0xC0);
1098            } else {
1099                isMwi = false;
1100
1101                Log.w(LOG_TAG, "MWI for fax, email, or other "
1102                        + (dataCodingScheme & 0xff));
1103            }
1104        } else if ((dataCodingScheme & 0xC0) == 0x80) {
1105            // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
1106            // 0x80..0xBF == Reserved coding groups
1107            if (dataCodingScheme == 0x84) {
1108                // This value used for KSC5601 by carriers in Korea.
1109                encodingType = ENCODING_KSC5601;
1110            } else {
1111                Log.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
1112                        + (dataCodingScheme & 0xff));
1113            }
1114        } else {
1115            Log.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
1116                    + (dataCodingScheme & 0xff));
1117        }
1118
1119        // set both the user data and the user data header.
1120        int count = p.constructUserData(hasUserDataHeader,
1121                encodingType == ENCODING_7BIT);
1122        this.userData = p.getUserData();
1123        this.userDataHeader = p.getUserDataHeader();
1124
1125        switch (encodingType) {
1126        case ENCODING_UNKNOWN:
1127        case ENCODING_8BIT:
1128            messageBody = null;
1129            break;
1130
1131        case ENCODING_7BIT:
1132            messageBody = p.getUserDataGSM7Bit(count,
1133                    hasUserDataHeader ? userDataHeader.languageTable : 0,
1134                    hasUserDataHeader ? userDataHeader.languageShiftTable : 0);
1135            break;
1136
1137        case ENCODING_16BIT:
1138            messageBody = p.getUserDataUCS2(count);
1139            break;
1140
1141        case ENCODING_KSC5601:
1142            messageBody = p.getUserDataKSC5601(count);
1143            break;
1144        }
1145
1146        if (false) Log.v(LOG_TAG, "SMS message body (raw): '" + messageBody + "'");
1147
1148        if (messageBody != null) {
1149            parseMessageBody();
1150        }
1151
1152        if (!hasMessageClass) {
1153            messageClass = MessageClass.UNKNOWN;
1154        } else {
1155            switch (dataCodingScheme & 0x3) {
1156            case 0:
1157                messageClass = MessageClass.CLASS_0;
1158                break;
1159            case 1:
1160                messageClass = MessageClass.CLASS_1;
1161                break;
1162            case 2:
1163                messageClass = MessageClass.CLASS_2;
1164                break;
1165            case 3:
1166                messageClass = MessageClass.CLASS_3;
1167                break;
1168            }
1169        }
1170    }
1171
1172    /**
1173     * {@inheritDoc}
1174     */
1175    @Override
1176    public MessageClass getMessageClass() {
1177        return messageClass;
1178    }
1179
1180    /**
1181     * Returns true if this is a (U)SIM data download type SM.
1182     * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
1183     *
1184     * @return true if this is a USIM data download message; false otherwise
1185     */
1186    boolean isUsimDataDownload() {
1187        return messageClass == MessageClass.CLASS_2 &&
1188                (protocolIdentifier == 0x7f || protocolIdentifier == 0x7c);
1189    }
1190}
1191