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