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