Merge "Add asn1 to platform."

This commit is contained in:
Holly Jiuyu Sun
2017-12-21 21:01:40 +00:00
committed by Gerrit Code Review
5 changed files with 1113 additions and 6 deletions

View File

@@ -32,6 +32,12 @@ import java.io.UnsupportedEncodingException;
public class 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
*
@@ -61,6 +67,41 @@ public class IccUtils {
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
* Returns a concatenated string of MCC+MNC, stripping
@@ -94,10 +135,10 @@ public class IccUtils {
int v;
v = data[i] & 0xf;
ret.append("0123456789abcdef".charAt(v));
ret.append(HEX_CHARS[v]);
v = (data[i] >> 4) & 0xf;
ret.append("0123456789abcdef".charAt(v));
ret.append(HEX_CHARS[v]);
}
return ret.toString();
@@ -305,7 +346,7 @@ public class IccUtils {
return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
}
static int
public static int
hexCharToInt(char c) {
if (c >= '0' && c <= '9') return (c - '0');
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
@@ -361,11 +402,11 @@ public class IccUtils {
b = 0x0f & (bytes[i] >> 4);
ret.append("0123456789abcdef".charAt(b));
ret.append(HEX_CHARS[b]);
b = 0x0f & bytes[i];
ret.append("0123456789abcdef".charAt(b));
ret.append(HEX_CHARS[b]);
}
return ret.toString();
@@ -416,7 +457,6 @@ public class IccUtils {
if ((data[offset] & 0x40) != 0) {
// FIXME(mkf) add country initials here
}
return ret;
@@ -575,4 +615,239 @@ public class IccUtils {
}
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 + ")";
}
}

View File

@@ -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 + ")";
}
}