Loading telephony/java/com/android/internal/telephony/uicc/IccUtils.java +281 −6 Original line number Diff line number Diff line Loading @@ -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 * Loading Loading @@ -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 Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading @@ -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(); Loading Loading @@ -416,7 +457,6 @@ public class IccUtils { if ((data[offset] & 0x40) != 0) { // FIXME(mkf) add country initials here } return ret; Loading Loading @@ -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; } } telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * 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; } } Loading
telephony/java/com/android/internal/telephony/uicc/IccUtils.java +281 −6 Original line number Diff line number Diff line Loading @@ -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 * Loading Loading @@ -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 Loading Loading @@ -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(); Loading Loading @@ -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); Loading Loading @@ -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(); Loading Loading @@ -416,7 +457,6 @@ public class IccUtils { if ((data[offset] & 0x40) != 0) { // FIXME(mkf) add country initials here } return ret; Loading Loading @@ -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; } }
telephony/java/com/android/internal/telephony/uicc/asn1/Asn1Decoder.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * 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; } }