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 * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg 473 * @return septet count, or -1 on failure 474 */ 475 public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg, 476 boolean force7BitEncoding, boolean isEntireMsg) { 477 TextEncodingDetails ted; 478 int septets = countAsciiSeptets(msg, force7BitEncoding); 479 if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) { 480 ted = new TextEncodingDetails(); 481 ted.msgCount = 1; 482 ted.codeUnitCount = septets; 483 ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets; 484 ted.codeUnitSize = SmsConstants.ENCODING_7BIT; 485 } else { 486 ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength( 487 msg, force7BitEncoding); 488 if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT && 489 isEntireMsg) { 490 // We don't support single-segment EMS, so calculate for 16-bit 491 // TODO: Consider supporting single-segment EMS 492 ted.codeUnitCount = msg.length(); 493 int octets = ted.codeUnitCount * 2; 494 if (octets > SmsConstants.MAX_USER_DATA_BYTES) { 495 // If EMS is not supported, break down EMS into single segment SMS 496 // and add page info " x/y". 497 // In the case of UCS2 encoding type, we need 8 bytes for this 498 // but we only have 6 bytes from UDH, so truncate the limit for 499 // each segment by 2 bytes (1 char). 500 int max_user_data_bytes_with_header = 501 SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER; 502 if (!android.telephony.SmsMessage.hasEmsSupport()) { 503 // make sure total number of segments is less than 10 504 if (octets <= 9 * (max_user_data_bytes_with_header - 2)) 505 max_user_data_bytes_with_header -= 2; 506 } 507 508 ted.msgCount = (octets + (max_user_data_bytes_with_header - 1)) / 509 max_user_data_bytes_with_header; 510 ted.codeUnitsRemaining = ((ted.msgCount * 511 max_user_data_bytes_with_header) - octets) / 2; 512 } else { 513 ted.msgCount = 1; 514 ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets)/2; 515 } 516 ted.codeUnitSize = SmsConstants.ENCODING_16BIT; 517 } 518 } 519 return ted; 520 } 521 522 private static byte[] encode7bitAscii(String msg, boolean force) 523 throws CodingException 524 { 525 try { 526 BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length()); 527 int msgLen = msg.length(); 528 for (int i = 0; i < msgLen; i++) { 529 int charCode = UserData.charToAscii.get(msg.charAt(i), -1); 530 if (charCode == -1) { 531 if (force) { 532 outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR); 533 } else { 534 throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")"); 535 } 536 } else { 537 outStream.write(7, charCode); 538 } 539 } 540 return outStream.toByteArray(); 541 } catch (BitwiseOutputStream.AccessException ex) { 542 throw new CodingException("7bit ASCII encode failed: " + ex); 543 } 544 } 545 546 private static byte[] encodeUtf16(String msg) 547 throws CodingException 548 { 549 try { 550 return msg.getBytes("utf-16be"); 551 } catch (java.io.UnsupportedEncodingException ex) { 552 throw new CodingException("UTF-16 encode failed: " + ex); 553 } 554 } 555 556 private static class Gsm7bitCodingResult { 557 int septets; 558 byte[] data; 559 } 560 561 private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force) 562 throws CodingException 563 { 564 try { 565 /* 566 * TODO(cleanup): It would be nice if GsmAlphabet provided 567 * an option to produce just the data without prepending 568 * the septet count, as this function is really just a 569 * wrapper to strip that off. Not to mention that the 570 * septet count is generally known prior to invocation of 571 * the encoder. Note that it cannot be derived from the 572 * resulting array length, since that cannot distinguish 573 * if the last contains either 1 or 8 valid bits. 574 * 575 * TODO(cleanup): The BitwiseXStreams could also be 576 * extended with byte-wise reversed endianness read/write 577 * routines to allow a corresponding implementation of 578 * stringToGsm7BitPacked, and potentially directly support 579 * access to the main bitwise stream from encode/decode. 580 */ 581 byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0); 582 Gsm7bitCodingResult result = new Gsm7bitCodingResult(); 583 result.data = new byte[fullData.length - 1]; 584 System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1); 585 result.septets = fullData[0] & 0x00FF; 586 return result; 587 } catch (com.android.internal.telephony.EncodeException ex) { 588 throw new CodingException("7bit GSM encode failed: " + ex); 589 } 590 } 591 592 private static void encode7bitEms(UserData uData, byte[] udhData, boolean force) 593 throws CodingException 594 { 595 int udhBytes = udhData.length + 1; // Add length octet. 596 int udhSeptets = ((udhBytes * 8) + 6) / 7; 597 Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force); 598 uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; 599 uData.msgEncodingSet = true; 600 uData.numFields = gcr.septets; 601 uData.payload = gcr.data; 602 uData.payload[0] = (byte)udhData.length; 603 System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); 604 } 605 606 private static void encode16bitEms(UserData uData, byte[] udhData) 607 throws CodingException 608 { 609 byte[] payload = encodeUtf16(uData.payloadStr); 610 int udhBytes = udhData.length + 1; // Add length octet. 611 int udhCodeUnits = (udhBytes + 1) / 2; 612 int payloadCodeUnits = payload.length / 2; 613 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 614 uData.msgEncodingSet = true; 615 uData.numFields = udhCodeUnits + payloadCodeUnits; 616 uData.payload = new byte[uData.numFields * 2]; 617 uData.payload[0] = (byte)udhData.length; 618 System.arraycopy(udhData, 0, uData.payload, 1, udhData.length); 619 System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length); 620 } 621 622 private static void encodeEmsUserDataPayload(UserData uData) 623 throws CodingException 624 { 625 byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader); 626 if (uData.msgEncodingSet) { 627 if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { 628 encode7bitEms(uData, headerData, true); 629 } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { 630 encode16bitEms(uData, headerData); 631 } else { 632 throw new CodingException("unsupported EMS user data encoding (" + 633 uData.msgEncoding + ")"); 634 } 635 } else { 636 try { 637 encode7bitEms(uData, headerData, false); 638 } catch (CodingException ex) { 639 encode16bitEms(uData, headerData); 640 } 641 } 642 } 643 644 private static byte[] encodeShiftJis(String msg) throws CodingException { 645 try { 646 return msg.getBytes("Shift_JIS"); 647 } catch (java.io.UnsupportedEncodingException ex) { 648 throw new CodingException("Shift-JIS encode failed: " + ex); 649 } 650 } 651 652 private static void encodeUserDataPayload(UserData uData) 653 throws CodingException 654 { 655 if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) { 656 Rlog.e(LOG_TAG, "user data with null payloadStr"); 657 uData.payloadStr = ""; 658 } 659 660 if (uData.userDataHeader != null) { 661 encodeEmsUserDataPayload(uData); 662 return; 663 } 664 665 if (uData.msgEncodingSet) { 666 if (uData.msgEncoding == UserData.ENCODING_OCTET) { 667 if (uData.payload == null) { 668 Rlog.e(LOG_TAG, "user data with octet encoding but null payload"); 669 uData.payload = new byte[0]; 670 uData.numFields = 0; 671 } else { 672 uData.numFields = uData.payload.length; 673 } 674 } else { 675 if (uData.payloadStr == null) { 676 Rlog.e(LOG_TAG, "non-octet user data with null payloadStr"); 677 uData.payloadStr = ""; 678 } 679 if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { 680 Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true); 681 uData.payload = gcr.data; 682 uData.numFields = gcr.septets; 683 } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) { 684 uData.payload = encode7bitAscii(uData.payloadStr, true); 685 uData.numFields = uData.payloadStr.length(); 686 } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { 687 uData.payload = encodeUtf16(uData.payloadStr); 688 uData.numFields = uData.payloadStr.length(); 689 } else if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) { 690 uData.payload = encodeShiftJis(uData.payloadStr); 691 uData.numFields = uData.payload.length; 692 } else { 693 throw new CodingException("unsupported user data encoding (" + 694 uData.msgEncoding + ")"); 695 } 696 } 697 } else { 698 try { 699 uData.payload = encode7bitAscii(uData.payloadStr, false); 700 uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; 701 } catch (CodingException ex) { 702 uData.payload = encodeUtf16(uData.payloadStr); 703 uData.msgEncoding = UserData.ENCODING_UNICODE_16; 704 } 705 uData.numFields = uData.payloadStr.length(); 706 uData.msgEncodingSet = true; 707 } 708 } 709 710 private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream) 711 throws BitwiseOutputStream.AccessException, CodingException 712 { 713 /* 714 * TODO(cleanup): Do we really need to set userData.payload as 715 * a side effect of encoding? If not, we could avoid data 716 * copies by passing outStream directly. 717 */ 718 encodeUserDataPayload(bData.userData); 719 bData.hasUserDataHeader = bData.userData.userDataHeader != null; 720 721 if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) { 722 throw new CodingException("encoded user data too large (" + 723 bData.userData.payload.length + 724 " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)"); 725 } 726 727 /* 728 * TODO(cleanup): figure out what the right answer is WRT paddingBits field 729 * 730 * userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7); 731 * userData.paddingBits = 0; // XXX this seems better, but why? 732 * 733 */ 734 int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits; 735 int paramBits = dataBits + 13; 736 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 737 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 738 paramBits += 8; 739 } 740 int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0); 741 int paddingBits = (paramBytes * 8) - paramBits; 742 outStream.write(8, paramBytes); 743 outStream.write(5, bData.userData.msgEncoding); 744 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 745 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 746 outStream.write(8, bData.userData.msgType); 747 } 748 outStream.write(8, bData.userData.numFields); 749 outStream.writeByteArray(dataBits, bData.userData.payload); 750 if (paddingBits > 0) outStream.write(paddingBits, 0); 751 } 752 753 private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream) 754 throws BitwiseOutputStream.AccessException 755 { 756 outStream.write(8, 1); 757 outStream.write(1, bData.userAckReq ? 1 : 0); 758 outStream.write(1, bData.deliveryAckReq ? 1 : 0); 759 outStream.write(1, bData.readAckReq ? 1 : 0); 760 outStream.write(1, bData.reportReq ? 1 : 0); 761 outStream.write(4, 0); 762 } 763 764 private static byte[] encodeDtmfSmsAddress(String address) { 765 int digits = address.length(); 766 int dataBits = digits * 4; 767 int dataBytes = (dataBits / 8); 768 dataBytes += (dataBits % 8) > 0 ? 1 : 0; 769 byte[] rawData = new byte[dataBytes]; 770 for (int i = 0; i < digits; i++) { 771 char c = address.charAt(i); 772 int val = 0; 773 if ((c >= '1') && (c <= '9')) val = c - '0'; 774 else if (c == '0') val = 10; 775 else if (c == '*') val = 11; 776 else if (c == '#') val = 12; 777 else return null; 778 rawData[i / 2] |= val << (4 - ((i % 2) * 4)); 779 } 780 return rawData; 781 } 782 783 /* 784 * TODO(cleanup): CdmaSmsAddress encoding should make use of 785 * CdmaSmsAddress.parse provided that DTMF encoding is unified, 786 * and the difference in 4-bit vs. 8-bit is resolved. 787 */ 788 789 private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException { 790 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 791 try { 792 addr.origBytes = addr.address.getBytes("US-ASCII"); 793 } catch (java.io.UnsupportedEncodingException ex) { 794 throw new CodingException("invalid SMS address, cannot convert to ASCII"); 795 } 796 } else { 797 addr.origBytes = encodeDtmfSmsAddress(addr.address); 798 } 799 } 800 801 private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream) 802 throws BitwiseOutputStream.AccessException, CodingException 803 { 804 CdmaSmsAddress addr = bData.callbackNumber; 805 encodeCdmaSmsAddress(addr); 806 int paramBits = 9; 807 int dataBits = 0; 808 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 809 paramBits += 7; 810 dataBits = addr.numberOfDigits * 8; 811 } else { 812 dataBits = addr.numberOfDigits * 4; 813 } 814 paramBits += dataBits; 815 int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0); 816 int paddingBits = (paramBytes * 8) - paramBits; 817 outStream.write(8, paramBytes); 818 outStream.write(1, addr.digitMode); 819 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 820 outStream.write(3, addr.ton); 821 outStream.write(4, addr.numberPlan); 822 } 823 outStream.write(8, addr.numberOfDigits); 824 outStream.writeByteArray(dataBits, addr.origBytes); 825 if (paddingBits > 0) outStream.write(paddingBits, 0); 826 } 827 828 private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream) 829 throws BitwiseOutputStream.AccessException 830 { 831 outStream.write(8, 1); 832 outStream.write(2, bData.errorClass); 833 outStream.write(6, bData.messageStatus); 834 } 835 836 private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream) 837 throws BitwiseOutputStream.AccessException 838 { 839 outStream.write(8, 1); 840 outStream.write(8, bData.numberOfMessages); 841 } 842 843 private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream) 844 throws BitwiseOutputStream.AccessException 845 { 846 outStream.write(8, 1); 847 outStream.write(8, bData.validityPeriodRelative); 848 } 849 850 private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream) 851 throws BitwiseOutputStream.AccessException 852 { 853 outStream.write(8, 1); 854 outStream.write(2, bData.privacy); 855 outStream.skip(6); 856 } 857 858 private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream) 859 throws BitwiseOutputStream.AccessException 860 { 861 outStream.write(8, 1); 862 outStream.write(8, bData.language); 863 } 864 865 private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream) 866 throws BitwiseOutputStream.AccessException 867 { 868 outStream.write(8, 1); 869 outStream.write(2, bData.displayMode); 870 outStream.skip(6); 871 } 872 873 private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream) 874 throws BitwiseOutputStream.AccessException 875 { 876 outStream.write(8, 1); 877 outStream.write(2, bData.priority); 878 outStream.skip(6); 879 } 880 881 private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream) 882 throws BitwiseOutputStream.AccessException 883 { 884 outStream.write(8, 1); 885 outStream.write(2, bData.alert); 886 outStream.skip(6); 887 } 888 889 private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream) 890 throws BitwiseOutputStream.AccessException 891 { 892 ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults; 893 outStream.write(8, (results.size() * 4)); // 4 octets per program result 894 for (CdmaSmsCbProgramResults result : results) { 895 int category = result.getCategory(); 896 outStream.write(8, category >> 8); 897 outStream.write(8, category); 898 outStream.write(8, result.getLanguage()); 899 outStream.write(4, result.getCategoryResult()); 900 outStream.skip(4); 901 } 902 } 903 904 /** 905 * Create serialized representation for BearerData object. 906 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 907 * 908 * @param bData an instance of BearerData. 909 * 910 * @return byte array of raw encoded SMS bearer data. 911 */ 912 public static byte[] encode(BearerData bData) { 913 bData.hasUserDataHeader = ((bData.userData != null) && 914 (bData.userData.userDataHeader != null)); 915 try { 916 BitwiseOutputStream outStream = new BitwiseOutputStream(200); 917 outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER); 918 encodeMessageId(bData, outStream); 919 if (bData.userData != null) { 920 outStream.write(8, SUBPARAM_USER_DATA); 921 encodeUserData(bData, outStream); 922 } 923 if (bData.callbackNumber != null) { 924 outStream.write(8, SUBPARAM_CALLBACK_NUMBER); 925 encodeCallbackNumber(bData, outStream); 926 } 927 if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) { 928 outStream.write(8, SUBPARAM_REPLY_OPTION); 929 encodeReplyOption(bData, outStream); 930 } 931 if (bData.numberOfMessages != 0) { 932 outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES); 933 encodeMsgCount(bData, outStream); 934 } 935 if (bData.validityPeriodRelativeSet) { 936 outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE); 937 encodeValidityPeriodRel(bData, outStream); 938 } 939 if (bData.privacyIndicatorSet) { 940 outStream.write(8, SUBPARAM_PRIVACY_INDICATOR); 941 encodePrivacyIndicator(bData, outStream); 942 } 943 if (bData.languageIndicatorSet) { 944 outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR); 945 encodeLanguageIndicator(bData, outStream); 946 } 947 if (bData.displayModeSet) { 948 outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE); 949 encodeDisplayMode(bData, outStream); 950 } 951 if (bData.priorityIndicatorSet) { 952 outStream.write(8, SUBPARAM_PRIORITY_INDICATOR); 953 encodePriorityIndicator(bData, outStream); 954 } 955 if (bData.alertIndicatorSet) { 956 outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY); 957 encodeMsgDeliveryAlert(bData, outStream); 958 } 959 if (bData.messageStatusSet) { 960 outStream.write(8, SUBPARAM_MESSAGE_STATUS); 961 encodeMsgStatus(bData, outStream); 962 } 963 if (bData.serviceCategoryProgramResults != null) { 964 outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS); 965 encodeScpResults(bData, outStream); 966 } 967 return outStream.toByteArray(); 968 } catch (BitwiseOutputStream.AccessException ex) { 969 Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); 970 } catch (CodingException ex) { 971 Rlog.e(LOG_TAG, "BearerData encode failed: " + ex); 972 } 973 return null; 974 } 975 976 private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream) 977 throws BitwiseInputStream.AccessException { 978 final int EXPECTED_PARAM_SIZE = 3 * 8; 979 boolean decodeSuccess = false; 980 int paramBits = inStream.read(8) * 8; 981 if (paramBits >= EXPECTED_PARAM_SIZE) { 982 paramBits -= EXPECTED_PARAM_SIZE; 983 decodeSuccess = true; 984 bData.messageType = inStream.read(4); 985 bData.messageId = inStream.read(8) << 8; 986 bData.messageId |= inStream.read(8); 987 bData.hasUserDataHeader = (inStream.read(1) == 1); 988 inStream.skip(3); 989 } 990 if ((! decodeSuccess) || (paramBits > 0)) { 991 Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " + 992 (decodeSuccess ? "succeeded" : "failed") + 993 " (extra bits = " + paramBits + ")"); 994 } 995 inStream.skip(paramBits); 996 return decodeSuccess; 997 } 998 999 private static boolean decodeReserved( 1000 BearerData bData, BitwiseInputStream inStream, int subparamId) 1001 throws BitwiseInputStream.AccessException, CodingException 1002 { 1003 boolean decodeSuccess = false; 1004 int subparamLen = inStream.read(8); // SUBPARAM_LEN 1005 int paramBits = subparamLen * 8; 1006 if (paramBits <= inStream.available()) { 1007 decodeSuccess = true; 1008 inStream.skip(paramBits); 1009 } 1010 Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode " 1011 + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")"); 1012 if (!decodeSuccess) { 1013 throw new CodingException("RESERVED bearer data subparameter " + subparamId 1014 + " had invalid SUBPARAM_LEN " + subparamLen); 1015 } 1016 1017 return decodeSuccess; 1018 } 1019 1020 private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream) 1021 throws BitwiseInputStream.AccessException 1022 { 1023 int paramBits = inStream.read(8) * 8; 1024 bData.userData = new UserData(); 1025 bData.userData.msgEncoding = inStream.read(5); 1026 bData.userData.msgEncodingSet = true; 1027 bData.userData.msgType = 0; 1028 int consumedBits = 5; 1029 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 1030 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 1031 bData.userData.msgType = inStream.read(8); 1032 consumedBits += 8; 1033 } 1034 bData.userData.numFields = inStream.read(8); 1035 consumedBits += 8; 1036 int dataBits = paramBits - consumedBits; 1037 bData.userData.payload = inStream.readByteArray(dataBits); 1038 return true; 1039 } 1040 1041 private static String decodeUtf8(byte[] data, int offset, int numFields) 1042 throws CodingException 1043 { 1044 return decodeCharset(data, offset, numFields, 1, "UTF-8"); 1045 } 1046 1047 private static String decodeUtf16(byte[] data, int offset, int numFields) 1048 throws CodingException 1049 { 1050 // Subtract header and possible padding byte (at end) from num fields. 1051 int padding = offset % 2; 1052 numFields -= (offset + padding) / 2; 1053 return decodeCharset(data, offset, numFields, 2, "utf-16be"); 1054 } 1055 1056 private static String decodeCharset(byte[] data, int offset, int numFields, int width, 1057 String charset) throws CodingException 1058 { 1059 if (numFields < 0 || (numFields * width + offset) > data.length) { 1060 // Try to decode the max number of characters in payload 1061 int padding = offset % width; 1062 int maxNumFields = (data.length - offset - padding) / width; 1063 if (maxNumFields < 0) { 1064 throw new CodingException(charset + " decode failed: offset out of range"); 1065 } 1066 Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = " 1067 + numFields + " data.length = " + data.length + " maxNumFields = " 1068 + maxNumFields); 1069 numFields = maxNumFields; 1070 } 1071 try { 1072 return new String(data, offset, numFields * width, charset); 1073 } catch (java.io.UnsupportedEncodingException ex) { 1074 throw new CodingException(charset + " decode failed: " + ex); 1075 } 1076 } 1077 1078 private static String decode7bitAscii(byte[] data, int offset, int numFields) 1079 throws CodingException 1080 { 1081 try { 1082 offset *= 8; 1083 StringBuffer strBuf = new StringBuffer(numFields); 1084 BitwiseInputStream inStream = new BitwiseInputStream(data); 1085 int wantedBits = (offset * 8) + (numFields * 7); 1086 if (inStream.available() < wantedBits) { 1087 throw new CodingException("insufficient data (wanted " + wantedBits + 1088 " bits, but only have " + inStream.available() + ")"); 1089 } 1090 inStream.skip(offset); 1091 for (int i = 0; i < numFields; i++) { 1092 int charCode = inStream.read(7); 1093 if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) && 1094 (charCode <= UserData.ASCII_MAP_MAX_INDEX)) { 1095 strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]); 1096 } else if (charCode == UserData.ASCII_NL_INDEX) { 1097 strBuf.append('\n'); 1098 } else if (charCode == UserData.ASCII_CR_INDEX) { 1099 strBuf.append('\r'); 1100 } else { 1101 /* For other charCodes, they are unprintable, and so simply use SPACE. */ 1102 strBuf.append(' '); 1103 } 1104 } 1105 return strBuf.toString(); 1106 } catch (BitwiseInputStream.AccessException ex) { 1107 throw new CodingException("7bit ASCII decode failed: " + ex); 1108 } 1109 } 1110 1111 private static String decode7bitGsm(byte[] data, int offset, int numFields) 1112 throws CodingException 1113 { 1114 // Start reading from the next 7-bit aligned boundary after offset. 1115 int offsetBits = offset * 8; 1116 int offsetSeptets = (offsetBits + 6) / 7; 1117 numFields -= offsetSeptets; 1118 int paddingBits = (offsetSeptets * 7) - offsetBits; 1119 String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits, 1120 0, 0); 1121 if (result == null) { 1122 throw new CodingException("7bit GSM decoding failed"); 1123 } 1124 return result; 1125 } 1126 1127 private static String decodeLatin(byte[] data, int offset, int numFields) 1128 throws CodingException 1129 { 1130 return decodeCharset(data, offset, numFields, 1, "ISO-8859-1"); 1131 } 1132 1133 private static String decodeShiftJis(byte[] data, int offset, int numFields) 1134 throws CodingException 1135 { 1136 return decodeCharset(data, offset, numFields, 1, "Shift_JIS"); 1137 } 1138 1139 private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader) 1140 throws CodingException 1141 { 1142 int offset = 0; 1143 if (hasUserDataHeader) { 1144 int udhLen = userData.payload[0] & 0x00FF; 1145 offset += udhLen + 1; 1146 byte[] headerData = new byte[udhLen]; 1147 System.arraycopy(userData.payload, 1, headerData, 0, udhLen); 1148 userData.userDataHeader = SmsHeader.fromByteArray(headerData); 1149 } 1150 switch (userData.msgEncoding) { 1151 case UserData.ENCODING_OCTET: 1152 /* 1153 * Octet decoding depends on the carrier service. 1154 */ 1155 boolean decodingtypeUTF8 = Resources.getSystem() 1156 .getBoolean(com.android.internal.R.bool.config_sms_utf8_support); 1157 1158 // Strip off any padding bytes, meaning any differences between the length of the 1159 // array and the target length specified by numFields. This is to avoid any 1160 // confusion by code elsewhere that only considers the payload array length. 1161 byte[] payload = new byte[userData.numFields]; 1162 int copyLen = userData.numFields < userData.payload.length 1163 ? userData.numFields : userData.payload.length; 1164 1165 System.arraycopy(userData.payload, 0, payload, 0, copyLen); 1166 userData.payload = payload; 1167 1168 if (!decodingtypeUTF8) { 1169 // There are many devices in the market that send 8bit text sms (latin encoded) as 1170 // octet encoded. 1171 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); 1172 } else { 1173 userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields); 1174 } 1175 break; 1176 1177 case UserData.ENCODING_IA5: 1178 case UserData.ENCODING_7BIT_ASCII: 1179 userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields); 1180 break; 1181 case UserData.ENCODING_UNICODE_16: 1182 userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields); 1183 break; 1184 case UserData.ENCODING_GSM_7BIT_ALPHABET: 1185 userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields); 1186 break; 1187 case UserData.ENCODING_LATIN: 1188 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); 1189 break; 1190 case UserData.ENCODING_SHIFT_JIS: 1191 userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields); 1192 break; 1193 default: 1194 throw new CodingException("unsupported user data encoding (" 1195 + userData.msgEncoding + ")"); 1196 } 1197 } 1198 1199 /** 1200 * IS-91 Voice Mail message decoding 1201 * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) 1202 * (For character encodings, see TIA/EIA/IS-91, Annex B) 1203 * 1204 * Protocol Summary: The user data payload may contain 3-14 1205 * characters. The first two characters are parsed as a number 1206 * and indicate the number of voicemails. The third character is 1207 * either a SPACE or '!' to indicate normal or urgent priority, 1208 * respectively. Any following characters are treated as normal 1209 * text user data payload. 1210 * 1211 * Note that the characters encoding is 6-bit packed. 1212 */ 1213 private static void decodeIs91VoicemailStatus(BearerData bData) 1214 throws BitwiseInputStream.AccessException, CodingException 1215 { 1216 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1217 int dataLen = inStream.available() / 6; // 6-bit packed character encoding. 1218 int numFields = bData.userData.numFields; 1219 if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { 1220 throw new CodingException("IS-91 voicemail status decoding failed"); 1221 } 1222 try { 1223 StringBuffer strbuf = new StringBuffer(dataLen); 1224 while (inStream.available() >= 6) { 1225 strbuf.append(UserData.ASCII_MAP[inStream.read(6)]); 1226 } 1227 String data = strbuf.toString(); 1228 bData.numberOfMessages = Integer.parseInt(data.substring(0, 2)); 1229 char prioCode = data.charAt(2); 1230 if (prioCode == ' ') { 1231 bData.priority = PRIORITY_NORMAL; 1232 } else if (prioCode == '!') { 1233 bData.priority = PRIORITY_URGENT; 1234 } else { 1235 throw new CodingException("IS-91 voicemail status decoding failed: " + 1236 "illegal priority setting (" + prioCode + ")"); 1237 } 1238 bData.priorityIndicatorSet = true; 1239 bData.userData.payloadStr = data.substring(3, numFields - 3); 1240 } catch (java.lang.NumberFormatException ex) { 1241 throw new CodingException("IS-91 voicemail status decoding failed: " + ex); 1242 } catch (java.lang.IndexOutOfBoundsException ex) { 1243 throw new CodingException("IS-91 voicemail status decoding failed: " + ex); 1244 } 1245 } 1246 1247 /** 1248 * IS-91 Short Message decoding 1249 * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) 1250 * (For character encodings, see TIA/EIA/IS-91, Annex B) 1251 * 1252 * Protocol Summary: The user data payload may contain 1-14 1253 * characters, which are treated as normal text user data payload. 1254 * Note that the characters encoding is 6-bit packed. 1255 */ 1256 private static void decodeIs91ShortMessage(BearerData bData) 1257 throws BitwiseInputStream.AccessException, CodingException 1258 { 1259 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1260 int dataLen = inStream.available() / 6; // 6-bit packed character encoding. 1261 int numFields = bData.userData.numFields; 1262 // dataLen may be > 14 characters due to octet padding 1263 if ((numFields > 14) || (dataLen < numFields)) { 1264 throw new CodingException("IS-91 short message decoding failed"); 1265 } 1266 StringBuffer strbuf = new StringBuffer(dataLen); 1267 for (int i = 0; i < numFields; i++) { 1268 strbuf.append(UserData.ASCII_MAP[inStream.read(6)]); 1269 } 1270 bData.userData.payloadStr = strbuf.toString(); 1271 } 1272 1273 /** 1274 * IS-91 CLI message (callback number) decoding 1275 * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1) 1276 * 1277 * Protocol Summary: The data payload may contain 1-32 digits, 1278 * encoded using standard 4-bit DTMF, which are treated as a 1279 * callback number. 1280 */ 1281 private static void decodeIs91Cli(BearerData bData) throws CodingException { 1282 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1283 int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding. 1284 int numFields = bData.userData.numFields; 1285 if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) { 1286 throw new CodingException("IS-91 voicemail status decoding failed"); 1287 } 1288 CdmaSmsAddress addr = new CdmaSmsAddress(); 1289 addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF; 1290 addr.origBytes = bData.userData.payload; 1291 addr.numberOfDigits = (byte)numFields; 1292 decodeSmsAddress(addr); 1293 bData.callbackNumber = addr; 1294 } 1295 1296 private static void decodeIs91(BearerData bData) 1297 throws BitwiseInputStream.AccessException, CodingException 1298 { 1299 switch (bData.userData.msgType) { 1300 case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS: 1301 decodeIs91VoicemailStatus(bData); 1302 break; 1303 case UserData.IS91_MSG_TYPE_CLI: 1304 decodeIs91Cli(bData); 1305 break; 1306 case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL: 1307 case UserData.IS91_MSG_TYPE_SHORT_MESSAGE: 1308 decodeIs91ShortMessage(bData); 1309 break; 1310 default: 1311 throw new CodingException("unsupported IS-91 message type (" + 1312 bData.userData.msgType + ")"); 1313 } 1314 } 1315 1316 private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream) 1317 throws BitwiseInputStream.AccessException { 1318 final int EXPECTED_PARAM_SIZE = 1 * 8; 1319 boolean decodeSuccess = false; 1320 int paramBits = inStream.read(8) * 8; 1321 if (paramBits >= EXPECTED_PARAM_SIZE) { 1322 paramBits -= EXPECTED_PARAM_SIZE; 1323 decodeSuccess = true; 1324 bData.userAckReq = (inStream.read(1) == 1); 1325 bData.deliveryAckReq = (inStream.read(1) == 1); 1326 bData.readAckReq = (inStream.read(1) == 1); 1327 bData.reportReq = (inStream.read(1) == 1); 1328 inStream.skip(4); 1329 } 1330 if ((! decodeSuccess) || (paramBits > 0)) { 1331 Rlog.d(LOG_TAG, "REPLY_OPTION decode " + 1332 (decodeSuccess ? "succeeded" : "failed") + 1333 " (extra bits = " + paramBits + ")"); 1334 } 1335 inStream.skip(paramBits); 1336 return decodeSuccess; 1337 } 1338 1339 private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream) 1340 throws BitwiseInputStream.AccessException { 1341 final int EXPECTED_PARAM_SIZE = 1 * 8; 1342 boolean decodeSuccess = false; 1343 int paramBits = inStream.read(8) * 8; 1344 if (paramBits >= EXPECTED_PARAM_SIZE) { 1345 paramBits -= EXPECTED_PARAM_SIZE; 1346 decodeSuccess = true; 1347 bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8)); 1348 } 1349 if ((! decodeSuccess) || (paramBits > 0)) { 1350 Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " + 1351 (decodeSuccess ? "succeeded" : "failed") + 1352 " (extra bits = " + paramBits + ")"); 1353 } 1354 inStream.skip(paramBits); 1355 return decodeSuccess; 1356 } 1357 1358 private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream) 1359 throws BitwiseInputStream.AccessException { 1360 final int EXPECTED_PARAM_SIZE = 2 * 8; 1361 boolean decodeSuccess = false; 1362 int paramBits = inStream.read(8) * 8; 1363 if (paramBits >= EXPECTED_PARAM_SIZE) { 1364 paramBits -= EXPECTED_PARAM_SIZE; 1365 decodeSuccess = true; 1366 bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8); 1367 } 1368 if ((! decodeSuccess) || (paramBits > 0)) { 1369 Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " + 1370 (decodeSuccess ? "succeeded" : "failed") + 1371 " (extra bits = " + paramBits + ")"); 1372 } 1373 inStream.skip(paramBits); 1374 return decodeSuccess; 1375 } 1376 1377 private static String decodeDtmfSmsAddress(byte[] rawData, int numFields) 1378 throws CodingException 1379 { 1380 /* DTMF 4-bit digit encoding, defined in at 1381 * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */ 1382 StringBuffer strBuf = new StringBuffer(numFields); 1383 for (int i = 0; i < numFields; i++) { 1384 int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4))); 1385 if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10)); 1386 else if (val == 10) strBuf.append('0'); 1387 else if (val == 11) strBuf.append('*'); 1388 else if (val == 12) strBuf.append('#'); 1389 else throw new CodingException("invalid SMS address DTMF code (" + val + ")"); 1390 } 1391 return strBuf.toString(); 1392 } 1393 1394 private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException { 1395 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 1396 try { 1397 /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually 1398 * just 7-bit ASCII encoding, with the MSB being zero. */ 1399 addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII"); 1400 } catch (java.io.UnsupportedEncodingException ex) { 1401 throw new CodingException("invalid SMS address ASCII code"); 1402 } 1403 } else { 1404 addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits); 1405 } 1406 } 1407 1408 private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream) 1409 throws BitwiseInputStream.AccessException, CodingException 1410 { 1411 final int EXPECTED_PARAM_SIZE = 1 * 8; //at least 1412 int paramBits = inStream.read(8) * 8; 1413 if (paramBits < EXPECTED_PARAM_SIZE) { 1414 inStream.skip(paramBits); 1415 return false; 1416 } 1417 CdmaSmsAddress addr = new CdmaSmsAddress(); 1418 addr.digitMode = inStream.read(1); 1419 byte fieldBits = 4; 1420 byte consumedBits = 1; 1421 if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) { 1422 addr.ton = inStream.read(3); 1423 addr.numberPlan = inStream.read(4); 1424 fieldBits = 8; 1425 consumedBits += 7; 1426 } 1427 addr.numberOfDigits = inStream.read(8); 1428 consumedBits += 8; 1429 int remainingBits = paramBits - consumedBits; 1430 int dataBits = addr.numberOfDigits * fieldBits; 1431 int paddingBits = remainingBits - dataBits; 1432 if (remainingBits < dataBits) { 1433 throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" + 1434 "remainingBits + " + remainingBits + ", dataBits + " + 1435 dataBits + ", paddingBits + " + paddingBits + ")"); 1436 } 1437 addr.origBytes = inStream.readByteArray(dataBits); 1438 inStream.skip(paddingBits); 1439 decodeSmsAddress(addr); 1440 bData.callbackNumber = addr; 1441 return true; 1442 } 1443 1444 private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream) 1445 throws BitwiseInputStream.AccessException { 1446 final int EXPECTED_PARAM_SIZE = 1 * 8; 1447 boolean decodeSuccess = false; 1448 int paramBits = inStream.read(8) * 8; 1449 if (paramBits >= EXPECTED_PARAM_SIZE) { 1450 paramBits -= EXPECTED_PARAM_SIZE; 1451 decodeSuccess = true; 1452 bData.errorClass = inStream.read(2); 1453 bData.messageStatus = inStream.read(6); 1454 } 1455 if ((! decodeSuccess) || (paramBits > 0)) { 1456 Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " + 1457 (decodeSuccess ? "succeeded" : "failed") + 1458 " (extra bits = " + paramBits + ")"); 1459 } 1460 inStream.skip(paramBits); 1461 bData.messageStatusSet = decodeSuccess; 1462 return decodeSuccess; 1463 } 1464 1465 private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream) 1466 throws BitwiseInputStream.AccessException { 1467 final int EXPECTED_PARAM_SIZE = 6 * 8; 1468 boolean decodeSuccess = false; 1469 int paramBits = inStream.read(8) * 8; 1470 if (paramBits >= EXPECTED_PARAM_SIZE) { 1471 paramBits -= EXPECTED_PARAM_SIZE; 1472 decodeSuccess = true; 1473 bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); 1474 } 1475 if ((! decodeSuccess) || (paramBits > 0)) { 1476 Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " + 1477 (decodeSuccess ? "succeeded" : "failed") + 1478 " (extra bits = " + paramBits + ")"); 1479 } 1480 inStream.skip(paramBits); 1481 return decodeSuccess; 1482 } 1483 1484 private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream) 1485 throws BitwiseInputStream.AccessException { 1486 final int EXPECTED_PARAM_SIZE = 6 * 8; 1487 boolean decodeSuccess = false; 1488 int paramBits = inStream.read(8) * 8; 1489 if (paramBits >= EXPECTED_PARAM_SIZE) { 1490 paramBits -= EXPECTED_PARAM_SIZE; 1491 decodeSuccess = true; 1492 bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); 1493 } 1494 if ((! decodeSuccess) || (paramBits > 0)) { 1495 Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " + 1496 (decodeSuccess ? "succeeded" : "failed") + 1497 " (extra bits = " + paramBits + ")"); 1498 } 1499 inStream.skip(paramBits); 1500 return decodeSuccess; 1501 } 1502 1503 private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream) 1504 throws BitwiseInputStream.AccessException { 1505 final int EXPECTED_PARAM_SIZE = 6 * 8; 1506 boolean decodeSuccess = false; 1507 int paramBits = inStream.read(8) * 8; 1508 if (paramBits >= EXPECTED_PARAM_SIZE) { 1509 paramBits -= EXPECTED_PARAM_SIZE; 1510 decodeSuccess = true; 1511 bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray( 1512 inStream.readByteArray(6 * 8)); 1513 } 1514 if ((! decodeSuccess) || (paramBits > 0)) { 1515 Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " + 1516 (decodeSuccess ? "succeeded" : "failed") + 1517 " (extra bits = " + paramBits + ")"); 1518 } 1519 inStream.skip(paramBits); 1520 return decodeSuccess; 1521 } 1522 1523 private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream) 1524 throws BitwiseInputStream.AccessException { 1525 final int EXPECTED_PARAM_SIZE = 1 * 8; 1526 boolean decodeSuccess = false; 1527 int paramBits = inStream.read(8) * 8; 1528 if (paramBits >= EXPECTED_PARAM_SIZE) { 1529 paramBits -= EXPECTED_PARAM_SIZE; 1530 decodeSuccess = true; 1531 bData.deferredDeliveryTimeRelative = inStream.read(8); 1532 } 1533 if ((! decodeSuccess) || (paramBits > 0)) { 1534 Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " + 1535 (decodeSuccess ? "succeeded" : "failed") + 1536 " (extra bits = " + paramBits + ")"); 1537 } 1538 inStream.skip(paramBits); 1539 bData.deferredDeliveryTimeRelativeSet = decodeSuccess; 1540 return decodeSuccess; 1541 } 1542 1543 private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream) 1544 throws BitwiseInputStream.AccessException { 1545 final int EXPECTED_PARAM_SIZE = 1 * 8; 1546 boolean decodeSuccess = false; 1547 int paramBits = inStream.read(8) * 8; 1548 if (paramBits >= EXPECTED_PARAM_SIZE) { 1549 paramBits -= EXPECTED_PARAM_SIZE; 1550 decodeSuccess = true; 1551 bData.validityPeriodRelative = inStream.read(8); 1552 } 1553 if ((! decodeSuccess) || (paramBits > 0)) { 1554 Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " + 1555 (decodeSuccess ? "succeeded" : "failed") + 1556 " (extra bits = " + paramBits + ")"); 1557 } 1558 inStream.skip(paramBits); 1559 bData.validityPeriodRelativeSet = decodeSuccess; 1560 return decodeSuccess; 1561 } 1562 1563 private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream) 1564 throws BitwiseInputStream.AccessException { 1565 final int EXPECTED_PARAM_SIZE = 1 * 8; 1566 boolean decodeSuccess = false; 1567 int paramBits = inStream.read(8) * 8; 1568 if (paramBits >= EXPECTED_PARAM_SIZE) { 1569 paramBits -= EXPECTED_PARAM_SIZE; 1570 decodeSuccess = true; 1571 bData.privacy = inStream.read(2); 1572 inStream.skip(6); 1573 } 1574 if ((! decodeSuccess) || (paramBits > 0)) { 1575 Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " + 1576 (decodeSuccess ? "succeeded" : "failed") + 1577 " (extra bits = " + paramBits + ")"); 1578 } 1579 inStream.skip(paramBits); 1580 bData.privacyIndicatorSet = decodeSuccess; 1581 return decodeSuccess; 1582 } 1583 1584 private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream) 1585 throws BitwiseInputStream.AccessException { 1586 final int EXPECTED_PARAM_SIZE = 1 * 8; 1587 boolean decodeSuccess = false; 1588 int paramBits = inStream.read(8) * 8; 1589 if (paramBits >= EXPECTED_PARAM_SIZE) { 1590 paramBits -= EXPECTED_PARAM_SIZE; 1591 decodeSuccess = true; 1592 bData.language = inStream.read(8); 1593 } 1594 if ((! decodeSuccess) || (paramBits > 0)) { 1595 Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " + 1596 (decodeSuccess ? "succeeded" : "failed") + 1597 " (extra bits = " + paramBits + ")"); 1598 } 1599 inStream.skip(paramBits); 1600 bData.languageIndicatorSet = decodeSuccess; 1601 return decodeSuccess; 1602 } 1603 1604 private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream) 1605 throws BitwiseInputStream.AccessException { 1606 final int EXPECTED_PARAM_SIZE = 1 * 8; 1607 boolean decodeSuccess = false; 1608 int paramBits = inStream.read(8) * 8; 1609 if (paramBits >= EXPECTED_PARAM_SIZE) { 1610 paramBits -= EXPECTED_PARAM_SIZE; 1611 decodeSuccess = true; 1612 bData.displayMode = inStream.read(2); 1613 inStream.skip(6); 1614 } 1615 if ((! decodeSuccess) || (paramBits > 0)) { 1616 Rlog.d(LOG_TAG, "DISPLAY_MODE decode " + 1617 (decodeSuccess ? "succeeded" : "failed") + 1618 " (extra bits = " + paramBits + ")"); 1619 } 1620 inStream.skip(paramBits); 1621 bData.displayModeSet = decodeSuccess; 1622 return decodeSuccess; 1623 } 1624 1625 private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream) 1626 throws BitwiseInputStream.AccessException { 1627 final int EXPECTED_PARAM_SIZE = 1 * 8; 1628 boolean decodeSuccess = false; 1629 int paramBits = inStream.read(8) * 8; 1630 if (paramBits >= EXPECTED_PARAM_SIZE) { 1631 paramBits -= EXPECTED_PARAM_SIZE; 1632 decodeSuccess = true; 1633 bData.priority = inStream.read(2); 1634 inStream.skip(6); 1635 } 1636 if ((! decodeSuccess) || (paramBits > 0)) { 1637 Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " + 1638 (decodeSuccess ? "succeeded" : "failed") + 1639 " (extra bits = " + paramBits + ")"); 1640 } 1641 inStream.skip(paramBits); 1642 bData.priorityIndicatorSet = decodeSuccess; 1643 return decodeSuccess; 1644 } 1645 1646 private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream) 1647 throws BitwiseInputStream.AccessException { 1648 final int EXPECTED_PARAM_SIZE = 1 * 8; 1649 boolean decodeSuccess = false; 1650 int paramBits = inStream.read(8) * 8; 1651 if (paramBits >= EXPECTED_PARAM_SIZE) { 1652 paramBits -= EXPECTED_PARAM_SIZE; 1653 decodeSuccess = true; 1654 bData.alert = inStream.read(2); 1655 inStream.skip(6); 1656 } 1657 if ((! decodeSuccess) || (paramBits > 0)) { 1658 Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " + 1659 (decodeSuccess ? "succeeded" : "failed") + 1660 " (extra bits = " + paramBits + ")"); 1661 } 1662 inStream.skip(paramBits); 1663 bData.alertIndicatorSet = decodeSuccess; 1664 return decodeSuccess; 1665 } 1666 1667 private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream) 1668 throws BitwiseInputStream.AccessException { 1669 final int EXPECTED_PARAM_SIZE = 1 * 8; 1670 boolean decodeSuccess = false; 1671 int paramBits = inStream.read(8) * 8; 1672 if (paramBits >= EXPECTED_PARAM_SIZE) { 1673 paramBits -= EXPECTED_PARAM_SIZE; 1674 decodeSuccess = true; 1675 bData.userResponseCode = inStream.read(8); 1676 } 1677 if ((! decodeSuccess) || (paramBits > 0)) { 1678 Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " + 1679 (decodeSuccess ? "succeeded" : "failed") + 1680 " (extra bits = " + paramBits + ")"); 1681 } 1682 inStream.skip(paramBits); 1683 bData.userResponseCodeSet = decodeSuccess; 1684 return decodeSuccess; 1685 } 1686 1687 private static boolean decodeServiceCategoryProgramData(BearerData bData, 1688 BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException 1689 { 1690 if (inStream.available() < 13) { 1691 throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " 1692 + inStream.available() + " bits available"); 1693 } 1694 1695 int paramBits = inStream.read(8) * 8; 1696 int msgEncoding = inStream.read(5); 1697 paramBits -= 5; 1698 1699 if (inStream.available() < paramBits) { 1700 throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " 1701 + inStream.available() + " bits available (" + paramBits + " bits expected)"); 1702 } 1703 1704 ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>(); 1705 1706 final int CATEGORY_FIELD_MIN_SIZE = 6 * 8; 1707 boolean decodeSuccess = false; 1708 while (paramBits >= CATEGORY_FIELD_MIN_SIZE) { 1709 int operation = inStream.read(4); 1710 int category = (inStream.read(8) << 8) | inStream.read(8); 1711 int language = inStream.read(8); 1712 int maxMessages = inStream.read(8); 1713 int alertOption = inStream.read(4); 1714 int numFields = inStream.read(8); 1715 paramBits -= CATEGORY_FIELD_MIN_SIZE; 1716 1717 int textBits = getBitsForNumFields(msgEncoding, numFields); 1718 if (paramBits < textBits) { 1719 throw new CodingException("category name is " + textBits + " bits in length," 1720 + " but there are only " + paramBits + " bits available"); 1721 } 1722 1723 UserData userData = new UserData(); 1724 userData.msgEncoding = msgEncoding; 1725 userData.msgEncodingSet = true; 1726 userData.numFields = numFields; 1727 userData.payload = inStream.readByteArray(textBits); 1728 paramBits -= textBits; 1729 1730 decodeUserDataPayload(userData, false); 1731 String categoryName = userData.payloadStr; 1732 CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category, 1733 language, maxMessages, alertOption, categoryName); 1734 programDataList.add(programData); 1735 1736 decodeSuccess = true; 1737 } 1738 1739 if ((! decodeSuccess) || (paramBits > 0)) { 1740 Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + 1741 (decodeSuccess ? "succeeded" : "failed") + 1742 " (extra bits = " + paramBits + ')'); 1743 } 1744 1745 inStream.skip(paramBits); 1746 bData.serviceCategoryProgramData = programDataList; 1747 return decodeSuccess; 1748 } 1749 1750 private static int serviceCategoryToCmasMessageClass(int serviceCategory) { 1751 switch (serviceCategory) { 1752 case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT: 1753 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; 1754 1755 case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: 1756 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; 1757 1758 case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: 1759 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; 1760 1761 case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: 1762 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; 1763 1764 case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: 1765 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; 1766 1767 default: 1768 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; 1769 } 1770 } 1771 1772 /** 1773 * Calculates the number of bits to read for the specified number of encoded characters. 1774 * @param msgEncoding the message encoding to use 1775 * @param numFields the number of characters to read. For Shift-JIS and Korean encodings, 1776 * this is the number of bytes to read. 1777 * @return the number of bits to read from the stream 1778 * @throws CodingException if the specified encoding is not supported 1779 */ 1780 private static int getBitsForNumFields(int msgEncoding, int numFields) 1781 throws CodingException { 1782 switch (msgEncoding) { 1783 case UserData.ENCODING_OCTET: 1784 case UserData.ENCODING_SHIFT_JIS: 1785 case UserData.ENCODING_KOREAN: 1786 case UserData.ENCODING_LATIN: 1787 case UserData.ENCODING_LATIN_HEBREW: 1788 return numFields * 8; 1789 1790 case UserData.ENCODING_IA5: 1791 case UserData.ENCODING_7BIT_ASCII: 1792 case UserData.ENCODING_GSM_7BIT_ALPHABET: 1793 return numFields * 7; 1794 1795 case UserData.ENCODING_UNICODE_16: 1796 return numFields * 16; 1797 1798 default: 1799 throw new CodingException("unsupported message encoding (" + msgEncoding + ')'); 1800 } 1801 } 1802 1803 /** 1804 * CMAS message decoding. 1805 * (See TIA-1149-0-1, CMAS over CDMA) 1806 * 1807 * @param serviceCategory is the service category from the SMS envelope 1808 */ 1809 private static void decodeCmasUserData(BearerData bData, int serviceCategory) 1810 throws BitwiseInputStream.AccessException, CodingException { 1811 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 1812 if (inStream.available() < 8) { 1813 throw new CodingException("emergency CB with no CMAE_protocol_version"); 1814 } 1815 int protocolVersion = inStream.read(8); 1816 if (protocolVersion != 0) { 1817 throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion); 1818 } 1819 1820 int messageClass = serviceCategoryToCmasMessageClass(serviceCategory); 1821 int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; 1822 int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; 1823 int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; 1824 int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; 1825 int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; 1826 1827 while (inStream.available() >= 16) { 1828 int recordType = inStream.read(8); 1829 int recordLen = inStream.read(8); 1830 switch (recordType) { 1831 case 0: // Type 0 elements (Alert text) 1832 UserData alertUserData = new UserData(); 1833 alertUserData.msgEncoding = inStream.read(5); 1834 alertUserData.msgEncodingSet = true; 1835 alertUserData.msgType = 0; 1836 1837 int numFields; // number of chars to decode 1838 switch (alertUserData.msgEncoding) { 1839 case UserData.ENCODING_OCTET: 1840 case UserData.ENCODING_LATIN: 1841 numFields = recordLen - 1; // subtract 1 byte for encoding 1842 break; 1843 1844 case UserData.ENCODING_IA5: 1845 case UserData.ENCODING_7BIT_ASCII: 1846 case UserData.ENCODING_GSM_7BIT_ALPHABET: 1847 numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding 1848 break; 1849 1850 case UserData.ENCODING_UNICODE_16: 1851 numFields = (recordLen - 1) / 2; 1852 break; 1853 1854 default: 1855 numFields = 0; // unsupported encoding 1856 } 1857 1858 alertUserData.numFields = numFields; 1859 alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5); 1860 decodeUserDataPayload(alertUserData, false); 1861 bData.userData = alertUserData; 1862 break; 1863 1864 case 1: // Type 1 elements 1865 category = inStream.read(8); 1866 responseType = inStream.read(8); 1867 severity = inStream.read(4); 1868 urgency = inStream.read(4); 1869 certainty = inStream.read(4); 1870 inStream.skip(recordLen * 8 - 28); 1871 break; 1872 1873 default: 1874 Rlog.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType); 1875 inStream.skip(recordLen * 8); 1876 break; 1877 } 1878 } 1879 1880 bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity, 1881 urgency, certainty); 1882 } 1883 1884 /** 1885 * Create BearerData object from serialized representation. 1886 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 1887 * 1888 * @param smsData byte array of raw encoded SMS bearer data. 1889 * @return an instance of BearerData. 1890 */ 1891 public static BearerData decode(byte[] smsData) { 1892 return decode(smsData, 0); 1893 } 1894 1895 private static boolean isCmasAlertCategory(int category) { 1896 return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT 1897 && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE; 1898 } 1899 1900 /** 1901 * Create BearerData object from serialized representation. 1902 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 1903 * 1904 * @param smsData byte array of raw encoded SMS bearer data. 1905 * @param serviceCategory the envelope service category (for CMAS alert handling) 1906 * @return an instance of BearerData. 1907 */ 1908 public static BearerData decode(byte[] smsData, int serviceCategory) { 1909 try { 1910 BitwiseInputStream inStream = new BitwiseInputStream(smsData); 1911 BearerData bData = new BearerData(); 1912 int foundSubparamMask = 0; 1913 while (inStream.available() > 0) { 1914 int subparamId = inStream.read(8); 1915 int subparamIdBit = 1 << subparamId; 1916 // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8) 1917 // as 32th bit is the max bit in int. 1918 // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers: 1919 // last defined subparam ID is 23 (00010111 = 0x17 = 23). 1920 // Only do duplicate subparam ID check if subparam is within defined value as 1921 // reserved subparams are just skipped. 1922 if ((foundSubparamMask & subparamIdBit) != 0 && 1923 (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER && 1924 subparamId <= SUBPARAM_ID_LAST_DEFINED)) { 1925 throw new CodingException("illegal duplicate subparameter (" + 1926 subparamId + ")"); 1927 } 1928 boolean decodeSuccess; 1929 switch (subparamId) { 1930 case SUBPARAM_MESSAGE_IDENTIFIER: 1931 decodeSuccess = decodeMessageId(bData, inStream); 1932 break; 1933 case SUBPARAM_USER_DATA: 1934 decodeSuccess = decodeUserData(bData, inStream); 1935 break; 1936 case SUBPARAM_USER_RESPONSE_CODE: 1937 decodeSuccess = decodeUserResponseCode(bData, inStream); 1938 break; 1939 case SUBPARAM_REPLY_OPTION: 1940 decodeSuccess = decodeReplyOption(bData, inStream); 1941 break; 1942 case SUBPARAM_NUMBER_OF_MESSAGES: 1943 decodeSuccess = decodeMsgCount(bData, inStream); 1944 break; 1945 case SUBPARAM_CALLBACK_NUMBER: 1946 decodeSuccess = decodeCallbackNumber(bData, inStream); 1947 break; 1948 case SUBPARAM_MESSAGE_STATUS: 1949 decodeSuccess = decodeMsgStatus(bData, inStream); 1950 break; 1951 case SUBPARAM_MESSAGE_CENTER_TIME_STAMP: 1952 decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream); 1953 break; 1954 case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE: 1955 decodeSuccess = decodeValidityAbs(bData, inStream); 1956 break; 1957 case SUBPARAM_VALIDITY_PERIOD_RELATIVE: 1958 decodeSuccess = decodeValidityRel(bData, inStream); 1959 break; 1960 case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE: 1961 decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream); 1962 break; 1963 case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE: 1964 decodeSuccess = decodeDeferredDeliveryRel(bData, inStream); 1965 break; 1966 case SUBPARAM_PRIVACY_INDICATOR: 1967 decodeSuccess = decodePrivacyIndicator(bData, inStream); 1968 break; 1969 case SUBPARAM_LANGUAGE_INDICATOR: 1970 decodeSuccess = decodeLanguageIndicator(bData, inStream); 1971 break; 1972 case SUBPARAM_MESSAGE_DISPLAY_MODE: 1973 decodeSuccess = decodeDisplayMode(bData, inStream); 1974 break; 1975 case SUBPARAM_PRIORITY_INDICATOR: 1976 decodeSuccess = decodePriorityIndicator(bData, inStream); 1977 break; 1978 case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY: 1979 decodeSuccess = decodeMsgDeliveryAlert(bData, inStream); 1980 break; 1981 case SUBPARAM_MESSAGE_DEPOSIT_INDEX: 1982 decodeSuccess = decodeDepositIndex(bData, inStream); 1983 break; 1984 case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA: 1985 decodeSuccess = decodeServiceCategoryProgramData(bData, inStream); 1986 break; 1987 default: 1988 decodeSuccess = decodeReserved(bData, inStream, subparamId); 1989 } 1990 if (decodeSuccess && 1991 (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER && 1992 subparamId <= SUBPARAM_ID_LAST_DEFINED)) { 1993 foundSubparamMask |= subparamIdBit; 1994 } 1995 } 1996 if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) { 1997 throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); 1998 } 1999 if (bData.userData != null) { 2000 if (isCmasAlertCategory(serviceCategory)) { 2001 decodeCmasUserData(bData, serviceCategory); 2002 } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { 2003 if ((foundSubparamMask ^ 2004 (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^ 2005 (1 << SUBPARAM_USER_DATA)) 2006 != 0) { 2007 Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" + 2008 foundSubparamMask + ")"); 2009 } 2010 decodeIs91(bData); 2011 } else { 2012 decodeUserDataPayload(bData.userData, bData.hasUserDataHeader); 2013 } 2014 } 2015 return bData; 2016 } catch (BitwiseInputStream.AccessException ex) { 2017 Rlog.e(LOG_TAG, "BearerData decode failed: " + ex); 2018 } catch (CodingException ex) { 2019 Rlog.e(LOG_TAG, "BearerData decode failed: " + ex); 2020 } 2021 return null; 2022 } 2023 } 2024