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