• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.internal.telephony;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.os.Build;
24 import android.telephony.SubscriptionManager;
25 import android.telephony.TelephonyManager;
26 import android.text.TextUtils;
27 import android.util.Pair;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.util.HexDump;
31 import com.android.telephony.Rlog;
32 
33 import java.io.UnsupportedEncodingException;
34 import java.nio.ByteBuffer;
35 import java.security.MessageDigest;
36 import java.security.NoSuchAlgorithmException;
37 import java.util.Arrays;
38 import java.util.Date;
39 
40 /**
41  * Tracker for an incoming SMS message ready to broadcast to listeners.
42  * This is similar to {@link com.android.internal.telephony.SMSDispatcher.SmsTracker} used for
43  * outgoing messages.
44  */
45 public class InboundSmsTracker {
46     // Need 8 bytes to get a message id as a long.
47     private static final int NUM_OF_BYTES_HASH_VALUE_FOR_MESSAGE_ID = 8;
48 
49     // Fields for single and multi-part messages
50     private final byte[] mPdu;
51     private final long mTimestamp;
52     private final int mDestPort;
53     private final boolean mIs3gpp2;
54     private final boolean mIs3gpp2WapPdu;
55     private final String mMessageBody;
56     private final boolean mIsClass0;
57     private final int mSubId;
58     private final long mMessageId;
59     private final @InboundSmsHandler.SmsSource int mSmsSource;
60 
61     // Fields for concatenating multi-part SMS messages
62     private final String mAddress;
63     private final int mReferenceNumber;
64     private final int mSequenceNumber;
65     private final int mMessageCount;
66 
67     // Fields for deleting this message after delivery
68     private String mDeleteWhere;
69     private String[] mDeleteWhereArgs;
70 
71     // BroadcastReceiver associated with this tracker
72     private InboundSmsHandler.SmsBroadcastReceiver mSmsBroadcastReceiver;
73     /**
74      * Copied from SmsMessageBase#getDisplayOriginatingAddress used for blocking messages.
75      * DisplayAddress could be email address if this message was from an email gateway, otherwise
76      * same as mAddress. Email gateway might set a generic gateway address as the mAddress which
77      * could not be used for blocking check and append the display email address at the beginning
78      * of the message body. In that case, display email address is only available for the first SMS
79      * in the Multi-part SMS.
80      */
81     private final String mDisplayAddress;
82 
83     @VisibleForTesting
84     /** Destination port flag bit for no destination port. */
85     public static final int DEST_PORT_FLAG_NO_PORT = (1 << 16);
86 
87     /** Destination port flag bit to indicate 3GPP format message. */
88     private static final int DEST_PORT_FLAG_3GPP = (1 << 17);
89 
90     @VisibleForTesting
91     /** Destination port flag bit to indicate 3GPP2 format message. */
92     public static final int DEST_PORT_FLAG_3GPP2 = (1 << 18);
93 
94     @VisibleForTesting
95     /** Destination port flag bit to indicate 3GPP2 format WAP message. */
96     public static final int DEST_PORT_FLAG_3GPP2_WAP_PDU = (1 << 19);
97 
98     /** Destination port mask (16-bit unsigned value on GSM and CDMA). */
99     private static final int DEST_PORT_MASK = 0xffff;
100 
101     @VisibleForTesting
102     public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND "
103             + "count=? AND (destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU
104             + "=0) AND deleted=0";
105 
106     @VisibleForTesting
107     public static final String SELECT_BY_REFERENCE_3GPP2WAP = "address=? AND reference_number=? "
108             + "AND count=? AND (destination_port & "
109             + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" + DEST_PORT_FLAG_3GPP2_WAP_PDU + ") AND deleted=0";
110 
111     /**
112      * Create a tracker for a single-part SMS.
113      *
114      * @param context
115      * @param pdu the message PDU
116      * @param timestamp the message timestamp
117      * @param destPort the destination port
118      * @param is3gpp2 true for 3GPP2 format; false for 3GPP format
119      * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
120      * @param address originating address
121      * @param displayAddress email address if this message was from an email gateway, otherwise same
122      *                       as originating address
123      * @param smsSource the source of the SMS message
124      */
InboundSmsTracker(Context context, byte[] pdu, long timestamp, int destPort, boolean is3gpp2, boolean is3gpp2WapPdu, String address, String displayAddress, String messageBody, boolean isClass0, int subId, @InboundSmsHandler.SmsSource int smsSource)125     public InboundSmsTracker(Context context, byte[] pdu, long timestamp, int destPort,
126             boolean is3gpp2, boolean is3gpp2WapPdu, String address, String displayAddress,
127             String messageBody, boolean isClass0, int subId,
128             @InboundSmsHandler.SmsSource int smsSource) {
129         mPdu = pdu;
130         mTimestamp = timestamp;
131         mDestPort = destPort;
132         mIs3gpp2 = is3gpp2;
133         mIs3gpp2WapPdu = is3gpp2WapPdu;
134         mMessageBody = messageBody;
135         mAddress = address;
136         mDisplayAddress = displayAddress;
137         mIsClass0 = isClass0;
138         // fields for multi-part SMS
139         mReferenceNumber = -1;
140         mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
141         mMessageCount = 1;
142         mSubId = subId;
143         mMessageId = createMessageId(context, timestamp, subId);
144         mSmsSource = smsSource;
145     }
146 
147     /**
148      * Create a tracker for a multi-part SMS. Sequence numbers start at 1 for 3GPP and regular
149      * concatenated 3GPP2 messages, but CDMA WAP push sequence numbers start at 0. The caller will
150      * subtract 1 if necessary so that the sequence number is always 0-based. When loading and
151      * saving to the raw table, the sequence number is adjusted if necessary for backwards
152      * compatibility.
153      *
154      * @param pdu the message PDU
155      * @param timestamp the message timestamp
156      * @param destPort the destination port
157      * @param is3gpp2 true for 3GPP2 format; false for 3GPP format
158      * @param address originating address, or email if this message was from an email gateway
159      * @param displayAddress email address if this message was from an email gateway, otherwise same
160      *                       as originating address
161      * @param referenceNumber the concatenated reference number
162      * @param sequenceNumber the sequence number of this segment (0-based)
163      * @param messageCount the total number of segments
164      * @param is3gpp2WapPdu true for 3GPP2 format WAP PDU; false otherwise
165      * @param smsSource the source of the SMS message
166      */
InboundSmsTracker(Context context, byte[] pdu, long timestamp, int destPort, boolean is3gpp2, String address, String displayAddress, int referenceNumber, int sequenceNumber, int messageCount, boolean is3gpp2WapPdu, String messageBody, boolean isClass0, int subId, @InboundSmsHandler.SmsSource int smsSource)167     public InboundSmsTracker(Context context, byte[] pdu, long timestamp, int destPort,
168              boolean is3gpp2, String address, String displayAddress, int referenceNumber,
169              int sequenceNumber, int messageCount, boolean is3gpp2WapPdu, String messageBody,
170              boolean isClass0, int subId, @InboundSmsHandler.SmsSource int smsSource) {
171         mPdu = pdu;
172         mTimestamp = timestamp;
173         mDestPort = destPort;
174         mIs3gpp2 = is3gpp2;
175         mIs3gpp2WapPdu = is3gpp2WapPdu;
176         mMessageBody = messageBody;
177         mIsClass0 = isClass0;
178         // fields used for check blocking message
179         mDisplayAddress = displayAddress;
180         // fields for multi-part SMS
181         mAddress = address;
182         mReferenceNumber = referenceNumber;
183         mSequenceNumber = sequenceNumber;
184         mMessageCount = messageCount;
185         mSubId = subId;
186         mMessageId = createMessageId(context, timestamp, subId);
187         mSmsSource = smsSource;
188     }
189 
190     /**
191      * Create a new tracker from the row of the raw table pointed to by Cursor.
192      * Since this constructor is used only for recovery during startup, the Dispatcher is null.
193      * @param cursor a Cursor pointing to the row to construct this SmsTracker for
194      */
InboundSmsTracker(Context context, Cursor cursor, boolean isCurrentFormat3gpp2)195     public InboundSmsTracker(Context context, Cursor cursor, boolean isCurrentFormat3gpp2) {
196         mPdu = HexDump.hexStringToByteArray(cursor.getString(InboundSmsHandler.PDU_COLUMN));
197 
198         // TODO: add a column to raw db to store this
199         mIsClass0 = false;
200 
201         if (cursor.isNull(InboundSmsHandler.DESTINATION_PORT_COLUMN)) {
202             mDestPort = -1;
203             mIs3gpp2 = isCurrentFormat3gpp2;
204             mIs3gpp2WapPdu = false;
205         } else {
206             int destPort = cursor.getInt(InboundSmsHandler.DESTINATION_PORT_COLUMN);
207             if ((destPort & DEST_PORT_FLAG_3GPP) != 0) {
208                 mIs3gpp2 = false;
209             } else if ((destPort & DEST_PORT_FLAG_3GPP2) != 0) {
210                 mIs3gpp2 = true;
211             } else {
212                 mIs3gpp2 = isCurrentFormat3gpp2;
213             }
214             mIs3gpp2WapPdu = ((destPort & DEST_PORT_FLAG_3GPP2_WAP_PDU) != 0);
215             mDestPort = getRealDestPort(destPort);
216         }
217 
218         mTimestamp = cursor.getLong(InboundSmsHandler.DATE_COLUMN);
219         mAddress = cursor.getString(InboundSmsHandler.ADDRESS_COLUMN);
220         mDisplayAddress = cursor.getString(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN);
221         mSubId = cursor.getInt(SmsBroadcastUndelivered.PDU_PENDING_MESSAGE_PROJECTION_INDEX_MAPPING
222                 .get(InboundSmsHandler.SUBID_COLUMN));
223 
224         if (cursor.getInt(InboundSmsHandler.COUNT_COLUMN) == 1) {
225             // single-part message
226             long rowId = cursor.getLong(InboundSmsHandler.ID_COLUMN);
227             mReferenceNumber = -1;
228             mSequenceNumber = getIndexOffset();     // 0 or 1, depending on type
229             mMessageCount = 1;
230             mDeleteWhere = InboundSmsHandler.SELECT_BY_ID;
231             mDeleteWhereArgs = new String[]{Long.toString(rowId)};
232         } else {
233             // multi-part message
234             mReferenceNumber = cursor.getInt(InboundSmsHandler.REFERENCE_NUMBER_COLUMN);
235             mMessageCount = cursor.getInt(InboundSmsHandler.COUNT_COLUMN);
236 
237             // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
238             mSequenceNumber = cursor.getInt(InboundSmsHandler.SEQUENCE_COLUMN);
239             int index = mSequenceNumber - getIndexOffset();
240 
241             if (index < 0 || index >= mMessageCount) {
242                 throw new IllegalArgumentException("invalid PDU sequence " + mSequenceNumber
243                         + " of " + mMessageCount);
244             }
245 
246             mDeleteWhere = getQueryForSegments();
247             mDeleteWhereArgs = new String[]{mAddress,
248                     Integer.toString(mReferenceNumber), Integer.toString(mMessageCount)};
249         }
250         mMessageBody = cursor.getString(InboundSmsHandler.MESSAGE_BODY_COLUMN);
251         mMessageId = createMessageId(context, mTimestamp, mSubId);
252         // TODO(b/167713264): Use the correct SMS source
253         mSmsSource = InboundSmsHandler.SOURCE_NOT_INJECTED;
254     }
255 
getContentValues()256     public ContentValues getContentValues() {
257         ContentValues values = new ContentValues();
258         values.put("pdu", HexDump.toHexString(mPdu));
259         values.put("date", mTimestamp);
260         // Always set the destination port, since it now contains message format flags.
261         // Port is a 16-bit value, or -1, so clear the upper bits before setting flags.
262         int destPort;
263         if (mDestPort == -1) {
264             destPort = DEST_PORT_FLAG_NO_PORT;
265         } else {
266             destPort = mDestPort & DEST_PORT_MASK;
267         }
268         if (mIs3gpp2) {
269             destPort |= DEST_PORT_FLAG_3GPP2;
270         } else {
271             destPort |= DEST_PORT_FLAG_3GPP;
272         }
273         if (mIs3gpp2WapPdu) {
274             destPort |= DEST_PORT_FLAG_3GPP2_WAP_PDU;
275         }
276         values.put("destination_port", destPort);
277         if (mAddress != null) {
278             values.put("address", mAddress);
279             values.put("display_originating_addr", mDisplayAddress);
280             values.put("reference_number", mReferenceNumber);
281             values.put("sequence", mSequenceNumber);
282         }
283         values.put("count", mMessageCount);
284         values.put("message_body", mMessageBody);
285         values.put("sub_id", mSubId);
286         return values;
287     }
288 
289     /**
290      * Get the port number, or -1 if there is no destination port.
291      * @param destPort the destination port value, with flags
292      * @return the real destination port, or -1 for no port
293      */
getRealDestPort(int destPort)294     public static int getRealDestPort(int destPort) {
295         if ((destPort & DEST_PORT_FLAG_NO_PORT) != 0) {
296             return -1;
297         } else {
298            return destPort & DEST_PORT_MASK;
299         }
300     }
301 
302     /**
303      * Update the values to delete all rows of the message from raw table.
304      * @param deleteWhere the selection to use
305      * @param deleteWhereArgs the selection args to use
306      */
setDeleteWhere(String deleteWhere, String[] deleteWhereArgs)307     public void setDeleteWhere(String deleteWhere, String[] deleteWhereArgs) {
308         mDeleteWhere = deleteWhere;
309         mDeleteWhereArgs = deleteWhereArgs;
310     }
311 
toString()312     public String toString() {
313         StringBuilder builder = new StringBuilder("SmsTracker{timestamp=");
314         builder.append(new Date(mTimestamp));
315         builder.append(" destPort=").append(mDestPort);
316         builder.append(" is3gpp2=").append(mIs3gpp2);
317         if (InboundSmsHandler.VDBG) {
318             builder.append(" address=").append(mAddress);
319             builder.append(" timestamp=").append(mTimestamp);
320             builder.append(" messageBody=").append(mMessageBody);
321         }
322         builder.append(" display_originating_addr=").append(mDisplayAddress);
323         builder.append(" refNumber=").append(mReferenceNumber);
324         builder.append(" seqNumber=").append(mSequenceNumber);
325         builder.append(" msgCount=").append(mMessageCount);
326         if (mDeleteWhere != null) {
327             builder.append(" deleteWhere(").append(mDeleteWhere);
328             builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs));
329             builder.append(')');
330         }
331         builder.append(" ");
332         builder.append(SmsController.formatCrossStackMessageId(mMessageId));
333         builder.append("}");
334         return builder.toString();
335     }
336 
getPdu()337     public byte[] getPdu() {
338         return mPdu;
339     }
340 
getTimestamp()341     public long getTimestamp() {
342         return mTimestamp;
343     }
344 
getDestPort()345     public int getDestPort() {
346         return mDestPort;
347     }
348 
is3gpp2()349     public boolean is3gpp2() {
350         return mIs3gpp2;
351     }
352 
isClass0()353     public boolean isClass0() {
354         return mIsClass0;
355     }
356 
getSubId()357     public int getSubId() {
358         return mSubId;
359     }
360 
361     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getFormat()362     public String getFormat() {
363         return mIs3gpp2 ? SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
364     }
365 
getQueryForSegments()366     public String getQueryForSegments() {
367         return mIs3gpp2WapPdu ? SELECT_BY_REFERENCE_3GPP2WAP : SELECT_BY_REFERENCE;
368     }
369 
370     /**
371      * Get the query to find the exact same message/message segment in the db.
372      * @return Pair with where as Pair.first and whereArgs as Pair.second
373      */
getExactMatchDupDetectQuery()374     public Pair<String, String[]> getExactMatchDupDetectQuery() {
375         // convert to strings for query
376         String address = getAddress();
377         String refNumber = Integer.toString(getReferenceNumber());
378         String count = Integer.toString(getMessageCount());
379         String seqNumber = Integer.toString(getSequenceNumber());
380         String date = Long.toString(getTimestamp());
381         String messageBody = getMessageBody();
382 
383         String where = "address=? AND reference_number=? AND count=? AND sequence=? AND "
384                 + "date=? AND message_body=?";
385         where = addDestPortQuery(where);
386         String[] whereArgs = new String[]{address, refNumber, count, seqNumber, date, messageBody};
387 
388         return new Pair<>(where, whereArgs);
389     }
390 
391     /**
392      * The key differences here compared to exact match are:
393      * - this is applicable only for multi-part message segments
394      * - this does not match date or message_body
395      * - this matches deleted=0 (undeleted segments)
396      * The only difference as compared to getQueryForSegments() is that this checks for sequence as
397      * well.
398      * @return Pair with where as Pair.first and whereArgs as Pair.second
399      */
getInexactMatchDupDetectQuery()400     public Pair<String, String[]> getInexactMatchDupDetectQuery() {
401         if (getMessageCount() == 1) return null;
402 
403         // convert to strings for query
404         String address = getAddress();
405         String refNumber = Integer.toString(getReferenceNumber());
406         String count = Integer.toString(getMessageCount());
407         String seqNumber = Integer.toString(getSequenceNumber());
408 
409         String where = "address=? AND reference_number=? AND count=? AND sequence=? AND "
410                 + "deleted=0";
411         where = addDestPortQuery(where);
412         String[] whereArgs = new String[]{address, refNumber, count, seqNumber};
413 
414         return new Pair<>(where, whereArgs);
415     }
416 
addDestPortQuery(String where)417     private String addDestPortQuery(String where) {
418         String whereDestPort;
419         if (mIs3gpp2WapPdu) {
420             whereDestPort = "destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "="
421                 + DEST_PORT_FLAG_3GPP2_WAP_PDU;
422         } else {
423             whereDestPort = "destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0";
424         }
425         return where + " AND (" + whereDestPort + ")";
426     }
427 
createMessageId(Context context, long timestamp, int subId)428     private static long createMessageId(Context context, long timestamp, int subId) {
429         int slotId = SubscriptionManager.getSlotIndex(subId);
430         TelephonyManager telephonyManager =
431                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
432         String deviceId = telephonyManager.getImei(slotId);
433         if (TextUtils.isEmpty(deviceId)) {
434             return 0L;
435         }
436         String messagePrint = deviceId + timestamp;
437         return getShaValue(messagePrint);
438     }
439 
getShaValue(String messagePrint)440     private static long getShaValue(String messagePrint) {
441         try {
442             return ByteBuffer.wrap(getShaBytes(messagePrint,
443                     NUM_OF_BYTES_HASH_VALUE_FOR_MESSAGE_ID)).getLong();
444         } catch (final NoSuchAlgorithmException | UnsupportedEncodingException e) {
445             Rlog.e("InboundSmsTracker", "Exception while getting SHA value for message",
446                     e);
447         }
448         return 0L;
449     }
450 
getShaBytes(String messagePrint, int maxNumOfBytes)451     private static byte[] getShaBytes(String messagePrint, int maxNumOfBytes)
452             throws NoSuchAlgorithmException, UnsupportedEncodingException {
453         MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
454         messageDigest.reset();
455         messageDigest.update(messagePrint.getBytes("UTF-8"));
456         byte[] hashResult = messageDigest.digest();
457         if (hashResult.length >= maxNumOfBytes) {
458             byte[] truncatedHashResult = new byte[maxNumOfBytes];
459             System.arraycopy(hashResult, 0, truncatedHashResult, 0, maxNumOfBytes);
460             return truncatedHashResult;
461         }
462         return hashResult;
463     }
464 
465     /**
466      * Sequence numbers for concatenated messages start at 1. The exception is CDMA WAP PDU
467      * messages, which use a 0-based index.
468      * @return the offset to use to convert between mIndex and the sequence number
469      */
470     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getIndexOffset()471     public int getIndexOffset() {
472         return (mIs3gpp2 && mIs3gpp2WapPdu) ? 0 : 1;
473     }
474 
getAddress()475     public String getAddress() {
476         return mAddress;
477     }
478 
getDisplayAddress()479     public String getDisplayAddress() {
480         return mDisplayAddress;
481     }
482 
getMessageBody()483     public String getMessageBody() {
484         return mMessageBody;
485     }
486 
getReferenceNumber()487     public int getReferenceNumber() {
488         return mReferenceNumber;
489     }
490 
getSequenceNumber()491     public int getSequenceNumber() {
492         return mSequenceNumber;
493     }
494 
getMessageCount()495     public int getMessageCount() {
496         return mMessageCount;
497     }
498 
getDeleteWhere()499     public String getDeleteWhere() {
500         return mDeleteWhere;
501     }
502 
getDeleteWhereArgs()503     public String[] getDeleteWhereArgs() {
504         return mDeleteWhereArgs;
505     }
506 
getMessageId()507     public long getMessageId() {
508         return mMessageId;
509     }
510 
getSource()511     public @InboundSmsHandler.SmsSource int getSource() {
512         return mSmsSource;
513     }
514 
515     /**
516      * Get/create the SmsBroadcastReceiver corresponding to the current tracker.
517      */
getSmsBroadcastReceiver( InboundSmsHandler handler)518     public InboundSmsHandler.SmsBroadcastReceiver getSmsBroadcastReceiver(
519             InboundSmsHandler handler) {
520         // lazy initialization
521         if (mSmsBroadcastReceiver == null) {
522             mSmsBroadcastReceiver = handler.new SmsBroadcastReceiver(this);
523         }
524         return mSmsBroadcastReceiver;
525     }
526 }
527