/* * 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; } }