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