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 import java.io.UnsupportedEncodingException;
27 
28 /**
29  * Various methods, useful for dealing with SIM data.
30  */
31 public class IccUtils {
32     static final String LOG_TAG="IccUtils";
33 
34     /**
35      * Many fields in GSM SIM's are stored as nibble-swizzled BCD
36      *
37      * Assumes left-justified field that may be padded right with 0xf
38      * values.
39      *
40      * Stops on invalid BCD value, returning string so far
41      */
42     public static String
bcdToString(byte[] data, int offset, int length)43     bcdToString(byte[] data, int offset, int length) {
44         StringBuilder ret = new StringBuilder(length*2);
45 
46         for (int i = offset ; i < offset + length ; i++) {
47             int v;
48 
49             v = data[i] & 0xf;
50             if (v > 9)  break;
51             ret.append((char)('0' + v));
52 
53             v = (data[i] >> 4) & 0xf;
54             // Some PLMNs have 'f' as high nibble, ignore it
55             if (v == 0xf) continue;
56             if (v > 9)  break;
57             ret.append((char)('0' + v));
58         }
59 
60         return ret.toString();
61     }
62 
63     /**
64      * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH
65      */
66     public static String
bchToString(byte[] data, int offset, int length)67     bchToString(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             ret.append("0123456789abcdef".charAt(v));
75 
76             v = (data[i] >> 4) & 0xf;
77             ret.append("0123456789abcdef".charAt(v));
78         }
79 
80         return ret.toString();
81     }
82 
83     /**
84      * Decode cdma byte into String.
85      */
86     public static String
cdmaBcdToString(byte[] data, int offset, int length)87     cdmaBcdToString(byte[] data, int offset, int length) {
88         StringBuilder ret = new StringBuilder(length);
89 
90         int count = 0;
91         for (int i = offset; count < length; i++) {
92             int v;
93             v = data[i] & 0xf;
94             if (v > 9)  v = 0;
95             ret.append((char)('0' + v));
96 
97             if (++count == length) break;
98 
99             v = (data[i] >> 4) & 0xf;
100             if (v > 9)  v = 0;
101             ret.append((char)('0' + v));
102             ++count;
103         }
104         return ret.toString();
105     }
106 
107     /**
108      * Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
109      *
110      * In GSM land, the least significant BCD digit is stored in the most
111      * significant nibble.
112      *
113      * Out-of-range digits are treated as 0 for the sake of the time stamp,
114      * because of this:
115      *
116      * TS 23.040 section 9.2.3.11
117      * "if the MS receives a non-integer value in the SCTS, it shall
118      * assume the digit is set to 0 but shall store the entire field
119      * exactly as received"
120      */
121     public static int
gsmBcdByteToInt(byte b)122     gsmBcdByteToInt(byte b) {
123         int ret = 0;
124 
125         // treat out-of-range BCD values as 0
126         if ((b & 0xf0) <= 0x90) {
127             ret = (b >> 4) & 0xf;
128         }
129 
130         if ((b & 0x0f) <= 0x09) {
131             ret +=  (b & 0xf) * 10;
132         }
133 
134         return ret;
135     }
136 
137     /**
138      * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but
139      * opposite nibble format. The least significant BCD digit
140      * is in the least significant nibble and the most significant
141      * is in the most significant nibble.
142      */
143     public static int
cdmaBcdByteToInt(byte b)144     cdmaBcdByteToInt(byte b) {
145         int ret = 0;
146 
147         // treat out-of-range BCD values as 0
148         if ((b & 0xf0) <= 0x90) {
149             ret = ((b >> 4) & 0xf) * 10;
150         }
151 
152         if ((b & 0x0f) <= 0x09) {
153             ret +=  (b & 0xf);
154         }
155 
156         return ret;
157     }
158 
159     /**
160      * Decodes a string field that's formatted like the EF[ADN] alpha
161      * identifier
162      *
163      * From TS 51.011 10.5.1:
164      *   Coding:
165      *       this alpha tagging shall use either
166      *      -    the SMS default 7 bit coded alphabet as defined in
167      *          TS 23.038 [12] with bit 8 set to 0. The alpha identifier
168      *          shall be left justified. Unused bytes shall be set to 'FF'; or
169      *      -    one of the UCS2 coded options as defined in annex B.
170      *
171      * Annex B from TS 11.11 V8.13.0:
172      *      1)  If the first octet in the alpha string is '80', then the
173      *          remaining octets are 16 bit UCS2 characters ...
174      *      2)  if the first octet in the alpha string is '81', then the
175      *          second octet contains a value indicating the number of
176      *          characters in the string, and the third octet contains an
177      *          8 bit number which defines bits 15 to 8 of a 16 bit
178      *          base pointer, where bit 16 is set to zero and bits 7 to 1
179      *          are also set to zero.  These sixteen bits constitute a
180      *          base pointer to a "half page" in the UCS2 code space, to be
181      *          used with some or all of the remaining octets in the string.
182      *          The fourth and subsequent octets contain codings as follows:
183      *          If bit 8 of the octet is set to zero, the remaining 7 bits
184      *          of the octet contain a GSM Default Alphabet character,
185      *          whereas if bit 8 of the octet is set to one, then the
186      *          remaining seven bits are an offset value added to the
187      *          16 bit base pointer defined earlier...
188      *      3)  If the first octet of the alpha string is set to '82', then
189      *          the second octet contains a value indicating the number of
190      *          characters in the string, and the third and fourth octets
191      *          contain a 16 bit number which defines the complete 16 bit
192      *          base pointer to a "half page" in the UCS2 code space...
193      */
194     public static String
adnStringFieldToString(byte[] data, int offset, int length)195     adnStringFieldToString(byte[] data, int offset, int length) {
196         if (length == 0) {
197             return "";
198         }
199         if (length >= 1) {
200             if (data[offset] == (byte) 0x80) {
201                 int ucslen = (length - 1) / 2;
202                 String ret = null;
203 
204                 try {
205                     ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
206                 } catch (UnsupportedEncodingException ex) {
207                     Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
208                           ex);
209                 }
210 
211                 if (ret != null) {
212                     // trim off trailing FFFF characters
213 
214                     ucslen = ret.length();
215                     while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
216                         ucslen--;
217 
218                     return ret.substring(0, ucslen);
219                 }
220             }
221         }
222 
223         boolean isucs2 = false;
224         char base = '\0';
225         int len = 0;
226 
227         if (length >= 3 && data[offset] == (byte) 0x81) {
228             len = data[offset + 1] & 0xFF;
229             if (len > length - 3)
230                 len = length - 3;
231 
232             base = (char) ((data[offset + 2] & 0xFF) << 7);
233             offset += 3;
234             isucs2 = true;
235         } else if (length >= 4 && data[offset] == (byte) 0x82) {
236             len = data[offset + 1] & 0xFF;
237             if (len > length - 4)
238                 len = length - 4;
239 
240             base = (char) (((data[offset + 2] & 0xFF) << 8) |
241                             (data[offset + 3] & 0xFF));
242             offset += 4;
243             isucs2 = true;
244         }
245 
246         if (isucs2) {
247             StringBuilder ret = new StringBuilder();
248 
249             while (len > 0) {
250                 // UCS2 subset case
251 
252                 if (data[offset] < 0) {
253                     ret.append((char) (base + (data[offset] & 0x7F)));
254                     offset++;
255                     len--;
256                 }
257 
258                 // GSM character set case
259 
260                 int count = 0;
261                 while (count < len && data[offset + count] >= 0)
262                     count++;
263 
264                 ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
265                            offset, count));
266 
267                 offset += count;
268                 len -= count;
269             }
270 
271             return ret.toString();
272         }
273 
274         Resources resource = Resources.getSystem();
275         String defaultCharset = "";
276         try {
277             defaultCharset =
278                     resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
279         } catch (NotFoundException e) {
280             // Ignore Exception and defaultCharset is set to a empty string.
281         }
282         return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
283     }
284 
285     static int
hexCharToInt(char c)286     hexCharToInt(char c) {
287         if (c >= '0' && c <= '9') return (c - '0');
288         if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
289         if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
290 
291         throw new RuntimeException ("invalid hex char '" + c + "'");
292     }
293 
294     /**
295      * Converts a hex String to a byte array.
296      *
297      * @param s A string of hexadecimal characters, must be an even number of
298      *          chars long
299      *
300      * @return byte array representation
301      *
302      * @throws RuntimeException on invalid format
303      */
304     public static byte[]
hexStringToBytes(String s)305     hexStringToBytes(String s) {
306         byte[] ret;
307 
308         if (s == null) return null;
309 
310         int sz = s.length();
311 
312         ret = new byte[sz/2];
313 
314         for (int i=0 ; i <sz ; i+=2) {
315             ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
316                                 | hexCharToInt(s.charAt(i+1)));
317         }
318 
319         return ret;
320     }
321 
322 
323     /**
324      * Converts a byte array into a String of hexadecimal characters.
325      *
326      * @param bytes an array of bytes
327      *
328      * @return hex string representation of bytes array
329      */
330     public static String
bytesToHexString(byte[] bytes)331     bytesToHexString(byte[] bytes) {
332         if (bytes == null) return null;
333 
334         StringBuilder ret = new StringBuilder(2*bytes.length);
335 
336         for (int i = 0 ; i < bytes.length ; i++) {
337             int b;
338 
339             b = 0x0f & (bytes[i] >> 4);
340 
341             ret.append("0123456789abcdef".charAt(b));
342 
343             b = 0x0f & bytes[i];
344 
345             ret.append("0123456789abcdef".charAt(b));
346         }
347 
348         return ret.toString();
349     }
350 
351 
352     /**
353      * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
354      * "offset" points to "octet 3", the coding scheme byte
355      * empty string returned on decode error
356      */
357     public static String
networkNameToString(byte[] data, int offset, int length)358     networkNameToString(byte[] data, int offset, int length) {
359         String ret;
360 
361         if ((data[offset] & 0x80) != 0x80 || length < 1) {
362             return "";
363         }
364 
365         switch ((data[offset] >>> 4) & 0x7) {
366             case 0:
367                 // SMS character set
368                 int countSeptets;
369                 int unusedBits = data[offset] & 7;
370                 countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
371                 ret =  GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets);
372             break;
373             case 1:
374                 // UCS2
375                 try {
376                     ret = new String(data,
377                             offset + 1, length - 1, "utf-16");
378                 } catch (UnsupportedEncodingException ex) {
379                     ret = "";
380                     Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
381                 }
382             break;
383 
384             // unsupported encoding
385             default:
386                 ret = "";
387             break;
388         }
389 
390         // "Add CI"
391         // "The MS should add the letters for the Country's Initials and
392         //  a separator (e.g. a space) to the text string"
393 
394         if ((data[offset] & 0x40) != 0) {
395             // FIXME(mkf) add country initials here
396 
397         }
398 
399         return ret;
400     }
401 
402     /**
403      * Convert a TS 131.102 image instance of code scheme '11' into Bitmap
404      * @param data The raw data
405      * @param length The length of image body
406      * @return The bitmap
407      */
parseToBnW(byte[] data, int length)408     public static Bitmap parseToBnW(byte[] data, int length){
409         int valueIndex = 0;
410         int width = data[valueIndex++] & 0xFF;
411         int height = data[valueIndex++] & 0xFF;
412         int numOfPixels = width*height;
413 
414         int[] pixels = new int[numOfPixels];
415 
416         int pixelIndex = 0;
417         int bitIndex = 7;
418         byte currentByte = 0x00;
419         while (pixelIndex < numOfPixels) {
420             // reassign data and index for every byte (8 bits).
421             if (pixelIndex % 8 == 0) {
422                 currentByte = data[valueIndex++];
423                 bitIndex = 7;
424             }
425             pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
426         }
427 
428         if (pixelIndex != numOfPixels) {
429             Rlog.e(LOG_TAG, "parse end and size error");
430         }
431         return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
432     }
433 
bitToRGB(int bit)434     private static int bitToRGB(int bit){
435         if(bit == 1){
436             return Color.WHITE;
437         } else {
438             return Color.BLACK;
439         }
440     }
441 
442     /**
443      * a TS 131.102 image instance of code scheme '11' into color Bitmap
444      *
445      * @param data The raw data
446      * @param length the length of image body
447      * @param transparency with or without transparency
448      * @return The color bitmap
449      */
parseToRGB(byte[] data, int length, boolean transparency)450     public static Bitmap parseToRGB(byte[] data, int length,
451             boolean transparency) {
452         int valueIndex = 0;
453         int width = data[valueIndex++] & 0xFF;
454         int height = data[valueIndex++] & 0xFF;
455         int bits = data[valueIndex++] & 0xFF;
456         int colorNumber = data[valueIndex++] & 0xFF;
457         int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
458                 | (data[valueIndex++] & 0xFF);
459 
460         int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
461         if (true == transparency) {
462             colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
463         }
464 
465         int[] resultArray = null;
466         if (0 == (8 % bits)) {
467             resultArray = mapTo2OrderBitColor(data, valueIndex,
468                     (width * height), colorIndexArray, bits);
469         } else {
470             resultArray = mapToNon2OrderBitColor(data, valueIndex,
471                     (width * height), colorIndexArray, bits);
472         }
473 
474         return Bitmap.createBitmap(resultArray, width, height,
475                 Bitmap.Config.RGB_565);
476     }
477 
mapTo2OrderBitColor(byte[] data, int valueIndex, int length, int[] colorArray, int bits)478     private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
479             int length, int[] colorArray, int bits) {
480         if (0 != (8 % bits)) {
481             Rlog.e(LOG_TAG, "not event number of color");
482             return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
483                     bits);
484         }
485 
486         int mask = 0x01;
487         switch (bits) {
488         case 1:
489             mask = 0x01;
490             break;
491         case 2:
492             mask = 0x03;
493             break;
494         case 4:
495             mask = 0x0F;
496             break;
497         case 8:
498             mask = 0xFF;
499             break;
500         }
501 
502         int[] resultArray = new int[length];
503         int resultIndex = 0;
504         int run = 8 / bits;
505         while (resultIndex < length) {
506             byte tempByte = data[valueIndex++];
507             for (int runIndex = 0; runIndex < run; ++runIndex) {
508                 int offset = run - runIndex - 1;
509                 resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
510                         & mask];
511             }
512         }
513         return resultArray;
514     }
515 
mapToNon2OrderBitColor(byte[] data, int valueIndex, int length, int[] colorArray, int bits)516     private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
517             int length, int[] colorArray, int bits) {
518         if (0 == (8 % bits)) {
519             Rlog.e(LOG_TAG, "not odd number of color");
520             return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
521                     bits);
522         }
523 
524         int[] resultArray = new int[length];
525         // TODO fix me:
526         return resultArray;
527     }
528 
getCLUT(byte[] rawData, int offset, int number)529     private static int[] getCLUT(byte[] rawData, int offset, int number) {
530         if (null == rawData) {
531             return null;
532         }
533 
534         int[] result = new int[number];
535         int endIndex = offset + (number * 3); // 1 color use 3 bytes
536         int valueIndex = offset;
537         int colorIndex = 0;
538         int alpha = 0xff << 24;
539         do {
540             result[colorIndex++] = alpha
541                     | ((rawData[valueIndex++] & 0xFF) << 16)
542                     | ((rawData[valueIndex++] & 0xFF) << 8)
543                     | ((rawData[valueIndex++] & 0xFF));
544         } while (valueIndex < endIndex);
545         return result;
546     }
547 }
548