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

Commit 6875b23d authored by Qingxi Li's avatar Qingxi Li
Browse files

Add parser for Extended APDU in ATR

Bug: 68999147
Test: Included
Change-Id: I69d4c100441fe5251f89aa47bb49b78f9efdd498
parent 800fafbc
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());
    }
}