1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.uicc.asn1;
18
19import com.android.internal.telephony.uicc.IccUtils;
20
21/**
22 * This represents a decoder helping decode an array of bytes or a hex string into
23 * {@link Asn1Node}s. This class tracks the next position for decoding. This class is not
24 * thread-safe.
25 */
26public final class Asn1Decoder {
27    // Source byte array.
28    private final byte[] mSrc;
29    // Next position of the byte in the source array for decoding.
30    private int mPosition;
31    // Exclusive end of the range in the array for decoding.
32    private final int mEnd;
33
34    /** Creates a decoder on a hex string. */
35    public Asn1Decoder(String hex) {
36        this(IccUtils.hexStringToBytes(hex));
37    }
38
39    /** Creates a decoder on a byte array. */
40    public Asn1Decoder(byte[] src) {
41        this(src, 0, src.length);
42    }
43
44    /**
45     * Creates a decoder on a byte array slice.
46     *
47     * @throws IndexOutOfBoundsException If the range defined by {@code offset} and {@code length}
48     *         exceeds the bounds of {@code bytes}.
49     */
50    public Asn1Decoder(byte[] bytes, int offset, int length) {
51        if (offset < 0 || length < 0 || offset + length > bytes.length) {
52            throw new IndexOutOfBoundsException(
53                    "Out of the bounds: bytes=["
54                            + bytes.length
55                            + "], offset="
56                            + offset
57                            + ", length="
58                            + length);
59        }
60        mSrc = bytes;
61        mPosition = offset;
62        mEnd = offset + length;
63    }
64
65    /** @return The next start position for decoding. */
66    public int getPosition() {
67        return mPosition;
68    }
69
70    /** Returns whether the node has a next node. */
71    public boolean hasNextNode() {
72        return mPosition < mEnd;
73    }
74
75    /**
76     * Parses the next node. If the node is a constructed node, its children will be parsed only
77     * when they are accessed, e.g., though {@link Asn1Node#getChildren()}.
78     *
79     * @return The next decoded {@link Asn1Node}. If success, the next decoding position will also
80     *         be updated. If any error happens, e.g., moving over the end position, {@code null}
81     *         will be returned and the next decoding position won't be modified.
82     * @throws InvalidAsn1DataException If the bytes cannot be parsed.
83     */
84    public Asn1Node nextNode() throws InvalidAsn1DataException {
85        if (mPosition >= mEnd) {
86            throw new IllegalStateException("No bytes to parse.");
87        }
88
89        int offset = mPosition;
90
91        // Extracts the tag.
92        int tagStart = offset;
93        byte b = mSrc[offset++];
94        if ((b & 0x1F) == 0x1F) {
95            // High-tag-number form
96            while (offset < mEnd && (mSrc[offset++] & 0x80) != 0) {
97                // Do nothing.
98            }
99        }
100        if (offset >= mEnd) {
101            // No length bytes or the tag is too long.
102            throw new InvalidAsn1DataException(0, "Invalid length at position: " + offset);
103        }
104        int tag;
105        try {
106            tag = IccUtils.bytesToInt(mSrc, tagStart, offset - tagStart);
107        } catch (IllegalArgumentException e) {
108            // Cannot parse the tag as an integer.
109            throw new InvalidAsn1DataException(0, "Cannot parse tag at position: " + tagStart, e);
110        }
111
112        // Extracts the length.
113        int dataLen;
114        b = mSrc[offset++];
115        if ((b & 0x80) == 0) {
116            // Short-form length
117            dataLen = b;
118        } else {
119            // Long-form length
120            int lenLen = b & 0x7F;
121            if (offset + lenLen > mEnd) {
122                // No enough bytes for the long-form length
123                throw new InvalidAsn1DataException(
124                        tag, "Cannot parse length at position: " + offset);
125            }
126            try {
127                dataLen = IccUtils.bytesToInt(mSrc, offset, lenLen);
128            } catch (IllegalArgumentException e) {
129                // Cannot parse the data length as an integer.
130                throw new InvalidAsn1DataException(
131                        tag, "Cannot parse length at position: " + offset, e);
132            }
133            offset += lenLen;
134        }
135        if (offset + dataLen > mEnd) {
136            // No enough data left.
137            throw new InvalidAsn1DataException(
138                    tag,
139                    "Incomplete data at position: "
140                            + offset
141                            + ", expected bytes: "
142                            + dataLen
143                            + ", actual bytes: "
144                            + (mEnd - offset));
145        }
146
147        Asn1Node root = new Asn1Node(tag, mSrc, offset, dataLen);
148        mPosition = offset + dataLen;
149        return root;
150    }
151}
152