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