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.content.Context; 21 import android.os.Build; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.preference.PreferenceManager; 25 import android.telephony.IccOpenLogicalChannelResponse; 26 import android.util.Base64; 27 28 import com.android.internal.annotations.GuardedBy; 29 import com.android.internal.telephony.CommandsInterface; 30 import com.android.internal.telephony.euicc.EuiccSession; 31 import com.android.internal.telephony.uicc.IccIoResult; 32 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; 33 import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper; 34 import com.android.telephony.Rlog; 35 36 import java.io.ByteArrayOutputStream; 37 import java.io.IOException; 38 import java.util.List; 39 40 /** 41 * This class sends a list of APDU commands to an AID on a UICC. A logical channel will be opened 42 * before sending and closed after all APDU commands are sent. The complete response of the last 43 * APDU command will be returned. If any APDU command returns an error status (other than 44 * {@link #STATUS_NO_ERROR}) or causing an exception, an {@link ApduException} will be returned 45 * immediately without sending the rest of commands. 46 * 47 * <p>If {@link EuiccSession} indicates ongoing session(s), the behavior changes: 1) before 48 * sending, check if a channel is opened already. If yes, reuse the channel and send APDU commands 49 * directly. If no, open a channel before sending. 2) The channel is closed when EuiccSession 50 * class ends all sessions, independent of APDU sending. 51 * 52 * <p>This class is thread-safe. 53 * 54 * @hide 55 */ 56 public class ApduSender { 57 // Parameter and response used by the command to get extra responses of an APDU command. 58 private static final int INS_GET_MORE_RESPONSE = 0xC0; 59 private static final int SW1_MORE_RESPONSE = 0x61; 60 61 // Status code of APDU response 62 private static final int STATUS_NO_ERROR = 0x9000; 63 private static final int SW1_NO_ERROR = 0x91; 64 private static final int STATUS_CHANNEL_CLOSED = 0x6881; // b/359336875 65 66 private static final int WAIT_TIME_MS = 2000; 67 private static final String CHANNEL_ID_PRE = "esim-channel"; 68 static final String ISD_R_AID = "A0000005591010FFFFFFFF8900000100"; 69 private static final String CHANNEL_RESPONSE_ID_PRE = "esim-res-id"; 70 71 private final String mAid; 72 private final boolean mSupportExtendedApdu; 73 private final OpenLogicalChannelInvocation mOpenChannel; 74 private final CloseLogicalChannelInvocation mCloseChannel; 75 private final TransmitApduLogicalChannelInvocation mTransmitApdu; 76 private final Context mContext; 77 private final String mChannelKey; 78 private final String mChannelResponseKey; 79 // closeAnyOpenChannel() needs a handler for its async callbacks. 80 private final Handler mHandler; 81 private final String mLogTag; 82 83 // Lock for accessing mChannelInUse. We only allow to open a single logical 84 // channel at any time for an AID and to invoke one command at any time. 85 // Only the thread (and its async callbacks) that sets mChannelInUse 86 // can open/close/send, and update mChannelOpened. 87 private final Object mChannelInUseLock = new Object(); 88 @GuardedBy("mChannelInUseLock") 89 private boolean mChannelInUse; 90 private boolean mChannelOpened; 91 92 /** 93 * @param aid The AID that will be used to open a logical channel to. 94 */ ApduSender(Context context, int phoneId, CommandsInterface ci, String aid, boolean supportExtendedApdu)95 public ApduSender(Context context, int phoneId, CommandsInterface ci, String aid, 96 boolean supportExtendedApdu) { 97 if (!aid.equals(ISD_R_AID) && !"user".equals(Build.TYPE)) { 98 throw new IllegalArgumentException("Only ISD-R AID is supported."); 99 } 100 mLogTag = "ApduSender-" + phoneId; 101 mAid = aid; 102 mContext = context; 103 mSupportExtendedApdu = supportExtendedApdu; 104 mOpenChannel = new OpenLogicalChannelInvocation(ci); 105 mCloseChannel = new CloseLogicalChannelInvocation(ci); 106 mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci); 107 mChannelKey = CHANNEL_ID_PRE + "_" + phoneId; 108 mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId; 109 mHandler = new Handler(); 110 mChannelInUse = false; 111 } 112 113 /** 114 * Sends APDU commands. 115 * 116 * @param requestProvider Will be called after a logical channel is opened successfully. This is 117 * in charge of building a request with all APDU commands to be sent. This won't be called 118 * if any error happens when opening a logical channel. 119 * @param resultCallback Will be called after an error or the last APDU command has been 120 * executed. The result will be the full response of the last APDU command. Error will be 121 * returned as an {@link ApduException} exception. 122 * @param handler The handler that {@code requestProvider} and {@code resultCallback} will be 123 * executed on. 124 */ send( RequestProvider requestProvider, ApduSenderResultCallback resultCallback, Handler handler)125 public void send( 126 RequestProvider requestProvider, 127 ApduSenderResultCallback resultCallback, 128 Handler handler) { 129 if (!acquireChannelLock()) { 130 AsyncResultHelper.throwException( 131 new ApduException("The logical channel is still in use."), 132 resultCallback, 133 handler); 134 return; 135 } 136 137 boolean euiccSession = EuiccSession.get(mContext).hasSession(); 138 // Case 1, channel was already opened AND EuiccSession is ongoing. 139 // sendCommand directly. Do not immediately close channel after sendCommand. 140 // Case 2, channel was already opened AND EuiccSession is not ongoing. This means 141 // EuiccSession#endSession is already called but closeAnyOpenChannel() is not 142 // yet executed because of waiting to acquire lock hold by this thread. 143 // sendCommand directly. Close channel immediately anyways after sendCommand. 144 // Case 3, channel is not open AND EuiccSession is ongoing. Open channel 145 // before sendCommand. Do not immediately close channel after sendCommand. 146 // Case 4, channel is not open AND EuiccSession is not ongoing. Open channel 147 // before sendCommand. Close channel immediately after sendCommand. 148 if (mChannelOpened) { // Case 1 or 2 149 if (euiccSession) { 150 EuiccSession.get(mContext).noteChannelOpen(this); 151 } 152 RequestBuilder builder = getRequestBuilderWithOpenedChannel(requestProvider, 153 !euiccSession /* closeChannelImmediately */, resultCallback, handler); 154 if (builder == null) { 155 return; 156 } 157 sendCommand(builder.getCommands(), 0 /* index */, 158 !euiccSession /* closeChannelImmediately */, resultCallback, handler); 159 } else { // Case 3 or 4 160 if (euiccSession) { 161 EuiccSession.get(mContext).noteChannelOpen(this); 162 } 163 openChannel(requestProvider, 164 !euiccSession /* closeChannelImmediately */, resultCallback, handler); 165 } 166 } 167 getRequestBuilderWithOpenedChannel( RequestProvider requestProvider, boolean closeChannelImmediately, ApduSenderResultCallback resultCallback, Handler handler)168 private RequestBuilder getRequestBuilderWithOpenedChannel( 169 RequestProvider requestProvider, 170 boolean closeChannelImmediately, 171 ApduSenderResultCallback resultCallback, 172 Handler handler) { 173 Throwable requestException = null; 174 int channel = 175 PreferenceManager.getDefaultSharedPreferences(mContext) 176 .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL); 177 String storedResponse = 178 PreferenceManager.getDefaultSharedPreferences(mContext) 179 .getString(mChannelResponseKey, ""); 180 byte[] selectResponse = Base64.decode(storedResponse, Base64.DEFAULT); 181 RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu); 182 try { 183 requestProvider.buildRequest(selectResponse, builder); 184 } catch (Throwable e) { 185 requestException = e; 186 } 187 if (builder.getCommands().isEmpty() || requestException != null) { 188 logd("Release as commands are empty or exception occurred"); 189 returnRespnseOrException(channel, closeChannelImmediately, 190 null /* response */, requestException, resultCallback, handler); 191 return null; 192 } 193 return builder; 194 } 195 openChannel( RequestProvider requestProvider, boolean closeChannelImmediately, ApduSenderResultCallback resultCallback, Handler handler)196 private void openChannel( 197 RequestProvider requestProvider, 198 boolean closeChannelImmediately, 199 ApduSenderResultCallback resultCallback, 200 Handler handler) { 201 mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() { 202 @Override 203 public void onResult(IccOpenLogicalChannelResponse openChannelResponse) { 204 int channel = openChannelResponse.getChannel(); 205 int status = openChannelResponse.getStatus(); 206 byte[] selectResponse = openChannelResponse.getSelectResponse(); 207 if (status == IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT) { 208 channel = PreferenceManager.getDefaultSharedPreferences(mContext) 209 .getInt(mChannelKey, 210 IccOpenLogicalChannelResponse.INVALID_CHANNEL); 211 if (channel != IccOpenLogicalChannelResponse.INVALID_CHANNEL) { 212 logv("Try to use already opened channel: " + channel); 213 status = IccOpenLogicalChannelResponse.STATUS_NO_ERROR; 214 String storedResponse = PreferenceManager 215 .getDefaultSharedPreferences(mContext) 216 .getString(mChannelResponseKey, ""); 217 selectResponse = Base64.decode(storedResponse, Base64.DEFAULT); 218 } 219 } 220 221 if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL 222 || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) { 223 mChannelOpened = false; 224 returnRespnseOrException( 225 channel, 226 /* closeChannelImmediately= */ false, 227 /* response= */ null, 228 new ApduException( 229 "Failed to open logical channel for AID: " 230 + mAid 231 + ", with status: " 232 + status), 233 resultCallback, 234 handler); 235 return; 236 } 237 PreferenceManager.getDefaultSharedPreferences(mContext) 238 .edit() 239 .putInt(mChannelKey, channel) 240 .putString(mChannelResponseKey, 241 Base64.encodeToString(selectResponse, Base64.DEFAULT)).apply(); 242 mChannelOpened = true; 243 244 RequestBuilder builder = 245 getRequestBuilderWithOpenedChannel(requestProvider, 246 closeChannelImmediately, resultCallback, handler); 247 if (builder == null) { 248 return; 249 } 250 251 sendCommand(builder.getCommands(), 0 /* index */, 252 closeChannelImmediately, resultCallback, handler); 253 } 254 }, 255 handler); 256 } 257 258 /** 259 * Sends the current command and then continue to send the next one. If this is the last 260 * command or any error happens, {@code resultCallback} will be called. 261 * 262 * @param commands All commands to be sent. 263 * @param index The current command index. 264 */ sendCommand( List<ApduCommand> commands, int index, boolean closeChannelImmediately, ApduSenderResultCallback resultCallback, Handler handler)265 private void sendCommand( 266 List<ApduCommand> commands, 267 int index, 268 boolean closeChannelImmediately, 269 ApduSenderResultCallback resultCallback, 270 Handler handler) { 271 ApduCommand command = commands.get(index); 272 mTransmitApdu.invoke(command, new AsyncResultCallback<IccIoResult>() { 273 @Override 274 public void onResult(IccIoResult response) { 275 // A long response may need to be fetched by multiple following-up APDU 276 // commands. Makes sure that we get the complete response. 277 getCompleteResponse(command.channel, response, null /* responseBuilder */, 278 new AsyncResultCallback<IccIoResult>() { 279 @Override 280 public void onResult(IccIoResult fullResponse) { 281 logv("Full APDU response: " + fullResponse); 282 int status = (fullResponse.sw1 << 8) | fullResponse.sw2; 283 if (status != STATUS_NO_ERROR 284 && fullResponse.sw1 != SW1_NO_ERROR) { 285 if (status == STATUS_CHANNEL_CLOSED) { 286 // Channel is closed by EUICC e.g. REFRESH. 287 tearDownPreferences(); 288 mChannelOpened = false; 289 // TODO: add retry 290 } 291 returnRespnseOrException( 292 command.channel, 293 closeChannelImmediately, 294 null /* response */, 295 new ApduException(status), 296 resultCallback, 297 handler); 298 return; 299 } 300 301 boolean continueSendCommand = index < commands.size() - 1 302 // Checks intermediate APDU result except the last one 303 && resultCallback.shouldContinueOnIntermediateResult( 304 fullResponse); 305 if (continueSendCommand) { 306 // Sends the next command 307 sendCommand(commands, index + 1, 308 closeChannelImmediately, resultCallback, handler); 309 } else { 310 // Returns the result of the last command 311 returnRespnseOrException( 312 command.channel, 313 closeChannelImmediately, 314 fullResponse.payload, 315 null /* exception */, 316 resultCallback, 317 handler); 318 } 319 } 320 }, handler); 321 } 322 }, handler); 323 } 324 325 /** 326 * Gets the full response. 327 * 328 * @param lastResponse Will be checked to see if we need to fetch more. 329 * @param responseBuilder For continuously building the full response. It should not contain the 330 * last response. If it's null, a new builder will be created. 331 * @param resultCallback Error will be included in the result and no exception will be returned. 332 */ getCompleteResponse( int channel, IccIoResult lastResponse, @Nullable ByteArrayOutputStream responseBuilder, AsyncResultCallback<IccIoResult> resultCallback, Handler handler)333 private void getCompleteResponse( 334 int channel, 335 IccIoResult lastResponse, 336 @Nullable ByteArrayOutputStream responseBuilder, 337 AsyncResultCallback<IccIoResult> resultCallback, 338 Handler handler) { 339 ByteArrayOutputStream resultBuilder = 340 responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder; 341 if (lastResponse.payload != null) { 342 try { 343 resultBuilder.write(lastResponse.payload); 344 } catch (IOException e) { 345 // Should never reach here. 346 } 347 } 348 if (lastResponse.sw1 != SW1_MORE_RESPONSE) { 349 lastResponse.payload = resultBuilder.toByteArray(); 350 resultCallback.onResult(lastResponse); 351 return; 352 } 353 354 mTransmitApdu.invoke( 355 new ApduCommand(channel, 0 /* cls */, INS_GET_MORE_RESPONSE, 0 /* p1 */, 356 0 /* p2 */, lastResponse.sw2, "" /* cmdHex */), 357 new AsyncResultCallback<IccIoResult>() { 358 @Override 359 public void onResult(IccIoResult response) { 360 getCompleteResponse( 361 channel, response, resultBuilder, resultCallback, handler); 362 } 363 }, handler); 364 } 365 tearDownPreferences()366 private void tearDownPreferences() { 367 PreferenceManager.getDefaultSharedPreferences(mContext) 368 .edit() 369 .remove(mChannelKey) 370 .remove(mChannelResponseKey) 371 .apply(); 372 } 373 374 /** 375 * Fires the {@code resultCallback} to return a response or exception. Also 376 * closes the open logical channel if {@code closeChannelImmediately} is {@code true}. 377 */ returnRespnseOrException( int channel, boolean closeChannelImmediately, @Nullable byte[] response, @Nullable Throwable exception, ApduSenderResultCallback resultCallback, Handler handler)378 private void returnRespnseOrException( 379 int channel, 380 boolean closeChannelImmediately, 381 @Nullable byte[] response, 382 @Nullable Throwable exception, 383 ApduSenderResultCallback resultCallback, 384 Handler handler) { 385 if (closeChannelImmediately) { 386 closeAndReturn( 387 channel, 388 response, 389 exception, 390 resultCallback, 391 handler); 392 } else { 393 releaseChannelLockAndReturn( 394 response, 395 exception, 396 resultCallback, 397 handler); 398 } 399 } 400 401 /** 402 * Closes the opened logical channel. 403 * 404 * @param response If {@code exception} is null, this will be returned to {@code resultCallback} 405 * after the channel has been closed. 406 * @param exception If not null, this will be returned to {@code resultCallback} after the 407 * channel has been closed. 408 */ closeAndReturn( int channel, @Nullable byte[] response, @Nullable Throwable exception, ApduSenderResultCallback resultCallback, Handler handler)409 private void closeAndReturn( 410 int channel, 411 @Nullable byte[] response, 412 @Nullable Throwable exception, 413 ApduSenderResultCallback resultCallback, 414 Handler handler) { 415 mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() { 416 @Override 417 public void onResult(Boolean aBoolean) { 418 tearDownPreferences(); 419 mChannelOpened = false; 420 releaseChannelLock(); 421 422 if (exception == null) { 423 resultCallback.onResult(response); 424 } else { 425 resultCallback.onException(exception); 426 } 427 } 428 }, handler); 429 } 430 431 /** 432 * Cleanup the existing opened channel which remained opened earlier due 433 * to: 434 * 435 * <p> 1) onging EuiccSession. This will be called by {@link EuiccSession#endSession()} 436 * from non-main-thread. Or, 437 * 438 * <p> 2) telephony crash. This will be called by constructor from main-thread. 439 */ closeAnyOpenChannel()440 public void closeAnyOpenChannel() { 441 if (!acquireChannelLock()) { 442 // This cannot happen for case 2) when called by constructor 443 loge("[closeAnyOpenChannel] failed to acquire channel lock"); 444 return; 445 } 446 int channelId = PreferenceManager.getDefaultSharedPreferences(mContext) 447 .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL); 448 if (channelId == IccOpenLogicalChannelResponse.INVALID_CHANNEL) { 449 releaseChannelLock(); 450 return; 451 } 452 logv("[closeAnyOpenChannel] closing the open channel : " + channelId); 453 mCloseChannel.invoke(channelId, new AsyncResultCallback<Boolean>() { 454 @Override 455 public void onResult(Boolean isSuccess) { 456 if (isSuccess) { 457 logv("[closeAnyOpenChannel] Channel closed successfully: " + channelId); 458 tearDownPreferences(); 459 } 460 // Even if CloseChannel failed, pretend that the channel is closed. 461 // So next send() will try open the channel again. If the channel is 462 // indeed still open, we use the channelId saved in sharedPref. 463 mChannelOpened = false; 464 releaseChannelLock(); 465 } 466 }, mHandler); 467 } 468 469 // releases channel and callback releaseChannelLockAndReturn( @ullable byte[] response, @Nullable Throwable exception, ApduSenderResultCallback resultCallback, Handler handler)470 private void releaseChannelLockAndReturn( 471 @Nullable byte[] response, 472 @Nullable Throwable exception, 473 ApduSenderResultCallback resultCallback, 474 Handler handler) { 475 handler.post( 476 () -> { 477 releaseChannelLock(); 478 if (exception == null) { 479 resultCallback.onResult(response); 480 } else { 481 resultCallback.onException(exception); 482 } 483 }); 484 } 485 releaseChannelLock()486 private void releaseChannelLock() { 487 synchronized (mChannelInUseLock) { 488 logd("Channel lock released."); 489 mChannelInUse = false; 490 mChannelInUseLock.notify(); 491 } 492 } 493 494 /** 495 * Acquires channel lock and returns {@code true} if successful. 496 * 497 * <p>It fails and returns {@code false} when: 498 * <ul> 499 * <li>Called from main thread, and mChannelInUse=true, fails immediately. 500 * <li>Called from non main thread, and mChannelInUse=true after 2 seconds waiting, fails. 501 * </ul> 502 */ acquireChannelLock()503 private boolean acquireChannelLock() { 504 synchronized (mChannelInUseLock) { 505 if (mChannelInUse) { 506 if (!Looper.getMainLooper().equals(Looper.myLooper())) { 507 logd("Logical channel is in use. Wait."); 508 try { 509 mChannelInUseLock.wait(WAIT_TIME_MS); 510 } catch (InterruptedException e) { 511 // nothing to do 512 } 513 if (mChannelInUse) { 514 return false; 515 } 516 } else { 517 return false; 518 } 519 } 520 mChannelInUse = true; 521 logd("Channel lock acquired."); 522 return true; 523 } 524 } 525 logv(String msg)526 private void logv(String msg) { 527 Rlog.v(mLogTag, msg); 528 } 529 logd(String msg)530 private void logd(String msg) { 531 Rlog.d(mLogTag, msg); 532 } 533 loge(String msg)534 private void loge(String msg) { 535 Rlog.e(mLogTag, msg); 536 } 537 } 538