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
17package com.android.internal.telephony.uicc;
18
19import android.content.res.Resources;
20import android.content.res.Resources.NotFoundException;
21import android.graphics.Bitmap;
22import android.graphics.Color;
23import android.telephony.Rlog;
24
25import com.android.internal.telephony.GsmAlphabet;
26
27import java.io.UnsupportedEncodingException;
28
29/**
30 * Various methods, useful for dealing with SIM data.
31 */
32public 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
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     */
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     */
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     */
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     */
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
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
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
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
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
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
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[]
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
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
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     */
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
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     */
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
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
766    public static int byteNumForSignedInt(int value) {
767        return byteNumForInt(value, true);
768    }
769
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
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     */
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     */
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     */
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     */
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