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