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