Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f77821db authored by Holly Jiuyu Sun's avatar Holly Jiuyu Sun
Browse files

Add asn1 to platform.

Add unit test.

Bug: 38206971
Test: unit test
Change-Id: Ifb7c7d13ad0f980ba986b3aa6c3213ad26658489
parent 533880cc
Loading
Loading
Loading
Loading
+281 −6
Original line number Diff line number Diff line
@@ -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
     *
@@ -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
@@ -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();
@@ -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);
@@ -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();
@@ -416,7 +457,6 @@ public class IccUtils {

        if ((data[offset] & 0x40) != 0) {
            // FIXME(mkf) add country initials here

        }

        return ret;
@@ -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;
    }
}
+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;
    }
}
+598 −0

File added.

Preview size limit exceeded, changes collapsed.

+45 −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;

/**
 * Exception for invalid ASN.1 data in DER encoding which cannot be parsed as a node or a specific
 * data type.
 */
public class InvalidAsn1DataException extends Exception {
    private final int mTag;

    public InvalidAsn1DataException(int tag, String message) {
        super(message);
        mTag = tag;
    }

    public InvalidAsn1DataException(int tag, String message, Throwable throwable) {
        super(message, throwable);
        mTag = tag;
    }

    /** @return The tag which has the invalid data. */
    public int getTag() {
        return mTag;
    }

    @Override
    public String getMessage() {
        return super.getMessage() + " (tag=" + mTag + ")";
    }
}
+38 −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;

/**
 * Exception for getting a child of a {@link Asn1Node} with a non-existing tag.
 */
public class TagNotFoundException extends Exception {
    private final int mTag;

    public TagNotFoundException(int tag) {
        mTag = tag;
    }

    /** @return The tag which has the invalid data. */
    public int getTag() {
        return mTag;
    }

    @Override
    public String getMessage() {
        return super.getMessage() + " (tag=" + mTag + ")";
    }
}