• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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