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