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