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

Commit bc610e14 authored by Jiuyu Sun's avatar Jiuyu Sun Committed by Android (Google) Code Review
Browse files

Merge "Add asn1 to platform."

parents 1d47305b f77821db
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 + ")";
    }
}