| /* |
| * Copyright (C) 2006 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.uicc; |
| |
| import android.content.res.Resources; |
| import android.content.res.Resources.NotFoundException; |
| import android.graphics.Bitmap; |
| import android.graphics.Color; |
| import android.telephony.Rlog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.GsmAlphabet; |
| |
| import dalvik.annotation.compat.UnsupportedAppUsage; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.util.List; |
| |
| /** |
| * Various methods, useful for dealing with SIM data. |
| */ |
| public class IccUtils { |
| static final String LOG_TAG="IccUtils"; |
| |
| // 3GPP specification constants |
| // Spec reference TS 31.102 section 4.2.16 |
| @VisibleForTesting |
| static final int FPLMN_BYTE_SIZE = 3; |
| |
| // A table mapping from a number to a hex character for fast encoding hex strings. |
| private static final char[] HEX_CHARS = { |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' |
| }; |
| |
| |
| /** |
| * Many fields in GSM SIM's are stored as nibble-swizzled BCD |
| * |
| * Assumes left-justified field that may be padded right with 0xf |
| * values. |
| * |
| * Stops on invalid BCD value, returning string so far |
| */ |
| @UnsupportedAppUsage |
| public static String |
| bcdToString(byte[] data, int offset, int length) { |
| StringBuilder ret = new StringBuilder(length*2); |
| |
| for (int i = offset ; i < offset + length ; i++) { |
| int v; |
| |
| v = data[i] & 0xf; |
| if (v > 9) break; |
| ret.append((char)('0' + v)); |
| |
| v = (data[i] >> 4) & 0xf; |
| // Some PLMNs have 'f' as high nibble, ignore it |
| if (v == 0xf) continue; |
| if (v > 9) break; |
| ret.append((char)('0' + v)); |
| } |
| |
| return ret.toString(); |
| } |
| |
| /** |
| * Converts a bcd byte array to String with offset 0 and byte array length. |
| */ |
| public static String bcdToString(byte[] data) { |
| return bcdToString(data, 0, data.length); |
| } |
| |
| /** |
| * Converts BCD string to bytes. |
| * |
| * @param bcd This should have an even length. If not, an "0" will be appended to the string. |
| */ |
| public static byte[] bcdToBytes(String bcd) { |
| byte[] output = new byte[(bcd.length() + 1) / 2]; |
| bcdToBytes(bcd, output); |
| return output; |
| } |
| |
| /** |
| * Converts BCD string to bytes and put it into the given byte array. |
| * |
| * @param bcd This should have an even length. If not, an "0" will be appended to the string. |
| * @param bytes If the array size is less than needed, the rest of the BCD string isn't be |
| * converted. If the array size is more than needed, the rest of array remains unchanged. |
| */ |
| public static void bcdToBytes(String bcd, byte[] bytes) { |
| bcdToBytes(bcd, bytes, 0); |
| } |
| |
| /** |
| * Converts BCD string to bytes and put it into the given byte array. |
| * |
| * @param bcd This should have an even length. If not, an "0" will be appended to the string. |
| * @param bytes If the array size is less than needed, the rest of the BCD string isn't be |
| * converted. If the array size is more than needed, the rest of array remains unchanged. |
| * @param offset the offset into the bytes[] to fill the data |
| */ |
| public static void bcdToBytes(String bcd, byte[] bytes, int offset) { |
| if (bcd.length() % 2 != 0) { |
| bcd += "0"; |
| } |
| int size = Math.min((bytes.length - offset) * 2, bcd.length()); |
| for (int i = 0, j = offset; i + 1 < size; i += 2, j++) { |
| bytes[j] = (byte) (charToByte(bcd.charAt(i + 1)) << 4 | charToByte(bcd.charAt(i))); |
| } |
| } |
| |
| /** |
| * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3 |
| * Returns a concatenated string of MCC+MNC, stripping |
| * all invalid character 'F' |
| */ |
| public static String bcdPlmnToString(byte[] data, int offset) { |
| if (offset + 3 > data.length) { |
| return null; |
| } |
| byte[] trans = new byte[3]; |
| trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF)); |
| trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF)); |
| trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF)); |
| String ret = bytesToHexString(trans); |
| |
| // For a valid plmn we trim all character 'F' |
| if (ret.contains("F")) { |
| ret = ret.replaceAll("F", ""); |
| } |
| return ret; |
| } |
| |
| /** |
| * Convert a 5 or 6 - digit PLMN string to a nibble-swizzled encoding as per 24.008 10.5.1.3 |
| * |
| * @param plmn the PLMN to convert |
| * @param data a byte array for the output |
| * @param offset the offset into data to start writing |
| */ |
| public static void stringToBcdPlmn(final String plmn, byte[] data, int offset) { |
| char digit6 = (plmn.length() > 5) ? plmn.charAt(5) : 'F'; |
| data[offset] = (byte) (charToByte(plmn.charAt(1)) << 4 | charToByte(plmn.charAt(0))); |
| data[offset + 1] = (byte) (charToByte(digit6) << 4 | charToByte(plmn.charAt(2))); |
| data[offset + 2] = (byte) (charToByte(plmn.charAt(4)) << 4 | charToByte(plmn.charAt(3))); |
| } |
| |
| /** |
| * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH |
| */ |
| public static String |
| bchToString(byte[] data, int offset, int length) { |
| StringBuilder ret = new StringBuilder(length*2); |
| |
| for (int i = offset ; i < offset + length ; i++) { |
| int v; |
| |
| v = data[i] & 0xf; |
| ret.append(HEX_CHARS[v]); |
| |
| v = (data[i] >> 4) & 0xf; |
| ret.append(HEX_CHARS[v]); |
| } |
| |
| return ret.toString(); |
| } |
| |
| /** |
| * Decode cdma byte into String. |
| */ |
| @UnsupportedAppUsage |
| public static String |
| cdmaBcdToString(byte[] data, int offset, int length) { |
| StringBuilder ret = new StringBuilder(length); |
| |
| int count = 0; |
| for (int i = offset; count < length; i++) { |
| int v; |
| v = data[i] & 0xf; |
| if (v > 9) v = 0; |
| ret.append((char)('0' + v)); |
| |
| if (++count == length) break; |
| |
| v = (data[i] >> 4) & 0xf; |
| if (v > 9) v = 0; |
| ret.append((char)('0' + v)); |
| ++count; |
| } |
| return ret.toString(); |
| } |
| |
| /** |
| * Decodes a GSM-style BCD byte, returning an int ranging from 0-99. |
| * |
| * In GSM land, the least significant BCD digit is stored in the most |
| * significant nibble. |
| * |
| * Out-of-range digits are treated as 0 for the sake of the time stamp, |
| * because of this: |
| * |
| * TS 23.040 section 9.2.3.11 |
| * "if the MS receives a non-integer value in the SCTS, it shall |
| * assume the digit is set to 0 but shall store the entire field |
| * exactly as received" |
| */ |
| @UnsupportedAppUsage |
| public static int |
| gsmBcdByteToInt(byte b) { |
| int ret = 0; |
| |
| // treat out-of-range BCD values as 0 |
| if ((b & 0xf0) <= 0x90) { |
| ret = (b >> 4) & 0xf; |
| } |
| |
| if ((b & 0x0f) <= 0x09) { |
| ret += (b & 0xf) * 10; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but |
| * opposite nibble format. The least significant BCD digit |
| * is in the least significant nibble and the most significant |
| * is in the most significant nibble. |
| */ |
| @UnsupportedAppUsage |
| public static int |
| cdmaBcdByteToInt(byte b) { |
| int ret = 0; |
| |
| // treat out-of-range BCD values as 0 |
| if ((b & 0xf0) <= 0x90) { |
| ret = ((b >> 4) & 0xf) * 10; |
| } |
| |
| if ((b & 0x0f) <= 0x09) { |
| ret += (b & 0xf); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Decodes a string field that's formatted like the EF[ADN] alpha |
| * identifier |
| * |
| * From TS 51.011 10.5.1: |
| * Coding: |
| * this alpha tagging shall use either |
| * - the SMS default 7 bit coded alphabet as defined in |
| * TS 23.038 [12] with bit 8 set to 0. The alpha identifier |
| * shall be left justified. Unused bytes shall be set to 'FF'; or |
| * - one of the UCS2 coded options as defined in annex B. |
| * |
| * Annex B from TS 11.11 V8.13.0: |
| * 1) If the first octet in the alpha string is '80', then the |
| * remaining octets are 16 bit UCS2 characters ... |
| * 2) if the first octet in the alpha string is '81', then the |
| * second octet contains a value indicating the number of |
| * characters in the string, and the third octet contains an |
| * 8 bit number which defines bits 15 to 8 of a 16 bit |
| * base pointer, where bit 16 is set to zero and bits 7 to 1 |
| * are also set to zero. These sixteen bits constitute a |
| * base pointer to a "half page" in the UCS2 code space, to be |
| * used with some or all of the remaining octets in the string. |
| * The fourth and subsequent octets contain codings as follows: |
| * If bit 8 of the octet is set to zero, the remaining 7 bits |
| * of the octet contain a GSM Default Alphabet character, |
| * whereas if bit 8 of the octet is set to one, then the |
| * remaining seven bits are an offset value added to the |
| * 16 bit base pointer defined earlier... |
| * 3) If the first octet of the alpha string is set to '82', then |
| * the second octet contains a value indicating the number of |
| * characters in the string, and the third and fourth octets |
| * contain a 16 bit number which defines the complete 16 bit |
| * base pointer to a "half page" in the UCS2 code space... |
| */ |
| @UnsupportedAppUsage |
| public static String |
| adnStringFieldToString(byte[] data, int offset, int length) { |
| if (length == 0) { |
| return ""; |
| } |
| if (length >= 1) { |
| if (data[offset] == (byte) 0x80) { |
| int ucslen = (length - 1) / 2; |
| String ret = null; |
| |
| try { |
| ret = new String(data, offset + 1, ucslen * 2, "utf-16be"); |
| } catch (UnsupportedEncodingException ex) { |
| Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", |
| ex); |
| } |
| |
| if (ret != null) { |
| // trim off trailing FFFF characters |
| |
| ucslen = ret.length(); |
| while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF') |
| ucslen--; |
| |
| return ret.substring(0, ucslen); |
| } |
| } |
| } |
| |
| boolean isucs2 = false; |
| char base = '\0'; |
| int len = 0; |
| |
| if (length >= 3 && data[offset] == (byte) 0x81) { |
| len = data[offset + 1] & 0xFF; |
| if (len > length - 3) |
| len = length - 3; |
| |
| base = (char) ((data[offset + 2] & 0xFF) << 7); |
| offset += 3; |
| isucs2 = true; |
| } else if (length >= 4 && data[offset] == (byte) 0x82) { |
| len = data[offset + 1] & 0xFF; |
| if (len > length - 4) |
| len = length - 4; |
| |
| base = (char) (((data[offset + 2] & 0xFF) << 8) | |
| (data[offset + 3] & 0xFF)); |
| offset += 4; |
| isucs2 = true; |
| } |
| |
| if (isucs2) { |
| StringBuilder ret = new StringBuilder(); |
| |
| while (len > 0) { |
| // UCS2 subset case |
| |
| if (data[offset] < 0) { |
| ret.append((char) (base + (data[offset] & 0x7F))); |
| offset++; |
| len--; |
| } |
| |
| // GSM character set case |
| |
| int count = 0; |
| while (count < len && data[offset + count] >= 0) |
| count++; |
| |
| ret.append(GsmAlphabet.gsm8BitUnpackedToString(data, |
| offset, count)); |
| |
| offset += count; |
| len -= count; |
| } |
| |
| return ret.toString(); |
| } |
| |
| Resources resource = Resources.getSystem(); |
| String defaultCharset = ""; |
| try { |
| defaultCharset = |
| resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset); |
| } catch (NotFoundException e) { |
| // Ignore Exception and defaultCharset is set to a empty string. |
| } |
| return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim()); |
| } |
| |
| @UnsupportedAppUsage |
| public static int |
| hexCharToInt(char c) { |
| if (c >= '0' && c <= '9') return (c - '0'); |
| if (c >= 'A' && c <= 'F') return (c - 'A' + 10); |
| if (c >= 'a' && c <= 'f') return (c - 'a' + 10); |
| |
| throw new RuntimeException ("invalid hex char '" + c + "'"); |
| } |
| |
| /** |
| * Converts a hex String to a byte array. |
| * |
| * @param s A string of hexadecimal characters, must be an even number of |
| * chars long |
| * |
| * @return byte array representation |
| * |
| * @throws RuntimeException on invalid format |
| */ |
| @UnsupportedAppUsage |
| public static byte[] |
| hexStringToBytes(String s) { |
| byte[] ret; |
| |
| if (s == null) return null; |
| |
| int sz = s.length(); |
| |
| ret = new byte[sz/2]; |
| |
| for (int i=0 ; i <sz ; i+=2) { |
| ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4) |
| | hexCharToInt(s.charAt(i+1))); |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * Converts a byte array into a String of hexadecimal characters. |
| * |
| * @param bytes an array of bytes |
| * |
| * @return hex string representation of bytes array |
| */ |
| @UnsupportedAppUsage |
| public static String |
| bytesToHexString(byte[] bytes) { |
| if (bytes == null) return null; |
| |
| StringBuilder ret = new StringBuilder(2*bytes.length); |
| |
| for (int i = 0 ; i < bytes.length ; i++) { |
| int b; |
| |
| b = 0x0f & (bytes[i] >> 4); |
| |
| ret.append(HEX_CHARS[b]); |
| |
| b = 0x0f & bytes[i]; |
| |
| ret.append(HEX_CHARS[b]); |
| } |
| |
| return ret.toString(); |
| } |
| |
| |
| /** |
| * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string |
| * "offset" points to "octet 3", the coding scheme byte |
| * empty string returned on decode error |
| */ |
| @UnsupportedAppUsage |
| public static String |
| networkNameToString(byte[] data, int offset, int length) { |
| String ret; |
| |
| if ((data[offset] & 0x80) != 0x80 || length < 1) { |
| return ""; |
| } |
| |
| switch ((data[offset] >>> 4) & 0x7) { |
| case 0: |
| // SMS character set |
| int countSeptets; |
| int unusedBits = data[offset] & 7; |
| countSeptets = (((length - 1) * 8) - unusedBits) / 7 ; |
| ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets); |
| break; |
| case 1: |
| // UCS2 |
| try { |
| ret = new String(data, |
| offset + 1, length - 1, "utf-16"); |
| } catch (UnsupportedEncodingException ex) { |
| ret = ""; |
| Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex); |
| } |
| break; |
| |
| // unsupported encoding |
| default: |
| ret = ""; |
| break; |
| } |
| |
| // "Add CI" |
| // "The MS should add the letters for the Country's Initials and |
| // a separator (e.g. a space) to the text string" |
| |
| if ((data[offset] & 0x40) != 0) { |
| // FIXME(mkf) add country initials here |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Convert a TS 131.102 image instance of code scheme '11' into Bitmap |
| * @param data The raw data |
| * @param length The length of image body |
| * @return The bitmap |
| */ |
| @UnsupportedAppUsage |
| public static Bitmap parseToBnW(byte[] data, int length){ |
| int valueIndex = 0; |
| int width = data[valueIndex++] & 0xFF; |
| int height = data[valueIndex++] & 0xFF; |
| int numOfPixels = width*height; |
| |
| int[] pixels = new int[numOfPixels]; |
| |
| int pixelIndex = 0; |
| int bitIndex = 7; |
| byte currentByte = 0x00; |
| while (pixelIndex < numOfPixels) { |
| // reassign data and index for every byte (8 bits). |
| if (pixelIndex % 8 == 0) { |
| currentByte = data[valueIndex++]; |
| bitIndex = 7; |
| } |
| pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01); |
| } |
| |
| if (pixelIndex != numOfPixels) { |
| Rlog.e(LOG_TAG, "parse end and size error"); |
| } |
| return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); |
| } |
| |
| private static int bitToRGB(int bit){ |
| if(bit == 1){ |
| return Color.WHITE; |
| } else { |
| return Color.BLACK; |
| } |
| } |
| |
| /** |
| * a TS 131.102 image instance of code scheme '11' into color Bitmap |
| * |
| * @param data The raw data |
| * @param length the length of image body |
| * @param transparency with or without transparency |
| * @return The color bitmap |
| */ |
| @UnsupportedAppUsage |
| public static Bitmap parseToRGB(byte[] data, int length, |
| boolean transparency) { |
| int valueIndex = 0; |
| int width = data[valueIndex++] & 0xFF; |
| int height = data[valueIndex++] & 0xFF; |
| int bits = data[valueIndex++] & 0xFF; |
| int colorNumber = data[valueIndex++] & 0xFF; |
| int clutOffset = ((data[valueIndex++] & 0xFF) << 8) |
| | (data[valueIndex++] & 0xFF); |
| |
| int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber); |
| if (true == transparency) { |
| colorIndexArray[colorNumber - 1] = Color.TRANSPARENT; |
| } |
| |
| int[] resultArray = null; |
| if (0 == (8 % bits)) { |
| resultArray = mapTo2OrderBitColor(data, valueIndex, |
| (width * height), colorIndexArray, bits); |
| } else { |
| resultArray = mapToNon2OrderBitColor(data, valueIndex, |
| (width * height), colorIndexArray, bits); |
| } |
| |
| return Bitmap.createBitmap(resultArray, width, height, |
| Bitmap.Config.RGB_565); |
| } |
| |
| private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex, |
| int length, int[] colorArray, int bits) { |
| if (0 != (8 % bits)) { |
| Rlog.e(LOG_TAG, "not event number of color"); |
| return mapToNon2OrderBitColor(data, valueIndex, length, colorArray, |
| bits); |
| } |
| |
| int mask = 0x01; |
| switch (bits) { |
| case 1: |
| mask = 0x01; |
| break; |
| case 2: |
| mask = 0x03; |
| break; |
| case 4: |
| mask = 0x0F; |
| break; |
| case 8: |
| mask = 0xFF; |
| break; |
| } |
| |
| int[] resultArray = new int[length]; |
| int resultIndex = 0; |
| int run = 8 / bits; |
| while (resultIndex < length) { |
| byte tempByte = data[valueIndex++]; |
| for (int runIndex = 0; runIndex < run; ++runIndex) { |
| int offset = run - runIndex - 1; |
| resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits)) |
| & mask]; |
| } |
| } |
| return resultArray; |
| } |
| |
| private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex, |
| int length, int[] colorArray, int bits) { |
| if (0 == (8 % bits)) { |
| Rlog.e(LOG_TAG, "not odd number of color"); |
| return mapTo2OrderBitColor(data, valueIndex, length, colorArray, |
| bits); |
| } |
| |
| int[] resultArray = new int[length]; |
| // TODO fix me: |
| return resultArray; |
| } |
| |
| private static int[] getCLUT(byte[] rawData, int offset, int number) { |
| if (null == rawData) { |
| return null; |
| } |
| |
| int[] result = new int[number]; |
| int endIndex = offset + (number * 3); // 1 color use 3 bytes |
| int valueIndex = offset; |
| int colorIndex = 0; |
| int alpha = 0xff << 24; |
| do { |
| result[colorIndex++] = alpha |
| | ((rawData[valueIndex++] & 0xFF) << 16) |
| | ((rawData[valueIndex++] & 0xFF) << 8) |
| | ((rawData[valueIndex++] & 0xFF)); |
| } while (valueIndex < endIndex); |
| return result; |
| } |
| |
| public static String getDecimalSubstring(String iccId) { |
| int position; |
| for (position = 0; position < iccId.length(); position ++) { |
| if (!Character.isDigit(iccId.charAt(position))) break; |
| } |
| return iccId.substring( 0, position ); |
| } |
| |
| /** |
| * Converts a series of bytes to an integer. This method currently only supports positive 32-bit |
| * integers. |
| * |
| * @param src The source bytes. |
| * @param offset The position of the first byte of the data to be converted. The data is base |
| * 256 with the most significant digit first. |
| * @param length The length of the data to be converted. It must be <= 4. |
| * @throws IllegalArgumentException If {@code length} is bigger than 4 or {@code src} cannot be |
| * parsed as a positive integer. |
| * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length} |
| * exceeds the bounds of {@code src}. |
| */ |
| public static int bytesToInt(byte[] src, int offset, int length) { |
| if (length > 4) { |
| throw new IllegalArgumentException( |
| "length must be <= 4 (only 32-bit integer supported): " + length); |
| } |
| if (offset < 0 || length < 0 || offset + length > src.length) { |
| throw new IndexOutOfBoundsException( |
| "Out of the bounds: src=[" |
| + src.length |
| + "], offset=" |
| + offset |
| + ", length=" |
| + length); |
| } |
| int result = 0; |
| for (int i = 0; i < length; i++) { |
| result = (result << 8) | (src[offset + i] & 0xFF); |
| } |
| if (result < 0) { |
| throw new IllegalArgumentException( |
| "src cannot be parsed as a positive integer: " + result); |
| } |
| return result; |
| } |
| |
| /** |
| * Converts a series of bytes to a raw long variable which can be both positive and negative. |
| * This method currently only supports 64-bit long variable. |
| * |
| * @param src The source bytes. |
| * @param offset The position of the first byte of the data to be converted. The data is base |
| * 256 with the most significant digit first. |
| * @param length The length of the data to be converted. It must be <= 8. |
| * @throws IllegalArgumentException If {@code length} is bigger than 8. |
| * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length} |
| * exceeds the bounds of {@code src}. |
| */ |
| public static long bytesToRawLong(byte[] src, int offset, int length) { |
| if (length > 8) { |
| throw new IllegalArgumentException( |
| "length must be <= 8 (only 64-bit long supported): " + length); |
| } |
| if (offset < 0 || length < 0 || offset + length > src.length) { |
| throw new IndexOutOfBoundsException( |
| "Out of the bounds: src=[" |
| + src.length |
| + "], offset=" |
| + offset |
| + ", length=" |
| + length); |
| } |
| long result = 0; |
| for (int i = 0; i < length; i++) { |
| result = (result << 8) | (src[offset + i] & 0xFF); |
| } |
| return result; |
| } |
| |
| /** |
| * Converts an integer to a new byte array with base 256 and the most significant digit first. |
| * |
| * @throws IllegalArgumentException If {@code value} is negative. |
| */ |
| public static byte[] unsignedIntToBytes(int value) { |
| if (value < 0) { |
| throw new IllegalArgumentException("value must be 0 or positive: " + value); |
| } |
| byte[] bytes = new byte[byteNumForUnsignedInt(value)]; |
| unsignedIntToBytes(value, bytes, 0); |
| return bytes; |
| } |
| |
| /** |
| * Converts an integer to a new byte array with base 256 and the most significant digit first. |
| * The first byte's highest bit is used for sign. If the most significant digit is larger than |
| * 127, an extra byte (0) will be prepended before it. This method currently doesn't support |
| * negative values. |
| * |
| * @throws IllegalArgumentException If {@code value} is negative. |
| */ |
| public static byte[] signedIntToBytes(int value) { |
| if (value < 0) { |
| throw new IllegalArgumentException("value must be 0 or positive: " + value); |
| } |
| byte[] bytes = new byte[byteNumForSignedInt(value)]; |
| signedIntToBytes(value, bytes, 0); |
| return bytes; |
| } |
| |
| /** |
| * Converts an integer to a series of bytes with base 256 and the most significant digit first. |
| * |
| * @param value The integer to be converted. |
| * @param dest The destination byte array. |
| * @param offset The start offset of the byte array. |
| * @return The number of byte needeed. |
| * @throws IllegalArgumentException If {@code value} is negative. |
| * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}. |
| */ |
| public static int unsignedIntToBytes(int value, byte[] dest, int offset) { |
| return intToBytes(value, dest, offset, false); |
| } |
| |
| /** |
| * Converts an integer to a series of bytes with base 256 and the most significant digit first. |
| * The first byte's highest bit is used for sign. If the most significant digit is larger than |
| * 127, an extra byte (0) will be prepended before it. This method currently doesn't support |
| * negative values. |
| * |
| * @throws IllegalArgumentException If {@code value} is negative. |
| * @throws IndexOutOfBoundsException If {@code offset} exceeds the bounds of {@code dest}. |
| */ |
| public static int signedIntToBytes(int value, byte[] dest, int offset) { |
| return intToBytes(value, dest, offset, true); |
| } |
| |
| /** |
| * Calculates the number of required bytes to represent {@code value}. The bytes will be base |
| * 256 with the most significant digit first. |
| * |
| * @throws IllegalArgumentException If {@code value} is negative. |
| */ |
| public static int byteNumForUnsignedInt(int value) { |
| return byteNumForInt(value, false); |
| } |
| |
| /** |
| * Calculates the number of required bytes to represent {@code value}. The bytes will be base |
| * 256 with the most significant digit first. If the most significant digit is larger than 127, |
| * an extra byte (0) will be prepended before it. This method currently only supports positive |
| * integers. |
| * |
| * @throws IllegalArgumentException If {@code value} is negative. |
| */ |
| public static int byteNumForSignedInt(int value) { |
| return byteNumForInt(value, true); |
| } |
| |
| private static int intToBytes(int value, byte[] dest, int offset, boolean signed) { |
| int l = byteNumForInt(value, signed); |
| if (offset < 0 || offset + l > dest.length) { |
| throw new IndexOutOfBoundsException("Not enough space to write. Required bytes: " + l); |
| } |
| for (int i = l - 1, v = value; i >= 0; i--, v >>>= 8) { |
| byte b = (byte) (v & 0xFF); |
| dest[offset + i] = b; |
| } |
| return l; |
| } |
| |
| private static int byteNumForInt(int value, boolean signed) { |
| if (value < 0) { |
| throw new IllegalArgumentException("value must be 0 or positive: " + value); |
| } |
| if (signed) { |
| if (value <= 0x7F) { |
| return 1; |
| } |
| if (value <= 0x7FFF) { |
| return 2; |
| } |
| if (value <= 0x7FFFFF) { |
| return 3; |
| } |
| } else { |
| if (value <= 0xFF) { |
| return 1; |
| } |
| if (value <= 0xFFFF) { |
| return 2; |
| } |
| if (value <= 0xFFFFFF) { |
| return 3; |
| } |
| } |
| return 4; |
| } |
| |
| |
| /** |
| * Counts the number of trailing zero bits of a byte. |
| */ |
| public static byte countTrailingZeros(byte b) { |
| if (b == 0) { |
| return 8; |
| } |
| int v = b & 0xFF; |
| byte c = 7; |
| if ((v & 0x0F) != 0) { |
| c -= 4; |
| } |
| if ((v & 0x33) != 0) { |
| c -= 2; |
| } |
| if ((v & 0x55) != 0) { |
| c -= 1; |
| } |
| return c; |
| } |
| |
| /** |
| * Converts a byte to a hex string. |
| */ |
| public static String byteToHex(byte b) { |
| return new String(new char[] {HEX_CHARS[(b & 0xFF) >>> 4], HEX_CHARS[b & 0xF]}); |
| } |
| |
| /** |
| * Strip all the trailing 'F' characters of a string, e.g., an ICCID. |
| */ |
| public static String stripTrailingFs(String s) { |
| return s == null ? null : s.replaceAll("(?i)f*$", ""); |
| } |
| |
| /** |
| * Converts a character of [0-9a-fA-F] to its hex value in a byte. If the character is not a |
| * hex number, 0 will be returned. |
| */ |
| private static byte charToByte(char c) { |
| if (c >= 0x30 && c <= 0x39) { |
| return (byte) (c - 0x30); |
| } else if (c >= 0x41 && c <= 0x46) { |
| return (byte) (c - 0x37); |
| } else if (c >= 0x61 && c <= 0x66) { |
| return (byte) (c - 0x57); |
| } |
| return 0; |
| } |
| |
| /** |
| * Encode the Fplmns into byte array to write to EF. |
| * |
| * @param fplmns Array of fplmns to be serialized. |
| * @param dataLength the size of the EF file. |
| * @return the encoded byte array in the correct format for FPLMN file. |
| */ |
| public static byte[] encodeFplmns(List<String> fplmns, int dataLength) { |
| byte[] serializedFplmns = new byte[dataLength]; |
| int offset = 0; |
| for (String fplmn : fplmns) { |
| if (offset >= dataLength) break; |
| stringToBcdPlmn(fplmn, serializedFplmns, offset); |
| offset += FPLMN_BYTE_SIZE; |
| } |
| //pads to the length of the EF file. |
| while (offset < dataLength) { |
| // required by 3GPP TS 31.102 spec 4.2.16 |
| serializedFplmns[offset++] = (byte) 0xff; |
| } |
| return serializedFplmns; |
| } |
| } |