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