All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.internal.telephony.uicc.AnswerToReset Maven / Gradle / Ivy

/*
 * Copyright 2018 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;

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

import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * This class parses an Answer To Reset (ATR) message.
 * The ATR message structure is defined in standard ISO/IEC 7816-3. The eUICC related ATR message
 * is defined in standard ETSI TS 102 221 V14.0.0.
 */
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 B8_MASK = 0x80;
    private static final int B7_MASK = 0x40;
    private static final int B2_MASK = 0x02;
    private static final int B1_MASK = 0x01;

    public static final byte DIRECT_CONVENTION = (byte) 0x3B;
    public static final byte INVERSE_CONVENTION = (byte) 0x3F;
    public static final int INTERFACE_BYTES_MASK = 0xF0;
    public static final int T_MASK = 0x0F;
    public static final int T_VALUE_FOR_GLOBAL_INTERFACE = 15;
    public static final int TA_MASK = 0x10;
    public static final int TB_MASK = 0x20;
    public static final int TC_MASK = 0x40;
    public static final int TD_MASK = 0x80;

    private boolean mIsDirectConvention;
    private boolean mOnlyTEqualsZero = true;
    private boolean mIsEuiccSupported;
    private boolean mIsMultipleEnabledProfilesSupported = false;
    private byte mFormatByte;
    private ArrayList mInterfaceBytes = new ArrayList<>();
    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 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 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 nodes, byte category) {
            mRawData = rawData;
            mNodes = nodes;
            mCategory = category;
        }

        private static int parseLtvNode(
                int index, ArrayMap 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.
     */
    public static AnswerToReset parseAtr(String atr) {
        AnswerToReset answerToReset = new AnswerToReset();
        if (answerToReset.parseAtrString(atr)) {
            return answerToReset;
        }
        return null;
    }

    private AnswerToReset() {}

    private static String byteToStringHex(Byte b) {
        return b == null ? null : IccUtils.byteToHex(b);
    }

    private void checkEuiccSupportedCapabilities() {
        // eUICC is supported only if the b8 and b2 of the first tB after T=15 are set to 1.
        // MEP is supported only if the b8 and b1 of the first tB after T=15 are set to 1.
        for (int i = 0; i < mInterfaceBytes.size() - 1; i++) {
            if (mInterfaceBytes.get(i).getTD() != null
                    && (mInterfaceBytes.get(i).getTD() & T_MASK) == T_VALUE_FOR_GLOBAL_INTERFACE
                    && mInterfaceBytes.get(i + 1).getTB() != null) {
                if ((mInterfaceBytes.get(i + 1).getTB() & B8_MASK) != 0
                        && (mInterfaceBytes.get(i + 1).getTB() & B2_MASK) != 0) {
                    mIsEuiccSupported = true;
                }
                if ((mInterfaceBytes.get(i + 1).getTB() & B8_MASK) != 0
                        && (mInterfaceBytes.get(i + 1).getTB() & B1_MASK) != 0) {
                    mIsMultipleEnabledProfilesSupported = true;
                }
                return;
            }
        }
    }

    private int parseConventionByte(byte[] atrBytes, int index) {
        if (index >= atrBytes.length) {
            loge("Failed to read the convention byte.");
            return -1;
        }
        byte value = atrBytes[index];
        if (value == DIRECT_CONVENTION) {
            mIsDirectConvention = true;
        } else if (value == INVERSE_CONVENTION) {
            mIsDirectConvention = false;
        } else {
            loge("Unrecognized convention byte " + IccUtils.byteToHex(value));
            return -1;
        }
        return index + 1;
    }

    private int parseFormatByte(byte[] atrBytes, int index) {
        if (index >= atrBytes.length) {
            loge("Failed to read the format byte.");
            return -1;
        }
        mFormatByte = atrBytes[index];
        if (VDBG) log("mHistoricalBytesLength: " + (mFormatByte & T_MASK));
        return index + 1;
    }

    private int parseInterfaceBytes(byte[] atrBytes, int index) {
        // The first lastTD is actually not any TD but instead the format byte.
        byte lastTD = mFormatByte;
        while (true) {
            if (VDBG) log("lastTD: " + IccUtils.byteToHex(lastTD));
            // Parse the interface bytes.
            if ((lastTD & INTERFACE_BYTES_MASK) == 0) {
                break;
            }

            InterfaceByte interfaceByte = new InterfaceByte();
            if (VDBG) log("lastTD & TA_MASK: " + IccUtils.byteToHex((byte) (lastTD & TA_MASK)));
            if ((lastTD & TA_MASK) != 0) {
                if (index >= atrBytes.length) {
                    loge("Failed to read the byte for TA.");
                    return -1;
                }
                interfaceByte.setTA(atrBytes[index]);
                index++;
            }
            if (VDBG) log("lastTD & TB_MASK: " + IccUtils.byteToHex((byte) (lastTD & TB_MASK)));
            if ((lastTD & TB_MASK) != 0) {
                if (index >= atrBytes.length) {
                    loge("Failed to read the byte for TB.");
                    return -1;
                }
                interfaceByte.setTB(atrBytes[index]);
                index++;
            }
            if (VDBG) log("lastTD & TC_MASK: " + IccUtils.byteToHex((byte) (lastTD & TC_MASK)));
            if ((lastTD & TC_MASK) != 0) {
                if (index >= atrBytes.length) {
                    loge("Failed to read the byte for TC.");
                    return -1;
                }
                interfaceByte.setTC(atrBytes[index]);
                index++;
            }
            if (VDBG) log("lastTD & TD_MASK: " + IccUtils.byteToHex((byte) (lastTD & TD_MASK)));
            if ((lastTD & TD_MASK) != 0) {
                if (index >= atrBytes.length) {
                    loge("Failed to read the byte for TD.");
                    return -1;
                }
                interfaceByte.setTD(atrBytes[index]);
                index++;
            }
            mInterfaceBytes.add(interfaceByte);
            Byte newTD = interfaceByte.getTD();
            if (VDBG) log("index=" + index + ", " + toString());
            if (newTD == null) {
                break;
            }
            lastTD = newTD;
            // Parse the T values from all the TD, here we only check whether T is equal to any
            // other values other than 0, since the check byte can be absent only when T is
            // equal to 0.
            if ((lastTD & T_MASK) != 0) {
                mOnlyTEqualsZero = false;
            }
        }
        return index;
    }

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

    private int parseCheckBytes(byte[] atrBytes, int index) {
        if (index < atrBytes.length) {
            mCheckByte = atrBytes[index];
            index++;
        } else {
            if (!mOnlyTEqualsZero) {
                loge("Check byte must be present because T equals to values other than 0.");
                return -1;
            } else {
                log("Check byte can be absent because T=0.");
            }
        }
        return index;
    }

    private boolean parseAtrString(String atr) {
        if (atr == null) {
            loge("The input ATR string can not be null");
            return false;
        }

        if (atr.length() % 2 != 0) {
            loge("The length of input ATR string " + atr.length() + " is not even.");
            return false;
        }

        if (atr.length() < 4) {
            loge("Valid ATR string must at least contains TS and T0.");
            return false;
        }

        byte[] atrBytes = IccUtils.hexStringToBytes(atr);
        if (atrBytes == null) {
            return false;
        }

        int index = parseConventionByte(atrBytes, 0);
        if (index == -1) {
            return false;
        }

        index = parseFormatByte(atrBytes, index);
        if (index == -1) {
            return false;
        }

        index = parseInterfaceBytes(atrBytes, index);
        if (index == -1) {
            return false;
        }

        index = parseHistoricalBytes(atrBytes, index);
        if (index == -1) {
            return false;
        }

        index = parseCheckBytes(atrBytes, index);
        if (index == -1) {
            return false;
        }

        if (index != atrBytes.length) {
            loge("Unexpected bytes after the check byte.");
            return false;
        }
        log("Successfully parsed the ATR string " + atr + " into " + toString());
        checkEuiccSupportedCapabilities();
        return true;
    }

    /**
     * This class holds the interface bytes.
     */
    public static class InterfaceByte {
        private Byte mTA;
        private Byte mTB;
        private Byte mTC;
        private Byte mTD;

        @Nullable
        public Byte getTA() {
            return mTA;
        }

        @Nullable
        public Byte getTB() {
            return mTB;
        }

        @Nullable
        public Byte getTC() {
            return mTC;
        }

        @Nullable
        public Byte getTD() {
            return mTD;
        }

        public void setTA(Byte tA) {
            mTA = tA;
        }

        public void setTB(Byte tB) {
            mTB = tB;
        }

        public void setTC(Byte tC) {
            mTC = tC;
        }

        public void setTD(Byte tD) {
            mTD = tD;
        }

        private InterfaceByte() {
            mTA = null;
            mTB = null;
            mTC = null;
            mTD = null;
        }

        @VisibleForTesting
        public InterfaceByte(Byte tA, Byte tB, Byte tC, Byte tD) {
            this.mTA = tA;
            this.mTB = tB;
            this.mTC = tC;
            this.mTD = tD;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            InterfaceByte ib = (InterfaceByte) o;
            return (Objects.equals(mTA, ib.getTA())
                    && Objects.equals(mTB, ib.getTB())
                    && Objects.equals(mTC, ib.getTC())
                    && Objects.equals(mTD, ib.getTD()));
        }

        @Override
        public int hashCode() {
            return Objects.hash(mTA, mTB, mTC, mTD);
        }

        @Override
        public String toString() {
            StringBuffer sb = new StringBuffer();
            sb.append("{");
            sb.append("TA=").append(byteToStringHex(mTA)).append(",");
            sb.append("TB=").append(byteToStringHex(mTB)).append(",");
            sb.append("TC=").append(byteToStringHex(mTC)).append(",");
            sb.append("TD=").append(byteToStringHex(mTD));
            sb.append("}");
            return sb.toString();
        }
    };

    private static void log(String msg) {
        Rlog.d(TAG, msg);
    }

    private static void loge(String msg) {
        Rlog.e(TAG, msg);
    }

    public byte getConventionByte() {
        return mIsDirectConvention ? DIRECT_CONVENTION : INVERSE_CONVENTION;
    }

    public byte getFormatByte() {
        return mFormatByte;
    }

    public List getInterfaceBytes() {
        return mInterfaceBytes;
    }

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

    @Nullable
    public Byte getCheckByte() {
        return mCheckByte;
    }

    public boolean isEuiccSupported() {
        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;
        }
    }

    public boolean isMultipleEnabledProfilesSupported() {
        return mIsMultipleEnabledProfilesSupported;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append("AnswerToReset:{");
        sb.append("mConventionByte=")
                .append(IccUtils.byteToHex(getConventionByte())).append(",");
        sb.append("mFormatByte=").append(byteToStringHex(mFormatByte)).append(",");
        sb.append("mInterfaceBytes={");
        for (InterfaceByte ib : mInterfaceBytes) {
            sb.append(ib.toString());
        }
        sb.append("},");
        sb.append("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("}");
        return sb.toString();
    }

    /**
     * Dump
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("AnswerToReset:");
        pw.println(toString());
        pw.flush();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy