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