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