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