blob: f518d53fa517718787e4881cb94456fa5aeb041e [file] [log] [blame]
/*
* 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.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.telephony.UiccPortInfo;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.GsmAlphabet;
import com.android.telephony.Rlog;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
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;
// ICCID used for tests by some OEMs
public static final String TEST_ICCID = UiccPortInfo.ICCID_REDACTED;
// 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;
}
/**
* Encodes a string to be formatted like the EF[ADN] alpha identifier.
*
* <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on
* the relevant specs.
*
* <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if
* there are characters that are not supported by it.
*
* @return the encoded string including the prefix byte necessary to identify the encoding.
* @see #adnStringFieldToString(byte[], int, int)
*/
@NonNull
public static byte[] stringToAdnStringField(@NonNull String alphaTag) {
int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0);
if (septets != -1) {
byte[] ret = new byte[septets];
GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length);
return ret;
}
// Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to
// validate that the string contains only valid UCS-2 characters. Since the read path
// in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine
// (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's
// already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction.
byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE);
byte[] ret = new byte[alphaTagBytes.length + 1];
// 0x80 tags the remaining bytes as UCS-2
ret[0] = (byte) 0x80;
System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length);
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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) {
if (TEST_ICCID.equals(s)) {
return s;
}
return s == null ? null : s.replaceAll("(?i)f*$", "");
}
/**
* Strip all the trailing 'F' characters of a string if exists and compare.
*/
public static boolean compareIgnoreTrailingFs(String a, String b) {
return TextUtils.equals(a, b) || TextUtils.equals(stripTrailingFs(a), stripTrailingFs(b));
}
/**
* 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;
}
}