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