ApduSender.java revision 388fa40468c01b8959ae4bac688241b6d362820f
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.uicc.euicc.apdu;
18
19import android.annotation.Nullable;
20import android.os.Handler;
21import android.telephony.IccOpenLogicalChannelResponse;
22import android.telephony.Rlog;
23
24import com.android.internal.telephony.CommandsInterface;
25import com.android.internal.telephony.uicc.IccIoResult;
26import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
27import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;
28
29import java.io.ByteArrayOutputStream;
30import java.io.IOException;
31import java.util.List;
32
33/**
34 * This class sends a list of APDU commands to an AID on a UICC. A logical channel will be opened
35 * before sending and closed after all APDU commands are sent. The complete response of the last
36 * APDU command will be returned. If any APDU command returns an error status (other than
37 * {@link #STATUS_NO_ERROR}) or causing an exception, an {@link ApduException} will be returned
38 * immediately without sending the rest of commands. This class is thread-safe.
39 *
40 * @hide
41 */
42public class ApduSender {
43    private static final String LOG_TAG = "ApduSender";
44
45    // Parameter and response used by the command to get extra responses of an APDU command.
46    private static final int INS_GET_MORE_RESPONSE = 0xC0;
47    private static final int SW1_MORE_RESPONSE = 0x61;
48
49    // Status code of APDU response
50    private static final int STATUS_NO_ERROR = 0x9000;
51
52    private static void logv(String msg) {
53        Rlog.v(LOG_TAG, msg);
54    }
55
56    private final String mAid;
57    private final boolean mSupportExtendedApdu;
58    private final OpenLogicalChannelInvocation mOpenChannel;
59    private final CloseLogicalChannelInvocation mCloseChannel;
60    private final TransmitApduLogicalChannelInvocation mTransmitApdu;
61
62    // Lock for accessing mChannelOpened. We only allow to open a single logical channel at any
63    // time for an AID.
64    private final Object mChannelLock = new Object();
65    private boolean mChannelOpened;
66
67    /**
68     * @param aid The AID that will be used to open a logical channel to.
69     */
70    public ApduSender(CommandsInterface ci, String aid, boolean supportExtendedApdu) {
71        mAid = aid;
72        mSupportExtendedApdu = supportExtendedApdu;
73        mOpenChannel = new OpenLogicalChannelInvocation(ci);
74        mCloseChannel = new CloseLogicalChannelInvocation(ci);
75        mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
76    }
77
78    /**
79     * Sends APDU commands.
80     *
81     * @param requestProvider Will be called after a logical channel is opened successfully. This is
82     *     in charge of building a request with all APDU commands to be sent. This won't be called
83     *     if any error happens when opening a logical channel.
84     * @param resultCallback Will be called after an error or the last APDU command has been
85     *     executed. The result will be the full response of the last APDU command. Error will be
86     *     returned as an {@link ApduException} exception.
87     * @param handler The handler that {@code requestProvider} and {@code resultCallback} will be
88     *     executed on.
89     */
90    public void send(
91            RequestProvider requestProvider,
92            AsyncResultCallback<byte[]> resultCallback,
93            Handler handler) {
94        synchronized (mChannelLock) {
95            if (mChannelOpened) {
96                AsyncResultHelper.throwException(
97                        new ApduException("Logical channel has already been opened."),
98                        resultCallback, handler);
99                return;
100            }
101            mChannelOpened = true;
102        }
103
104        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
105            @Override
106            public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
107                int channel = openChannelResponse.getChannel();
108                int status = openChannelResponse.getStatus();
109                if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
110                        || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
111                    synchronized (mChannelLock) {
112                        mChannelOpened = false;
113                    }
114                    resultCallback.onException(
115                            new ApduException("Failed to open logical channel opened for AID: "
116                                    + mAid + ", with status: " + status));
117                    return;
118                }
119
120                RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
121                Throwable requestException = null;
122                try {
123                    requestProvider.buildRequest(openChannelResponse.getSelectResponse(), builder);
124                } catch (Throwable e) {
125                    requestException = e;
126                }
127                if (builder.getCommands().isEmpty() || requestException != null) {
128                    // Just close the channel if we don't have commands to send or an error
129                    // was encountered.
130                    closeAndReturn(channel, null /* response */, requestException, resultCallback,
131                            handler);
132                    return;
133                }
134                sendCommand(builder.getCommands(), 0 /* index */, resultCallback, handler);
135            }
136        }, handler);
137    }
138
139    /**
140     * Sends the current command and then continue to send the next one. If this is the last
141     * command or any error happens, {@code resultCallback} will be called.
142     *
143     * @param commands All commands to be sent.
144     * @param index The current command index.
145     */
146    private void sendCommand(
147            List<ApduCommand> commands,
148            int index,
149            AsyncResultCallback<byte[]> resultCallback,
150            Handler handler) {
151        ApduCommand command = commands.get(index);
152        mTransmitApdu.invoke(command, new AsyncResultCallback<IccIoResult>() {
153            @Override
154            public void onResult(IccIoResult response) {
155                // A long response may need to be fetched by multiple following-up APDU
156                // commands. Makes sure that we get the complete response.
157                getCompleteResponse(command.channel, response, null /* responseBuilder */,
158                        new AsyncResultCallback<IccIoResult>() {
159                            @Override
160                            public void onResult(IccIoResult fullResponse) {
161                                logv("Full APDU response: " + fullResponse);
162
163                                int status = (fullResponse.sw1 << 8) | fullResponse.sw2;
164                                if (status != STATUS_NO_ERROR) {
165                                    closeAndReturn(command.channel, null /* response */,
166                                            new ApduException(status), resultCallback, handler);
167                                    return;
168                                }
169
170                                // Last command
171                                if (index == commands.size() - 1) {
172                                    closeAndReturn(command.channel, fullResponse.payload,
173                                            null /* exception */, resultCallback, handler);
174                                    return;
175                                }
176
177                                // Sends the next command
178                                sendCommand(commands, index + 1, resultCallback, handler);
179                            }
180                        }, handler);
181            }
182        }, handler);
183    }
184
185    /**
186     * Gets the full response.
187     *
188     * @param lastResponse Will be checked to see if we need to fetch more.
189     * @param responseBuilder For continuously building the full response. It should not contain the
190     *     last response. If it's null, a new builder will be created.
191     * @param resultCallback Error will be included in the result and no exception will be returned.
192     */
193    private void getCompleteResponse(
194            int channel,
195            IccIoResult lastResponse,
196            @Nullable ByteArrayOutputStream responseBuilder,
197            AsyncResultCallback<IccIoResult> resultCallback,
198            Handler handler) {
199        ByteArrayOutputStream resultBuilder =
200                responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
201        try {
202            resultBuilder.write(lastResponse.payload);
203        } catch (IOException e) {
204            // Should never reach here.
205        }
206        if (lastResponse.sw1 != SW1_MORE_RESPONSE) {
207            lastResponse.payload = resultBuilder.toByteArray();
208            resultCallback.onResult(lastResponse);
209            return;
210        }
211
212        mTransmitApdu.invoke(
213                new ApduCommand(channel, 0 /* cls  */, INS_GET_MORE_RESPONSE, 0 /* p1 */,
214                        0 /* p2 */, lastResponse.sw2, "" /* cmdHex */),
215                new AsyncResultCallback<IccIoResult>() {
216                    @Override
217                    public void onResult(IccIoResult response) {
218                        getCompleteResponse(
219                                channel, response, resultBuilder, resultCallback, handler);
220                    }
221                }, handler);
222    }
223
224    /**
225     * Closes the opened logical channel.
226     *
227     * @param response If {@code exception} is null, this will be returned to {@code resultCallback}
228     *     after the channel has been closed.
229     * @param exception If not null, this will be returned to {@code resultCallback} after the
230     *     channel has been closed.
231     */
232    private void closeAndReturn(
233            int channel,
234            @Nullable byte[] response,
235            @Nullable Throwable exception,
236            AsyncResultCallback<byte[]> resultCallback,
237            Handler handler) {
238        mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() {
239            @Override
240            public void onResult(Boolean aBoolean) {
241                synchronized (mChannelLock) {
242                    mChannelOpened = false;
243                }
244
245                if (exception == null) {
246                    resultCallback.onResult(response);
247                } else {
248                    resultCallback.onException(exception);
249                }
250            }
251        }, handler);
252    }
253}
254