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 android.annotation.Nullable;
20
21import com.android.internal.telephony.uicc.IccUtils;
22
23import java.nio.charset.StandardCharsets;
24import java.util.ArrayList;
25import java.util.Collections;
26import java.util.List;
27
28/**
29 * This represents a primitive or constructed data defined by ASN.1. A constructed node can have
30 * child nodes. A non-constructed node can have a value. This class is read-only. To build a node,
31 * you can use the {@link #newBuilder(int)} method to get a {@link Builder} instance. This class is
32 * not thread-safe.
33 */
34public final class Asn1Node {
35    private static final int INT_BYTES = Integer.SIZE / Byte.SIZE;
36    private static final List<Asn1Node> EMPTY_NODE_LIST = Collections.emptyList();
37
38    // Bytes for boolean values.
39    private static final byte[] TRUE_BYTES = new byte[] {-1};
40    private static final byte[] FALSE_BYTES = new byte[] {0};
41
42    /**
43     * This class is used to build an Asn1Node instance of a constructed tag. This class is not
44     * thread-safe.
45     */
46    public static final class Builder {
47        private final int mTag;
48        private final List<Asn1Node> mChildren;
49
50        private Builder(int tag) {
51            if (!isConstructedTag(tag)) {
52                throw new IllegalArgumentException(
53                        "Builder should be created for a constructed tag: " + tag);
54            }
55            mTag = tag;
56            mChildren = new ArrayList<>();
57        }
58
59        /**
60         * Adds a child from an existing node.
61         *
62         * @return This builder.
63         * @throws IllegalArgumentException If the child is a non-existing node.
64         */
65        public Builder addChild(Asn1Node child) {
66            mChildren.add(child);
67            return this;
68        }
69
70        /**
71         * Adds a child from another builder. The child will be built with the call to this method,
72         * and any changes to the child builder after the call to this method doesn't have effect.
73         *
74         * @return This builder.
75         */
76        public Builder addChild(Builder child) {
77            mChildren.add(child.build());
78            return this;
79        }
80
81        /**
82         * Adds children from bytes. This method calls {@link Asn1Decoder} to verify the {@code
83         * encodedBytes} and adds all nodes parsed from it as children.
84         *
85         * @return This builder.
86         * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
87         */
88        public Builder addChildren(byte[] encodedBytes) throws InvalidAsn1DataException {
89            Asn1Decoder subDecoder = new Asn1Decoder(encodedBytes, 0, encodedBytes.length);
90            while (subDecoder.hasNextNode()) {
91                mChildren.add(subDecoder.nextNode());
92            }
93            return this;
94        }
95
96        /**
97         * Adds a child of non-constructed tag with an integer as the data.
98         *
99         * @return This builder.
100         * @throws IllegalStateException If the {@code tag} is not constructed..
101         */
102        public Builder addChildAsInteger(int tag, int value) {
103            if (isConstructedTag(tag)) {
104                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
105            }
106            byte[] dataBytes = IccUtils.signedIntToBytes(value);
107            addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
108            return this;
109        }
110
111        /**
112         * Adds a child of non-constructed tag with a string as the data.
113         *
114         * @return This builder.
115         * @throws IllegalStateException If the {@code tag} is not constructed..
116         */
117        public Builder addChildAsString(int tag, String value) {
118            if (isConstructedTag(tag)) {
119                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
120            }
121            byte[] dataBytes = value.getBytes(StandardCharsets.UTF_8);
122            addChild(new Asn1Node(tag, dataBytes, 0, dataBytes.length));
123            return this;
124        }
125
126        /**
127         * Adds a child of non-constructed tag with a byte array as the data.
128         *
129         * @param value The value will be owned by this node.
130         * @return This builder.
131         * @throws IllegalStateException If the {@code tag} is not constructed..
132         */
133        public Builder addChildAsBytes(int tag, byte[] value) {
134            if (isConstructedTag(tag)) {
135                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
136            }
137            addChild(new Asn1Node(tag, value, 0, value.length));
138            return this;
139        }
140
141        /**
142         * Adds a child of non-constructed tag with a byte array as the data from a hex string.
143         *
144         * @return This builder.
145         * @throws IllegalStateException If the {@code tag} is not constructed..
146         */
147        public Builder addChildAsBytesFromHex(int tag, String hex) {
148            return addChildAsBytes(tag, IccUtils.hexStringToBytes(hex));
149        }
150
151        /**
152         * Adds a child of non-constructed tag with bits as the data.
153         *
154         * @return This builder.
155         * @throws IllegalStateException If the {@code tag} is not constructed..
156         */
157        public Builder addChildAsBits(int tag, int value) {
158            if (isConstructedTag(tag)) {
159                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
160            }
161            // Always allocate 5 bytes for simplicity.
162            byte[] dataBytes = new byte[INT_BYTES + 1];
163            // Puts the integer into the byte[1-4].
164            value = Integer.reverse(value);
165            int dataLength = 0;
166            for (int i = 1; i < dataBytes.length; i++) {
167                dataBytes[i] = (byte) (value >> ((INT_BYTES - i) * Byte.SIZE));
168                if (dataBytes[i] != 0) {
169                    dataLength = i;
170                }
171            }
172            dataLength++;
173            // The first byte is the number of trailing zeros of the last byte.
174            dataBytes[0] = IccUtils.countTrailingZeros(dataBytes[dataLength - 1]);
175            addChild(new Asn1Node(tag, dataBytes, 0, dataLength));
176            return this;
177        }
178
179        /**
180         * Adds a child of non-constructed tag with a boolean as the data.
181         *
182         * @return This builder.
183         * @throws IllegalStateException If the {@code tag} is not constructed..
184         */
185        public Builder addChildAsBoolean(int tag, boolean value) {
186            if (isConstructedTag(tag)) {
187                throw new IllegalStateException("Cannot set value of a constructed tag: " + tag);
188            }
189            addChild(new Asn1Node(tag, value ? TRUE_BYTES : FALSE_BYTES, 0, 1));
190            return this;
191        }
192
193        /** Builds the node. */
194        public Asn1Node build() {
195            return new Asn1Node(mTag, mChildren);
196        }
197    }
198
199    private final int mTag;
200    private final boolean mConstructed;
201    // Do not use this field directly in the methods other than the constructor and encoding
202    // methods (e.g., toBytes()), but always use getChildren() instead.
203    private final List<Asn1Node> mChildren;
204
205    // Byte array that actually holds the data. For a non-constructed node, this stores its actual
206    // value. If the value is not set, this is null. For constructed node, this stores encoded data
207    // of its children, which will be decoded on the first call to getChildren().
208    private @Nullable byte[] mDataBytes;
209    // Offset of the data in above byte array.
210    private int mDataOffset;
211    // Length of the data in above byte array. If it's a constructed node, this is always the total
212    // length of all its children.
213    private int mDataLength;
214    // Length of the total bytes required to encode this node.
215    private int mEncodedLength;
216
217    /**
218     * Creates a new ASN.1 data node builder with the given tag. The tag is an encoded tag including
219     * the tag class, tag number, and constructed mask.
220     */
221    public static Builder newBuilder(int tag) {
222        return new Builder(tag);
223    }
224
225    private static boolean isConstructedTag(int tag) {
226        // Constructed mask is at the 6th bit.
227        byte[] tagBytes = IccUtils.unsignedIntToBytes(tag);
228        return (tagBytes[0] & 0x20) != 0;
229    }
230
231    private static int calculateEncodedBytesNumForLength(int length) {
232        // Constructed mask is at the 6th bit.
233        int len = 1;
234        if (length > 127) {
235            len += IccUtils.byteNumForUnsignedInt(length);
236        }
237        return len;
238    }
239
240    /**
241     * Creates a node with given data bytes. If it is a constructed node, its children will be
242     * parsed when they are visited.
243     */
244    Asn1Node(int tag, @Nullable byte[] src, int offset, int length) {
245        mTag = tag;
246        // Constructed mask is at the 6th bit.
247        mConstructed = isConstructedTag(tag);
248        mDataBytes = src;
249        mDataOffset = offset;
250        mDataLength = length;
251        mChildren = mConstructed ? new ArrayList<Asn1Node>() : EMPTY_NODE_LIST;
252        mEncodedLength =
253                IccUtils.byteNumForUnsignedInt(mTag)
254                        + calculateEncodedBytesNumForLength(mDataLength)
255                        + mDataLength;
256    }
257
258    /** Creates a constructed node with given children. */
259    private Asn1Node(int tag, List<Asn1Node> children) {
260        mTag = tag;
261        mConstructed = true;
262        mChildren = children;
263
264        mDataLength = 0;
265        int size = children.size();
266        for (int i = 0; i < size; i++) {
267            mDataLength += children.get(i).mEncodedLength;
268        }
269        mEncodedLength =
270                IccUtils.byteNumForUnsignedInt(mTag)
271                        + calculateEncodedBytesNumForLength(mDataLength)
272                        + mDataLength;
273    }
274
275    public int getTag() {
276        return mTag;
277    }
278
279    public boolean isConstructed() {
280        return mConstructed;
281    }
282
283    /**
284     * Tests if a node has a child.
285     *
286     * @param tag The tag of an immediate child.
287     * @param tags The tags of lineal descendant.
288     */
289    public boolean hasChild(int tag, int... tags) throws InvalidAsn1DataException {
290        try {
291            getChild(tag, tags);
292        } catch (TagNotFoundException e) {
293            return false;
294        }
295        return true;
296    }
297
298    /**
299     * Gets the first child node having the given {@code tag} and {@code tags}.
300     *
301     * @param tag The tag of an immediate child.
302     * @param tags The tags of lineal descendant.
303     * @throws TagNotFoundException If the child cannot be found.
304     */
305    public Asn1Node getChild(int tag, int... tags)
306            throws TagNotFoundException, InvalidAsn1DataException {
307        if (!mConstructed) {
308            throw new TagNotFoundException(tag);
309        }
310        int index = 0;
311        Asn1Node node = this;
312        while (node != null) {
313            List<Asn1Node> children = node.getChildren();
314            int size = children.size();
315            Asn1Node foundChild = null;
316            for (int i = 0; i < size; i++) {
317                Asn1Node child = children.get(i);
318                if (child.getTag() == tag) {
319                    foundChild = child;
320                    break;
321                }
322            }
323            node = foundChild;
324            if (index >= tags.length) {
325                break;
326            }
327            tag = tags[index++];
328        }
329        if (node == null) {
330            throw new TagNotFoundException(tag);
331        }
332        return node;
333    }
334
335    /**
336     * Gets all child nodes which have the given {@code tag}.
337     *
338     * @return If this is primitive or no such children are found, an empty list will be returned.
339     */
340    public List<Asn1Node> getChildren(int tag)
341            throws TagNotFoundException, InvalidAsn1DataException {
342        if (!mConstructed) {
343            return EMPTY_NODE_LIST;
344        }
345
346        List<Asn1Node> children = getChildren();
347        if (children.isEmpty()) {
348            return EMPTY_NODE_LIST;
349        }
350        List<Asn1Node> output = new ArrayList<>();
351        int size = children.size();
352        for (int i = 0; i < size; i++) {
353            Asn1Node child = children.get(i);
354            if (child.getTag() == tag) {
355                output.add(child);
356            }
357        }
358        return output.isEmpty() ? EMPTY_NODE_LIST : output;
359    }
360
361    /**
362     * Gets all child nodes of this node. If it's a constructed node having encoded data, it's
363     * children will be decoded here.
364     *
365     * @return If this is primitive, an empty list will be returned. Do not modify the returned list
366     *     directly.
367     */
368    public List<Asn1Node> getChildren() throws InvalidAsn1DataException {
369        if (!mConstructed) {
370            return EMPTY_NODE_LIST;
371        }
372
373        if (mDataBytes != null) {
374            Asn1Decoder subDecoder = new Asn1Decoder(mDataBytes, mDataOffset, mDataLength);
375            while (subDecoder.hasNextNode()) {
376                mChildren.add(subDecoder.nextNode());
377            }
378            mDataBytes = null;
379            mDataOffset = 0;
380        }
381        return mChildren;
382    }
383
384    /** @return Whether this node has a value. False will be returned for a constructed node. */
385    public boolean hasValue() {
386        return !mConstructed && mDataBytes != null;
387    }
388
389    /**
390     * @return The data as an integer. If the data length is larger than 4, only the first 4 bytes
391     *     will be parsed.
392     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
393     */
394    public int asInteger() throws InvalidAsn1DataException {
395        if (mConstructed) {
396            throw new IllegalStateException("Cannot get value of a constructed node.");
397        }
398        if (mDataBytes == null) {
399            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
400        }
401        try {
402            return IccUtils.bytesToInt(mDataBytes, mDataOffset, mDataLength);
403        } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
404            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
405        }
406    }
407
408    /**
409     * @return The data as a long variable which can be both positive and negative. If the data
410     *     length is larger than 8, only the first 8 bytes will be parsed.
411     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
412     */
413    public long asRawLong() throws InvalidAsn1DataException {
414        if (mConstructed) {
415            throw new IllegalStateException("Cannot get value of a constructed node.");
416        }
417        if (mDataBytes == null) {
418            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
419        }
420        try {
421            return IccUtils.bytesToRawLong(mDataBytes, mDataOffset, mDataLength);
422        } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
423            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
424        }
425    }
426
427    /**
428     * @return The data as a string in UTF-8 encoding.
429     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
430     */
431    public String asString() throws InvalidAsn1DataException {
432        if (mConstructed) {
433            throw new IllegalStateException("Cannot get value of a constructed node.");
434        }
435        if (mDataBytes == null) {
436            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
437        }
438        try {
439            return new String(mDataBytes, mDataOffset, mDataLength, StandardCharsets.UTF_8);
440        } catch (IndexOutOfBoundsException e) {
441            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
442        }
443    }
444
445    /**
446     * @return The data as a byte array.
447     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
448     */
449    public byte[] asBytes() throws InvalidAsn1DataException {
450        if (mConstructed) {
451            throw new IllegalStateException("Cannot get value of a constructed node.");
452        }
453        if (mDataBytes == null) {
454            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
455        }
456        byte[] output = new byte[mDataLength];
457        try {
458            System.arraycopy(mDataBytes, mDataOffset, output, 0, mDataLength);
459        } catch (IndexOutOfBoundsException e) {
460            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
461        }
462        return output;
463    }
464
465    /**
466     * Gets the data as an integer for BIT STRING. DER actually stores the bits in a reversed order.
467     * The returned integer here has the order fixed (first bit is at the lowest position). This
468     * method currently only support at most 32 bits which fit in an integer.
469     *
470     * @return The data as an integer. If this is constructed, a {@code null} will be returned.
471     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
472     */
473    public int asBits() throws InvalidAsn1DataException {
474        if (mConstructed) {
475            throw new IllegalStateException("Cannot get value of a constructed node.");
476        }
477        if (mDataBytes == null) {
478            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
479        }
480        int bits;
481        try {
482            bits = IccUtils.bytesToInt(mDataBytes, mDataOffset + 1, mDataLength - 1);
483        } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
484            throw new InvalidAsn1DataException(mTag, "Cannot parse data bytes.", e);
485        }
486        for (int i = mDataLength - 1; i < INT_BYTES; i++) {
487            bits <<= Byte.SIZE;
488        }
489        return Integer.reverse(bits);
490    }
491
492    /**
493     * @return The data as a boolean.
494     * @throws InvalidAsn1DataException If the data bytes cannot be parsed.
495     */
496    public boolean asBoolean() throws InvalidAsn1DataException {
497        if (mConstructed) {
498            throw new IllegalStateException("Cannot get value of a constructed node.");
499        }
500        if (mDataBytes == null) {
501            throw new InvalidAsn1DataException(mTag, "Data bytes cannot be null.");
502        }
503        if (mDataLength != 1) {
504            throw new InvalidAsn1DataException(
505                    mTag, "Cannot parse data bytes as boolean: length=" + mDataLength);
506        }
507        if (mDataOffset < 0 || mDataOffset >= mDataBytes.length) {
508            throw new InvalidAsn1DataException(
509                    mTag,
510                    "Cannot parse data bytes.",
511                    new ArrayIndexOutOfBoundsException(mDataOffset));
512        }
513        // ASN.1 has "true" as 0xFF.
514        if (mDataBytes[mDataOffset] == -1) {
515            return Boolean.TRUE;
516        } else if (mDataBytes[mDataOffset] == 0) {
517            return Boolean.FALSE;
518        }
519        throw new InvalidAsn1DataException(
520                mTag, "Cannot parse data bytes as boolean: " + mDataBytes[mDataOffset]);
521    }
522
523    /** @return The number of required bytes for encoding this node in DER. */
524    public int getEncodedLength() {
525        return mEncodedLength;
526    }
527
528    /** @return The number of required bytes for encoding this node's data in DER. */
529    public int getDataLength() {
530        return mDataLength;
531    }
532
533    /**
534     * Writes the DER encoded bytes of this node into a byte array. The number of written bytes is
535     * {@link #getEncodedLength()}.
536     *
537     * @throws IndexOutOfBoundsException If the {@code dest} doesn't have enough space to write.
538     */
539    public void writeToBytes(byte[] dest, int offset) {
540        if (offset < 0 || offset + mEncodedLength > dest.length) {
541            throw new IndexOutOfBoundsException(
542                    "Not enough space to write. Required bytes: " + mEncodedLength);
543        }
544        write(dest, offset);
545    }
546
547    /** Writes the DER encoded bytes of this node into a new byte array. */
548    public byte[] toBytes() {
549        byte[] dest = new byte[mEncodedLength];
550        write(dest, 0);
551        return dest;
552    }
553
554    /** Gets a hex string representing the DER encoded bytes of this node. */
555    public String toHex() {
556        return IccUtils.bytesToHexString(toBytes());
557    }
558
559    /** Gets header (tag + length) as hex string. */
560    public String getHeadAsHex() {
561        String headHex = IccUtils.bytesToHexString(IccUtils.unsignedIntToBytes(mTag));
562        if (mDataLength <= 127) {
563            headHex += IccUtils.byteToHex((byte) mDataLength);
564        } else {
565            byte[] lenBytes = IccUtils.unsignedIntToBytes(mDataLength);
566            headHex += IccUtils.byteToHex((byte) (lenBytes.length | 0x80));
567            headHex += IccUtils.bytesToHexString(lenBytes);
568        }
569        return headHex;
570    }
571
572    /** Returns the new offset where to write the next node data. */
573    private int write(byte[] dest, int offset) {
574        // Writes the tag.
575        offset += IccUtils.unsignedIntToBytes(mTag, dest, offset);
576        // Writes the length.
577        if (mDataLength <= 127) {
578            dest[offset++] = (byte) mDataLength;
579        } else {
580            // Bytes required for encoding the length
581            int lenLen = IccUtils.unsignedIntToBytes(mDataLength, dest, ++offset);
582            dest[offset - 1] = (byte) (lenLen | 0x80);
583            offset += lenLen;
584        }
585        // Writes the data.
586        if (mConstructed && mDataBytes == null) {
587            int size = mChildren.size();
588            for (int i = 0; i < size; i++) {
589                Asn1Node child = mChildren.get(i);
590                offset = child.write(dest, offset);
591            }
592        } else if (mDataBytes != null) {
593            System.arraycopy(mDataBytes, mDataOffset, dest, offset, mDataLength);
594            offset += mDataLength;
595        }
596        return offset;
597    }
598}
599