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