1 /* 2 * Copyright (C) 2019 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.cellbroadcastservice; 18 19 import android.content.Context; 20 import android.telephony.SmsCbCmasInfo; 21 import android.telephony.cdma.CdmaSmsCbProgramData; 22 import android.util.Log; 23 24 /** 25 * An object to decode CDMA SMS bearer data. 26 */ 27 public final class BearerData { 28 private final static String LOG_TAG = "BearerData"; 29 30 /** 31 * Bearer Data Subparameter Identifiers 32 * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1) 33 * NOTE: Unneeded subparameter types are not included 34 */ 35 private static final byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00; 36 private static final byte SUBPARAM_USER_DATA = 0x01; 37 private static final byte SUBPARAM_PRIORITY_INDICATOR = 0x08; 38 private static final byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D; 39 40 // All other values after this are reserved. 41 private static final byte SUBPARAM_ID_LAST_DEFINED = 0x17; 42 43 /** 44 * Supported priority modes for CDMA SMS messages 45 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) 46 */ 47 public static final int PRIORITY_NORMAL = 0x0; 48 public static final int PRIORITY_INTERACTIVE = 0x1; 49 public static final int PRIORITY_URGENT = 0x2; 50 public static final int PRIORITY_EMERGENCY = 0x3; 51 52 /** 53 * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B, 54 * v2, 4.5.14) is ambiguous as to the meaning of this field, as it 55 * refers to C.R1001-D but that reference has been crossed out. 56 * It would seem reasonable to assume the values from C.R1001-F 57 * (table 9.2-1) are to be used instead. 58 */ 59 public static final int LANGUAGE_UNKNOWN = 0x00; 60 public static final int LANGUAGE_ENGLISH = 0x01; 61 public static final int LANGUAGE_FRENCH = 0x02; 62 public static final int LANGUAGE_SPANISH = 0x03; 63 public static final int LANGUAGE_JAPANESE = 0x04; 64 public static final int LANGUAGE_KOREAN = 0x05; 65 public static final int LANGUAGE_CHINESE = 0x06; 66 public static final int LANGUAGE_HEBREW = 0x07; 67 68 /** 69 * Supported message types for CDMA SMS messages 70 * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1) 71 * Used for CdmaSmsCbTest. 72 */ 73 public static final int MESSAGE_TYPE_DELIVER = 0x01; 74 75 /** 76 * 16-bit value indicating the message ID, which increments modulo 65536. 77 * (Special rules apply for WAP-messages.) 78 * (See 3GPP2 C.S0015-B, v2, 4.5.1) 79 */ 80 public int messageId; 81 82 /** 83 * Priority modes for CDMA SMS message (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) 84 */ 85 public int priority = PRIORITY_NORMAL; 86 87 /** 88 * Language indicator for CDMA SMS message. 89 */ 90 public int language = LANGUAGE_UNKNOWN; 91 92 /** 93 * 1-bit value that indicates whether a User Data Header (UDH) is present. 94 * (See 3GPP2 C.S0015-B, v2, 4.5.1) 95 * 96 * NOTE: during encoding, this value will be set based on the 97 * presence of a UDH in the structured data, any existing setting 98 * will be overwritten. 99 */ 100 public boolean hasUserDataHeader; 101 102 /** 103 * Information on the user data 104 * (e.g. padding bits, user data, user data header, etc) 105 * (See 3GPP2 C.S.0015-B, v2, 4.5.2) 106 */ 107 public UserData userData; 108 109 /** 110 * CMAS warning notification information. 111 * 112 * @see #decodeCmasUserData(BearerData, int) 113 */ 114 public SmsCbCmasInfo cmasWarningInfo; 115 116 /** 117 * Construct an empty BearerData. 118 */ BearerData()119 private BearerData() { 120 } 121 122 private static class CodingException extends Exception { CodingException(String s)123 public CodingException(String s) { 124 super(s); 125 } 126 } 127 128 /** 129 * Returns the language indicator as a two-character ISO 639 string. 130 * 131 * @return a two character ISO 639 language code 132 */ getLanguage()133 public String getLanguage() { 134 return getLanguageCodeForValue(language); 135 } 136 137 /** 138 * Converts a CDMA language indicator value to an ISO 639 two character language code. 139 * 140 * @param languageValue the CDMA language value to convert 141 * @return the two character ISO 639 language code for the specified value, or null if unknown 142 */ getLanguageCodeForValue(int languageValue)143 private static String getLanguageCodeForValue(int languageValue) { 144 switch (languageValue) { 145 case LANGUAGE_ENGLISH: 146 return "en"; 147 148 case LANGUAGE_FRENCH: 149 return "fr"; 150 151 case LANGUAGE_SPANISH: 152 return "es"; 153 154 case LANGUAGE_JAPANESE: 155 return "ja"; 156 157 case LANGUAGE_KOREAN: 158 return "ko"; 159 160 case LANGUAGE_CHINESE: 161 return "zh"; 162 163 case LANGUAGE_HEBREW: 164 return "he"; 165 166 default: 167 return null; 168 } 169 } 170 171 @Override toString()172 public String toString() { 173 StringBuilder builder = new StringBuilder(); 174 builder.append("BearerData "); 175 builder.append(", messageId=" + messageId); 176 builder.append(", hasUserDataHeader=" + hasUserDataHeader); 177 builder.append(", userData=" + userData); 178 builder.append(" }"); 179 return builder.toString(); 180 } 181 decodeMessageId(BearerData bData, BitwiseInputStream inStream)182 private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream) 183 throws BitwiseInputStream.AccessException { 184 final int EXPECTED_PARAM_SIZE = 3 * 8; 185 boolean decodeSuccess = false; 186 int paramBits = inStream.read(8) * 8; 187 if (paramBits >= EXPECTED_PARAM_SIZE) { 188 paramBits -= EXPECTED_PARAM_SIZE; 189 decodeSuccess = true; 190 inStream.skip(4); // skip messageType 191 bData.messageId = inStream.read(8) << 8; 192 bData.messageId |= inStream.read(8); 193 bData.hasUserDataHeader = (inStream.read(1) == 1); 194 inStream.skip(3); 195 } 196 if ((!decodeSuccess) || (paramBits > 0)) { 197 Log.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " 198 + (decodeSuccess ? "succeeded" : "failed") 199 + " (extra bits = " + paramBits + ")"); 200 } 201 inStream.skip(paramBits); 202 return decodeSuccess; 203 } 204 decodeReserved(BitwiseInputStream inStream, int subparamId)205 private static boolean decodeReserved(BitwiseInputStream inStream, int subparamId) 206 throws BitwiseInputStream.AccessException, CodingException { 207 boolean decodeSuccess = false; 208 int subparamLen = inStream.read(8); // SUBPARAM_LEN 209 int paramBits = subparamLen * 8; 210 if (paramBits <= inStream.available()) { 211 decodeSuccess = true; 212 inStream.skip(paramBits); 213 } 214 Log.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode " 215 + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")"); 216 if (!decodeSuccess) { 217 throw new CodingException("RESERVED bearer data subparameter " + subparamId 218 + " had invalid SUBPARAM_LEN " + subparamLen); 219 } 220 221 return decodeSuccess; 222 } 223 decodeUserData(BearerData bData, BitwiseInputStream inStream)224 private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream) 225 throws BitwiseInputStream.AccessException { 226 int paramBits = inStream.read(8) * 8; 227 bData.userData = new UserData(); 228 bData.userData.msgEncoding = inStream.read(5); 229 bData.userData.msgEncodingSet = true; 230 bData.userData.msgType = 0; 231 int consumedBits = 5; 232 if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) || 233 (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) { 234 bData.userData.msgType = inStream.read(8); 235 consumedBits += 8; 236 } 237 bData.userData.numFields = inStream.read(8); 238 consumedBits += 8; 239 int dataBits = paramBits - consumedBits; 240 bData.userData.payload = inStream.readByteArray(dataBits); 241 return true; 242 } 243 decodeUtf8(byte[] data, int offset, int numFields)244 private static String decodeUtf8(byte[] data, int offset, int numFields) 245 throws CodingException { 246 return decodeCharset(data, offset, numFields, 1, "UTF-8"); 247 } 248 decodeUtf16(byte[] data, int offset, int numFields)249 private static String decodeUtf16(byte[] data, int offset, int numFields) 250 throws CodingException { 251 // Subtract header and possible padding byte (at end) from num fields. 252 int padding = offset % 2; 253 numFields -= (offset + padding) / 2; 254 return decodeCharset(data, offset, numFields, 2, "utf-16be"); 255 } 256 decodeCharset(byte[] data, int offset, int numFields, int width, String charset)257 private static String decodeCharset(byte[] data, int offset, int numFields, int width, 258 String charset) throws CodingException { 259 if (numFields < 0 || (numFields * width + offset) > data.length) { 260 // Try to decode the max number of characters in payload 261 int padding = offset % width; 262 int maxNumFields = (data.length - offset - padding) / width; 263 if (maxNumFields < 0) { 264 throw new CodingException(charset + " decode failed: offset out of range"); 265 } 266 Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = " 267 + numFields + " data.length = " + data.length + " maxNumFields = " 268 + maxNumFields); 269 numFields = maxNumFields; 270 } 271 try { 272 return new String(data, offset, numFields * width, charset); 273 } catch (java.io.UnsupportedEncodingException ex) { 274 throw new CodingException(charset + " decode failed: " + ex); 275 } 276 } 277 decode7bitAscii(byte[] data, int offset, int numFields)278 private static String decode7bitAscii(byte[] data, int offset, int numFields) 279 throws CodingException { 280 try { 281 int offsetBits = offset * 8; 282 int offsetSeptets = (offsetBits + 6) / 7; 283 numFields -= offsetSeptets; 284 285 StringBuffer strBuf = new StringBuffer(numFields); 286 BitwiseInputStream inStream = new BitwiseInputStream(data); 287 int wantedBits = (offsetSeptets * 7) + (numFields * 7); 288 if (inStream.available() < wantedBits) { 289 throw new CodingException("insufficient data (wanted " + wantedBits + 290 " bits, but only have " + inStream.available() + ")"); 291 } 292 inStream.skip(offsetSeptets * 7); 293 for (int i = 0; i < numFields; i++) { 294 int charCode = inStream.read(7); 295 if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) && 296 (charCode <= UserData.ASCII_MAP_MAX_INDEX)) { 297 strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]); 298 } else if (charCode == UserData.ASCII_NL_INDEX) { 299 strBuf.append('\n'); 300 } else if (charCode == UserData.ASCII_CR_INDEX) { 301 strBuf.append('\r'); 302 } else { 303 /* For other charCodes, they are unprintable, and so simply use SPACE. */ 304 strBuf.append(' '); 305 } 306 } 307 return strBuf.toString(); 308 } catch (BitwiseInputStream.AccessException ex) { 309 throw new CodingException("7bit ASCII decode failed: " + ex); 310 } 311 } 312 decode7bitGsm(byte[] data, int offset, int numFields)313 private static String decode7bitGsm(byte[] data, int offset, int numFields) 314 throws CodingException { 315 // Start reading from the next 7-bit aligned boundary after offset. 316 int offsetBits = offset * 8; 317 int offsetSeptets = (offsetBits + 6) / 7; 318 numFields -= offsetSeptets; 319 int paddingBits = (offsetSeptets * 7) - offsetBits; 320 String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, 321 paddingBits, 0, 0); 322 if (result == null) { 323 throw new CodingException("7bit GSM decoding failed"); 324 } 325 return result; 326 } 327 decodeLatin(byte[] data, int offset, int numFields)328 private static String decodeLatin(byte[] data, int offset, int numFields) 329 throws CodingException { 330 return decodeCharset(data, offset, numFields, 1, "ISO-8859-1"); 331 } 332 decodeShiftJis(byte[] data, int offset, int numFields)333 private static String decodeShiftJis(byte[] data, int offset, int numFields) 334 throws CodingException { 335 return decodeCharset(data, offset, numFields, 1, "Shift_JIS"); 336 } 337 decodeGsmDcs(byte[] data, int offset, int numFields, int msgType)338 private static String decodeGsmDcs(byte[] data, int offset, int numFields, 339 int msgType) 340 throws CodingException { 341 if ((msgType & 0xC0) != 0) { 342 throw new CodingException("unsupported coding group (" 343 + msgType + ")"); 344 } 345 346 switch ((msgType >> 2) & 0x3) { 347 case UserData.ENCODING_GSM_DCS_7BIT: 348 return decode7bitGsm(data, offset, numFields); 349 case UserData.ENCODING_GSM_DCS_8BIT: 350 return decodeUtf8(data, offset, numFields); 351 case UserData.ENCODING_GSM_DCS_16BIT: 352 return decodeUtf16(data, offset, numFields); 353 default: 354 throw new CodingException("unsupported user msgType encoding (" 355 + msgType + ")"); 356 } 357 } 358 decodeUserDataPayload(Context context, UserData userData, boolean hasUserDataHeader)359 private static void decodeUserDataPayload(Context context, UserData userData, 360 boolean hasUserDataHeader) throws CodingException { 361 int offset = 0; 362 if (hasUserDataHeader) { 363 int udhLen = userData.payload[0] & 0x00FF; 364 offset += udhLen + 1; 365 byte[] headerData = new byte[udhLen]; 366 System.arraycopy(userData.payload, 1, headerData, 0, udhLen); 367 userData.userDataHeader = SmsHeader.fromByteArray(headerData); 368 } 369 switch (userData.msgEncoding) { 370 case UserData.ENCODING_OCTET: 371 /* 372 * Octet decoding depends on the carrier service. 373 */ 374 boolean decodingtypeUTF8 = context.getResources() 375 .getBoolean(R.bool.config_sms_utf8_support); 376 377 // Strip off any padding bytes, meaning any differences between the length of the 378 // array and the target length specified by numFields. This is to avoid any 379 // confusion by code elsewhere that only considers the payload array length. 380 byte[] payload = new byte[userData.numFields]; 381 int copyLen = userData.numFields < userData.payload.length 382 ? userData.numFields : userData.payload.length; 383 384 System.arraycopy(userData.payload, 0, payload, 0, copyLen); 385 userData.payload = payload; 386 387 if (!decodingtypeUTF8) { 388 // There are many devices in the market that send 8bit text sms (latin 389 // encoded) as 390 // octet encoded. 391 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); 392 } else { 393 userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields); 394 } 395 break; 396 397 case UserData.ENCODING_IA5: 398 case UserData.ENCODING_7BIT_ASCII: 399 userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields); 400 break; 401 case UserData.ENCODING_UNICODE_16: 402 userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields); 403 break; 404 case UserData.ENCODING_GSM_7BIT_ALPHABET: 405 userData.payloadStr = decode7bitGsm(userData.payload, offset, 406 userData.numFields); 407 break; 408 case UserData.ENCODING_LATIN: 409 userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields); 410 break; 411 case UserData.ENCODING_SHIFT_JIS: 412 userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields); 413 break; 414 case UserData.ENCODING_GSM_DCS: 415 userData.payloadStr = decodeGsmDcs(userData.payload, offset, 416 userData.numFields, userData.msgType); 417 break; 418 default: 419 throw new CodingException("unsupported user data encoding (" 420 + userData.msgEncoding + ")"); 421 } 422 } 423 424 private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream) 425 throws BitwiseInputStream.AccessException { 426 final int EXPECTED_PARAM_SIZE = 1 * 8; 427 boolean decodeSuccess = false; 428 int paramBits = inStream.read(8) * 8; 429 if (paramBits >= EXPECTED_PARAM_SIZE) { 430 paramBits -= EXPECTED_PARAM_SIZE; 431 decodeSuccess = true; 432 bData.language = inStream.read(8); 433 } 434 if ((!decodeSuccess) || (paramBits > 0)) { 435 Log.d(LOG_TAG, "LANGUAGE_INDICATOR decode " 436 + (decodeSuccess ? "succeeded" : "failed") 437 + " (extra bits = " + paramBits + ")"); 438 } 439 inStream.skip(paramBits); 440 return decodeSuccess; 441 } 442 decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)443 private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream) 444 throws BitwiseInputStream.AccessException { 445 final int EXPECTED_PARAM_SIZE = 1 * 8; 446 boolean decodeSuccess = false; 447 int paramBits = inStream.read(8) * 8; 448 if (paramBits >= EXPECTED_PARAM_SIZE) { 449 paramBits -= EXPECTED_PARAM_SIZE; 450 decodeSuccess = true; 451 bData.priority = inStream.read(2); 452 inStream.skip(6); 453 } 454 if ((!decodeSuccess) || (paramBits > 0)) { 455 Log.d(LOG_TAG, "PRIORITY_INDICATOR decode " 456 + (decodeSuccess ? "succeeded" : "failed") 457 + " (extra bits = " + paramBits + ")"); 458 } 459 inStream.skip(paramBits); 460 return decodeSuccess; 461 } 462 serviceCategoryToCmasMessageClass(int serviceCategory)463 private static int serviceCategoryToCmasMessageClass(int serviceCategory) { 464 switch (serviceCategory) { 465 case CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT: 466 return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; 467 468 case CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT: 469 return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; 470 471 case CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT: 472 return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; 473 474 case CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: 475 return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; 476 477 case CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE: 478 return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; 479 480 default: 481 return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; 482 } 483 } 484 485 /** 486 * CMAS message decoding. 487 * (See TIA-1149-0-1, CMAS over CDMA) 488 * 489 * @param serviceCategory is the service category from the SMS envelope 490 */ decodeCmasUserData(Context context, BearerData bData, int serviceCategory)491 private static void decodeCmasUserData(Context context, BearerData bData, int serviceCategory) 492 throws BitwiseInputStream.AccessException, CodingException { 493 BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); 494 if (inStream.available() < 8) { 495 throw new CodingException("emergency CB with no CMAE_protocol_version"); 496 } 497 int protocolVersion = inStream.read(8); 498 if (protocolVersion != 0) { 499 throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion); 500 } 501 502 int messageClass = serviceCategoryToCmasMessageClass(serviceCategory); 503 int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; 504 int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; 505 int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; 506 int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; 507 int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; 508 509 while (inStream.available() >= 16) { 510 int recordType = inStream.read(8); 511 int recordLen = inStream.read(8); 512 switch (recordType) { 513 case 0: // Type 0 elements (Alert text) 514 UserData alertUserData = new UserData(); 515 alertUserData.msgEncoding = inStream.read(5); 516 alertUserData.msgEncodingSet = true; 517 alertUserData.msgType = 0; 518 519 int numFields; // number of chars to decode 520 switch (alertUserData.msgEncoding) { 521 case UserData.ENCODING_OCTET: 522 case UserData.ENCODING_LATIN: 523 numFields = recordLen - 1; // subtract 1 byte for encoding 524 break; 525 526 case UserData.ENCODING_IA5: 527 case UserData.ENCODING_7BIT_ASCII: 528 case UserData.ENCODING_GSM_7BIT_ALPHABET: 529 numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding 530 break; 531 532 case UserData.ENCODING_UNICODE_16: 533 numFields = (recordLen - 1) / 2; 534 break; 535 536 default: 537 numFields = 0; // unsupported encoding 538 } 539 540 alertUserData.numFields = numFields; 541 alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5); 542 decodeUserDataPayload(context, alertUserData, false); 543 bData.userData = alertUserData; 544 break; 545 546 case 1: // Type 1 elements 547 category = inStream.read(8); 548 responseType = inStream.read(8); 549 severity = inStream.read(4); 550 urgency = inStream.read(4); 551 certainty = inStream.read(4); 552 inStream.skip(recordLen * 8 - 28); 553 break; 554 555 default: 556 Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType); 557 inStream.skip(recordLen * 8); 558 break; 559 } 560 } 561 562 bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity, 563 urgency, certainty); 564 } 565 isCmasAlertCategory(int category)566 private static boolean isCmasAlertCategory(int category) { 567 return category >= CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT 568 && category <= CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE; 569 } 570 571 /** 572 * Create BearerData object from serialized representation. 573 * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) 574 * 575 * @param smsData byte array of raw encoded SMS bearer data. 576 * @param serviceCategory the envelope service category (for CMAS alert handling) 577 * @return an instance of BearerData. 578 */ decode(Context context, byte[] smsData, int serviceCategory)579 public static BearerData decode(Context context, byte[] smsData, int serviceCategory) 580 throws CodingException, BitwiseInputStream.AccessException { 581 BitwiseInputStream inStream = new BitwiseInputStream(smsData); 582 BearerData bData = new BearerData(); 583 int foundSubparamMask = 0; 584 while (inStream.available() > 0) { 585 int subparamId = inStream.read(8); 586 int subparamIdBit = 1 << subparamId; 587 // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8) 588 // as 32th bit is the max bit in int. 589 // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers: 590 // last defined subparam ID is 23 (00010111 = 0x17 = 23). 591 // Only do duplicate subparam ID check if subparam is within defined value as 592 // reserved subparams are just skipped. 593 if ((foundSubparamMask & subparamIdBit) != 0 && ( 594 subparamId >= SUBPARAM_MESSAGE_IDENTIFIER 595 && subparamId <= SUBPARAM_ID_LAST_DEFINED)) { 596 throw new CodingException("illegal duplicate subparameter (" + subparamId + ")"); 597 } 598 boolean decodeSuccess; 599 switch (subparamId) { 600 case SUBPARAM_MESSAGE_IDENTIFIER: 601 decodeSuccess = decodeMessageId(bData, inStream); 602 break; 603 case SUBPARAM_USER_DATA: 604 decodeSuccess = decodeUserData(bData, inStream); 605 break; 606 case SUBPARAM_LANGUAGE_INDICATOR: 607 decodeSuccess = decodeLanguageIndicator(bData, inStream); 608 break; 609 case SUBPARAM_PRIORITY_INDICATOR: 610 decodeSuccess = decodePriorityIndicator(bData, inStream); 611 break; 612 default: 613 decodeSuccess = decodeReserved(inStream, subparamId); 614 } 615 if (decodeSuccess && (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER 616 && subparamId <= SUBPARAM_ID_LAST_DEFINED)) { 617 foundSubparamMask |= subparamIdBit; 618 } 619 } 620 if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) { 621 throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); 622 } 623 if (bData.userData != null) { 624 if (isCmasAlertCategory(serviceCategory)) { 625 decodeCmasUserData(context, bData, serviceCategory); 626 } else { 627 decodeUserDataPayload(context, bData.userData, bData.hasUserDataHeader); 628 } 629 } 630 return bData; 631 } 632 } 633