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

Commit cddf2b52 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add parser for Extended APDU in ATR" into pi-dev

parents f24387f5 6875b23d
Loading
Loading
Loading
Loading
+103 −10
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.internal.telephony.uicc;

import android.annotation.Nullable;
import android.telephony.Rlog;
import android.util.ArrayMap;

import com.android.internal.annotations.VisibleForTesting;

@@ -35,6 +36,10 @@ import java.util.Objects;
public class AnswerToReset {
    private static final String TAG = "AnswerToReset";
    private static final boolean VDBG = false; // STOPSHIP if true
    private static final int TAG_CARD_CAPABILITIES = 0x07;
    private static final int EXTENDED_APDU_INDEX = 2;
    private static final int B7_MASK = 0x40;
    private static final int B2_MASK = 0x02;

    public static final byte EUICC_SUPPORTED = (byte) 0x82;
    public static final byte DIRECT_CONVENTION = (byte) 0x3B;
@@ -52,9 +57,79 @@ public class AnswerToReset {
    private boolean mIsEuiccSupported;
    private byte mFormatByte;
    private ArrayList<InterfaceByte> mInterfaceBytes = new ArrayList<>();
    private byte[] mHistoricalBytes;
    private HistoricalBytes mHistoricalBytes;
    private Byte mCheckByte;

    /** Class for the historical bytes. */
    public static class HistoricalBytes {
        private static final int TAG_MASK = 0xF0;
        private static final int LENGTH_MASK = 0x0F;

        private final byte[] mRawData;
        private final ArrayMap<Integer, byte[]> mNodes;
        private final byte mCategory;

        /** Get the category of the historical bytes. */
        public byte getCategory() {
            return mCategory;
        }

        /** Get the raw data of historical bytes. */
        public byte[] getRawData() {
            return mRawData;
        }

        /** Get the value of the tag in historical bytes. */
        @Nullable
        public byte[] getValue(int tag) {
            return mNodes.get(tag);
        }

        @Nullable
        private static HistoricalBytes parseHistoricalBytes(
                byte[] originalData, int startIndex, int length) {
            if (length <= 0 || startIndex + length > originalData.length) {
                return null;
            }
            ArrayMap<Integer, byte[]> nodes = new ArrayMap<>();

            // Start parsing from second byte since the first one is category.
            int index = startIndex + 1;
            while (index < startIndex + length && index > 0) {
                index = parseLtvNode(index, nodes, originalData, startIndex + length - 1);
            }
            if (index < 0) {
                return null;
            }
            byte[] rawData = new byte[length];
            System.arraycopy(originalData, startIndex, rawData, 0, length);
            return new HistoricalBytes(rawData, nodes, rawData[0]);
        }

        private HistoricalBytes(byte[] rawData, ArrayMap<Integer, byte[]> nodes, byte category) {
            mRawData = rawData;
            mNodes = nodes;
            mCategory = category;
        }

        private static int parseLtvNode(
                int index, ArrayMap<Integer, byte[]> nodes, byte[] data, int lastByteIndex) {
            if (index > lastByteIndex) {
                return -1;
            }
            int tag = (data[index] & TAG_MASK) >> 4;
            int length = data[index++] & LENGTH_MASK;
            if (index + length > lastByteIndex + 1 || length == 0) {
                return -1;
            }
            byte[] value = new byte[length];
            System.arraycopy(data, index, value, 0, length);
            nodes.put(tag, value);
            return index + length;
        }
    }


    /**
     * Returns an AnswerToReset by parsing the input atr string, return null if the parsing fails.
     */
@@ -108,8 +183,7 @@ public class AnswerToReset {
            return -1;
        }
        mFormatByte = atrBytes[index];
        mHistoricalBytes = new byte[mFormatByte & T_MASK];
        if (VDBG) log("mHistoricalBytesLength: " + mHistoricalBytes.length);
        if (VDBG) log("mHistoricalBytesLength: " + (mFormatByte & T_MASK));
        return index + 1;
    }

@@ -178,14 +252,15 @@ public class AnswerToReset {
    }

    private int parseHistoricalBytes(byte[] atrBytes, int index) {
        if (mHistoricalBytes.length + index > atrBytes.length) {
        int length = mFormatByte & T_MASK;
        if (length + index > atrBytes.length) {
            loge("Failed to read the historical bytes.");
            return -1;
        }
        if (mHistoricalBytes.length > 0) {
            System.arraycopy(atrBytes, index, mHistoricalBytes, 0, mHistoricalBytes.length);
        if (length > 0) {
            mHistoricalBytes = HistoricalBytes.parseHistoricalBytes(atrBytes, index, length);
        }
        return index + mHistoricalBytes.length;
        return index + length;
    }

    private int parseCheckBytes(byte[] atrBytes, int index) {
@@ -372,7 +447,7 @@ public class AnswerToReset {
    }

    @Nullable
    public byte[] getHistoricalBytes() {
    public HistoricalBytes getHistoricalBytes() {
        return mHistoricalBytes;
    }

@@ -385,6 +460,22 @@ public class AnswerToReset {
        return mIsEuiccSupported;
    }

    /** Return whether the extended LC & LE is supported. */
    public boolean isExtendedApduSupported() {
        if (mHistoricalBytes == null) {
            return false;
        }
        byte[] cardCapabilities = mHistoricalBytes.getValue(TAG_CARD_CAPABILITIES);
        if (cardCapabilities == null || cardCapabilities.length < 3) {
            return false;
        }
        if (mIsDirectConvention) {
            return (cardCapabilities[EXTENDED_APDU_INDEX] & B7_MASK) > 0;
        } else {
            return (cardCapabilities[EXTENDED_APDU_INDEX] & B2_MASK) > 0;
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
@@ -399,9 +490,11 @@ public class AnswerToReset {
        }
        sb.append("},");
        sb.append("mHistoricalBytes={");
        for (byte b : mHistoricalBytes) {
        if (mHistoricalBytes != null) {
            for (byte b : mHistoricalBytes.getRawData()) {
                sb.append(IccUtils.byteToHex(b)).append(",");
            }
        }
        sb.append("},");
        sb.append("mCheckByte=").append(byteToStringHex(mCheckByte));
        sb.append("}");
+86 −30
Original line number Diff line number Diff line
@@ -33,14 +33,14 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestNullString() {
    public void testAnswerToRestNullString() {
        AnswerToReset atr = AnswerToReset.parseAtr(null);
        assertNull(atr);
    }

    @Test
    @SmallTest
    public void tesAnswerToRestOddLength() {
    public void testAnswerToRestOddLength() {
        String str = "3B02145";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNull(atr);
@@ -48,7 +48,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestTooShortLength() {
    public void testAnswerToRestTooShortLength() {
        String str = "3B";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNull(atr);
@@ -56,20 +56,20 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestNoInterfaceByteNoHistoricalByte() {
    public void testAnswerToRestNoInterfaceByteNoHistoricalByte() {
        String str = "3B00";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
        assertEquals(atr.getConventionByte(), (byte) 0x3B);
        assertEquals(atr.getFormatByte(), (byte) 0x00);
        assertTrue(atr.getInterfaceBytes().isEmpty());
        assertEquals(atr.getHistoricalBytes().length, 0);
        assertNull(atr.getHistoricalBytes());
        assertNull(atr.getCheckByte());
    }

    @Test
    @SmallTest
    public void tesAnswerToRestNoHistoricalByte() {
    public void testAnswerToRestNoHistoricalByte() {
        String str = "3F909580B1FE001F4297";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -90,30 +90,30 @@ public class AnswerToResetTest {
        );
        assertEquals(expect, atr.getInterfaceBytes());

        assertEquals(atr.getHistoricalBytes().length, 0);
        assertNull(atr.getHistoricalBytes());
        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));
    }

    @Test
    @SmallTest
    public void tesAnswerToRestNoInterfaceByte() {
    public void testAnswerToRestNoInterfaceByte() {
        String str = "3F078031A073BE211797";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
        assertEquals(atr.getConventionByte(), (byte) 0x3F);
        assertEquals(atr.getFormatByte(), (byte) 0x07);
        assertTrue(atr.getInterfaceBytes().isEmpty());
        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect = new byte[]{
                (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
                (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect));
        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));
    }

    @Test
    @SmallTest
    public void tesAnswerToRestSuccess() {
    public void testAnswerToRestSuccess() {
        String str = "3F979580B1FE001F428031A073BE211797";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -133,17 +133,17 @@ public class AnswerToResetTest {
                Arrays.asList(expect_t1, expect_t2, expect_t3, expect_t4)
        );
        assertEquals(expect_ib, atr.getInterfaceBytes());
        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect_hb = new byte[]{
                (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
                (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect_hb));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect_hb));
        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));
    }

    @Test
    @SmallTest
    public void tesAnswerToRestSuccessWithoutCheckByte() {
    public void testAnswerToRestSuccessWithoutCheckByte() {
        String str = "3F979580B0FE0010428031A073BE2117";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -164,11 +164,11 @@ public class AnswerToResetTest {
        );
        assertEquals(expect_ib, atr.getInterfaceBytes());

        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect_hb = new byte[]{
                (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
                (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect_hb));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect_hb));

        assertEquals(atr.getCheckByte(), null);
        assertFalse(atr.isEuiccSupported());
@@ -176,7 +176,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestFailWithoutCheckByte() {
    public void testAnswerToRestFailWithoutCheckByte() {
        String str = "3F979581B0FE0010428031A073BE2117";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNull(atr);
@@ -184,7 +184,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestFailWithExtraByte() {
    public void testAnswerToRestFailWithExtraByte() {
        String str = "3F979580B1FE001F428031A073BE21179718";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNull(atr);
@@ -192,7 +192,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestEuiccSupported() {
    public void testAnswerToRestEuiccSupported() {
        String str = "3F979580BFFE8210428031A073BE211797";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -213,11 +213,11 @@ public class AnswerToResetTest {
        );
        assertEquals(expect_ib, atr.getInterfaceBytes());

        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect_hb = new byte[]{
                (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
                (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect_hb));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect_hb));

        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));

@@ -226,7 +226,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestEuiccSupportedWithLowerCaseString() {
    public void testAnswerToRestEuiccSupportedWithLowerCaseString() {
        String str = "3f979580bffe8210428031a073be211797";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -247,11 +247,11 @@ public class AnswerToResetTest {
        );
        assertEquals(expect_ib, atr.getInterfaceBytes());

        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect_hb = new byte[]{
            (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
            (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect_hb));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect_hb));

        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));

@@ -260,7 +260,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestEuiccNotSupportedDueToIncorrectT() {
    public void testAnswerToRestEuiccNotSupportedDueToIncorrectT() {
        String str = "3F979580BEFE8210428031A073BE211797";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -281,11 +281,11 @@ public class AnswerToResetTest {
        );
        assertEquals(expect_ib, atr.getInterfaceBytes());

        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect_hb = new byte[]{
                (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
                (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect_hb));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect_hb));

        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));

@@ -294,7 +294,7 @@ public class AnswerToResetTest {

    @Test
    @SmallTest
    public void tesAnswerToRestEuiccNotSupportedDueToIncorrectTB() {
    public void testAnswerToRestEuiccNotSupportedDueToIncorrectTB() {
        String str = "3F979580BFFE8110428031A073BE211797";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNotNull(atr);
@@ -315,14 +315,70 @@ public class AnswerToResetTest {
        );
        assertEquals(expect_ib, atr.getInterfaceBytes());

        assertEquals(atr.getHistoricalBytes().length, 7);
        assertEquals(atr.getHistoricalBytes().getRawData().length, 7);
        byte[] expect_hb = new byte[]{
                (byte) 0x80, (byte) 0x31, (byte) 0xA0, (byte) 0x73,
                (byte) 0xBE, (byte) 0x21, (byte) 0x17};
        assertTrue(Arrays.equals(atr.getHistoricalBytes(), expect_hb));
        assertTrue(Arrays.equals(atr.getHistoricalBytes().getRawData(), expect_hb));

        assertEquals(atr.getCheckByte(), Byte.valueOf((byte) 0x97));

        assertFalse(atr.isEuiccSupported());
    }

    @Test
    @SmallTest
    public void testAnswerToResetExtendedApduSupported() {
        String str = "3B9F96803FC7828031E073F62158574A4D020C6030005F";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertTrue(atr.isExtendedApduSupported());

        str = "3B9F96803FC7828031E073F62182574A4D020C6030005F";
        atr = AnswerToReset.parseAtr(str);
        assertFalse(atr.isExtendedApduSupported());

        str = "3F9F96803FC7828031E073F62158574A4D020C6030005F";
        atr = AnswerToReset.parseAtr(str);
        assertFalse(atr.isExtendedApduSupported());

        str = "3F9F96803FC7828031E073F62182574A4D020C6030005F";
        atr = AnswerToReset.parseAtr(str);
        assertTrue(atr.isExtendedApduSupported());
    }

    @Test
    @SmallTest
    public void testAnswerToResetExtendedApduNotSupportedDueToNoTag() {
        String str = "3F6D000080318065B00501025E83009000";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertFalse(atr.isExtendedApduSupported());

        str = "3B6D000080318065B00501025E83009000";
        atr = AnswerToReset.parseAtr(str);
        assertFalse(atr.isExtendedApduSupported());
    }

    @Test
    @SmallTest
    public void testAnswerToResetExtendedApduNotSupportedDueToLessLength() {
        String str = "3B9E96803FC7828031E072F621574A4D020C6030005F";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertFalse(atr.isExtendedApduSupported());

        str = "3F9D96803FC7828031E071F6574A4D020C6030005F";
        atr = AnswerToReset.parseAtr(str);
        assertFalse(atr.isExtendedApduSupported());
    }

    @Test
    @SmallTest
    public void testAnswerToResetParseLtvNodeWithIncorrectLength() {
        String str = "3B9E96803FC7828031E073F621574A4D020C6030005F";
        AnswerToReset atr = AnswerToReset.parseAtr(str);
        assertNull(atr.getHistoricalBytes());

        str = "3B9E96803FC7828031E071F621574A4D020C6030005F";
        atr = AnswerToReset.parseAtr(str);
        assertNull(atr.getHistoricalBytes());
    }
}