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

Commit 7d2466bc authored by Jun Yin's avatar Jun Yin Committed by Gerrit Code Review
Browse files

Merge changes from topic "apdu-sender"

* changes:
  ApduSender
  RequestBuilder for ApduSender
  AsyncMessageInvocation and implementations
parents ce0d687b 388fa404
Loading
Loading
Loading
Loading
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.euicc.apdu;

/**
 * Parts of an APDU command.
 *
 * @hide
 */
class ApduCommand {
    /** Channel of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final int channel;

    /** Class of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final int cla;

    /** Instruction of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final int ins;

    /** Parameter 1 of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final int p1;

    /** Parameter 2 of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final int p2;

    /** Parameter 3 of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final int p3;

    /** Command data of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
    public final String cmdHex;

    /** The parameters are defined as in GlobalPlatform Card Specification v.2.3. */
    ApduCommand(int channel, int cla, int ins, int p1, int p2, int p3, String cmdHex) {
        this.channel = channel;
        this.cla = cla;
        this.ins = ins;
        this.p1 = p1;
        this.p2 = p2;
        this.p3 = p3;
        this.cmdHex = cmdHex;
    }

    @Override
    public String toString() {
        return "ApduCommand(channel=" + channel + ", cla=" + cla + ", ins=" + ins + ", p1=" + p1
                + ", p2=" + p2 + ", p3=" + p3 + ", cmd=" + cmdHex + ")";
    }
}
+63 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.euicc.apdu;

import android.telephony.IccOpenLogicalChannelResponse;

/**
 * The exception of failing to execute an APDU command. It can be caused by an error happening on
 * opening the basic or logical channel, or the response of the APDU command is not success
 * ({@link ApduSender#STATUS_NO_ERROR}).
 *
 * @hide
 */
public class ApduException extends Exception {
    private final int mApduStatus;

    /** Creates an exception with the apduStatus code of the response of an APDU command. */
    public ApduException(int apduStatus) {
        super();
        mApduStatus = apduStatus;
    }

    public ApduException(String message) {
        super(message);
        mApduStatus = 0;
    }

    /**
     * @return The error status of the response of an APDU command. An error status can be any
     *         positive 16-bit integer (i.e., SW1 & SW2) other than
     *         {@link ApduSender#STATUS_NO_ERROR} which means no error. For an error encountered
     *         when opening a logical channel before the APDU command gets sent, this is not the
     *         status defined in {@link IccOpenLogicalChannelResponse}. In this caes, 0 will be
     *         returned and the message of this exception will have the detailed error information.
     */
    public int getApduStatus() {
        return mApduStatus;
    }

    /** @return The hex string of the error status. */
    public String getStatusHex() {
        return Integer.toHexString(mApduStatus);
    }

    @Override
    public String getMessage() {
        return super.getMessage() + " (apduStatus=" + getStatusHex() + ")";
    }
}
+253 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.euicc.apdu;

import android.annotation.Nullable;
import android.os.Handler;
import android.telephony.IccOpenLogicalChannelResponse;
import android.telephony.Rlog;

import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.uicc.IccIoResult;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

/**
 * This class sends a list of APDU commands to an AID on a UICC. A logical channel will be opened
 * before sending and closed after all APDU commands are sent. The complete response of the last
 * APDU command will be returned. If any APDU command returns an error status (other than
 * {@link #STATUS_NO_ERROR}) or causing an exception, an {@link ApduException} will be returned
 * immediately without sending the rest of commands. This class is thread-safe.
 *
 * @hide
 */
public class ApduSender {
    private static final String LOG_TAG = "ApduSender";

    // Parameter and response used by the command to get extra responses of an APDU command.
    private static final int INS_GET_MORE_RESPONSE = 0xC0;
    private static final int SW1_MORE_RESPONSE = 0x61;

    // Status code of APDU response
    private static final int STATUS_NO_ERROR = 0x9000;

    private static void logv(String msg) {
        Rlog.v(LOG_TAG, msg);
    }

    private final String mAid;
    private final boolean mSupportExtendedApdu;
    private final OpenLogicalChannelInvocation mOpenChannel;
    private final CloseLogicalChannelInvocation mCloseChannel;
    private final TransmitApduLogicalChannelInvocation mTransmitApdu;

    // Lock for accessing mChannelOpened. We only allow to open a single logical channel at any
    // time for an AID.
    private final Object mChannelLock = new Object();
    private boolean mChannelOpened;

    /**
     * @param aid The AID that will be used to open a logical channel to.
     */
    public ApduSender(CommandsInterface ci, String aid, boolean supportExtendedApdu) {
        mAid = aid;
        mSupportExtendedApdu = supportExtendedApdu;
        mOpenChannel = new OpenLogicalChannelInvocation(ci);
        mCloseChannel = new CloseLogicalChannelInvocation(ci);
        mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
    }

    /**
     * Sends APDU commands.
     *
     * @param requestProvider Will be called after a logical channel is opened successfully. This is
     *     in charge of building a request with all APDU commands to be sent. This won't be called
     *     if any error happens when opening a logical channel.
     * @param resultCallback Will be called after an error or the last APDU command has been
     *     executed. The result will be the full response of the last APDU command. Error will be
     *     returned as an {@link ApduException} exception.
     * @param handler The handler that {@code requestProvider} and {@code resultCallback} will be
     *     executed on.
     */
    public void send(
            RequestProvider requestProvider,
            AsyncResultCallback<byte[]> resultCallback,
            Handler handler) {
        synchronized (mChannelLock) {
            if (mChannelOpened) {
                AsyncResultHelper.throwException(
                        new ApduException("Logical channel has already been opened."),
                        resultCallback, handler);
                return;
            }
            mChannelOpened = true;
        }

        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
            @Override
            public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
                int channel = openChannelResponse.getChannel();
                int status = openChannelResponse.getStatus();
                if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
                        || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
                    synchronized (mChannelLock) {
                        mChannelOpened = false;
                    }
                    resultCallback.onException(
                            new ApduException("Failed to open logical channel opened for AID: "
                                    + mAid + ", with status: " + status));
                    return;
                }

                RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
                Throwable requestException = null;
                try {
                    requestProvider.buildRequest(openChannelResponse.getSelectResponse(), builder);
                } catch (Throwable e) {
                    requestException = e;
                }
                if (builder.getCommands().isEmpty() || requestException != null) {
                    // Just close the channel if we don't have commands to send or an error
                    // was encountered.
                    closeAndReturn(channel, null /* response */, requestException, resultCallback,
                            handler);
                    return;
                }
                sendCommand(builder.getCommands(), 0 /* index */, resultCallback, handler);
            }
        }, handler);
    }

    /**
     * Sends the current command and then continue to send the next one. If this is the last
     * command or any error happens, {@code resultCallback} will be called.
     *
     * @param commands All commands to be sent.
     * @param index The current command index.
     */
    private void sendCommand(
            List<ApduCommand> commands,
            int index,
            AsyncResultCallback<byte[]> resultCallback,
            Handler handler) {
        ApduCommand command = commands.get(index);
        mTransmitApdu.invoke(command, new AsyncResultCallback<IccIoResult>() {
            @Override
            public void onResult(IccIoResult response) {
                // A long response may need to be fetched by multiple following-up APDU
                // commands. Makes sure that we get the complete response.
                getCompleteResponse(command.channel, response, null /* responseBuilder */,
                        new AsyncResultCallback<IccIoResult>() {
                            @Override
                            public void onResult(IccIoResult fullResponse) {
                                logv("Full APDU response: " + fullResponse);

                                int status = (fullResponse.sw1 << 8) | fullResponse.sw2;
                                if (status != STATUS_NO_ERROR) {
                                    closeAndReturn(command.channel, null /* response */,
                                            new ApduException(status), resultCallback, handler);
                                    return;
                                }

                                // Last command
                                if (index == commands.size() - 1) {
                                    closeAndReturn(command.channel, fullResponse.payload,
                                            null /* exception */, resultCallback, handler);
                                    return;
                                }

                                // Sends the next command
                                sendCommand(commands, index + 1, resultCallback, handler);
                            }
                        }, handler);
            }
        }, handler);
    }

    /**
     * Gets the full response.
     *
     * @param lastResponse Will be checked to see if we need to fetch more.
     * @param responseBuilder For continuously building the full response. It should not contain the
     *     last response. If it's null, a new builder will be created.
     * @param resultCallback Error will be included in the result and no exception will be returned.
     */
    private void getCompleteResponse(
            int channel,
            IccIoResult lastResponse,
            @Nullable ByteArrayOutputStream responseBuilder,
            AsyncResultCallback<IccIoResult> resultCallback,
            Handler handler) {
        ByteArrayOutputStream resultBuilder =
                responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
        try {
            resultBuilder.write(lastResponse.payload);
        } catch (IOException e) {
            // Should never reach here.
        }
        if (lastResponse.sw1 != SW1_MORE_RESPONSE) {
            lastResponse.payload = resultBuilder.toByteArray();
            resultCallback.onResult(lastResponse);
            return;
        }

        mTransmitApdu.invoke(
                new ApduCommand(channel, 0 /* cls  */, INS_GET_MORE_RESPONSE, 0 /* p1 */,
                        0 /* p2 */, lastResponse.sw2, "" /* cmdHex */),
                new AsyncResultCallback<IccIoResult>() {
                    @Override
                    public void onResult(IccIoResult response) {
                        getCompleteResponse(
                                channel, response, resultBuilder, resultCallback, handler);
                    }
                }, handler);
    }

    /**
     * Closes the opened logical channel.
     *
     * @param response If {@code exception} is null, this will be returned to {@code resultCallback}
     *     after the channel has been closed.
     * @param exception If not null, this will be returned to {@code resultCallback} after the
     *     channel has been closed.
     */
    private void closeAndReturn(
            int channel,
            @Nullable byte[] response,
            @Nullable Throwable exception,
            AsyncResultCallback<byte[]> resultCallback,
            Handler handler) {
        mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() {
            @Override
            public void onResult(Boolean aBoolean) {
                synchronized (mChannelLock) {
                    mChannelOpened = false;
                }

                if (exception == null) {
                    resultCallback.onResult(response);
                } else {
                    resultCallback.onException(exception);
                }
            }
        }, handler);
    }
}
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.euicc.apdu;

import android.os.AsyncResult;
import android.os.Message;
import android.telephony.Rlog;

import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation;

/**
 * Invokes {@link CommandsInterface#iccCloseLogicalChannel(int, Message)}. This takes a channel id
 * (Integer) as the input and return a boolean to indicate if closing the logical channel is
 * succeeded. No exception will be returned to the result callback.
 *
 * @hide
 */
class CloseLogicalChannelInvocation extends AsyncMessageInvocation<Integer, Boolean> {
    private static final String LOG_TAG = "CloseChan";

    private final CommandsInterface mCi;

    CloseLogicalChannelInvocation(CommandsInterface ci) {
        mCi = ci;
    }

    @Override
    protected void sendRequestMessage(Integer channel, Message msg) {
        Rlog.v(LOG_TAG, "Channel: " + channel);
        mCi.iccCloseLogicalChannel(channel, msg);
    }

    @Override
    protected Boolean parseResult(AsyncResult ar) {
        if (ar.exception == null) {
            return true;
        }
        if (ar.exception instanceof CommandException) {
            Rlog.e(LOG_TAG, "CommandException", ar.exception);
        } else {
            Rlog.e(LOG_TAG, "Unknown exception", ar.exception);
        }
        return false;
    }
}
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 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.euicc.apdu;

import android.os.AsyncResult;
import android.os.Message;
import android.telephony.IccOpenLogicalChannelResponse;
import android.telephony.Rlog;

import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation;

/**
 * Invokes {@link CommandsInterface#iccOpenLogicalChannel(String, int, Message)}. This takes AID
 * (String) as the input and return the response of opening a logical channel. Error will be
 * included in the {@link IccOpenLogicalChannelResponse} result and no exception will be returned to
 * the result callback.
 *
 * @hide
 */
class OpenLogicalChannelInvocation
        extends AsyncMessageInvocation<String, IccOpenLogicalChannelResponse> {
    private static final String LOG_TAG = "OpenChan";

    private final CommandsInterface mCi;

    OpenLogicalChannelInvocation(CommandsInterface ci) {
        mCi = ci;
    }

    @Override
    protected void sendRequestMessage(String aid, Message msg) {
        mCi.iccOpenLogicalChannel(aid, 0, msg);
    }

    @Override
    protected IccOpenLogicalChannelResponse parseResult(AsyncResult ar) {
        IccOpenLogicalChannelResponse openChannelResp;
        // The code below is copied from PhoneInterfaceManager.java.
        // TODO: move this code into IccOpenLogicalChannelResponse so that it can be shared.
        if (ar.exception == null && ar.result != null) {
            int[] result = (int[]) ar.result;
            int channel = result[0];
            byte[] selectResponse = null;
            if (result.length > 1) {
                selectResponse = new byte[result.length - 1];
                for (int i = 1; i < result.length; ++i) {
                    selectResponse[i - 1] = (byte) result[i];
                }
            }
            openChannelResp = new IccOpenLogicalChannelResponse(
                    channel, IccOpenLogicalChannelResponse.STATUS_NO_ERROR, selectResponse);
        } else {
            if (ar.result == null) {
                Rlog.e(LOG_TAG, "Empty response");
            }
            if (ar.exception != null) {
                Rlog.e(LOG_TAG, "Exception", ar.exception);
            }

            int errorCode = IccOpenLogicalChannelResponse.STATUS_UNKNOWN_ERROR;
            if (ar.exception instanceof CommandException) {
                CommandException.Error error =
                        ((CommandException) (ar.exception)).getCommandError();
                if (error == CommandException.Error.MISSING_RESOURCE) {
                    errorCode = IccOpenLogicalChannelResponse.STATUS_MISSING_RESOURCE;
                } else if (error == CommandException.Error.NO_SUCH_ELEMENT) {
                    errorCode = IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT;
                }
            }
            openChannelResp = new IccOpenLogicalChannelResponse(
                    IccOpenLogicalChannelResponse.INVALID_CHANNEL, errorCode, null);
        }

        Rlog.v(LOG_TAG, "Response: " + openChannelResp);
        return openChannelResp;
    }
}
Loading