1 /* 2 * Copyright (C) 2008 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.cdma.sms; 18 19 import android.content.res.Resources; 20 import android.telephony.SmsCbCmasInfo; 21 import android.telephony.cdma.CdmaSmsCbProgramData; 22 import android.telephony.cdma.CdmaSmsCbProgramResults; 23 import android.text.format.Time; 24 import android.telephony.Rlog; 25 26 import com.android.internal.telephony.GsmAlphabet; 27 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; 28 import com.android.internal.telephony.SmsConstants; 29 import com.android.internal.telephony.SmsHeader; 30 import com.android.internal.telephony.SmsMessageBase; 31 import com.android.internal.telephony.uicc.IccUtils; 32 import com.android.internal.util.BitwiseInputStream; 33 import com.android.internal.util.BitwiseOutputStream; 34 35 import java.util.ArrayList; 36 import java.util.TimeZone; 37 38 /** 39 * An object to encode and decode CDMA SMS bearer data. 40 */ 41 public final class BearerData { 42 private final static String LOG_TAG = "BearerData"; 43 44 /** 45 * Bearer Data Subparameter Identifiers 46 * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1) 47 * NOTE: Commented subparameter types are not implemented. 48 */ 49 private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00; 50 private final static byte SUBPARAM_USER_DATA = 0x01; 51 private final static byte SUBPARAM_USER_RESPONSE_CODE = 0x02; 52 private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP = 0x03; 53 private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04; 54 private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05; 55 private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06; 56 private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07; 57 private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08; 58 private final static byte SUBPARAM_PRIVACY_INDICATOR = 0x09; 59 private final static byte SUBPARAM_REPLY_OPTION = 0x0A; 60 private final static byte SUBPARAM_NUMBER_OF_MESSAGES = 0x0B; 61 private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY = 0x0C; 62 private final static byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D; 63 private final static byte SUBPARAM_CALLBACK_NUMBER = 0x0E; 64 private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F; 65 //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10; 66 private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11; 67 private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; 68 private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13; 69 private final static byte SUBPARAM_MESSAGE_STATUS = 0x14; 70 //private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15; 71 //private final static byte SUBPARAM_ENHANCED_VMN = 0x16; 72 //private final static byte SUBPARAM_ENHANCED_VMN_ACK = 0x17; 73 74 // All other values after this are reserved. 75 private final static byte SUBPARAM_ID_LAST_DEFINED = 0x17; 76 77 /** 78 * Supported message types for CDMA SMS messages 79 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1) 80 */ 81 public static final int MESSAGE_TYPE_DELIVER = 0x01; 82 public static final int MESSAGE_TYPE_SUBMIT = 0x02; 83 public static final int MESSAGE_TYPE_CANCELLATION = 0x03; 84 public static final int MESSAGE_TYPE_DELIVERY_ACK = 0x04; 85 public static final int MESSAGE_TYPE_USER_ACK = 0x05; 86 public static final int MESSAGE_TYPE_READ_ACK = 0x06; 87 public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07; 88 public static final int MESSAGE_TYPE_SUBMIT_REPORT = 0x08; 89 90 public int messageType; 91 92 /** 93 * 16-bit value indicating the message ID, which increments modulo 65536. 94 * (Special rules apply for WAP-messages.) 95 * (See 3GPP2 C.S0015-B, v2, 4.5.1) 96 */ 97 public int messageId; 98 99 /** 100 * Supported priority modes for CDMA SMS messages 101 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) 102 */ 103 public static final int PRIORITY_NORMAL = 0x0; 104 public static final int PRIORITY_INTERACTIVE = 0x1; 105 public static final int PRIORITY_URGENT = 0x2; 106 public static final int PRIORITY_EMERGENCY = 0x3; 107 108 public boolean priorityIndicatorSet = false; 109 public int priority = PRIORITY_NORMAL; 110 111 /** 112 * Supported privacy modes for CDMA SMS messages 113 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1) 114 */ 115 public static final int PRIVACY_NOT_RESTRICTED = 0x0; 116 public static final int PRIVACY_RESTRICTED = 0x1; 117 public static final int PRIVACY_CONFIDENTIAL = 0x2; 118 public static final int PRIVACY_SECRET = 0x3; 119 120 public boolean privacyIndicatorSet = false; 121 public int privacy = PRIVACY_NOT_RESTRICTED; 122 123 /** 124 * Supported alert priority modes for CDMA SMS messages 125 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1) 126 */ 127 public static final int ALERT_DEFAULT = 0x0; 128 public static final int ALERT_LOW_PRIO = 0x1; 129 public static final int ALERT_MEDIUM_PRIO = 0x2; 130 public static final int ALERT_HIGH_PRIO = 0x3; 131 132 public boolean alertIndicatorSet = false; 133 public int alert = ALERT_DEFAULT; 134 135 /** 136 * Supported display modes for CDMA SMS messages. Display mode is 137 * a 2-bit value used to indicate to the mobile station when to 138 * display the received message. (See 3GPP2 C.S0015-B, v2, 139 * 4.5.16) 140 */ 141 public static final int DISPLAY_MODE_IMMEDIATE = 0x0; 142 public static final int DISPLAY_MODE_DEFAULT = 0x1; 143 public static final int DISPLAY_MODE_USER = 0x2; 144 145 public boolean displayModeSet = false; 146 public int displayMode = DISPLAY_MODE_DEFAULT; 147 148 /** 149 * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B, 150 * v2, 4.5.14) is ambiguous as to the meaning of this field, as it 151 * refers to C.R1001-D but that reference has been crossed out. 152 * It would seem reasonable to assume the values from C.R1001-F 153 * (table 9.2-1) are to be used instead. 154 */ 155 public static final int LANGUAGE_UNKNOWN = 0x00; 156 public static final int LANGUAGE_ENGLISH = 0x01; 157 public static final int LANGUAGE_FRENCH = 0x02; 158 public static final int LANGUAGE_SPANISH = 0x03; 159 public static final int LANGUAGE_JAPANESE = 0x04; 160 public static final int LANGUAGE_KOREAN = 0x05; 161 public static final int LANGUAGE_CHINESE = 0x06; 162 public static final int LANGUAGE_HEBREW = 0x07; 163 164 public boolean languageIndicatorSet = false; 165 public int language = LANGUAGE_UNKNOWN; 166 167 /** 168 * SMS Message Status Codes. The first component of the Message 169 * status indicates if an error has occurred and whether the error 170 * is considered permanent or temporary. The second component of 171 * the Message status indicates the cause of the error (if any). 172 * (See 3GPP2 C.S0015-B, v2.0, 4.5.21) 173 */ 174 /* no-error codes */ 175 public static final int ERROR_NONE = 0x00; 176 public static final int STATUS_ACCEPTED = 0x00; 177 public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01; 178 public static final int STATUS_DELIVERED = 0x02; 179 public static final int STATUS_CANCELLED = 0x03; 180 /* temporary-error and permanent-error codes */ 181 public static final int ERROR_TEMPORARY = 0x02; 182 public static final int STATUS_NETWORK_CONGESTION = 0x04; 183 public static final int STATUS_NETWORK_ERROR = 0x05; 184 public static final int STATUS_UNKNOWN_ERROR = 0x1F; 185 /* permanent-error codes */ 186 public static final int ERROR_PERMANENT = 0x03; 187 public static final int STATUS_CANCEL_FAILED = 0x06; 188 public static final int STATUS_BLOCKED_DESTINATION = 0x07; 189 public static final int STATUS_TEXT_TOO_LONG = 0x08; 190 public static final int STATUS_DUPLICATE_MESSAGE = 0x09; 191 public static final int STATUS_INVALID_DESTINATION = 0x0A; 192 public static final int STATUS_MESSAGE_EXPIRED = 0x0D; 193 /* undefined-status codes */ 194 public static final int ERROR_UNDEFINED = 0xFF; 195 public static final int STATUS_UNDEFINED = 0xFF; 196 197 public boolean messageStatusSet = false; 198 public int errorClass = ERROR_UNDEFINED; 199 public int messageStatus = STATUS_UNDEFINED; 200 201 /** 202 * 1-bit value that indicates whether a User Data Header (UDH) is present. 203 * (See 3GPP2 C.S0015-B, v2, 4.5.1) 204 * 205 * NOTE: during encoding, this value will be set based on the 206 * presence of a UDH in the structured data, any existing setting 207 * will be overwritten. 208 */ 209 public boolean hasUserDataHeader; 210 211 /** 212 * provides the information for the user data 213 * (e.g. padding bits, user data, user data header, etc) 214 * (See 3GPP2 C.S.0015-B, v2, 4.5.2) 215 */ 216 public UserData userData; 217 218 /** 219 * The User Response Code subparameter is used in the SMS User 220 * Acknowledgment Message to respond to previously received short 221 * messages. This message center-specific element carries the 222 * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2, 223 * 4.5.3) 224 */ 225 public boolean userResponseCodeSet = false; 226 public int userResponseCode; 227 228 /** 229 * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4 230 */ 231 public static class TimeStamp extends Time { 232 TimeStamp()233 public TimeStamp() { 234 super(TimeZone.getDefault().getID()); // 3GPP2 timestamps use the local timezone 235 } 236 fromByteArray(byte[] data)237 public static TimeStamp fromByteArray(byte[] data) { 238 TimeStamp ts = new TimeStamp(); 239 // C.S0015-B v2.0, 4.5.4: range is 1996-2095 240 int year = IccUtils.cdmaBcdByteToInt(data[0]); 241 if (year > 99 || year < 0) return null; 242 ts.year = year >= 96 ? year + 1900 : year + 2000; 243 int month = IccUtils.cdmaBcdByteToInt(data[1]); 244 if (month < 1 || month > 12) return null; 245 ts.month = month - 1; 246 int day = IccUtils.cdmaBcdByteToInt(data[2]); 247 if (day < 1 || day > 31) return null; 248 ts.monthDay = day; 249 int hour = IccUtils.cdmaBcdByteToInt(data[3]); 250 if (hour < 0 || hour > 23) return null; 251 ts.hour = hour; 252 int minute = IccUtils.cdmaBcdByteToInt(data[4]); 253 if (minute < 0 || minute > 59) return null; 254 ts.minute = minute; 255 int second = IccUtils.cdmaBcdByteToInt(data[5]); 256 if (second < 0 || second > 59) return null; 257 ts.second = second; 258 return ts; 259 } 260 261 @Override toString()262 public String toString() { 263 StringBuilder builder = new StringBuilder(); 264 builder.append("TimeStamp "); 265 builder.append("{ year=" + year); 266 builder.append(", month=" + month); 267 builder.append(", day=" + monthDay); 268 builder.append(", hour=" + hour); 269 builder.append(", minute=" + minute); 270 builder.append(", second=" + second); 271 builder.append(" }"); 272 return builder.toString(); 273 } 274 } 275 276 public TimeStamp msgCenterTimeStamp; 277 public TimeStamp validityPeriodAbsolute; 278 public TimeStamp deferredDeliveryTimeAbsolute; 279 280 /** 281 * Relative time is specified as one byte, the value of which 282 * falls into a series of ranges, as specified below. The idea is 283 * that shorter time intervals allow greater precision -- the 284 * value means minutes from zero until the MINS_LIMIT (inclusive), 285 * upon which it means hours until the HOURS_LIMIT, and so 286 * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1) 287 */ 288 public static final int RELATIVE_TIME_MINS_LIMIT = 143; 289 public static final int RELATIVE_TIME_HOURS_LIMIT = 167; 290 public static final int RELATIVE_TIME_DAYS_LIMIT = 196; 291 public static final int RELATIVE_TIME_WEEKS_LIMIT = 244; 292 public static final int RELATIVE_TIME_INDEFINITE = 245; 293 public static final int RELATIVE_TIME_NOW = 246; 294 public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247; 295 public static final int RELATIVE_TIME_RESERVED = 248; 296 297 public boolean validityPeriodRelativeSet; 298 public int validityPeriodRelative; 299 public boolean deferredDeliveryTimeRelativeSet; 300 public int deferredDeliveryTimeRelative; 301 302 /** 303 * The Reply Option subparameter contains 1-bit values which 304 * indicate whether SMS acknowledgment is requested or not. (See 305 * 3GPP2 C.S0015-B, v2, 4.5.11) 306 */ 307 public boolean userAckReq; 308 public boolean deliveryAckReq; 309 public boolean readAckReq; 310 public boolean reportReq; 311 312 /** 313 * The Number of Messages subparameter (8-bit value) is a decimal 314 * number in the 0 to 99 range representing the number of messages 315 * stored at the Voice Mail System. This element is used by the 316 * Voice Mail Notification service. (See 3GPP2 C.S0015-B, v2, 317 * 4.5.12) 318 */ 319 public int numberOfMessages; 320 321 /** 322 * The Message Deposit Index subparameter is assigned by the 323 * message center as a unique index to the contents of the User 324 * Data subparameter in each message sent to a particular mobile 325 * station. The mobile station, when replying to a previously 326 * received short message which included a Message Deposit Index 327 * subparameter, may include the Message Deposit Index of the 328 * received message to indicate to the message center that the 329 * original contents of the message are to be included in the 330 * reply. (See 3GPP2 C.S0015-B, v2, 4.5.18) 331 */ 332 public int depositIndex; 333 334 /** 335 * 4-bit or 8-bit value that indicates the number to be dialed in reply to a 336 * received SMS message. 337 * (See 3GPP2 C.S0015-B, v2, 4.5.15) 338 */ 339 public CdmaSmsAddress callbackNumber; 340 341 /** 342 * CMAS warning notification information. 343 * @see #decodeCmasUserData(BearerData, int) 344 */ 345 public SmsCbCmasInfo cmasWarningInfo; 346 347 /** 348 * The Service Category Program Data subparameter is used to enable and disable 349 * SMS broadcast service categories to display. If this subparameter is present, 350 * this field will contain a list of one or more 351 * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the 352 * operation(s) to perform. 353 */ 354 public ArrayList<CdmaSmsCbProgramData> serviceCategoryProgramData; 355 356 /** 357 * The Service Category Program Results subparameter informs the message center 358 * of the results of a Service Category Program Data request. 359 */ 360 public ArrayList<CdmaSmsCbProgramResults> serviceCategoryProgramResults; 361 362 363 private static class CodingException extends Exception { CodingException(String s)364 public CodingException(String s) { 365 super(s); 366 } 367 } 368 369 /** 370 * Returns the language indicator as a two-character ISO 639 string. 371 * @return a two character ISO 639 language code 372 */ getLanguage()373 public String getLanguage() { 374 return getLanguageCodeForValue(language); 375 } 376 377 /** 378 * Converts a CDMA language indicator value to an ISO 639 two character language code. 379 * @param languageValue the CDMA language value to convert 380 * @return the two character ISO 639 language code for the specified value, or null if unknown 381 */ getLanguageCodeForValue(int languageValue)382 private static String getLanguageCodeForValue(int languageValue) { 383 switch (languageValue) { 384 case LANGUAGE_ENGLISH: 385 return "en"; 386 387 case LANGUAGE_FRENCH: 388 return "fr"; 389 390 case LANGUAGE_SPANISH: 391 return "es"; 392 393 case LANGUAGE_JAPANESE: 394 return "ja"; 395 396 case LANGUAGE_KOREAN: 397 return "ko"; 398 399 case LANGUAGE_CHINESE: 400 return "zh"; 401 402 case LANGUAGE_HEBREW: 403 return "he"; 404 405 default: 406 return null; 407 } 408 } 409 410 @Override toString()411 public String toString() { 412 StringBuilder builder = new StringBuilder(); 413 builder.append("BearerData "); 414 builder.append("{ messageType=" + messageType); 415 builder.append(", messageId=" + messageId); 416 builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset")); 417 builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset")); 418 builder.append(", alert=" + (alertIndicatorSet ? alert : "unset")); 419 builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset")); 420 builder.append(", language=" + (languageIndicatorSet ? language : "unset")); 421 builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset")); 422 builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset")); 423 builder.append(", msgCenterTimeStamp=" + 424 ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset")); 425 builder.append(", validityPeriodAbsolute=" + 426 ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset")); 427 builder.append(", validityPeriodRelative=" + 428 ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset")); 429 builder.append(", deferredDeliveryTimeAbsolute=" + 430 ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset")); 431 builder.append(", deferredDeliveryTimeRelative=" + 432 ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset")); 433 builder.append(", userAckReq=" + userAckReq); 434 builder.append(", deliveryAckReq=" + deliveryAckReq); 435 builder.append(", readAckReq=" + readAckReq); 436 builder.append(", reportReq=" + reportReq); 437 builder.append(", numberOfMessages=" + numberOfMessages); 438 builder.append(", callbackNumber=" + Rlog.pii(LOG_TAG, callbackNumber)); 439 builder.append(", depositIndex=" + depositIndex); 440 builder.append(", hasUserDataHeader=" + hasUserDataHeader); 441 builder.append(", userData=" + userData); 442 builder.append(" }"); 443 return builder.toString(); 444 } 445 encodeMessageId(BearerData bData, BitwiseOutputStream outStream)446 private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream) 447 throws BitwiseOutputStream.AccessException 448 { 449 outStream.write(8, 3); 450 outStream.write(4, bData.messageType); 451 outStream.write(8, bData.messageId >> 8); 452 outStream.write(8, bData.messageId); 453 outStream.write(1, bData.hasUserDataHeader ? 1 : 0); 454 outStream.skip(3); 455 } 456 countAsciiSeptets(CharSequence msg, boolean force)457 private static int countAsciiSeptets(CharSequence msg, boolean force) { 458 int msgLen = msg.length(); 459 if (force) return msgLen; 460 for (int i = 0; i < msgLen; i++) { 461 if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) { 462 return -1; 463 } 464 } 465 return msgLen; 466 } 467 468 /** 469 * Calculate the message text encoding length, fragmentation, and other details. 470 * 471 * @param msg message text 472 * @param force7BitEncoding ignore (but still count) illegal characters if true 473 * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg 474 * @return septet count, or -1 on failure 475 */ calcTextEncodingDetails(CharSequence msg, boolean force7BitEncoding, boolean isEntireMsg)476 public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg, 477 boolean force7BitEncoding, boolean isEntireMsg) { 478 TextEncodingDetails ted; 479 int septets = countAsciiSeptets(msg, force7BitEncoding); 480 if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) { 481 ted = new TextEncodingDetails(); 482 ted.msgCount = 1; 483 ted.codeUnitCount = septets; 484 ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets; 485 ted.codeUnitSize = SmsConstants.ENCODING_7BIT; 486 } else { 487 ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength( 488 msg, force7BitEncoding); 489 if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT && 490 isEntireMsg) { 491 // We don't support single-segment EMS, so calculate for 16-bit 492 // TODO: Consider supporting single-segment EMS 493 return SmsMessageBase.calcUnicodeEncodingDetails(msg); 494 } 495 } 496 return ted; 497 } 498 encode7bitAscii(String msg, boolean force)499 private static byte[] encode7bitAscii(String msg, boolean force) 500 throws CodingException 501 { 502 try { 503 BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length()); 504 int msgLen = msg.length(); 505 for (int i = 0; i < msgLen; i++) { 506 int charCode = UserData.charToAscii.get(msg.charAt(i), -1); 507 if (charCode == -1) { 508 if (force) { 509 outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR); 510 } else { 511 throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")"); 512 } 513 } else { 514 outStream.write(7, charCode); 515 } 516 } 517 return outStream.toByteArray(); 518 } catch (BitwiseOutputStream.AccessException ex) { 519 throw new CodingException("7bit ASCII encode failed: " + ex); 520 } 521 } 522 encodeUtf16(String msg)523 private static byte[] encodeUtf16(String msg) 524 throws CodingException 525 { 526 try { 527 return msg.getBytes("utf-16be"); 528 } catch (java.io.UnsupportedEncodingException ex) { 529 throw new CodingException("UTF-16 encode failed: " + ex); 530 } 531 } 532 533 private static class Gsm7bitCodingResult { 534 int septets; 535 byte[] data; 536 } 537 encode7bitGsm(String msg, int septetOffset, boolean force)538 private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force) 539 throws CodingException 540 { 541 try { 542 /* 543 * TODO(cleanup): It would be nice if GsmAlphabet provided 544 * an option to produce just the data without prepending 545 * the septet count, as this function is really just a 546 * wrapper to strip that off. Not to mention that the 547 * septet count is generally known prior to invocation of 548 * the encoder. Note that it cannot be derived from the 549 * resulting array length, since that cannot distinguish 550 * if the last contains either 1 or 8 valid bits. 551 * 552 * TODO(cleanup): The BitwiseXStreams could also be 553 * extended with byte-wise reversed endianness read/write 554 * routines to allow a corresponding implementation of 555 * stringToGsm7BitPacked, and potentially directly support 556 * access to the main bitwise stream from encode/decode. 557 */ 558 byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0); 559 Gsm7bitCodingResult result = new Gsm7bitCodingResult(); 560 result.data = new byte[fullData.length - 1]; 561 System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1); 562 result.septets = fullData[0] & 0x00FF; 563 return result; 564 } catch (com.android.internal.telephony.EncodeException ex) { 565 throw new CodingException("7bit GSM encode failed: " + ex); 566 } 567 } 568 encode7bitEms(UserData uData, byte[] udhData, boolean force)569 private static void encode7bitEms(UserData uData, byte[] udhData, boolean force) 570 throws CodingException 571 { 572 int udhBytes = udhData.length + 1; // Add length octet. 573 int udhSeptets = ((udhBytes * 8) + 6) / 7; 574 Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force); 575 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; 576 uData.msgEncodingSet = true; 577 uData.numFields = gcr.septets; 578 uData.payload = gcr.data; 579 uData.payload[0] = (byte)udhData.length; 580 System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); 581 } 582 encode16bitEms(UserData uData, byte[] udhData)583 private static void encode16bitEms(UserData uData, byte[] udhData) 584 throws CodingException 585 { 586 byte[] payload = encodeUtf16(uData.payloadStr); 587 int udhBytes = udhData.length + 1; // Add length octet. 588 int udhCodeUnits = (udhBytes + 1) / 2; 589 int payloadCodeUnits = payload.length / 2; 590 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 591 uData.msgEncodingSet = true; 592 uData.numFields = udhCodeUnits + payloadCodeUnits; 593 uData.payload = new byte[uData.numFields * 2]; 594 uData.payload[0] = (byte)udhData.length; 595 System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); 596 System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); 597 } 598 encodeEmsUserDataPayload(UserData uData)599 private static void encodeEmsUserDataPayload(UserData uData) 600 throws CodingException 601 { 602 byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader); 603 if (uData.msgEncodingSet) { 604 if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { 605 encode7bitEms(uData, headerData, true); 606 } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { 607 encode16bitEms(uData, headerData); 608 } else { 609 throw new CodingException("unsupported EMS user data encoding (" + 610 uData.msgEncoding + ")"); 611 } 612 } else { 613 try { 614 encode7bitEms(uData, headerData, false); 615 } catch (CodingException ex) { 616 encode16bitEms(uData, headerData); 617 } 618 } 619 } 620 encodeShiftJis(String msg)621 private static byte[] encodeShiftJis(String msg) throws CodingException { 622 try { 623 return msg.getBytes("Shift_JIS"); 624 } catch (java.io.UnsupportedEncodingException ex) { 625 throw new CodingException("Shift-JIS encode failed: " + ex); 626 } 627 } 628 encodeUserDataPayload(UserData uData)629 private static void encodeUserDataPayload(UserData uData) 630 throws CodingException 631 { 632 if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) { 633 Rlog.e(LOG_TAG, "user data with null payloadStr"); 634 uData.payloadStr = ""; 635 } 636 637 if (uData.userDataHeader != null) { 638 encodeEmsUserDataPayload(uData); 639 return; 640 } 641 642 if (uData.msgEncodingSet) { 643 if (uData.msgEncoding == UserData.ENCODING_OCTET) { 644 if (uData.payload == null) { 645 Rlog.e(LOG_TAG, "user data with octet encoding but null payload"); 646 uData.payload = new byte[0]; 647 uData.numFields = 0; 648 } else { 649 uData.numFields = uData.payload.length; 650 } 651 } else { 652 if (uData.payloadStr == null) { 653 Rlog.e(LOG_TAG, "non-octet user data with null payloadStr"); 654 uData.payloadStr = ""; 655 } 656 if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { 657 Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true); 658 uData.payload = gcr.data; 659 uData.numFields = gcr.septets; 660 } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) { 661 uData.payload = encode7bitAscii(uData.payloadStr, true); 662 uData.numFields = uData.payloadStr.length(); 663 } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { 664 uData.payload = encodeUtf16(uData.payloadStr); 665 uData.numFields = uData.payloadStr.length(); 666 } else if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) { 667 uData.payload = encodeShiftJis(uData.payloadStr); 668 uData.numFields = uData.payload.length; 669 } else { 670 throw new CodingException("unsupported user data encoding (" + 671 uData.msgEncoding + ")"); 672 } 673 } 674 } else { 675 try { 676 uData.payload = encode7bitAscii(uData.payloadStr, false); 677 uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; 678 } catch (CodingException ex) { 679 uData.payload = encodeUtf16(uData.payloadStr); 680 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 681 } 682 uData.numFields = uData.payloadStr.length(); 683 uData.msgEncodingSet = true; 684 } 685 } 686 encodeUserData(BearerData bData, BitwiseOutputStream outStream)687 private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream) 688 throws BitwiseOutputStream.AccessException, CodingException 689 { 690 /* 691 * TODO(cleanup): Do we really need to set userData.payload as 692 * a side effect of encoding? If not, we could avoid data 693 * copies by passing outStream directly. 694 */ 695 encodeUserDataPayload(bData.userData); 696 bData.hasUserDataHeader = bData.userData.userDataHeader != null; 697 698 if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) { 699 throw new CodingException("encoded user data too large (" + 700 bData.userData.payload.length + 701 " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)"); 702 } 703 704 /* 705 * TODO(cleanup): figure out what the right answer is WRT paddingBits field 706 * 707 * userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7); 708 * userData.paddingBits = 0; // XXX this seems better, but why? 709 * 710 */ 711 int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits; 712 int paramBits = dataBits + 13; 713 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 714 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 715 paramBits += 8; 716 } 717 int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0); 718 int paddingBits = (paramBytes * 8) - paramBits; 719 outStream.write(8, paramBytes); 720 outStream.write(5, bData.userData.msgEncoding); 721 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 722 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 723 outStream.write(8, bData.userData.msgType); 724 } 725 outStream.write(8, bData.userData.numFields); 726 outStream.writeByteArray(dataBits, bData.userData.payload); 727 if (paddingBits > 0) outStream.write(paddingBits, 0); 728 } 729 encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)730 private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream) 731 throws BitwiseOutputStream.AccessException 732 { 733 outStream.write(8, 1); 734 outStream.write(1, bData.userAckReq ? 1 : 0); 735 outStream.write(1, bData.deliveryAckReq ? 1 : 0); 736 outStream.write(1, bData.readAckReq ? 1 : 0); 737 outStream.write(1, bData.reportReq ? 1 : 0); 738 outStream.write(4, 0); 739 } 740 encodeDtmfSmsAddress(String address)741 private static byte[] encodeDtmfSmsAddress(String address) { 742 int digits = address.length(); 743 int dataBits = digits * 4; 744 int dataBytes = (dataBits / 8); 745 dataBytes += (dataBits % 8) > 0 ? 1 : 0; 746 byte[] rawData = new byte[dataBytes]; 747 for (int i = 0; i < digits; i++) { 748 char c = address.charAt(i); 749 int val = 0; 750 if ((c >= '1') && (c <= '9')) val = c - '0'; 751 else if (c == '0') val = 10; 752 else if (c == '*') val = 11; 753 else if (c == '#') val = 12; 754 else return null; 755 rawData[i / 2] |= val << (4 - ((i % 2) * 4)); 756 } 757 return rawData; 758 } 759 760 /* 761 * TODO(cleanup): CdmaSmsAddress encoding should make use of 762 * CdmaSmsAddress.parse provided that DTMF encoding is unified, 763 * and the difference in 4-bit vs. 8-bit is resolved. 764 */ 765 encodeCdmaSmsAddress(CdmaSmsAddress addr)766 private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException { 767 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 768 try { 769 addr.origBytes = addr.address.getBytes("US-ASCII"); 770 } catch (java.io.UnsupportedEncodingException ex) { 771 throw new CodingException("invalid SMS address, cannot convert to ASCII"); 772 } 773 } else { 774 addr.origBytes = encodeDtmfSmsAddress(addr.address); 775 } 776 } 777 encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)778 private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream) 779 throws BitwiseOutputStream.AccessException, CodingException 780 { 781 CdmaSmsAddress addr = bData.callbackNumber; 782 encodeCdmaSmsAddress(addr); 783 int paramBits = 9; 784 int dataBits = 0; 785 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 786 paramBits += 7; 787 dataBits = addr.numberOfDigits * 8; 788 } else { 789 dataBits = addr.numberOfDigits * 4; 790 } 791 paramBits += dataBits; 792 int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0); 793 int paddingBits = (paramBytes * 8) - paramBits; 794 outStream.write(8, paramBytes); 795 outStream.write(1, addr.digitMode); 796 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 797 outStream.write(3, addr.ton); 798 outStream.write(4, addr.numberPlan); 799 } 800 outStream.write(8, addr.numberOfDigits); 801 outStream.writeByteArray(dataBits, addr.origBytes); 802 if (paddingBits > 0) outStream.write(paddingBits, 0); 803 } 804 encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)805 private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream) 806 throws BitwiseOutputStream.AccessException 807 { 808 outStream.write(8, 1); 809 outStream.write(2, bData.errorClass); 810 outStream.write(6, bData.messageStatus); 811 } 812 encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)813 private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream) 814 throws BitwiseOutputStream.AccessException 815 { 816 outStream.write(8, 1); 817 outStream.write(8, bData.numberOfMessages); 818 } 819 encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)820 private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream) 821 throws BitwiseOutputStream.AccessException 822 { 823 outStream.write(8, 1); 824 outStream.write(8, bData.validityPeriodRelative); 825 } 826 encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)827 private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream) 828 throws BitwiseOutputStream.AccessException 829 { 830 outStream.write(8, 1); 831 outStream.write(2, bData.privacy); 832 outStream.skip(6); 833 } 834 encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)835 private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream) 836 throws BitwiseOutputStream.AccessException 837 { 838 outStream.write(8, 1); 839 outStream.write(8, bData.language); 840 } 841 encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)842 private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream) 843 throws BitwiseOutputStream.AccessException 844 { 845 outStream.write(8, 1); 846 outStream.write(2, bData.displayMode); 847 outStream.skip(6); 848 } 849 encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)850 private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream) 851 throws BitwiseOutputStream.AccessException 852 { 853 outStream.write(8, 1); 854 outStream.write(2, bData.priority); 855 outStream.skip(6); 856 } 857 encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)858 private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream) 859 throws BitwiseOutputStream.AccessException 860 { 861 outStream.write(8, 1); 862 outStream.write(2, bData.alert); 863 outStream.skip(6); 864 } 865 encodeScpResults(BearerData bData, BitwiseOutputStream outStream)866 private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream) 867 throws BitwiseOutputStream.AccessException 868 { 869 ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults; 870 outStream.write(8, (results.size() * 4)); // 4 octets per program result 871 for (CdmaSmsCbProgramResults result : results) { 872 int category = result.getCategory(); 873 outStream.write(8, category >> 8); 874 outStream.write(8, category); 875 outStream.write(8, result.getLanguage()); 876 outStream.write(4, result.getCategoryResult()); 877 outStream.skip(4); 878 } 879 } 880 881 /** 882 * Create serialized representation for BearerData object. 883 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 884 * 885 * @param bData an instance of BearerData. 886 * 887 * @return byte array of raw encoded SMS bearer data. 888 */ encode(BearerData bData)889 public static byte[] encode(BearerData bData) { 890 bData.hasUserDataHeader = ((bData.userData != null) && 891 (bData.userData.userDataHeader != null)); 892 try { 893 BitwiseOutputStream outStream = new BitwiseOutputStream(200); 894 outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER); 895 encodeMessageId(bData, outStream); 896 if (bData.userData != null) { 897 outStream.write(8, SUBPARAM_USER_DATA); 898 encodeUserData(bData, outStream); 899 } 900 if (bData.callbackNumber != null) { 901 outStream.write(8, SUBPARAM_CALLBACK_NUMBER); 902 encodeCallbackNumber(bData, outStream); 903 } 904 if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) { 905 outStream.write(8, SUBPARAM_REPLY_OPTION); 906 encodeReplyOption(bData, outStream); 907 } 908 if (bData.numberOfMessages != 0) { 909 outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES); 910 encodeMsgCount(bData, outStream); 911 } 912 if (bData.validityPeriodRelativeSet) { 913 outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE); 914 encodeValidityPeriodRel(bData, outStream); 915 } 916 if (bData.privacyIndicatorSet) { 917 outStream.write(8, SUBPARAM_PRIVACY_INDICATOR); 918 encodePrivacyIndicator(bData, outStream); 919 } 920 if (bData.languageIndicatorSet) { 921 outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR); 922 encodeLanguageIndicator(bData, outStream); 923 } 924 if (bData.displayModeSet) { 925 outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE); 926 encodeDisplayMode(bData, outStream); 927 } 928 if (bData.priorityIndicatorSet) { 929 outStream.write(8, SUBPARAM_PRIORITY_INDICATOR); 930 encodePriorityIndicator(bData, outStream); 931 } 932 if (bData.alertIndicatorSet) { 933 outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY); 934 encodeMsgDeliveryAlert(bData, outStream); 935 } 936 if (bData.messageStatusSet) { 937 outStream.write(8, SUBPARAM_MESSAGE_STATUS); 938 encodeMsgStatus(bData, outStream); 939 } 940 if (bData.serviceCategoryProgramResults != null) { 941 outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS); 942 encodeScpResults(bData, outStream); 943 } 944 return outStream.toByteArray(); 945 } catch (BitwiseOutputStream.AccessException ex) { 946 Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); 947 } catch (CodingException ex) { 948 Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); 949 } 950 return null; 951 } 952 decodeMessageId(BearerData bData, BitwiseInputStream inStream)953 private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream) 954 throws BitwiseInputStream.AccessException { 955 final int EXPECTED_PARAM_SIZE = 3 * 8; 956 boolean decodeSuccess = false; 957 int paramBits = inStream.read(8) * 8; 958 if (paramBits >= EXPECTED_PARAM_SIZE) { 959 paramBits -= EXPECTED_PARAM_SIZE; 960 decodeSuccess = true; 961 bData.messageType = inStream.read(4); 962 bData.messageId = inStream.read(8) << 8; 963 bData.messageId |= inStream.read(8); 964 bData.hasUserDataHeader = (inStream.read(1) == 1); 965 inStream.skip(3); 966 } 967 if ((! decodeSuccess) || (paramBits > 0)) { 968 Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " + 969 (decodeSuccess ? "succeeded" : "failed") + 970 " (extra bits = " + paramBits + ")"); 971 } 972 inStream.skip(paramBits); 973 return decodeSuccess; 974 } 975 decodeReserved( BearerData bData, BitwiseInputStream inStream, int subparamId)976 private static boolean decodeReserved( 977 BearerData bData, BitwiseInputStream inStream, int subparamId) 978 throws BitwiseInputStream.AccessException, CodingException 979 { 980 boolean decodeSuccess = false; 981 int subparamLen = inStream.read(8); // SUBPARAM_LEN 982 int paramBits = subparamLen * 8; 983 if (paramBits <= inStream.available()) { 984 decodeSuccess = true; 985 inStream.skip(paramBits); 986 } 987 Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode " 988 + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")"); 989 if (!decodeSuccess) { 990 throw new CodingException("RESERVED bearer data subparameter " + subparamId 991 + " had invalid SUBPARAM_LEN " + subparamLen); 992 } 993 994 return decodeSuccess; 995 } 996 decodeUserData(BearerData bData, BitwiseInputStream inStream)997 private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream) 998 throws BitwiseInputStream.AccessException 999 { 1000 int paramBits = inStream.read(8) * 8; 1001 bData.userData = new UserData(); 1002 bData.userData.msgEncoding = inStream.read(5); 1003 bData.userData.msgEncodingSet = true; 1004 bData.userData.msgType = 0; 1005 int consumedBits = 5; 1006 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 1007 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 1008 bData.userData.msgType = inStream.read(8); 1009 consumedBits += 8; 1010 } 1011 bData.userData.numFields = inStream.read(8); 1012 consumedBits += 8; 1013 int dataBits = paramBits - consumedBits; 1014 bData.userData.payload = inStream.readByteArray(dataBits); 1015 return true; 1016 } 1017 decodeUtf8(byte[] data, int offset, int numFields)1018 private static String decodeUtf8(byte[] data, int offset, int numFields) 1019 throws CodingException 1020 { 1021 return decodeCharset(data, offset, numFields, 1, "UTF-8"); 1022 } 1023 decodeUtf16(byte[] data, int offset, int numFields)1024 private static String decodeUtf16(byte[] data, int offset, int numFields) 1025 throws CodingException 1026 { 1027 // Subtract header and possible padding byte (at end) from num fields. 1028 int padding = offset % 2; 1029 numFields -= (offset + padding) / 2; 1030 return decodeCharset(data, offset, numFields, 2, "utf-16be"); 1031 } 1032 decodeCharset(byte[] data, int offset, int numFields, int width, String charset)1033 private static String decodeCharset(byte[] data, int offset, int numFields, int width, 1034 String charset) throws CodingException 1035 { 1036 if (numFields < 0 || (numFields * width + offset) > data.length) { 1037 // Try to decode the max number of characters in payload 1038 int padding = offset % width; 1039 int maxNumFields = (data.length - offset - padding) / width; 1040 if (maxNumFields < 0) { 1041 throw new CodingException(charset + " decode failed: offset out of range"); 1042 } 1043 Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = " 1044 + numFields + " data.length = " + data.length + " maxNumFields = " 1045 + maxNumFields); 1046 numFields = maxNumFields; 1047 } 1048 try { 1049 return new String(data, offset, numFields * width, charset); 1050 } catch (java.io.UnsupportedEncodingException ex) { 1051 throw new CodingException(charset + " decode failed: " + ex); 1052 } 1053 } 1054 decode7bitAscii(byte[] data, int offset, int numFields)1055 private static String decode7bitAscii(byte[] data, int offset, int numFields) 1056 throws CodingException 1057 { 1058 try { 1059 offset *= 8; 1060 StringBuffer strBuf = new StringBuffer(numFields); 1061 BitwiseInputStream inStream = new BitwiseInputStream(data); 1062 int wantedBits = (offset * 8) + (numFields * 7); 1063 if (inStream.available() < wantedBits) { 1064 throw new CodingException("insufficient data (wanted " + wantedBits + 1065 " bits, but only have " + inStream.available() + ")"); 1066 } 1067 inStream.skip(offset); 1068 for (int i = 0; i < numFields; i++) { 1069 int charCode = inStream.read(7); 1070 if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) && 1071 (charCode <= UserData.ASCII_MAP_MAX_INDEX)) { 1072 strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]); 1073 } else if (charCode == UserData.ASCII_NL_INDEX) { 1074 strBuf.append('\n'); 1075 } else if (charCode == UserData.ASCII_CR_INDEX) { 1076 strBuf.append('\r'); 1077 } else { 1078 /* For other charCodes, they are unprintable, and so simply use SPACE. */ 1079 strBuf.append(' '); 1080 } 1081 } 1082 return strBuf.toString(); 1083 } catch (BitwiseInputStream.AccessException ex) { 1084 throw new CodingException("7bit ASCII decode failed: " + ex); 1085 } 1086 } 1087 decode7bitGsm(byte[] data, int offset, int numFields)1088 private static String decode7bitGsm(byte[] data, int offset, int numFields) 1089 throws CodingException 1090 { 1091 // Start reading from the next 7-bit aligned boundary after offset. 1092 int offsetBits = offset * 8; 1093 int offsetSeptets = (offsetBits + 6) / 7; 1094 numFields -= offsetSeptets; 1095 int paddingBits = (offsetSeptets * 7) - offsetBits; 1096 String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits, 1097 0, 0); 1098 if (result == null) { 1099 throw new CodingException("7bit GSM decoding failed"); 1100 } 1101 return result; 1102 } 1103 decodeLatin(byte[] data, int offset, int numFields)1104 private static String decodeLatin(byte[] data, int offset, int numFields) 1105 throws CodingException 1106 { 1107 return decodeCharset(data, offset, numFields, 1, "ISO-8859-1"); 1108 } 1109 decodeShiftJis(byte[] data, int offset, int numFields)1110 private static String decodeShiftJis(byte[] data, int offset, int numFields) 1111 throws CodingException 1112 { 1113 return decodeCharset(data, offset, numFields, 1, "Shift_JIS"); 1114 } 1115 decodeGsmDcs(byte[] data, int offset, int numFields, int msgType)1116 private static String decodeGsmDcs(byte[] data, int offset, int numFields, int msgType) 1117 throws CodingException 1118 { 1119 if ((msgType & 0xC0) != 0) { 1120 throw new CodingException("unsupported coding group (" 1121 + msgType + ")"); 1122 } 1123 1124 switch ((msgType >> 2) & 0x3) { 1125 case UserData.ENCODING_GSM_DCS_7BIT: 1126 return decode7bitGsm(data, offset, numFields); 1127 case UserData.ENCODING_GSM_DCS_8BIT: 1128 return decodeUtf8(data, offset, numFields); 1129 case UserData.ENCODING_GSM_DCS_16BIT: 1130 return decodeUtf16(data, offset, numFields); 1131 default: 1132 throw new CodingException("unsupported user msgType encoding (" 1133 + msgType + ")"); 1134 } 1135 } 1136 decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)1137 private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader) 1138 throws CodingException 1139 { 1140 int offset = 0; 1141 if (hasUserDataHeader) { 1142 int udhLen = userData.payload[0] & 0x00FF; 1143 offset += udhLen + 1; 1144 byte[] headerData = new byte[udhLen]; 1145 System.arraycopy(userData.payload, 1, headerData, 0, udhLen); 1146 userData.userDataHeader = SmsHeader.fromByteArray(headerData); 1147 } 1148 switch (userData.msgEncoding) { 1149 case UserData.ENCODING_OCTET: 1150 /* 1151 * Octet decoding depends on the carrier service. 1152 */ 1153 boolean decodingtypeUTF8 = Resources.getSystem() 1154 .getBoolean(com.android.internal.R.bool.config_sms_utf8_support); 1155 1156 // Strip off any padding bytes, meaning any differences between the length of the 1157 // array and the target length specified by numFields. This is to avoid any 1158 // confusion by code elsewhere that only considers the payload array length. 1159 byte[] payload = new byte[userData.numFields]; 1160 int copyLen = userData.numFields < userData.payload.length 1161 ? userData.numFields : userData.payload.length; 1162 1163 System.arraycopy(userData.payload, 0, payload, 0, copyLen); 1164 userData.payload = payload; 1165 1166 if (!decodingtypeUTF8) { 1167 // There are many devices in the market that send 8bit text sms (latin encoded) as 1168 // octet encoded. 1169 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); 1170 } else { 1171 userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields); 1172 } 1173 break; 1174 1175 case UserData.ENCODING_IA5: 1176 case UserData.ENCODING_7BIT_ASCII: 1177 userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields); 1178 break; 1179 case UserData.ENCODING_UNICODE_16: 1180 userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields); 1181 break; 1182 case UserData.ENCODING_GSM_7BIT_ALPHABET: 1183 userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields); 1184 break; 1185 case UserData.ENCODING_LATIN: 1186 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); 1187 break; 1188 case UserData.ENCODING_SHIFT_JIS: 1189 userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields); 1190 break; 1191 case UserData.ENCODING_GSM_DCS: 1192 userData.payloadStr = decodeGsmDcs(userData.payload, offset, 1193 userData.numFields, userData.msgType); 1194 break; 1195 default: 1196 throw new CodingException("unsupported user data encoding (" 1197 + userData.msgEncoding + ")"); 1198 } 1199 } 1200 1201 /** 1202 * IS-91 Voice Mail message decoding 1203 * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) 1204 * (For character encodings, see TIA/EIA/IS-91, Annex B) 1205 * 1206 * Protocol Summary: The user data payload may contain 3-14 1207 * characters. The first two characters are parsed as a number 1208 * and indicate the number of voicemails. The third character is 1209 * either a SPACE or '!' to indicate normal or urgent priority, 1210 * respectively. Any following characters are treated as normal 1211 * text user data payload. 1212 * 1213 * Note that the characters encoding is 6-bit packed. 1214 */ 1215 private static void decodeIs91VoicemailStatus(BearerData bData) 1216 throws BitwiseInputStream.AccessException, CodingException 1217 { 1218 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1219 int dataLen = inStream.available() / 6; // 6-bit packed character encoding. 1220 int numFields = bData.userData.numFields; 1221 if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { 1222 throw new CodingException("IS-91 voicemail status decoding failed"); 1223 } 1224 try { 1225 StringBuffer strbuf = new StringBuffer(dataLen); 1226 while (inStream.available() >= 6) { 1227 strbuf.append(UserData.ASCII_MAP[inStream.read(6)]); 1228 } 1229 String data = strbuf.toString(); 1230 bData.numberOfMessages = Integer.parseInt(data.substring(0, 2)); 1231 char prioCode = data.charAt(2); 1232 if (prioCode == ' ') { 1233 bData.priority = PRIORITY_NORMAL; 1234 } else if (prioCode == '!') { 1235 bData.priority = PRIORITY_URGENT; 1236 } else { 1237 throw new CodingException("IS-91 voicemail status decoding failed: " + 1238 "illegal priority setting (" + prioCode + ")"); 1239 } 1240 bData.priorityIndicatorSet = true; 1241 bData.userData.payloadStr = data.substring(3, numFields - 3); 1242 } catch (java.lang.NumberFormatException ex) { 1243 throw new CodingException("IS-91 voicemail status decoding failed: " + ex); 1244 } catch (java.lang.IndexOutOfBoundsException ex) { 1245 throw new CodingException("IS-91 voicemail status decoding failed: " + ex); 1246 } 1247 } 1248 1249 /** 1250 * IS-91 Short Message decoding 1251 * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) 1252 * (For character encodings, see TIA/EIA/IS-91, Annex B) 1253 * 1254 * Protocol Summary: The user data payload may contain 1-14 1255 * characters, which are treated as normal text user data payload. 1256 * Note that the characters encoding is 6-bit packed. 1257 */ 1258 private static void decodeIs91ShortMessage(BearerData bData) 1259 throws BitwiseInputStream.AccessException, CodingException 1260 { 1261 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1262 int dataLen = inStream.available() / 6; // 6-bit packed character encoding. 1263 int numFields = bData.userData.numFields; 1264 // dataLen may be > 14 characters due to octet padding 1265 if ((numFields > 14) || (dataLen < numFields)) { 1266 throw new CodingException("IS-91 short message decoding failed"); 1267 } 1268 StringBuffer strbuf = new StringBuffer(dataLen); 1269 for (int i = 0; i < numFields; i++) { 1270 strbuf.append(UserData.ASCII_MAP[inStream.read(6)]); 1271 } 1272 bData.userData.payloadStr = strbuf.toString(); 1273 } 1274 1275 /** 1276 * IS-91 CLI message (callback number) decoding 1277 * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) 1278 * 1279 * Protocol Summary: The data payload may contain 1-32 digits, 1280 * encoded using standard 4-bit DTMF, which are treated as a 1281 * callback number. 1282 */ decodeIs91Cli(BearerData bData)1283 private static void decodeIs91Cli(BearerData bData) throws CodingException { 1284 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1285 int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding. 1286 int numFields = bData.userData.numFields; 1287 if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { 1288 throw new CodingException("IS-91 voicemail status decoding failed"); 1289 } 1290 CdmaSmsAddress addr = new CdmaSmsAddress(); 1291 addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF; 1292 addr.origBytes = bData.userData.payload; 1293 addr.numberOfDigits = (byte)numFields; 1294 decodeSmsAddress(addr); 1295 bData.callbackNumber = addr; 1296 } 1297 decodeIs91(BearerData bData)1298 private static void decodeIs91(BearerData bData) 1299 throws BitwiseInputStream.AccessException, CodingException 1300 { 1301 switch (bData.userData.msgType) { 1302 case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS: 1303 decodeIs91VoicemailStatus(bData); 1304 break; 1305 case UserData.IS91_MSG_TYPE_CLI: 1306 decodeIs91Cli(bData); 1307 break; 1308 case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL: 1309 case UserData.IS91_MSG_TYPE_SHORT_MESSAGE: 1310 decodeIs91ShortMessage(bData); 1311 break; 1312 default: 1313 throw new CodingException("unsupported IS-91 message type (" + 1314 bData.userData.msgType + ")"); 1315 } 1316 } 1317 decodeReplyOption(BearerData bData, BitwiseInputStream inStream)1318 private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream) 1319 throws BitwiseInputStream.AccessException { 1320 final int EXPECTED_PARAM_SIZE = 1 * 8; 1321 boolean decodeSuccess = false; 1322 int paramBits = inStream.read(8) * 8; 1323 if (paramBits >= EXPECTED_PARAM_SIZE) { 1324 paramBits -= EXPECTED_PARAM_SIZE; 1325 decodeSuccess = true; 1326 bData.userAckReq = (inStream.read(1) == 1); 1327 bData.deliveryAckReq = (inStream.read(1) == 1); 1328 bData.readAckReq = (inStream.read(1) == 1); 1329 bData.reportReq = (inStream.read(1) == 1); 1330 inStream.skip(4); 1331 } 1332 if ((! decodeSuccess) || (paramBits > 0)) { 1333 Rlog.d(LOG_TAG, "REPLY_OPTION decode " + 1334 (decodeSuccess ? "succeeded" : "failed") + 1335 " (extra bits = " + paramBits + ")"); 1336 } 1337 inStream.skip(paramBits); 1338 return decodeSuccess; 1339 } 1340 decodeMsgCount(BearerData bData, BitwiseInputStream inStream)1341 private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream) 1342 throws BitwiseInputStream.AccessException { 1343 final int EXPECTED_PARAM_SIZE = 1 * 8; 1344 boolean decodeSuccess = false; 1345 int paramBits = inStream.read(8) * 8; 1346 if (paramBits >= EXPECTED_PARAM_SIZE) { 1347 paramBits -= EXPECTED_PARAM_SIZE; 1348 decodeSuccess = true; 1349 bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8)); 1350 } 1351 if ((! decodeSuccess) || (paramBits > 0)) { 1352 Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " + 1353 (decodeSuccess ? "succeeded" : "failed") + 1354 " (extra bits = " + paramBits + ")"); 1355 } 1356 inStream.skip(paramBits); 1357 return decodeSuccess; 1358 } 1359 decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)1360 private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream) 1361 throws BitwiseInputStream.AccessException { 1362 final int EXPECTED_PARAM_SIZE = 2 * 8; 1363 boolean decodeSuccess = false; 1364 int paramBits = inStream.read(8) * 8; 1365 if (paramBits >= EXPECTED_PARAM_SIZE) { 1366 paramBits -= EXPECTED_PARAM_SIZE; 1367 decodeSuccess = true; 1368 bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8); 1369 } 1370 if ((! decodeSuccess) || (paramBits > 0)) { 1371 Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " + 1372 (decodeSuccess ? "succeeded" : "failed") + 1373 " (extra bits = " + paramBits + ")"); 1374 } 1375 inStream.skip(paramBits); 1376 return decodeSuccess; 1377 } 1378 decodeDtmfSmsAddress(byte[] rawData, int numFields)1379 private static String decodeDtmfSmsAddress(byte[] rawData, int numFields) 1380 throws CodingException 1381 { 1382 /* DTMF 4-bit digit encoding, defined in at 1383 * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */ 1384 StringBuffer strBuf = new StringBuffer(numFields); 1385 for (int i = 0; i < numFields; i++) { 1386 int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4))); 1387 if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10)); 1388 else if (val == 10) strBuf.append('0'); 1389 else if (val == 11) strBuf.append('*'); 1390 else if (val == 12) strBuf.append('#'); 1391 else throw new CodingException("invalid SMS address DTMF code (" + val + ")"); 1392 } 1393 return strBuf.toString(); 1394 } 1395 decodeSmsAddress(CdmaSmsAddress addr)1396 private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException { 1397 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 1398 try { 1399 /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually 1400 * just 7-bit ASCII encoding, with the MSB being zero. */ 1401 addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII"); 1402 } catch (java.io.UnsupportedEncodingException ex) { 1403 throw new CodingException("invalid SMS address ASCII code"); 1404 } 1405 } else { 1406 addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits); 1407 } 1408 } 1409 decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)1410 private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream) 1411 throws BitwiseInputStream.AccessException, CodingException 1412 { 1413 final int EXPECTED_PARAM_SIZE = 1 * 8; //at least 1414 int paramBits = inStream.read(8) * 8; 1415 if (paramBits < EXPECTED_PARAM_SIZE) { 1416 inStream.skip(paramBits); 1417 return false; 1418 } 1419 CdmaSmsAddress addr = new CdmaSmsAddress(); 1420 addr.digitMode = inStream.read(1); 1421 byte fieldBits = 4; 1422 byte consumedBits = 1; 1423 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 1424 addr.ton = inStream.read(3); 1425 addr.numberPlan = inStream.read(4); 1426 fieldBits = 8; 1427 consumedBits += 7; 1428 } 1429 addr.numberOfDigits = inStream.read(8); 1430 consumedBits += 8; 1431 int remainingBits = paramBits - consumedBits; 1432 int dataBits = addr.numberOfDigits * fieldBits; 1433 int paddingBits = remainingBits - dataBits; 1434 if (remainingBits < dataBits) { 1435 throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" + 1436 "remainingBits + " + remainingBits + ", dataBits + " + 1437 dataBits + ", paddingBits + " + paddingBits + ")"); 1438 } 1439 addr.origBytes = inStream.readByteArray(dataBits); 1440 inStream.skip(paddingBits); 1441 decodeSmsAddress(addr); 1442 bData.callbackNumber = addr; 1443 return true; 1444 } 1445 decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)1446 private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream) 1447 throws BitwiseInputStream.AccessException { 1448 final int EXPECTED_PARAM_SIZE = 1 * 8; 1449 boolean decodeSuccess = false; 1450 int paramBits = inStream.read(8) * 8; 1451 if (paramBits >= EXPECTED_PARAM_SIZE) { 1452 paramBits -= EXPECTED_PARAM_SIZE; 1453 decodeSuccess = true; 1454 bData.errorClass = inStream.read(2); 1455 bData.messageStatus = inStream.read(6); 1456 } 1457 if ((! decodeSuccess) || (paramBits > 0)) { 1458 Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " + 1459 (decodeSuccess ? "succeeded" : "failed") + 1460 " (extra bits = " + paramBits + ")"); 1461 } 1462 inStream.skip(paramBits); 1463 bData.messageStatusSet = decodeSuccess; 1464 return decodeSuccess; 1465 } 1466 decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)1467 private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream) 1468 throws BitwiseInputStream.AccessException { 1469 final int EXPECTED_PARAM_SIZE = 6 * 8; 1470 boolean decodeSuccess = false; 1471 int paramBits = inStream.read(8) * 8; 1472 if (paramBits >= EXPECTED_PARAM_SIZE) { 1473 paramBits -= EXPECTED_PARAM_SIZE; 1474 decodeSuccess = true; 1475 bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); 1476 } 1477 if ((! decodeSuccess) || (paramBits > 0)) { 1478 Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " + 1479 (decodeSuccess ? "succeeded" : "failed") + 1480 " (extra bits = " + paramBits + ")"); 1481 } 1482 inStream.skip(paramBits); 1483 return decodeSuccess; 1484 } 1485 decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)1486 private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream) 1487 throws BitwiseInputStream.AccessException { 1488 final int EXPECTED_PARAM_SIZE = 6 * 8; 1489 boolean decodeSuccess = false; 1490 int paramBits = inStream.read(8) * 8; 1491 if (paramBits >= EXPECTED_PARAM_SIZE) { 1492 paramBits -= EXPECTED_PARAM_SIZE; 1493 decodeSuccess = true; 1494 bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); 1495 } 1496 if ((! decodeSuccess) || (paramBits > 0)) { 1497 Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " + 1498 (decodeSuccess ? "succeeded" : "failed") + 1499 " (extra bits = " + paramBits + ")"); 1500 } 1501 inStream.skip(paramBits); 1502 return decodeSuccess; 1503 } 1504 decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)1505 private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream) 1506 throws BitwiseInputStream.AccessException { 1507 final int EXPECTED_PARAM_SIZE = 6 * 8; 1508 boolean decodeSuccess = false; 1509 int paramBits = inStream.read(8) * 8; 1510 if (paramBits >= EXPECTED_PARAM_SIZE) { 1511 paramBits -= EXPECTED_PARAM_SIZE; 1512 decodeSuccess = true; 1513 bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray( 1514 inStream.readByteArray(6 * 8)); 1515 } 1516 if ((! decodeSuccess) || (paramBits > 0)) { 1517 Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " + 1518 (decodeSuccess ? "succeeded" : "failed") + 1519 " (extra bits = " + paramBits + ")"); 1520 } 1521 inStream.skip(paramBits); 1522 return decodeSuccess; 1523 } 1524 decodeValidityRel(BearerData bData, BitwiseInputStream inStream)1525 private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream) 1526 throws BitwiseInputStream.AccessException { 1527 final int EXPECTED_PARAM_SIZE = 1 * 8; 1528 boolean decodeSuccess = false; 1529 int paramBits = inStream.read(8) * 8; 1530 if (paramBits >= EXPECTED_PARAM_SIZE) { 1531 paramBits -= EXPECTED_PARAM_SIZE; 1532 decodeSuccess = true; 1533 bData.deferredDeliveryTimeRelative = inStream.read(8); 1534 } 1535 if ((! decodeSuccess) || (paramBits > 0)) { 1536 Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " + 1537 (decodeSuccess ? "succeeded" : "failed") + 1538 " (extra bits = " + paramBits + ")"); 1539 } 1540 inStream.skip(paramBits); 1541 bData.deferredDeliveryTimeRelativeSet = decodeSuccess; 1542 return decodeSuccess; 1543 } 1544 decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)1545 private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream) 1546 throws BitwiseInputStream.AccessException { 1547 final int EXPECTED_PARAM_SIZE = 1 * 8; 1548 boolean decodeSuccess = false; 1549 int paramBits = inStream.read(8) * 8; 1550 if (paramBits >= EXPECTED_PARAM_SIZE) { 1551 paramBits -= EXPECTED_PARAM_SIZE; 1552 decodeSuccess = true; 1553 bData.validityPeriodRelative = inStream.read(8); 1554 } 1555 if ((! decodeSuccess) || (paramBits > 0)) { 1556 Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " + 1557 (decodeSuccess ? "succeeded" : "failed") + 1558 " (extra bits = " + paramBits + ")"); 1559 } 1560 inStream.skip(paramBits); 1561 bData.validityPeriodRelativeSet = decodeSuccess; 1562 return decodeSuccess; 1563 } 1564 decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)1565 private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream) 1566 throws BitwiseInputStream.AccessException { 1567 final int EXPECTED_PARAM_SIZE = 1 * 8; 1568 boolean decodeSuccess = false; 1569 int paramBits = inStream.read(8) * 8; 1570 if (paramBits >= EXPECTED_PARAM_SIZE) { 1571 paramBits -= EXPECTED_PARAM_SIZE; 1572 decodeSuccess = true; 1573 bData.privacy = inStream.read(2); 1574 inStream.skip(6); 1575 } 1576 if ((! decodeSuccess) || (paramBits > 0)) { 1577 Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " + 1578 (decodeSuccess ? "succeeded" : "failed") + 1579 " (extra bits = " + paramBits + ")"); 1580 } 1581 inStream.skip(paramBits); 1582 bData.privacyIndicatorSet = decodeSuccess; 1583 return decodeSuccess; 1584 } 1585 decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)1586 private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream) 1587 throws BitwiseInputStream.AccessException { 1588 final int EXPECTED_PARAM_SIZE = 1 * 8; 1589 boolean decodeSuccess = false; 1590 int paramBits = inStream.read(8) * 8; 1591 if (paramBits >= EXPECTED_PARAM_SIZE) { 1592 paramBits -= EXPECTED_PARAM_SIZE; 1593 decodeSuccess = true; 1594 bData.language = inStream.read(8); 1595 } 1596 if ((! decodeSuccess) || (paramBits > 0)) { 1597 Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " + 1598 (decodeSuccess ? "succeeded" : "failed") + 1599 " (extra bits = " + paramBits + ")"); 1600 } 1601 inStream.skip(paramBits); 1602 bData.languageIndicatorSet = decodeSuccess; 1603 return decodeSuccess; 1604 } 1605 decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)1606 private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream) 1607 throws BitwiseInputStream.AccessException { 1608 final int EXPECTED_PARAM_SIZE = 1 * 8; 1609 boolean decodeSuccess = false; 1610 int paramBits = inStream.read(8) * 8; 1611 if (paramBits >= EXPECTED_PARAM_SIZE) { 1612 paramBits -= EXPECTED_PARAM_SIZE; 1613 decodeSuccess = true; 1614 bData.displayMode = inStream.read(2); 1615 inStream.skip(6); 1616 } 1617 if ((! decodeSuccess) || (paramBits > 0)) { 1618 Rlog.d(LOG_TAG, "DISPLAY_MODE decode " + 1619 (decodeSuccess ? "succeeded" : "failed") + 1620 " (extra bits = " + paramBits + ")"); 1621 } 1622 inStream.skip(paramBits); 1623 bData.displayModeSet = decodeSuccess; 1624 return decodeSuccess; 1625 } 1626 decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)1627 private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream) 1628 throws BitwiseInputStream.AccessException { 1629 final int EXPECTED_PARAM_SIZE = 1 * 8; 1630 boolean decodeSuccess = false; 1631 int paramBits = inStream.read(8) * 8; 1632 if (paramBits >= EXPECTED_PARAM_SIZE) { 1633 paramBits -= EXPECTED_PARAM_SIZE; 1634 decodeSuccess = true; 1635 bData.priority = inStream.read(2); 1636 inStream.skip(6); 1637 } 1638 if ((! decodeSuccess) || (paramBits > 0)) { 1639 Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " + 1640 (decodeSuccess ? "succeeded" : "failed") + 1641 " (extra bits = " + paramBits + ")"); 1642 } 1643 inStream.skip(paramBits); 1644 bData.priorityIndicatorSet = decodeSuccess; 1645 return decodeSuccess; 1646 } 1647 decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)1648 private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream) 1649 throws BitwiseInputStream.AccessException { 1650 final int EXPECTED_PARAM_SIZE = 1 * 8; 1651 boolean decodeSuccess = false; 1652 int paramBits = inStream.read(8) * 8; 1653 if (paramBits >= EXPECTED_PARAM_SIZE) { 1654 paramBits -= EXPECTED_PARAM_SIZE; 1655 decodeSuccess = true; 1656 bData.alert = inStream.read(2); 1657 inStream.skip(6); 1658 } 1659 if ((! decodeSuccess) || (paramBits > 0)) { 1660 Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " + 1661 (decodeSuccess ? "succeeded" : "failed") + 1662 " (extra bits = " + paramBits + ")"); 1663 } 1664 inStream.skip(paramBits); 1665 bData.alertIndicatorSet = decodeSuccess; 1666 return decodeSuccess; 1667 } 1668 decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)1669 private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream) 1670 throws BitwiseInputStream.AccessException { 1671 final int EXPECTED_PARAM_SIZE = 1 * 8; 1672 boolean decodeSuccess = false; 1673 int paramBits = inStream.read(8) * 8; 1674 if (paramBits >= EXPECTED_PARAM_SIZE) { 1675 paramBits -= EXPECTED_PARAM_SIZE; 1676 decodeSuccess = true; 1677 bData.userResponseCode = inStream.read(8); 1678 } 1679 if ((! decodeSuccess) || (paramBits > 0)) { 1680 Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " + 1681 (decodeSuccess ? "succeeded" : "failed") + 1682 " (extra bits = " + paramBits + ")"); 1683 } 1684 inStream.skip(paramBits); 1685 bData.userResponseCodeSet = decodeSuccess; 1686 return decodeSuccess; 1687 } 1688 decodeServiceCategoryProgramData(BearerData bData, BitwiseInputStream inStream)1689 private static boolean decodeServiceCategoryProgramData(BearerData bData, 1690 BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException 1691 { 1692 if (inStream.available() < 13) { 1693 throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " 1694 + inStream.available() + " bits available"); 1695 } 1696 1697 int paramBits = inStream.read(8) * 8; 1698 int msgEncoding = inStream.read(5); 1699 paramBits -= 5; 1700 1701 if (inStream.available() < paramBits) { 1702 throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " 1703 + inStream.available() + " bits available (" + paramBits + " bits expected)"); 1704 } 1705 1706 ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>(); 1707 1708 final int CATEGORY_FIELD_MIN_SIZE = 6 * 8; 1709 boolean decodeSuccess = false; 1710 while (paramBits >= CATEGORY_FIELD_MIN_SIZE) { 1711 int operation = inStream.read(4); 1712 int category = (inStream.read(8) << 8) | inStream.read(8); 1713 int language = inStream.read(8); 1714 int maxMessages = inStream.read(8); 1715 int alertOption = inStream.read(4); 1716 int numFields = inStream.read(8); 1717 paramBits -= CATEGORY_FIELD_MIN_SIZE; 1718 1719 int textBits = getBitsForNumFields(msgEncoding, numFields); 1720 if (paramBits < textBits) { 1721 throw new CodingException("category name is " + textBits + " bits in length," 1722 + " but there are only " + paramBits + " bits available"); 1723 } 1724 1725 UserData userData = new UserData(); 1726 userData.msgEncoding = msgEncoding; 1727 userData.msgEncodingSet = true; 1728 userData.numFields = numFields; 1729 userData.payload = inStream.readByteArray(textBits); 1730 paramBits -= textBits; 1731 1732 decodeUserDataPayload(userData, false); 1733 String categoryName = userData.payloadStr; 1734 CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category, 1735 language, maxMessages, alertOption, categoryName); 1736 programDataList.add(programData); 1737 1738 decodeSuccess = true; 1739 } 1740 1741 if ((! decodeSuccess) || (paramBits > 0)) { 1742 Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + 1743 (decodeSuccess ? "succeeded" : "failed") + 1744 " (extra bits = " + paramBits + ')'); 1745 } 1746 1747 inStream.skip(paramBits); 1748 bData.serviceCategoryProgramData = programDataList; 1749 return decodeSuccess; 1750 } 1751 serviceCategoryToCmasMessageClass(int serviceCategory)1752 private static int serviceCategoryToCmasMessageClass(int serviceCategory) { 1753 switch (serviceCategory) { 1754 case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT: 1755 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; 1756 1757 case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: 1758 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; 1759 1760 case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: 1761 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; 1762 1763 case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: 1764 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; 1765 1766 case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: 1767 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; 1768 1769 default: 1770 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; 1771 } 1772 } 1773 1774 /** 1775 * Calculates the number of bits to read for the specified number of encoded characters. 1776 * @param msgEncoding the message encoding to use 1777 * @param numFields the number of characters to read. For Shift-JIS and Korean encodings, 1778 * this is the number of bytes to read. 1779 * @return the number of bits to read from the stream 1780 * @throws CodingException if the specified encoding is not supported 1781 */ getBitsForNumFields(int msgEncoding, int numFields)1782 private static int getBitsForNumFields(int msgEncoding, int numFields) 1783 throws CodingException { 1784 switch (msgEncoding) { 1785 case UserData.ENCODING_OCTET: 1786 case UserData.ENCODING_SHIFT_JIS: 1787 case UserData.ENCODING_KOREAN: 1788 case UserData.ENCODING_LATIN: 1789 case UserData.ENCODING_LATIN_HEBREW: 1790 return numFields * 8; 1791 1792 case UserData.ENCODING_IA5: 1793 case UserData.ENCODING_7BIT_ASCII: 1794 case UserData.ENCODING_GSM_7BIT_ALPHABET: 1795 return numFields * 7; 1796 1797 case UserData.ENCODING_UNICODE_16: 1798 return numFields * 16; 1799 1800 default: 1801 throw new CodingException("unsupported message encoding (" + msgEncoding + ')'); 1802 } 1803 } 1804 1805 /** 1806 * CMAS message decoding. 1807 * (See TIA-1149-0-1, CMAS over CDMA) 1808 * 1809 * @param serviceCategory is the service category from the SMS envelope 1810 */ decodeCmasUserData(BearerData bData, int serviceCategory)1811 private static void decodeCmasUserData(BearerData bData, int serviceCategory) 1812 throws BitwiseInputStream.AccessException, CodingException { 1813 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1814 if (inStream.available() < 8) { 1815 throw new CodingException("emergency CB with no CMAE_protocol_version"); 1816 } 1817 int protocolVersion = inStream.read(8); 1818 if (protocolVersion != 0) { 1819 throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion); 1820 } 1821 1822 int messageClass = serviceCategoryToCmasMessageClass(serviceCategory); 1823 int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; 1824 int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; 1825 int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; 1826 int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; 1827 int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; 1828 1829 while (inStream.available() >= 16) { 1830 int recordType = inStream.read(8); 1831 int recordLen = inStream.read(8); 1832 switch (recordType) { 1833 case 0: // Type 0 elements (Alert text) 1834 UserData alertUserData = new UserData(); 1835 alertUserData.msgEncoding = inStream.read(5); 1836 alertUserData.msgEncodingSet = true; 1837 alertUserData.msgType = 0; 1838 1839 int numFields; // number of chars to decode 1840 switch (alertUserData.msgEncoding) { 1841 case UserData.ENCODING_OCTET: 1842 case UserData.ENCODING_LATIN: 1843 numFields = recordLen - 1; // subtract 1 byte for encoding 1844 break; 1845 1846 case UserData.ENCODING_IA5: 1847 case UserData.ENCODING_7BIT_ASCII: 1848 case UserData.ENCODING_GSM_7BIT_ALPHABET: 1849 numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding 1850 break; 1851 1852 case UserData.ENCODING_UNICODE_16: 1853 numFields = (recordLen - 1) / 2; 1854 break; 1855 1856 default: 1857 numFields = 0; // unsupported encoding 1858 } 1859 1860 alertUserData.numFields = numFields; 1861 alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5); 1862 decodeUserDataPayload(alertUserData, false); 1863 bData.userData = alertUserData; 1864 break; 1865 1866 case 1: // Type 1 elements 1867 category = inStream.read(8); 1868 responseType = inStream.read(8); 1869 severity = inStream.read(4); 1870 urgency = inStream.read(4); 1871 certainty = inStream.read(4); 1872 inStream.skip(recordLen * 8 - 28); 1873 break; 1874 1875 default: 1876 Rlog.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType); 1877 inStream.skip(recordLen * 8); 1878 break; 1879 } 1880 } 1881 1882 bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity, 1883 urgency, certainty); 1884 } 1885 1886 /** 1887 * Create BearerData object from serialized representation. 1888 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 1889 * 1890 * @param smsData byte array of raw encoded SMS bearer data. 1891 * @return an instance of BearerData. 1892 */ decode(byte[] smsData)1893 public static BearerData decode(byte[] smsData) { 1894 return decode(smsData, 0); 1895 } 1896 isCmasAlertCategory(int category)1897 private static boolean isCmasAlertCategory(int category) { 1898 return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT 1899 && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE; 1900 } 1901 1902 /** 1903 * Create BearerData object from serialized representation. 1904 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 1905 * 1906 * @param smsData byte array of raw encoded SMS bearer data. 1907 * @param serviceCategory the envelope service category (for CMAS alert handling) 1908 * @return an instance of BearerData. 1909 */ decode(byte[] smsData, int serviceCategory)1910 public static BearerData decode(byte[] smsData, int serviceCategory) { 1911 try { 1912 BitwiseInputStream inStream = new BitwiseInputStream(smsData); 1913 BearerData bData = new BearerData(); 1914 int foundSubparamMask = 0; 1915 while (inStream.available() > 0) { 1916 int subparamId = inStream.read(8); 1917 int subparamIdBit = 1 << subparamId; 1918 // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8) 1919 // as 32th bit is the max bit in int. 1920 // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers: 1921 // last defined subparam ID is 23 (00010111 = 0x17 = 23). 1922 // Only do duplicate subparam ID check if subparam is within defined value as 1923 // reserved subparams are just skipped. 1924 if ((foundSubparamMask & subparamIdBit) != 0 && 1925 (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER && 1926 subparamId <= SUBPARAM_ID_LAST_DEFINED)) { 1927 throw new CodingException("illegal duplicate subparameter (" + 1928 subparamId + ")"); 1929 } 1930 boolean decodeSuccess; 1931 switch (subparamId) { 1932 case SUBPARAM_MESSAGE_IDENTIFIER: 1933 decodeSuccess = decodeMessageId(bData, inStream); 1934 break; 1935 case SUBPARAM_USER_DATA: 1936 decodeSuccess = decodeUserData(bData, inStream); 1937 break; 1938 case SUBPARAM_USER_RESPONSE_CODE: 1939 decodeSuccess = decodeUserResponseCode(bData, inStream); 1940 break; 1941 case SUBPARAM_REPLY_OPTION: 1942 decodeSuccess = decodeReplyOption(bData, inStream); 1943 break; 1944 case SUBPARAM_NUMBER_OF_MESSAGES: 1945 decodeSuccess = decodeMsgCount(bData, inStream); 1946 break; 1947 case SUBPARAM_CALLBACK_NUMBER: 1948 decodeSuccess = decodeCallbackNumber(bData, inStream); 1949 break; 1950 case SUBPARAM_MESSAGE_STATUS: 1951 decodeSuccess = decodeMsgStatus(bData, inStream); 1952 break; 1953 case SUBPARAM_MESSAGE_CENTER_TIME_STAMP: 1954 decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream); 1955 break; 1956 case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE: 1957 decodeSuccess = decodeValidityAbs(bData, inStream); 1958 break; 1959 case SUBPARAM_VALIDITY_PERIOD_RELATIVE: 1960 decodeSuccess = decodeValidityRel(bData, inStream); 1961 break; 1962 case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE: 1963 decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream); 1964 break; 1965 case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE: 1966 decodeSuccess = decodeDeferredDeliveryRel(bData, inStream); 1967 break; 1968 case SUBPARAM_PRIVACY_INDICATOR: 1969 decodeSuccess = decodePrivacyIndicator(bData, inStream); 1970 break; 1971 case SUBPARAM_LANGUAGE_INDICATOR: 1972 decodeSuccess = decodeLanguageIndicator(bData, inStream); 1973 break; 1974 case SUBPARAM_MESSAGE_DISPLAY_MODE: 1975 decodeSuccess = decodeDisplayMode(bData, inStream); 1976 break; 1977 case SUBPARAM_PRIORITY_INDICATOR: 1978 decodeSuccess = decodePriorityIndicator(bData, inStream); 1979 break; 1980 case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY: 1981 decodeSuccess = decodeMsgDeliveryAlert(bData, inStream); 1982 break; 1983 case SUBPARAM_MESSAGE_DEPOSIT_INDEX: 1984 decodeSuccess = decodeDepositIndex(bData, inStream); 1985 break; 1986 case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA: 1987 decodeSuccess = decodeServiceCategoryProgramData(bData, inStream); 1988 break; 1989 default: 1990 decodeSuccess = decodeReserved(bData, inStream, subparamId); 1991 } 1992 if (decodeSuccess && 1993 (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER && 1994 subparamId <= SUBPARAM_ID_LAST_DEFINED)) { 1995 foundSubparamMask |= subparamIdBit; 1996 } 1997 } 1998 if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) { 1999 throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); 2000 } 2001 if (bData.userData != null) { 2002 if (isCmasAlertCategory(serviceCategory)) { 2003 decodeCmasUserData(bData, serviceCategory); 2004 } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { 2005 if ((foundSubparamMask ^ 2006 (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^ 2007 (1 << SUBPARAM_USER_DATA)) 2008 != 0) { 2009 Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" + 2010 foundSubparamMask + ")"); 2011 } 2012 decodeIs91(bData); 2013 } else { 2014 decodeUserDataPayload(bData.userData, bData.hasUserDataHeader); 2015 } 2016 } 2017 return bData; 2018 } catch (BitwiseInputStream.AccessException ex) { 2019 Rlog.e(LOG_TAG, "BearerData decode failed: " + ex); 2020 } catch (CodingException ex) { 2021 Rlog.e(LOG_TAG, "BearerData decode failed: " + ex); 2022 } 2023 return null; 2024 } 2025 } 2026