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