Merge "Add asn1 to platform."
am: bdd0da8fa7
Change-Id: Ic34f9bd45e7fb46bae2afe5be0cc453d87b276bd
This commit is contained in:
@@ -32,6 +32,12 @@ import java.io.UnsupportedEncodingException;
|
|||||||
public class IccUtils {
|
public class IccUtils {
|
||||||
static final String LOG_TAG="IccUtils";
|
static final String LOG_TAG="IccUtils";
|
||||||
|
|
||||||
|
// 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
|
* Many fields in GSM SIM's are stored as nibble-swizzled BCD
|
||||||
*
|
*
|
||||||
@@ -61,6 +67,41 @@ public class IccUtils {
|
|||||||
return ret.toString();
|
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) {
|
||||||
|
if (bcd.length() % 2 != 0) {
|
||||||
|
bcd += "0";
|
||||||
|
}
|
||||||
|
int size = Math.min(bytes.length * 2, bcd.length());
|
||||||
|
for (int i = 0, j = 0; 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
|
* PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
|
||||||
* Returns a concatenated string of MCC+MNC, stripping
|
* Returns a concatenated string of MCC+MNC, stripping
|
||||||
@@ -94,10 +135,10 @@ public class IccUtils {
|
|||||||
int v;
|
int v;
|
||||||
|
|
||||||
v = data[i] & 0xf;
|
v = data[i] & 0xf;
|
||||||
ret.append("0123456789abcdef".charAt(v));
|
ret.append(HEX_CHARS[v]);
|
||||||
|
|
||||||
v = (data[i] >> 4) & 0xf;
|
v = (data[i] >> 4) & 0xf;
|
||||||
ret.append("0123456789abcdef".charAt(v));
|
ret.append(HEX_CHARS[v]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.toString();
|
return ret.toString();
|
||||||
@@ -305,7 +346,7 @@ public class IccUtils {
|
|||||||
return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
|
return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
public static int
|
||||||
hexCharToInt(char c) {
|
hexCharToInt(char c) {
|
||||||
if (c >= '0' && c <= '9') return (c - '0');
|
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);
|
||||||
@@ -361,11 +402,11 @@ public class IccUtils {
|
|||||||
|
|
||||||
b = 0x0f & (bytes[i] >> 4);
|
b = 0x0f & (bytes[i] >> 4);
|
||||||
|
|
||||||
ret.append("0123456789abcdef".charAt(b));
|
ret.append(HEX_CHARS[b]);
|
||||||
|
|
||||||
b = 0x0f & bytes[i];
|
b = 0x0f & bytes[i];
|
||||||
|
|
||||||
ret.append("0123456789abcdef".charAt(b));
|
ret.append(HEX_CHARS[b]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.toString();
|
return ret.toString();
|
||||||
@@ -416,7 +457,6 @@ public class IccUtils {
|
|||||||
|
|
||||||
if ((data[offset] & 0x40) != 0) {
|
if ((data[offset] & 0x40) != 0) {
|
||||||
// FIXME(mkf) add country initials here
|
// FIXME(mkf) add country initials here
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -575,4 +615,239 @@ public class IccUtils {
|
|||||||
}
|
}
|
||||||
return iccId.substring( 0, position );
|
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]});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a character of [0-9a-aA-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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.asn1;
|
||||||
|
|
||||||
|
import com.android.internal.telephony.uicc.IccUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a decoder helping decode an array of bytes or a hex string into
|
||||||
|
* {@link Asn1Node}s. This class tracks the next position for decoding. This class is not
|
||||||
|
* thread-safe.
|
||||||
|
*/
|
||||||
|
public final class Asn1Decoder {
|
||||||
|
// Source byte array.
|
||||||
|
private final byte[] mSrc;
|
||||||
|
// Next position of the byte in the source array for decoding.
|
||||||
|
private int mPosition;
|
||||||
|
// Exclusive end of the range in the array for decoding.
|
||||||
|
private final int mEnd;
|
||||||
|
|
||||||
|
/** Creates a decoder on a hex string. */
|
||||||
|
public Asn1Decoder(String hex) {
|
||||||
|
this(IccUtils.hexStringToBytes(hex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a decoder on a byte array. */
|
||||||
|
public Asn1Decoder(byte[] src) {
|
||||||
|
this(src, 0, src.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a decoder on a byte array slice.
|
||||||
|
*
|
||||||
|
* @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
|
||||||
|
* exceeds the bounds of {@code bytes}.
|
||||||
|
*/
|
||||||
|
public Asn1Decoder(byte[] bytes, int offset, int length) {
|
||||||
|
if (offset < 0 || length < 0 || offset + length > bytes.length) {
|
||||||
|
throw new IndexOutOfBoundsException(
|
||||||
|
"Out of the bounds: bytes=["
|
||||||
|
+ bytes.length
|
||||||
|
+ "], offset="
|
||||||
|
+ offset
|
||||||
|
+ ", length="
|
||||||
|
+ length);
|
||||||
|
}
|
||||||
|
mSrc = bytes;
|
||||||
|
mPosition = offset;
|
||||||
|
mEnd = offset + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The next start position for decoding. */
|
||||||
|
public int getPosition() {
|
||||||
|
return mPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the node has a next node. */
|
||||||
|
public boolean hasNextNode() {
|
||||||
|
return mPosition < mEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the next node. If the node is a constructed node, its children will be parsed only
|
||||||
|
* when they are accessed, e.g., though {@link Asn1Node#getChildren()}.
|
||||||
|
*
|
||||||
|
* @return The next decoded {@link Asn1Node}. If success, the next decoding position will also
|
||||||
|
* be updated. If any error happens, e.g., moving over the end position, {@code null}
|
||||||
|
* will be returned and the next decoding position won't be modified.
|
||||||
|
* @throws InvalidAsn1DataException If the bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public Asn1Node nextNode() throws InvalidAsn1DataException {
|
||||||
|
if (mPosition >= mEnd) {
|
||||||
|
throw new IllegalStateException("No bytes to parse.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = mPosition;
|
||||||
|
|
||||||
|
// Extracts the tag.
|
||||||
|
int tagStart = offset;
|
||||||
|
byte b = mSrc[offset++];
|
||||||
|
if ((b & 0x1F) == 0x1F) {
|
||||||
|
// High-tag-number form
|
||||||
|
while (offset < mEnd && (mSrc[offset++] & 0x80) != 0) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offset >= mEnd) {
|
||||||
|
// No length bytes or the tag is too long.
|
||||||
|
throw new InvalidAsn1DataException(0, "Invalid length at position: " + offset);
|
||||||
|
}
|
||||||
|
int tag;
|
||||||
|
try {
|
||||||
|
tag = IccUtils.bytesToInt(mSrc, tagStart, offset - tagStart);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Cannot parse the tag as an integer.
|
||||||
|
throw new InvalidAsn1DataException(0, "Cannot parse tag at position: " + tagStart, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracts the length.
|
||||||
|
int dataLen;
|
||||||
|
b = mSrc[offset++];
|
||||||
|
if ((b & 0x80) == 0) {
|
||||||
|
// Short-form length
|
||||||
|
dataLen = b;
|
||||||
|
} else {
|
||||||
|
// Long-form length
|
||||||
|
int lenLen = b & 0x7F;
|
||||||
|
if (offset + lenLen > mEnd) {
|
||||||
|
// No enough bytes for the long-form length
|
||||||
|
throw new InvalidAsn1DataException(
|
||||||
|
tag, "Cannot parse length at position: " + offset);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dataLen = IccUtils.bytesToInt(mSrc, offset, lenLen);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Cannot parse the data length as an integer.
|
||||||
|
throw new InvalidAsn1DataException(
|
||||||
|
tag, "Cannot parse length at position: " + offset, e);
|
||||||
|
}
|
||||||
|
offset += lenLen;
|
||||||
|
}
|
||||||
|
if (offset + dataLen > mEnd) {
|
||||||
|
// No enough data left.
|
||||||
|
throw new InvalidAsn1DataException(
|
||||||
|
tag,
|
||||||
|
"Incomplete data at position: "
|
||||||
|
+ offset
|
||||||
|
+ ", expected bytes: "
|
||||||
|
+ dataLen
|
||||||
|
+ ", actual bytes: "
|
||||||
|
+ (mEnd - offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
Asn1Node root = new Asn1Node(tag, mSrc, offset, dataLen);
|
||||||
|
mPosition = offset + dataLen;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,598 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.asn1;
|
||||||
|
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.internal.telephony.uicc.IccUtils;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a primitive or constructed data defined by ASN.1. A constructed node can have
|
||||||
|
* child nodes. A non-constructed node can have a value. This class is read-only. To build a node,
|
||||||
|
* you can use the {@link #newBuilder(int)} method to get a {@link Builder} instance. This class is
|
||||||
|
* not thread-safe.
|
||||||
|
*/
|
||||||
|
public final class Asn1Node {
|
||||||
|
private static final int INT_BYTES = Integer.SIZE / Byte.SIZE;
|
||||||
|
private static final List<Asn1Node> EMPTY_NODE_LIST = Collections.emptyList();
|
||||||
|
|
||||||
|
// Bytes for boolean values.
|
||||||
|
private static final byte[] TRUE_BYTES = new byte[] {-1};
|
||||||
|
private static final byte[] FALSE_BYTES = new byte[] {0};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to build an Asn1Node instance of a constructed tag. This class is not
|
||||||
|
* thread-safe.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
private final int mTag;
|
||||||
|
private final List<Asn1Node> mChildren;
|
||||||
|
|
||||||
|
private Builder(int tag) {
|
||||||
|
if (!isConstructedTag(tag)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Builder should be created for a constructed tag: " + tag);
|
||||||
|
}
|
||||||
|
mTag = tag;
|
||||||
|
mChildren = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child from an existing node.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalArgumentException If the child is a non-existing node.
|
||||||
|
*/
|
||||||
|
public Builder addChild(Asn1Node child) {
|
||||||
|
mChildren.add(child);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child from another builder. The child will be built with the call to this method,
|
||||||
|
* and any changes to the child builder after the call to this method doesn't have effect.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
public Builder addChild(Builder child) {
|
||||||
|
mChildren.add(child.build());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds children from bytes. This method calls {@link Asn1Decoder} to verify the {@code
|
||||||
|
* encodedBytes} and adds all nodes parsed from it as children.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public Builder addChildren(byte[] encodedBytes) throws InvalidAsn1DataException {
|
||||||
|
Asn1Decoder subDecoder = new Asn1Decoder(encodedBytes, 0, encodedBytes.length);
|
||||||
|
while (subDecoder.hasNextNode()) {
|
||||||
|
mChildren.add(subDecoder.nextNode());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child of non-constructed tag with an integer as the data.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If the {@code tag} is not constructed..
|
||||||
|
*/
|
||||||
|
public Builder addChildAsInteger(int tag, int value) {
|
||||||
|
if (isConstructedTag(tag)) {
|
||||||
|
throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
|
||||||
|
}
|
||||||
|
byte[] dataBytes = IccUtils.signedIntToBytes(value);
|
||||||
|
addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child of non-constructed tag with a string as the data.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If the {@code tag} is not constructed..
|
||||||
|
*/
|
||||||
|
public Builder addChildAsString(int tag, String value) {
|
||||||
|
if (isConstructedTag(tag)) {
|
||||||
|
throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
|
||||||
|
}
|
||||||
|
byte[] dataBytes = value.getBytes(StandardCharsets.UTF_8);
|
||||||
|
addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child of non-constructed tag with a byte array as the data.
|
||||||
|
*
|
||||||
|
* @param value The value will be owned by this node.
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If the {@code tag} is not constructed..
|
||||||
|
*/
|
||||||
|
public Builder addChildAsBytes(int tag, byte[] value) {
|
||||||
|
if (isConstructedTag(tag)) {
|
||||||
|
throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
|
||||||
|
}
|
||||||
|
addChild(new Asn1Node(tag, value, 0, value.length));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child of non-constructed tag with a byte array as the data from a hex string.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If the {@code tag} is not constructed..
|
||||||
|
*/
|
||||||
|
public Builder addChildAsBytesFromHex(int tag, String hex) {
|
||||||
|
return addChildAsBytes(tag, IccUtils.hexStringToBytes(hex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child of non-constructed tag with bits as the data.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If the {@code tag} is not constructed..
|
||||||
|
*/
|
||||||
|
public Builder addChildAsBits(int tag, int value) {
|
||||||
|
if (isConstructedTag(tag)) {
|
||||||
|
throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
|
||||||
|
}
|
||||||
|
// Always allocate 5 bytes for simplicity.
|
||||||
|
byte[] dataBytes = new byte[INT_BYTES + 1];
|
||||||
|
// Puts the integer into the byte[1-4].
|
||||||
|
value = Integer.reverse(value);
|
||||||
|
int dataLength = 0;
|
||||||
|
for (int i = 1; i < dataBytes.length; i++) {
|
||||||
|
dataBytes[i] = (byte) (value >> ((INT_BYTES - i) * Byte.SIZE));
|
||||||
|
if (dataBytes[i] != 0) {
|
||||||
|
dataLength = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataLength++;
|
||||||
|
// The first byte is the number of trailing zeros of the last byte.
|
||||||
|
dataBytes[0] = IccUtils.countTrailingZeros(dataBytes[dataLength - 1]);
|
||||||
|
addChild(new Asn1Node(tag, dataBytes, 0, dataLength));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a child of non-constructed tag with a boolean as the data.
|
||||||
|
*
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If the {@code tag} is not constructed..
|
||||||
|
*/
|
||||||
|
public Builder addChildAsBoolean(int tag, boolean value) {
|
||||||
|
if (isConstructedTag(tag)) {
|
||||||
|
throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
|
||||||
|
}
|
||||||
|
addChild(new Asn1Node(tag, value ? TRUE_BYTES : FALSE_BYTES, 0, 1));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the node. */
|
||||||
|
public Asn1Node build() {
|
||||||
|
return new Asn1Node(mTag, mChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int mTag;
|
||||||
|
private final boolean mConstructed;
|
||||||
|
// Do not use this field directly in the methods other than the constructor and encoding
|
||||||
|
// methods (e.g., toBytes()), but always use getChildren() instead.
|
||||||
|
private final List<Asn1Node> mChildren;
|
||||||
|
|
||||||
|
// Byte array that actually holds the data. For a non-constructed node, this stores its actual
|
||||||
|
// value. If the value is not set, this is null. For constructed node, this stores encoded data
|
||||||
|
// of its children, which will be decoded on the first call to getChildren().
|
||||||
|
private @Nullable byte[] mDataBytes;
|
||||||
|
// Offset of the data in above byte array.
|
||||||
|
private int mDataOffset;
|
||||||
|
// Length of the data in above byte array. If it's a constructed node, this is always the total
|
||||||
|
// length of all its children.
|
||||||
|
private int mDataLength;
|
||||||
|
// Length of the total bytes required to encode this node.
|
||||||
|
private int mEncodedLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ASN.1 data node builder with the given tag. The tag is an encoded tag including
|
||||||
|
* the tag class, tag number, and constructed mask.
|
||||||
|
*/
|
||||||
|
public static Builder newBuilder(int tag) {
|
||||||
|
return new Builder(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isConstructedTag(int tag) {
|
||||||
|
// Constructed mask is at the 6th bit.
|
||||||
|
byte[] tagBytes = IccUtils.unsignedIntToBytes(tag);
|
||||||
|
return (tagBytes[0] & 0x20) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateEncodedBytesNumForLength(int length) {
|
||||||
|
// Constructed mask is at the 6th bit.
|
||||||
|
int len = 1;
|
||||||
|
if (length > 127) {
|
||||||
|
len += IccUtils.byteNumForUnsignedInt(length);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a node with given data bytes. If it is a constructed node, its children will be
|
||||||
|
* parsed when they are visited.
|
||||||
|
*/
|
||||||
|
Asn1Node(int tag, @Nullable byte[] src, int offset, int length) {
|
||||||
|
mTag = tag;
|
||||||
|
// Constructed mask is at the 6th bit.
|
||||||
|
mConstructed = isConstructedTag(tag);
|
||||||
|
mDataBytes = src;
|
||||||
|
mDataOffset = offset;
|
||||||
|
mDataLength = length;
|
||||||
|
mChildren = mConstructed ? new ArrayList<Asn1Node>() : EMPTY_NODE_LIST;
|
||||||
|
mEncodedLength =
|
||||||
|
IccUtils.byteNumForUnsignedInt(mTag)
|
||||||
|
+ calculateEncodedBytesNumForLength(mDataLength)
|
||||||
|
+ mDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a constructed node with given children. */
|
||||||
|
private Asn1Node(int tag, List<Asn1Node> children) {
|
||||||
|
mTag = tag;
|
||||||
|
mConstructed = true;
|
||||||
|
mChildren = children;
|
||||||
|
|
||||||
|
mDataLength = 0;
|
||||||
|
int size = children.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
mDataLength += children.get(i).mEncodedLength;
|
||||||
|
}
|
||||||
|
mEncodedLength =
|
||||||
|
IccUtils.byteNumForUnsignedInt(mTag)
|
||||||
|
+ calculateEncodedBytesNumForLength(mDataLength)
|
||||||
|
+ mDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTag() {
|
||||||
|
return mTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConstructed() {
|
||||||
|
return mConstructed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if a node has a child.
|
||||||
|
*
|
||||||
|
* @param tag The tag of an immediate child.
|
||||||
|
* @param tags The tags of lineal descendant.
|
||||||
|
*/
|
||||||
|
public boolean hasChild(int tag, int... tags) throws InvalidAsn1DataException {
|
||||||
|
try {
|
||||||
|
getChild(tag, tags);
|
||||||
|
} catch (TagNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first child node having the given {@code tag} and {@code tags}.
|
||||||
|
*
|
||||||
|
* @param tag The tag of an immediate child.
|
||||||
|
* @param tags The tags of lineal descendant.
|
||||||
|
* @throws TagNotFoundException If the child cannot be found.
|
||||||
|
*/
|
||||||
|
public Asn1Node getChild(int tag, int... tags)
|
||||||
|
throws TagNotFoundException, InvalidAsn1DataException {
|
||||||
|
if (!mConstructed) {
|
||||||
|
throw new TagNotFoundException(tag);
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
Asn1Node node = this;
|
||||||
|
while (node != null) {
|
||||||
|
List<Asn1Node> children = node.getChildren();
|
||||||
|
int size = children.size();
|
||||||
|
Asn1Node foundChild = null;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Asn1Node child = children.get(i);
|
||||||
|
if (child.getTag() == tag) {
|
||||||
|
foundChild = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = foundChild;
|
||||||
|
if (index >= tags.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tag = tags[index++];
|
||||||
|
}
|
||||||
|
if (node == null) {
|
||||||
|
throw new TagNotFoundException(tag);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all child nodes which have the given {@code tag}.
|
||||||
|
*
|
||||||
|
* @return If this is primitive or no such children are found, an empty list will be returned.
|
||||||
|
*/
|
||||||
|
public List<Asn1Node> getChildren(int tag)
|
||||||
|
throws TagNotFoundException, InvalidAsn1DataException {
|
||||||
|
if (!mConstructed) {
|
||||||
|
return EMPTY_NODE_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Asn1Node> children = getChildren();
|
||||||
|
if (children.isEmpty()) {
|
||||||
|
return EMPTY_NODE_LIST;
|
||||||
|
}
|
||||||
|
List<Asn1Node> output = new ArrayList<>();
|
||||||
|
int size = children.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Asn1Node child = children.get(i);
|
||||||
|
if (child.getTag() == tag) {
|
||||||
|
output.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output.isEmpty() ? EMPTY_NODE_LIST : output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all child nodes of this node. If it's a constructed node having encoded data, it's
|
||||||
|
* children will be decoded here.
|
||||||
|
*
|
||||||
|
* @return If this is primitive, an empty list will be returned. Do not modify the returned list
|
||||||
|
* directly.
|
||||||
|
*/
|
||||||
|
public List<Asn1Node> getChildren() throws InvalidAsn1DataException {
|
||||||
|
if (!mConstructed) {
|
||||||
|
return EMPTY_NODE_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDataBytes != null) {
|
||||||
|
Asn1Decoder subDecoder = new Asn1Decoder(mDataBytes, mDataOffset, mDataLength);
|
||||||
|
while (subDecoder.hasNextNode()) {
|
||||||
|
mChildren.add(subDecoder.nextNode());
|
||||||
|
}
|
||||||
|
mDataBytes = null;
|
||||||
|
mDataOffset = 0;
|
||||||
|
}
|
||||||
|
return mChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Whether this node has a value. False will be returned for a constructed node. */
|
||||||
|
public boolean hasValue() {
|
||||||
|
return !mConstructed && mDataBytes != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The data as an integer. If the data length is larger than 4, only the first 4 bytes
|
||||||
|
* will be parsed.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public int asInteger() throws InvalidAsn1DataException {
|
||||||
|
if (mConstructed) {
|
||||||
|
throw new IllegalStateException("Cannot get value of a constructed node.");
|
||||||
|
}
|
||||||
|
if (mDataBytes == null) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return IccUtils.bytesToInt(mDataBytes, mDataOffset, mDataLength);
|
||||||
|
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The data as a long variable which can be both positive and negative. If the data
|
||||||
|
* length is larger than 8, only the first 8 bytes will be parsed.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public long asRawLong() throws InvalidAsn1DataException {
|
||||||
|
if (mConstructed) {
|
||||||
|
throw new IllegalStateException("Cannot get value of a constructed node.");
|
||||||
|
}
|
||||||
|
if (mDataBytes == null) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return IccUtils.bytesToRawLong(mDataBytes, mDataOffset, mDataLength);
|
||||||
|
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The data as a string in UTF-8 encoding.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public String asString() throws InvalidAsn1DataException {
|
||||||
|
if (mConstructed) {
|
||||||
|
throw new IllegalStateException("Cannot get value of a constructed node.");
|
||||||
|
}
|
||||||
|
if (mDataBytes == null) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return new String(mDataBytes, mDataOffset, mDataLength, StandardCharsets.UTF_8);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The data as a byte array.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public byte[] asBytes() throws InvalidAsn1DataException {
|
||||||
|
if (mConstructed) {
|
||||||
|
throw new IllegalStateException("Cannot get value of a constructed node.");
|
||||||
|
}
|
||||||
|
if (mDataBytes == null) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
|
||||||
|
}
|
||||||
|
byte[] output = new byte[mDataLength];
|
||||||
|
try {
|
||||||
|
System.arraycopy(mDataBytes, mDataOffset, output, 0, mDataLength);
|
||||||
|
} catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the data as an integer for BIT STRING. DER actually stores the bits in a reversed order.
|
||||||
|
* The returned integer here has the order fixed (first bit is at the lowest position). This
|
||||||
|
* method currently only support at most 32 bits which fit in an integer.
|
||||||
|
*
|
||||||
|
* @return The data as an integer. If this is constructed, a {@code null} will be returned.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public int asBits() throws InvalidAsn1DataException {
|
||||||
|
if (mConstructed) {
|
||||||
|
throw new IllegalStateException("Cannot get value of a constructed node.");
|
||||||
|
}
|
||||||
|
if (mDataBytes == null) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
|
||||||
|
}
|
||||||
|
int bits;
|
||||||
|
try {
|
||||||
|
bits = IccUtils.bytesToInt(mDataBytes, mDataOffset + 1, mDataLength - 1);
|
||||||
|
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
|
||||||
|
}
|
||||||
|
for (int i = mDataLength - 1; i < INT_BYTES; i++) {
|
||||||
|
bits <<= Byte.SIZE;
|
||||||
|
}
|
||||||
|
return Integer.reverse(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The data as a boolean.
|
||||||
|
* @throws InvalidAsn1DataException If the data bytes cannot be parsed.
|
||||||
|
*/
|
||||||
|
public boolean asBoolean() throws InvalidAsn1DataException {
|
||||||
|
if (mConstructed) {
|
||||||
|
throw new IllegalStateException("Cannot get value of a constructed node.");
|
||||||
|
}
|
||||||
|
if (mDataBytes == null) {
|
||||||
|
throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
|
||||||
|
}
|
||||||
|
if (mDataLength != 1) {
|
||||||
|
throw new InvalidAsn1DataException(
|
||||||
|
mTag, "Cannot parse data bytes as boolean: length=" + mDataLength);
|
||||||
|
}
|
||||||
|
if (mDataOffset < 0 || mDataOffset >= mDataBytes.length) {
|
||||||
|
throw new InvalidAsn1DataException(
|
||||||
|
mTag,
|
||||||
|
"Cannot parse data bytes.",
|
||||||
|
new ArrayIndexOutOfBoundsException(mDataOffset));
|
||||||
|
}
|
||||||
|
// ASN.1 has "true" as 0xFF.
|
||||||
|
if (mDataBytes[mDataOffset] == -1) {
|
||||||
|
return Boolean.TRUE;
|
||||||
|
} else if (mDataBytes[mDataOffset] == 0) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
throw new InvalidAsn1DataException(
|
||||||
|
mTag, "Cannot parse data bytes as boolean: " + mDataBytes[mDataOffset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The number of required bytes for encoding this node in DER. */
|
||||||
|
public int getEncodedLength() {
|
||||||
|
return mEncodedLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The number of required bytes for encoding this node's data in DER. */
|
||||||
|
public int getDataLength() {
|
||||||
|
return mDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the DER encoded bytes of this node into a byte array. The number of written bytes is
|
||||||
|
* {@link #getEncodedLength()}.
|
||||||
|
*
|
||||||
|
* @throws IndexOutOfBoundsException If the {@code dest} doesn't have enough space to write.
|
||||||
|
*/
|
||||||
|
public void writeToBytes(byte[] dest, int offset) {
|
||||||
|
if (offset < 0 || offset + mEncodedLength > dest.length) {
|
||||||
|
throw new IndexOutOfBoundsException(
|
||||||
|
"Not enough space to write. Required bytes: " + mEncodedLength);
|
||||||
|
}
|
||||||
|
write(dest, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Writes the DER encoded bytes of this node into a new byte array. */
|
||||||
|
public byte[] toBytes() {
|
||||||
|
byte[] dest = new byte[mEncodedLength];
|
||||||
|
write(dest, 0);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets a hex string representing the DER encoded bytes of this node. */
|
||||||
|
public String toHex() {
|
||||||
|
return IccUtils.bytesToHexString(toBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets header (tag + length) as hex string. */
|
||||||
|
public String getHeadAsHex() {
|
||||||
|
String headHex = IccUtils.bytesToHexString(IccUtils.unsignedIntToBytes(mTag));
|
||||||
|
if (mDataLength <= 127) {
|
||||||
|
headHex += IccUtils.byteToHex((byte) mDataLength);
|
||||||
|
} else {
|
||||||
|
byte[] lenBytes = IccUtils.unsignedIntToBytes(mDataLength);
|
||||||
|
headHex += IccUtils.byteToHex((byte) (lenBytes.length | 0x80));
|
||||||
|
headHex += IccUtils.bytesToHexString(lenBytes);
|
||||||
|
}
|
||||||
|
return headHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the new offset where to write the next node data. */
|
||||||
|
private int write(byte[] dest, int offset) {
|
||||||
|
// Writes the tag.
|
||||||
|
offset += IccUtils.unsignedIntToBytes(mTag, dest, offset);
|
||||||
|
// Writes the length.
|
||||||
|
if (mDataLength <= 127) {
|
||||||
|
dest[offset++] = (byte) mDataLength;
|
||||||
|
} else {
|
||||||
|
// Bytes required for encoding the length
|
||||||
|
int lenLen = IccUtils.unsignedIntToBytes(mDataLength, dest, ++offset);
|
||||||
|
dest[offset - 1] = (byte) (lenLen | 0x80);
|
||||||
|
offset += lenLen;
|
||||||
|
}
|
||||||
|
// Writes the data.
|
||||||
|
if (mConstructed && mDataBytes == null) {
|
||||||
|
int size = mChildren.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Asn1Node child = mChildren.get(i);
|
||||||
|
offset = child.write(dest, offset);
|
||||||
|
}
|
||||||
|
} else if (mDataBytes != null) {
|
||||||
|
System.arraycopy(mDataBytes, mDataOffset, dest, offset, mDataLength);
|
||||||
|
offset += mDataLength;
|
||||||
|
}
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.asn1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for invalid ASN.1 data in DER encoding which cannot be parsed as a node or a specific
|
||||||
|
* data type.
|
||||||
|
*/
|
||||||
|
public class InvalidAsn1DataException extends Exception {
|
||||||
|
private final int mTag;
|
||||||
|
|
||||||
|
public InvalidAsn1DataException(int tag, String message) {
|
||||||
|
super(message);
|
||||||
|
mTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidAsn1DataException(int tag, String message, Throwable throwable) {
|
||||||
|
super(message, throwable);
|
||||||
|
mTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The tag which has the invalid data. */
|
||||||
|
public int getTag() {
|
||||||
|
return mTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return super.getMessage() + " (tag=" + mTag + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.asn1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for getting a child of a {@link Asn1Node} with a non-existing tag.
|
||||||
|
*/
|
||||||
|
public class TagNotFoundException extends Exception {
|
||||||
|
private final int mTag;
|
||||||
|
|
||||||
|
public TagNotFoundException(int tag) {
|
||||||
|
mTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return The tag which has the invalid data. */
|
||||||
|
public int getTag() {
|
||||||
|
return mTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return super.getMessage() + " (tag=" + mTag + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user