1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.uicc; 18 19 import android.annotation.NonNull; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.res.Resources; 22 import android.content.res.Resources.NotFoundException; 23 import android.graphics.Bitmap; 24 import android.graphics.Color; 25 import android.os.Build; 26 import android.telephony.UiccPortInfo; 27 import android.text.TextUtils; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.telephony.GsmAlphabet; 31 import com.android.telephony.Rlog; 32 33 import java.io.UnsupportedEncodingException; 34 import java.nio.charset.StandardCharsets; 35 import java.util.List; 36 37 /** 38 * Various methods, useful for dealing with SIM data. 39 */ 40 public class IccUtils { 41 static final String LOG_TAG="IccUtils"; 42 43 // 3GPP specification constants 44 // Spec reference TS 31.102 section 4.2.16 45 @VisibleForTesting 46 static final int FPLMN_BYTE_SIZE = 3; 47 48 // ICCID used for tests by some OEMs 49 public static final String TEST_ICCID = UiccPortInfo.ICCID_REDACTED; 50 51 // A table mapping from a number to a hex character for fast encoding hex strings. 52 private static final char[] HEX_CHARS = { 53 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 54 }; 55 56 57 /** 58 * Many fields in GSM SIM's are stored as nibble-swizzled BCD 59 * 60 * Assumes left-justified field that may be padded right with 0xf 61 * values. 62 * 63 * Stops on invalid BCD value, returning string so far 64 */ 65 @UnsupportedAppUsage 66 public static String bcdToString(byte[] data, int offset, int length)67 bcdToString(byte[] data, int offset, int length) { 68 StringBuilder ret = new StringBuilder(length*2); 69 70 for (int i = offset ; i < offset + length ; i++) { 71 int v; 72 73 v = data[i] & 0xf; 74 if (v > 9) break; 75 ret.append((char)('0' + v)); 76 77 v = (data[i] >> 4) & 0xf; 78 // Some PLMNs have 'f' as high nibble, ignore it 79 if (v == 0xf) continue; 80 if (v > 9) break; 81 ret.append((char)('0' + v)); 82 } 83 84 return ret.toString(); 85 } 86 87 /** 88 * Converts a bcd byte array to String with offset 0 and byte array length. 89 */ bcdToString(byte[] data)90 public static String bcdToString(byte[] data) { 91 return bcdToString(data, 0, data.length); 92 } 93 94 /** 95 * Converts BCD string to bytes. 96 * 97 * @param bcd This should have an even length. If not, an "0" will be appended to the string. 98 */ bcdToBytes(String bcd)99 public static byte[] bcdToBytes(String bcd) { 100 byte[] output = new byte[(bcd.length() + 1) / 2]; 101 bcdToBytes(bcd, output); 102 return output; 103 } 104 105 /** 106 * Converts BCD string to bytes and put it into the given byte array. 107 * 108 * @param bcd This should have an even length. If not, an "0" will be appended to the string. 109 * @param bytes If the array size is less than needed, the rest of the BCD string isn't be 110 * converted. If the array size is more than needed, the rest of array remains unchanged. 111 */ bcdToBytes(String bcd, byte[] bytes)112 public static void bcdToBytes(String bcd, byte[] bytes) { 113 bcdToBytes(bcd, bytes, 0); 114 } 115 116 /** 117 * Converts BCD string to bytes and put it into the given byte array. 118 * 119 * @param bcd This should have an even length. If not, an "0" will be appended to the string. 120 * @param bytes If the array size is less than needed, the rest of the BCD string isn't be 121 * converted. If the array size is more than needed, the rest of array remains unchanged. 122 * @param offset the offset into the bytes[] to fill the data 123 */ bcdToBytes(String bcd, byte[] bytes, int offset)124 public static void bcdToBytes(String bcd, byte[] bytes, int offset) { 125 if (bcd.length() % 2 != 0) { 126 bcd += "0"; 127 } 128 int size = Math.min((bytes.length - offset) * 2, bcd.length()); 129 for (int i = 0, j = offset; i + 1 < size; i += 2, j++) { 130 bytes[j] = (byte) (charToByte(bcd.charAt(i + 1)) << 4 | charToByte(bcd.charAt(i))); 131 } 132 } 133 134 /** 135 * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3 136 * Returns a concatenated string of MCC+MNC, stripping 137 * all invalid character 'F' 138 */ bcdPlmnToString(byte[] data, int offset)139 public static String bcdPlmnToString(byte[] data, int offset) { 140 if (offset + 3 > data.length) { 141 return null; 142 } 143 byte[] trans = new byte[3]; 144 trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF)); 145 trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF)); 146 trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF)); 147 String ret = bytesToHexString(trans); 148 149 // For a valid plmn we trim all character 'F' 150 if (ret.contains("F")) { 151 ret = ret.replaceAll("F", ""); 152 } 153 return ret; 154 } 155 156 /** 157 * Convert a 5 or 6 - digit PLMN string to a nibble-swizzled encoding as per 24.008 10.5.1.3 158 * 159 * @param plmn the PLMN to convert 160 * @param data a byte array for the output 161 * @param offset the offset into data to start writing 162 */ stringToBcdPlmn(final String plmn, byte[] data, int offset)163 public static void stringToBcdPlmn(final String plmn, byte[] data, int offset) { 164 char digit6 = (plmn.length() > 5) ? plmn.charAt(5) : 'F'; 165 data[offset] = (byte) (charToByte(plmn.charAt(1)) << 4 | charToByte(plmn.charAt(0))); 166 data[offset + 1] = (byte) (charToByte(digit6) << 4 | charToByte(plmn.charAt(2))); 167 data[offset + 2] = (byte) (charToByte(plmn.charAt(4)) << 4 | charToByte(plmn.charAt(3))); 168 } 169 170 /** 171 * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH 172 */ 173 public static String bchToString(byte[] data, int offset, int length)174 bchToString(byte[] data, int offset, int length) { 175 StringBuilder ret = new StringBuilder(length*2); 176 177 for (int i = offset ; i < offset + length ; i++) { 178 int v; 179 180 v = data[i] & 0xf; 181 ret.append(HEX_CHARS[v]); 182 183 v = (data[i] >> 4) & 0xf; 184 ret.append(HEX_CHARS[v]); 185 } 186 187 return ret.toString(); 188 } 189 190 /** 191 * Decode cdma byte into String. 192 */ 193 @UnsupportedAppUsage 194 public static String cdmaBcdToString(byte[] data, int offset, int length)195 cdmaBcdToString(byte[] data, int offset, int length) { 196 StringBuilder ret = new StringBuilder(length); 197 198 int count = 0; 199 for (int i = offset; count < length; i++) { 200 int v; 201 v = data[i] & 0xf; 202 if (v > 9) v = 0; 203 ret.append((char)('0' + v)); 204 205 if (++count == length) break; 206 207 v = (data[i] >> 4) & 0xf; 208 if (v > 9) v = 0; 209 ret.append((char)('0' + v)); 210 ++count; 211 } 212 return ret.toString(); 213 } 214 215 /** 216 * Decodes a GSM-style BCD byte, returning an int ranging from 0-99. 217 * 218 * In GSM land, the least significant BCD digit is stored in the most 219 * significant nibble. 220 * 221 * Out-of-range digits are treated as 0 for the sake of the time stamp, 222 * because of this: 223 * 224 * TS 23.040 section 9.2.3.11 225 * "if the MS receives a non-integer value in the SCTS, it shall 226 * assume the digit is set to 0 but shall store the entire field 227 * exactly as received" 228 */ 229 @UnsupportedAppUsage 230 public static int gsmBcdByteToInt(byte b)231 gsmBcdByteToInt(byte b) { 232 int ret = 0; 233 234 // treat out-of-range BCD values as 0 235 if ((b & 0xf0) <= 0x90) { 236 ret = (b >> 4) & 0xf; 237 } 238 239 if ((b & 0x0f) <= 0x09) { 240 ret += (b & 0xf) * 10; 241 } 242 243 return ret; 244 } 245 246 /** 247 * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but 248 * opposite nibble format. The least significant BCD digit 249 * is in the least significant nibble and the most significant 250 * is in the most significant nibble. 251 */ 252 @UnsupportedAppUsage 253 public static int cdmaBcdByteToInt(byte b)254 cdmaBcdByteToInt(byte b) { 255 int ret = 0; 256 257 // treat out-of-range BCD values as 0 258 if ((b & 0xf0) <= 0x90) { 259 ret = ((b >> 4) & 0xf) * 10; 260 } 261 262 if ((b & 0x0f) <= 0x09) { 263 ret += (b & 0xf); 264 } 265 266 return ret; 267 } 268 269 /** 270 * Encodes a string to be formatted like the EF[ADN] alpha identifier. 271 * 272 * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on 273 * the relevant specs. 274 * 275 * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if 276 * there are characters that are not supported by it. 277 * 278 * @return the encoded string including the prefix byte necessary to identify the encoding. 279 * @see #adnStringFieldToString(byte[], int, int) 280 */ 281 @NonNull stringToAdnStringField(@onNull String alphaTag)282 public static byte[] stringToAdnStringField(@NonNull String alphaTag) { 283 int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0); 284 if (septets != -1) { 285 byte[] ret = new byte[septets]; 286 GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length); 287 return ret; 288 } 289 290 // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to 291 // validate that the string contains only valid UCS-2 characters. Since the read path 292 // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine 293 // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's 294 // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction. 295 byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE); 296 byte[] ret = new byte[alphaTagBytes.length + 1]; 297 // 0x80 tags the remaining bytes as UCS-2 298 ret[0] = (byte) 0x80; 299 System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length); 300 301 return ret; 302 } 303 304 /** 305 * Decodes a string field that's formatted like the EF[ADN] alpha 306 * identifier 307 * 308 * From TS 51.011 10.5.1: 309 * Coding: 310 * this alpha tagging shall use either 311 * - the SMS default 7 bit coded alphabet as defined in 312 * TS 23.038 [12] with bit 8 set to 0. The alpha identifier 313 * shall be left justified. Unused bytes shall be set to 'FF'; or 314 * - one of the UCS2 coded options as defined in annex B. 315 * 316 * Annex B from TS 11.11 V8.13.0: 317 * 1) If the first octet in the alpha string is '80', then the 318 * remaining octets are 16 bit UCS2 characters ... 319 * 2) if the first octet in the alpha string is '81', then the 320 * second octet contains a value indicating the number of 321 * characters in the string, and the third octet contains an 322 * 8 bit number which defines bits 15 to 8 of a 16 bit 323 * base pointer, where bit 16 is set to zero and bits 7 to 1 324 * are also set to zero. These sixteen bits constitute a 325 * base pointer to a "half page" in the UCS2 code space, to be 326 * used with some or all of the remaining octets in the string. 327 * The fourth and subsequent octets contain codings as follows: 328 * If bit 8 of the octet is set to zero, the remaining 7 bits 329 * of the octet contain a GSM Default Alphabet character, 330 * whereas if bit 8 of the octet is set to one, then the 331 * remaining seven bits are an offset value added to the 332 * 16 bit base pointer defined earlier... 333 * 3) If the first octet of the alpha string is set to '82', then 334 * the second octet contains a value indicating the number of 335 * characters in the string, and the third and fourth octets 336 * contain a 16 bit number which defines the complete 16 bit 337 * base pointer to a "half page" in the UCS2 code space... 338 */ 339 @UnsupportedAppUsage 340 public static String adnStringFieldToString(byte[] data, int offset, int length)341 adnStringFieldToString(byte[] data, int offset, int length) { 342 if (length == 0) { 343 return ""; 344 } 345 if (length >= 1) { 346 if (data[offset] == (byte) 0x80) { 347 int ucslen = (length - 1) / 2; 348 String ret = null; 349 350 try { 351 ret = new String(data, offset + 1, ucslen * 2, "utf-16be"); 352 } catch (UnsupportedEncodingException ex) { 353 Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", 354 ex); 355 } 356 357 if (ret != null) { 358 // trim off trailing FFFF characters 359 360 ucslen = ret.length(); 361 while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF') 362 ucslen--; 363 364 return ret.substring(0, ucslen); 365 } 366 } 367 } 368 369 boolean isucs2 = false; 370 char base = '\0'; 371 int len = 0; 372 373 if (length >= 3 && data[offset] == (byte) 0x81) { 374 len = data[offset + 1] & 0xFF; 375 if (len > length - 3) 376 len = length - 3; 377 378 base = (char) ((data[offset + 2] & 0xFF) << 7); 379 offset += 3; 380 isucs2 = true; 381 } else if (length >= 4 && data[offset] == (byte) 0x82) { 382 len = data[offset + 1] & 0xFF; 383 if (len > length - 4) 384 len = length - 4; 385 386 base = (char) (((data[offset + 2] & 0xFF) << 8) | 387 (data[offset + 3] & 0xFF)); 388 offset += 4; 389 isucs2 = true; 390 } 391 392 if (isucs2) { 393 StringBuilder ret = new StringBuilder(); 394 395 while (len > 0) { 396 // UCS2 subset case 397 398 if (data[offset] < 0) { 399 ret.append((char) (base + (data[offset] & 0x7F))); 400 offset++; 401 len--; 402 } 403 404 // GSM character set case 405 406 int count = 0; 407 while (count < len && data[offset + count] >= 0) 408 count++; 409 410 ret.append(GsmAlphabet.gsm8BitUnpackedToString(data, 411 offset, count)); 412 413 offset += count; 414 len -= count; 415 } 416 417 return ret.toString(); 418 } 419 420 Resources resource = Resources.getSystem(); 421 String defaultCharset = ""; 422 try { 423 defaultCharset = 424 resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset); 425 } catch (NotFoundException e) { 426 // Ignore Exception and defaultCharset is set to a empty string. 427 } 428 return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim()); 429 } 430 431 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 432 public static int hexCharToInt(char c)433 hexCharToInt(char c) { 434 if (c >= '0' && c <= '9') return (c - '0'); 435 if (c >= 'A' && c <= 'F') return (c - 'A' + 10); 436 if (c >= 'a' && c <= 'f') return (c - 'a' + 10); 437 438 throw new RuntimeException ("invalid hex char '" + c + "'"); 439 } 440 441 /** 442 * Converts a hex String to a byte array. 443 * 444 * @param s A string of hexadecimal characters, must be an even number of 445 * chars long 446 * 447 * @return byte array representation 448 * 449 * @throws RuntimeException on invalid format 450 */ 451 @UnsupportedAppUsage 452 public static byte[] hexStringToBytes(String s)453 hexStringToBytes(String s) { 454 byte[] ret; 455 456 if (s == null) return null; 457 458 int sz = s.length(); 459 460 ret = new byte[sz/2]; 461 462 for (int i=0 ; i <sz ; i+=2) { 463 ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4) 464 | hexCharToInt(s.charAt(i+1))); 465 } 466 467 return ret; 468 } 469 470 471 /** 472 * Converts a byte array into a String of hexadecimal characters. 473 * 474 * @param bytes an array of bytes 475 * 476 * @return hex string representation of bytes array 477 */ 478 @UnsupportedAppUsage 479 public static String bytesToHexString(byte[] bytes)480 bytesToHexString(byte[] bytes) { 481 if (bytes == null) return null; 482 483 StringBuilder ret = new StringBuilder(2*bytes.length); 484 485 for (int i = 0 ; i < bytes.length ; i++) { 486 int b; 487 488 b = 0x0f & (bytes[i] >> 4); 489 490 ret.append(HEX_CHARS[b]); 491 492 b = 0x0f & bytes[i]; 493 494 ret.append(HEX_CHARS[b]); 495 } 496 497 return ret.toString(); 498 } 499 500 501 /** 502 * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string 503 * "offset" points to "octet 3", the coding scheme byte 504 * empty string returned on decode error 505 */ 506 @UnsupportedAppUsage 507 public static String networkNameToString(byte[] data, int offset, int length)508 networkNameToString(byte[] data, int offset, int length) { 509 String ret; 510 511 if ((data[offset] & 0x80) != 0x80 || length < 1) { 512 return ""; 513 } 514 515 switch ((data[offset] >>> 4) & 0x7) { 516 case 0: 517 // SMS character set 518 int countSeptets; 519 int unusedBits = data[offset] & 7; 520 countSeptets = (((length - 1) * 8) - unusedBits) / 7 ; 521 ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets); 522 break; 523 case 1: 524 // UCS2 525 try { 526 ret = new String(data, 527 offset + 1, length - 1, "utf-16"); 528 } catch (UnsupportedEncodingException ex) { 529 ret = ""; 530 Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex); 531 } 532 break; 533 534 // unsupported encoding 535 default: 536 ret = ""; 537 break; 538 } 539 540 // "Add CI" 541 // "The MS should add the letters for the Country's Initials and 542 // a separator (e.g. a space) to the text string" 543 544 if ((data[offset] & 0x40) != 0) { 545 // FIXME(mkf) add country initials here 546 } 547 548 return ret; 549 } 550 551 /** 552 * Convert a TS 131.102 image instance of code scheme '11' into Bitmap 553 * @param data The raw data 554 * @param length The length of image body 555 * @return The bitmap 556 */ 557 @UnsupportedAppUsage parseToBnW(byte[] data, int length)558 public static Bitmap parseToBnW(byte[] data, int length){ 559 int valueIndex = 0; 560 int width = data[valueIndex++] & 0xFF; 561 int height = data[valueIndex++] & 0xFF; 562 int numOfPixels = width*height; 563 564 int[] pixels = new int[numOfPixels]; 565 566 int pixelIndex = 0; 567 int bitIndex = 7; 568 byte currentByte = 0x00; 569 while (pixelIndex < numOfPixels) { 570 // reassign data and index for every byte (8 bits). 571 if (pixelIndex % 8 == 0) { 572 currentByte = data[valueIndex++]; 573 bitIndex = 7; 574 } 575 pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01); 576 } 577 578 if (pixelIndex != numOfPixels) { 579 Rlog.e(LOG_TAG, "parse end and size error"); 580 } 581 return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); 582 } 583 bitToRGB(int bit)584 private static int bitToRGB(int bit){ 585 if(bit == 1){ 586 return Color.WHITE; 587 } else { 588 return Color.BLACK; 589 } 590 } 591 592 /** 593 * a TS 131.102 image instance of code scheme '11' into color Bitmap 594 * 595 * @param data The raw data 596 * @param length the length of image body 597 * @param transparency with or without transparency 598 * @return The color bitmap 599 */ 600 @UnsupportedAppUsage parseToRGB(byte[] data, int length, boolean transparency)601 public static Bitmap parseToRGB(byte[] data, int length, 602 boolean transparency) { 603 int valueIndex = 0; 604 int width = data[valueIndex++] & 0xFF; 605 int height = data[valueIndex++] & 0xFF; 606 int bits = data[valueIndex++] & 0xFF; 607 int colorNumber = data[valueIndex++] & 0xFF; 608 int clutOffset = ((data[valueIndex++] & 0xFF) << 8) 609 | (data[valueIndex++] & 0xFF); 610 611 int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber); 612 if (true == transparency) { 613 colorIndexArray[colorNumber - 1] = Color.TRANSPARENT; 614 } 615 616 int[] resultArray = null; 617 if (0 == (8 % bits)) { 618 resultArray = mapTo2OrderBitColor(data, valueIndex, 619 (width * height), colorIndexArray, bits); 620 } else { 621 resultArray = mapToNon2OrderBitColor(data, valueIndex, 622 (width * height), colorIndexArray, bits); 623 } 624 625 return Bitmap.createBitmap(resultArray, width, height, 626 Bitmap.Config.RGB_565); 627 } 628 mapTo2OrderBitColor(byte[] data, int valueIndex, int length, int[] colorArray, int bits)629 private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex, 630 int length, int[] colorArray, int bits) { 631 if (0 != (8 % bits)) { 632 Rlog.e(LOG_TAG, "not event number of color"); 633 return mapToNon2OrderBitColor(data, valueIndex, length, colorArray, 634 bits); 635 } 636 637 int mask = 0x01; 638 switch (bits) { 639 case 1: 640 mask = 0x01; 641 break; 642 case 2: 643 mask = 0x03; 644 break; 645 case 4: 646 mask = 0x0F; 647 break; 648 case 8: 649 mask = 0xFF; 650 break; 651 } 652 653 int[] resultArray = new int[length]; 654 int resultIndex = 0; 655 int run = 8 / bits; 656 while (resultIndex < length) { 657 byte tempByte = data[valueIndex++]; 658 for (int runIndex = 0; runIndex < run; ++runIndex) { 659 int offset = run - runIndex - 1; 660 resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits)) 661 & mask]; 662 } 663 } 664 return resultArray; 665 } 666 mapToNon2OrderBitColor(byte[] data, int valueIndex, int length, int[] colorArray, int bits)667 private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex, 668 int length, int[] colorArray, int bits) { 669 if (0 == (8 % bits)) { 670 Rlog.e(LOG_TAG, "not odd number of color"); 671 return mapTo2OrderBitColor(data, valueIndex, length, colorArray, 672 bits); 673 } 674 675 int[] resultArray = new int[length]; 676 // TODO fix me: 677 return resultArray; 678 } 679 getCLUT(byte[] rawData, int offset, int number)680 private static int[] getCLUT(byte[] rawData, int offset, int number) { 681 if (null == rawData) { 682 return null; 683 } 684 685 int[] result = new int[number]; 686 int endIndex = offset + (number * 3); // 1 color use 3 bytes 687 int valueIndex = offset; 688 int colorIndex = 0; 689 int alpha = 0xff << 24; 690 do { 691 result[colorIndex++] = alpha 692 | ((rawData[valueIndex++] & 0xFF) << 16) 693 | ((rawData[valueIndex++] & 0xFF) << 8) 694 | ((rawData[valueIndex++] & 0xFF)); 695 } while (valueIndex < endIndex); 696 return result; 697 } 698 getDecimalSubstring(String iccId)699 public static String getDecimalSubstring(String iccId) { 700 int position; 701 for (position = 0; position < iccId.length(); position ++) { 702 if (!Character.isDigit(iccId.charAt(position))) break; 703 } 704 return iccId.substring( 0, position ); 705 } 706 707 /** 708 * Converts a series of bytes to an integer. This method currently only supports positive 32-bit 709 * integers. 710 * 711 * @param src The source bytes. 712 * @param offset The position of the first byte of the data to be converted. The data is base 713 * 256 with the most significant digit first. 714 * @param length The length of the data to be converted. It must be <= 4. 715 * @throws IllegalArgumentException If {@code length} is bigger than 4 or {@code src} cannot be 716 * parsed as a positive integer. 717 * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length} 718 * exceeds the bounds of {@code src}. 719 */ bytesToInt(byte[] src, int offset, int length)720 public static int bytesToInt(byte[] src, int offset, int length) { 721 if (length > 4) { 722 throw new IllegalArgumentException( 723 "length must be <= 4 (only 32-bit integer supported): " + length); 724 } 725 if (offset < 0 || length < 0 || offset + length > src.length) { 726 throw new IndexOutOfBoundsException( 727 "Out of the bounds: src=[" 728 + src.length 729 + "], offset=" 730 + offset 731 + ", length=" 732 + length); 733 } 734 int result = 0; 735 for (int i = 0; i < length; i++) { 736 result = (result << 8) | (src[offset + i] & 0xFF); 737 } 738 if (result < 0) { 739 throw new IllegalArgumentException( 740 "src cannot be parsed as a positive integer: " + result); 741 } 742 return result; 743 } 744 745 /** 746 * Converts a series of bytes to a raw long variable which can be both positive and negative. 747 * This method currently only supports 64-bit long variable. 748 * 749 * @param src The source bytes. 750 * @param offset The position of the first byte of the data to be converted. The data is base 751 * 256 with the most significant digit first. 752 * @param length The length of the data to be converted. It must be <= 8. 753 * @throws IllegalArgumentException If {@code length} is bigger than 8. 754 * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length} 755 * exceeds the bounds of {@code src}. 756 */ bytesToRawLong(byte[] src, int offset, int length)757 public static long bytesToRawLong(byte[] src, int offset, int length) { 758 if (length > 8) { 759 throw new IllegalArgumentException( 760 "length must be <= 8 (only 64-bit long supported): " + length); 761 } 762 if (offset < 0 || length < 0 || offset + length > src.length) { 763 throw new IndexOutOfBoundsException( 764 "Out of the bounds: src=[" 765 + src.length 766 + "], offset=" 767 + offset 768 + ", length=" 769 + length); 770 } 771 long result = 0; 772 for (int i = 0; i < length; i++) { 773 result = (result << 8) | (src[offset + i] & 0xFF); 774 } 775 return result; 776 } 777 778 /** 779 * Converts an integer to a new byte array with base 256 and the most significant digit first. 780 * 781 * @throws IllegalArgumentException If {@code value} is negative. 782 */ unsignedIntToBytes(int value)783 public static byte[] unsignedIntToBytes(int value) { 784 if (value < 0) { 785 throw new IllegalArgumentException("value must be 0 or positive: " + value); 786 } 787 byte[] bytes = new byte[byteNumForUnsignedInt(value)]; 788 unsignedIntToBytes(value, bytes, 0); 789 return bytes; 790 } 791 792 /** 793 * Converts an integer to a new byte array with base 256 and the most significant digit first. 794 * The first byte's highest bit is used for sign. If the most significant digit is larger than 795 * 127, an extra byte (0) will be prepended before it. This method currently doesn't support 796 * negative values. 797 * 798 * @throws IllegalArgumentException If {@code value} is negative. 799 */ signedIntToBytes(int value)800 public static byte[] signedIntToBytes(int value) { 801 if (value < 0) { 802 throw new IllegalArgumentException("value must be 0 or positive: " + value); 803 } 804 byte[] bytes = new byte[byteNumForSignedInt(value)]; 805 signedIntToBytes(value, bytes, 0); 806 return bytes; 807 } 808 809 /** 810 * Converts an integer to a series of bytes with base 256 and the most significant digit first. 811 * 812 * @param value The integer to be converted. 813 * @param dest The destination byte array. 814 * @param offset The start offset of the byte array. 815 * @return The number of byte needeed. 816 * @throws IllegalArgumentException If {@code value} is negative. 817 * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}. 818 */ unsignedIntToBytes(int value, byte[] dest, int offset)819 public static int unsignedIntToBytes(int value, byte[] dest, int offset) { 820 return intToBytes(value, dest, offset, false); 821 } 822 823 /** 824 * Converts an integer to a series of bytes with base 256 and the most significant digit first. 825 * The first byte's highest bit is used for sign. If the most significant digit is larger than 826 * 127, an extra byte (0) will be prepended before it. This method currently doesn't support 827 * negative values. 828 * 829 * @throws IllegalArgumentException If {@code value} is negative. 830 * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}. 831 */ signedIntToBytes(int value, byte[] dest, int offset)832 public static int signedIntToBytes(int value, byte[] dest, int offset) { 833 return intToBytes(value, dest, offset, true); 834 } 835 836 /** 837 * Calculates the number of required bytes to represent {@code value}. The bytes will be base 838 * 256 with the most significant digit first. 839 * 840 * @throws IllegalArgumentException If {@code value} is negative. 841 */ byteNumForUnsignedInt(int value)842 public static int byteNumForUnsignedInt(int value) { 843 return byteNumForInt(value, false); 844 } 845 846 /** 847 * Calculates the number of required bytes to represent {@code value}. The bytes will be base 848 * 256 with the most significant digit first. If the most significant digit is larger than 127, 849 * an extra byte (0) will be prepended before it. This method currently only supports positive 850 * integers. 851 * 852 * @throws IllegalArgumentException If {@code value} is negative. 853 */ byteNumForSignedInt(int value)854 public static int byteNumForSignedInt(int value) { 855 return byteNumForInt(value, true); 856 } 857 intToBytes(int value, byte[] dest, int offset, boolean signed)858 private static int intToBytes(int value, byte[] dest, int offset, boolean signed) { 859 int l = byteNumForInt(value, signed); 860 if (offset < 0 || offset + l > dest.length) { 861 throw new IndexOutOfBoundsException("Not enough space to write. Required bytes: " + l); 862 } 863 for (int i = l - 1, v = value; i >= 0; i--, v >>>= 8) { 864 byte b = (byte) (v & 0xFF); 865 dest[offset + i] = b; 866 } 867 return l; 868 } 869 byteNumForInt(int value, boolean signed)870 private static int byteNumForInt(int value, boolean signed) { 871 if (value < 0) { 872 throw new IllegalArgumentException("value must be 0 or positive: " + value); 873 } 874 if (signed) { 875 if (value <= 0x7F) { 876 return 1; 877 } 878 if (value <= 0x7FFF) { 879 return 2; 880 } 881 if (value <= 0x7FFFFF) { 882 return 3; 883 } 884 } else { 885 if (value <= 0xFF) { 886 return 1; 887 } 888 if (value <= 0xFFFF) { 889 return 2; 890 } 891 if (value <= 0xFFFFFF) { 892 return 3; 893 } 894 } 895 return 4; 896 } 897 898 899 /** 900 * Counts the number of trailing zero bits of a byte. 901 */ countTrailingZeros(byte b)902 public static byte countTrailingZeros(byte b) { 903 if (b == 0) { 904 return 8; 905 } 906 int v = b & 0xFF; 907 byte c = 7; 908 if ((v & 0x0F) != 0) { 909 c -= 4; 910 } 911 if ((v & 0x33) != 0) { 912 c -= 2; 913 } 914 if ((v & 0x55) != 0) { 915 c -= 1; 916 } 917 return c; 918 } 919 920 /** 921 * Converts a byte to a hex string. 922 */ byteToHex(byte b)923 public static String byteToHex(byte b) { 924 return new String(new char[] {HEX_CHARS[(b & 0xFF) >>> 4], HEX_CHARS[b & 0xF]}); 925 } 926 927 /** 928 * Strip all the trailing 'F' characters of a string, e.g., an ICCID. 929 */ stripTrailingFs(String s)930 public static String stripTrailingFs(String s) { 931 if (TEST_ICCID.equals(s)) { 932 return s; 933 } 934 return s == null ? null : s.replaceAll("(?i)f*$", ""); 935 } 936 937 /** 938 * Strip all the trailing 'F' characters of a string if exists and compare. 939 */ compareIgnoreTrailingFs(String a, String b)940 public static boolean compareIgnoreTrailingFs(String a, String b) { 941 return TextUtils.equals(a, b) || TextUtils.equals(stripTrailingFs(a), stripTrailingFs(b)); 942 } 943 944 /** 945 * Converts a character of [0-9a-fA-F] to its hex value in a byte. If the character is not a 946 * hex number, 0 will be returned. 947 */ charToByte(char c)948 private static byte charToByte(char c) { 949 if (c >= 0x30 && c <= 0x39) { 950 return (byte) (c - 0x30); 951 } else if (c >= 0x41 && c <= 0x46) { 952 return (byte) (c - 0x37); 953 } else if (c >= 0x61 && c <= 0x66) { 954 return (byte) (c - 0x57); 955 } 956 return 0; 957 } 958 959 /** 960 * Encode the Fplmns into byte array to write to EF. 961 * 962 * @param fplmns Array of fplmns to be serialized. 963 * @param dataLength the size of the EF file. 964 * @return the encoded byte array in the correct format for FPLMN file. 965 */ encodeFplmns(List<String> fplmns, int dataLength)966 public static byte[] encodeFplmns(List<String> fplmns, int dataLength) { 967 byte[] serializedFplmns = new byte[dataLength]; 968 int offset = 0; 969 for (String fplmn : fplmns) { 970 if (offset >= dataLength) break; 971 stringToBcdPlmn(fplmn, serializedFplmns, offset); 972 offset += FPLMN_BYTE_SIZE; 973 } 974 //pads to the length of the EF file. 975 while (offset < dataLength) { 976 // required by 3GPP TS 31.102 spec 4.2.16 977 serializedFplmns[offset++] = (byte) 0xff; 978 } 979 return serializedFplmns; 980 } 981 } 982