Loading src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java +59 −22 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.uicc.IccCardStatus; import com.android.internal.telephony.uicc.IccIoResult; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.UiccCard; import com.android.internal.telephony.uicc.asn1.Asn1Decoder; Loading @@ -47,6 +48,7 @@ import com.android.internal.telephony.uicc.asn1.TagNotFoundException; import com.android.internal.telephony.uicc.euicc.EuiccCardErrorException.OperationCode; import com.android.internal.telephony.uicc.euicc.apdu.ApduException; import com.android.internal.telephony.uicc.euicc.apdu.ApduSender; import com.android.internal.telephony.uicc.euicc.apdu.ApduSenderResultCallback; import com.android.internal.telephony.uicc.euicc.apdu.RequestBuilder; import com.android.internal.telephony.uicc.euicc.apdu.RequestProvider; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; Loading Loading @@ -109,6 +111,10 @@ public class EuiccCard extends UiccCard { throws EuiccCardException, TagNotFoundException, InvalidAsn1DataException; } private interface ApduIntermediateResultHandler { boolean shouldContinue(IccIoResult intermediateResult); } private interface ApduExceptionHandler { void handleException(Throwable e); } Loading Loading @@ -230,7 +236,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES) .addChildAsBytes(Tags.TAG_TAG_LIST, Tags.EUICC_PROFILE_TAGS) .build().toHex())), (byte[] response) -> { response -> { List<Asn1Node> profileNodes = new Asn1Decoder(response).nextNode() .getChild(Tags.TAG_CTX_COMP_0).getChildren(Tags.TAG_PROFILE_INFO); int size = profileNodes.size(); Loading Loading @@ -274,7 +280,7 @@ public class EuiccCard extends UiccCard { .build()) .addChildAsBytes(Tags.TAG_TAG_LIST, Tags.EUICC_PROFILE_TAGS) .build().toHex())), (byte[] response) -> { response -> { List<Asn1Node> profileNodes = new Asn1Decoder(response).nextNode() .getChild(Tags.TAG_CTX_COMP_0).getChildren(Tags.TAG_PROFILE_INFO); if (profileNodes.isEmpty()) { Loading Loading @@ -310,7 +316,7 @@ public class EuiccCard extends UiccCard { .addChildAsBoolean(Tags.TAG_CTX_1, refresh) .build().toHex()); }), (byte[] response) -> { response -> { int result; // SGP.22 v2.0 DisableProfileResponse result = parseSimpleResult(response); Loading Loading @@ -349,7 +355,7 @@ public class EuiccCard extends UiccCard { .addChildAsBoolean(Tags.TAG_CTX_1, refresh) .build().toHex()); }), (byte[] response) -> { response -> { int result; // SGP.22 v2.0 EnableProfileResponse result = parseSimpleResult(response); Loading Loading @@ -393,7 +399,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_EID) .addChildAsBytes(Tags.TAG_TAG_LIST, new byte[] {Tags.TAG_EID}) .build().toHex())), (byte[] response) -> { response -> { String eid = IccUtils.bytesToHexString(parseResponse(response) .getChild(Tags.TAG_EID).asBytes()); synchronized (mLock) { Loading Loading @@ -421,7 +427,7 @@ public class EuiccCard extends UiccCard { IccUtils.bcdToBytes(padTrailingFs(iccid))) .addChildAsString(Tags.TAG_NICKNAME, nickname) .build().toHex())), (byte[] response) -> { response -> { // SGP.22 v2.0 SetNicknameResponse int result = parseSimpleResult(response); if (result != CODE_OK) { Loading @@ -448,7 +454,7 @@ public class EuiccCard extends UiccCard { .addChildAsBytes(Tags.TAG_ICCID, iccidBytes) .build().toHex()); }), (byte[] response) -> { response -> { // SGP.22 v2.0 DeleteProfileRequest int result = parseSimpleResult(response); if (result != CODE_OK) { Loading @@ -475,7 +481,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_EUICC_MEMORY_RESET) .addChildAsBits(Tags.TAG_CTX_2, options) .build().toHex())), (byte[] response) -> { response -> { int result = parseSimpleResult(response); if (result != CODE_OK && result != CODE_NOTHING_TO_DELETE) { throw new EuiccCardErrorException( Loading Loading @@ -535,7 +541,7 @@ public class EuiccCard extends UiccCard { Asn1Node.newBuilder(Tags.TAG_SET_DEFAULT_SMDP_ADDRESS) .addChildAsString(Tags.TAG_CTX_0, defaultSmdpAddress) .build().toHex())), (byte[] response) -> { response -> { // SGP.22 v2.0 SetDefaultDpAddressResponse int result = parseSimpleResult(response); if (result != CODE_OK) { Loading @@ -560,7 +566,7 @@ public class EuiccCard extends UiccCard { newRequestProvider((RequestBuilder requestBuilder) -> requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_RAT) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); List<Asn1Node> nodes = root.getChildren(Tags.TAG_CTX_COMP_0); EuiccRulesAuthTable.Builder builder = Loading Loading @@ -682,7 +688,7 @@ public class EuiccCard extends UiccCard { .addChild(ctxParams1Builder) .build().toHex()); }), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_CTX_COMP_1, Tags.TAG_UNI_2)) { throw new EuiccCardErrorException( Loading Loading @@ -720,7 +726,7 @@ public class EuiccCard extends UiccCard { builder.addChild(new Asn1Decoder(smdpCertificate).nextNode()) .build().toHex()); }), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_CTX_COMP_1, Tags.TAG_UNI_2)) { throw new EuiccCardErrorException( Loading Loading @@ -779,7 +785,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(elementSeqs.get(i).toHex()); } }), (byte[] response) -> { response -> { // SGP.22 v2.0 ErrorResult Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_PROFILE_INSTALLATION_RESULT_DATA, Loading @@ -793,6 +799,18 @@ public class EuiccCard extends UiccCard { } return root.toBytes(); }, intermediateResult -> { byte[] payload = intermediateResult.payload; if (payload != null && payload.length > 2) { int tag = (payload[0] & 0xFF) << 8 | (payload[1] & 0xFF); // Stops if the installation result has been returned if (tag == Tags.TAG_PROFILE_INSTALLATION_RESULT) { logd("loadBoundProfilePackage failed due to an early error."); return false; } } return true; }, callback, handler); } Loading Loading @@ -834,7 +852,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_LIST_NOTIFICATION) .addChildAsBits(Tags.TAG_CTX_1, events) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponseAndCheckSimpleError(response, EuiccCardErrorException.OPERATION_LIST_NOTIFICATIONS); List<Asn1Node> nodes = root.getChild(Tags.TAG_CTX_COMP_0).getChildren(); Loading Loading @@ -864,7 +882,7 @@ public class EuiccCard extends UiccCard { .addChild(Asn1Node.newBuilder(Tags.TAG_CTX_COMP_0) .addChildAsBits(Tags.TAG_CTX_1, events)) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_CTX_1)) { // SGP.22 v2.0 RetrieveNotificationsListResponse Loading Loading @@ -905,7 +923,7 @@ public class EuiccCard extends UiccCard { .addChild(Asn1Node.newBuilder(Tags.TAG_CTX_COMP_0) .addChildAsInteger(Tags.TAG_CTX_0, seqNumber)) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponseAndCheckSimpleError(response, EuiccCardErrorException.OPERATION_RETRIEVE_NOTIFICATION); List<Asn1Node> nodes = root.getChild(Tags.TAG_CTX_COMP_0).getChildren(); Loading Loading @@ -933,7 +951,7 @@ public class EuiccCard extends UiccCard { Asn1Node.newBuilder(Tags.TAG_REMOVE_NOTIFICATION_FROM_LIST) .addChildAsInteger(Tags.TAG_CTX_0, seqNumber) .build().toHex())), (byte[] response) -> { response -> { // SGP.22 v2.0 NotificationSentResponse int result = parseSimpleResult(response); if (result != CODE_OK && result != CODE_NOTHING_TO_DELETE) { Loading Loading @@ -1111,7 +1129,7 @@ public class EuiccCard extends UiccCard { } /** * A wrapper on {@link ApduSender#send(RequestProvider, AsyncResultCallback, Handler)} to * A wrapper on {@link ApduSender#send(RequestProvider, ApduSenderResultCallback, Handler)} to * leverage lambda to simplify the sending APDU code.EuiccCardErrorException. * * @param requestBuilder Builds the request of APDU commands. Loading @@ -1123,7 +1141,16 @@ public class EuiccCard extends UiccCard { Handler handler) { sendApdu(requestBuilder, responseHandler, (e) -> callback.onException(new EuiccCardException("Cannot send APDU.", e)), callback, handler); null, callback, handler); } private <T> void sendApdu(RequestProvider requestBuilder, ApduResponseHandler<T> responseHandler, ApduIntermediateResultHandler intermediateResultHandler, AsyncResultCallback<T> callback, Handler handler) { sendApdu(requestBuilder, responseHandler, (e) -> callback.onException(new EuiccCardException("Cannot send APDU.", e)), intermediateResultHandler, callback, handler); } /** Loading @@ -1146,14 +1173,16 @@ public class EuiccCard extends UiccCard { } else { callback.onException(new EuiccCardException("Cannot send APDU.", e)); } }, callback, handler); }, null, callback, handler); } private <T> void sendApdu(RequestProvider requestBuilder, ApduResponseHandler<T> responseHandler, ApduExceptionHandler exceptionHandler, AsyncResultCallback<T> callback, ApduExceptionHandler exceptionHandler, @Nullable ApduIntermediateResultHandler intermediateResultHandler, AsyncResultCallback<T> callback, Handler handler) { mApduSender.send(requestBuilder, new AsyncResultCallback<byte[]>() { mApduSender.send(requestBuilder, new ApduSenderResultCallback() { @Override public void onResult(byte[] response) { try { Loading @@ -1166,6 +1195,14 @@ public class EuiccCard extends UiccCard { } } @Override public boolean shouldContinueOnIntermediateResult(IccIoResult result) { if (intermediateResultHandler == null) { return true; } return intermediateResultHandler.shouldContinue(result); } @Override public void onException(Throwable e) { exceptionHandler.handleException(e); Loading src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java +14 −11 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ public class ApduSender { // Status code of APDU response private static final int STATUS_NO_ERROR = 0x9000; private static final int SW1_NO_ERROR = 0x91; private static void logv(String msg) { Rlog.v(LOG_TAG, msg); Loading Loading @@ -89,7 +90,7 @@ public class ApduSender { */ public void send( RequestProvider requestProvider, AsyncResultCallback<byte[]> resultCallback, ApduSenderResultCallback resultCallback, Handler handler) { synchronized (mChannelLock) { if (mChannelOpened) { Loading Loading @@ -146,7 +147,7 @@ public class ApduSender { private void sendCommand( List<ApduCommand> commands, int index, AsyncResultCallback<byte[]> resultCallback, ApduSenderResultCallback resultCallback, Handler handler) { ApduCommand command = commands.get(index); mTransmitApdu.invoke(command, new AsyncResultCallback<IccIoResult>() { Loading @@ -159,23 +160,25 @@ public class ApduSender { @Override public void onResult(IccIoResult fullResponse) { logv("Full APDU response: " + fullResponse); int status = (fullResponse.sw1 << 8) | fullResponse.sw2; if (status != STATUS_NO_ERROR) { if (status != STATUS_NO_ERROR && fullResponse.sw1 != SW1_NO_ERROR) { closeAndReturn(command.channel, null /* response */, new ApduException(status), resultCallback, handler); return; } // Last command if (index == commands.size() - 1) { boolean continueSendCommand = index < commands.size() - 1 // Checks intermediate APDU result except the last one && resultCallback.shouldContinueOnIntermediateResult( fullResponse); if (continueSendCommand) { // Sends the next command sendCommand(commands, index + 1, resultCallback, handler); } else { // Returns the result of the last command closeAndReturn(command.channel, fullResponse.payload, null /* exception */, resultCallback, handler); return; } // Sends the next command sendCommand(commands, index + 1, resultCallback, handler); } }, handler); } Loading Loading @@ -235,7 +238,7 @@ public class ApduSender { int channel, @Nullable byte[] response, @Nullable Throwable exception, AsyncResultCallback<byte[]> resultCallback, ApduSenderResultCallback resultCallback, Handler handler) { mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() { @Override Loading src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderResultCallback.java 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony.uicc.euicc.apdu; import com.android.internal.telephony.uicc.IccIoResult; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; /** * Class to deliver the returned bytes from {@link ApduSender}. * * @hide */ public abstract class ApduSenderResultCallback extends AsyncResultCallback<byte[]> { /** * This will be called when a result to an intermediate APDU is returned. It will be called on * all APDU commands except the last one and executed in the {@link ApduSender}'s thread. * * @return True if next APDU command should be sent, otherwise the remaining APDU commands will * be skipped. */ public abstract boolean shouldContinueOnIntermediateResult(IccIoResult result); } tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java +90 −1 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.asn1.Asn1Node; import com.android.internal.telephony.uicc.asn1.InvalidAsn1DataException; import com.android.internal.telephony.uicc.asn1.TagNotFoundException; import com.android.internal.telephony.uicc.euicc.apdu.ApduException; import com.android.internal.telephony.uicc.euicc.apdu.LogicalChannelMocker; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; Loading Loading @@ -724,7 +725,7 @@ public class EuiccCardTest extends TelephonyTest { } @Test public void testLoadBoundProfilePackage_Error() { public void testLoadBoundProfilePackage_ErrorAtEnd() { int channel = mockLogicalChannelResponses( // For boundProfilePackage head + initialiseSecureChannelRequest // (ES8+.InitialiseSecureChannel) Loading Loading @@ -770,6 +771,94 @@ public class EuiccCardTest extends TelephonyTest { verifyStoreData(channel, "86030A0B0C"); // ES8+.LoadProfileElements } @Test public void testLoadBoundProfilePackage_ErrorInMiddle() { int channel = mockLogicalChannelResponses( // For boundProfilePackage head + initialiseSecureChannelRequest // (ES8+.InitialiseSecureChannel) "9000", // For firstSequenceOf87 (ES8+.ConfigureISDP) "9000", // For head of sequenceOf88 (ES8+.StoreMetadata) "9000", // For body (element 1) of sequenceOf88 (ES8+.StoreMetadata) "BF370ABF2707A205A1038101039000", "9000", // For head of sequenceOf86 (ES8+.LoadProfileElements) "9000", // For body (element 1) of sequenceOf86 (ES8+.LoadProfileElements) "9000", // Profile installation result (element 2 of sequenceOf86) "9000"); ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>(); mEuiccCard.loadBoundProfilePackage( Asn1Node.newBuilder(0xBF36) .addChild(Asn1Node.newBuilder(0xBF23)) .addChild(Asn1Node.newBuilder(0xA0) .addChildAsBytes(0x87, new byte[] {1, 2, 3})) .addChild(Asn1Node.newBuilder(0xA1) .addChildAsBytes(0x88, new byte[] {4, 5, 6})) .addChild(Asn1Node.newBuilder(0xA2)) .addChild(Asn1Node.newBuilder(0xA3) .addChildAsBytes(0x86, new byte[] {7, 8, 9}) .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC})) .build().toBytes(), resultCaptor, mHandler); resultCaptor.await(); assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode()); verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel verifyStoreData(channel, "A0058703010203"); // ES8+.ConfigureISDP verifyStoreData(channel, "A105"); // ES8+.StoreMetadata verifyStoreData(channel, "8803040506"); // ES8+.StoreMetadata } @Test public void testLoadBoundProfilePackage_ErrorStatus() { int channel = mockLogicalChannelResponses( // For boundProfilePackage head + initialiseSecureChannelRequest // (ES8+.InitialiseSecureChannel) "9000", // For firstSequenceOf87 (ES8+.ConfigureISDP) "9000", // For head of sequenceOf88 (ES8+.StoreMetadata) "9000", // For body (element 1) of sequenceOf88 (ES8+.StoreMetadata) "6985", "9000", // For head of sequenceOf86 (ES8+.LoadProfileElements) "9000", // For body (element 1) of sequenceOf86 (ES8+.LoadProfileElements) "9000", // Profile installation result (element 2 of sequenceOf86) "9000"); ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>(); mEuiccCard.loadBoundProfilePackage( Asn1Node.newBuilder(0xBF36) .addChild(Asn1Node.newBuilder(0xBF23)) .addChild(Asn1Node.newBuilder(0xA0) .addChildAsBytes(0x87, new byte[] {1, 2, 3})) .addChild(Asn1Node.newBuilder(0xA1) .addChildAsBytes(0x88, new byte[] {4, 5, 6})) .addChild(Asn1Node.newBuilder(0xA2)) .addChild(Asn1Node.newBuilder(0xA3) .addChildAsBytes(0x86, new byte[] {7, 8, 9}) .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC})) .build().toBytes(), resultCaptor, mHandler); resultCaptor.await(); EuiccCardException e = (EuiccCardException) resultCaptor.exception; assertEquals(0x6985, ((ApduException) e.getCause()).getApduStatus()); verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel verifyStoreData(channel, "A0058703010203"); // ES8+.ConfigureISDP verifyStoreData(channel, "A105"); // ES8+.StoreMetadata verifyStoreData(channel, "8803040506"); // ES8+.StoreMetadata } @Test public void testCancelSession() { int channel = mockLogicalChannelResponses("BF41009000"); Loading tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java +43 −2 Original line number Diff line number Diff line Loading @@ -32,8 +32,8 @@ import android.os.HandlerThread; import com.android.internal.telephony.CommandException; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.uicc.IccIoResult; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; import org.junit.After; import org.junit.Before; Loading @@ -47,9 +47,10 @@ import java.util.concurrent.TimeUnit; public class ApduSenderTest { private static final long WAIT_TIMEOUT_MLLIS = 5000; private static class ResponseCaptor extends AsyncResultCallback<byte[]> { private static class ResponseCaptor extends ApduSenderResultCallback { public byte[] response; public Throwable exception; public int stopApduIndex = -1; private final CountDownLatch mLatch = new CountDownLatch(1); Loading @@ -63,6 +64,18 @@ public class ApduSenderTest { } } @Override public boolean shouldContinueOnIntermediateResult(IccIoResult result) { if (stopApduIndex < 0) { return true; } if (stopApduIndex == 0) { return false; } stopApduIndex--; return true; } @Override public void onResult(byte[] bytes) { response = bytes; Loading Loading @@ -185,6 +198,34 @@ public class ApduSenderTest { eq(0), eq(2), eq("abcd"), any()); } @Test public void testSendMultiApdusStopEarly() throws InterruptedException { String aid = "B2C3D4"; ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */); int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000", "A39000", "A49000"); LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel); mResponseCaptor.stopApduIndex = 2; sender.send((selectResponse, requestBuilder) -> { requestBuilder.addApdu(10, 1, 2, 3, 0, "a"); requestBuilder.addApdu(10, 1, 2, 3, "ab"); requestBuilder.addApdu(10, 1, 2, 3); requestBuilder.addStoreData("abcd"); }, mResponseCaptor, mHandler); mResponseCaptor.await(); assertEquals("A3", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), eq(3), eq(1), eq("ab"), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq(""), any()); } @Test public void testSendLongResponse() throws InterruptedException { String aid = "B2C3D4"; Loading Loading
src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java +59 −22 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.uicc.IccCardStatus; import com.android.internal.telephony.uicc.IccIoResult; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.UiccCard; import com.android.internal.telephony.uicc.asn1.Asn1Decoder; Loading @@ -47,6 +48,7 @@ import com.android.internal.telephony.uicc.asn1.TagNotFoundException; import com.android.internal.telephony.uicc.euicc.EuiccCardErrorException.OperationCode; import com.android.internal.telephony.uicc.euicc.apdu.ApduException; import com.android.internal.telephony.uicc.euicc.apdu.ApduSender; import com.android.internal.telephony.uicc.euicc.apdu.ApduSenderResultCallback; import com.android.internal.telephony.uicc.euicc.apdu.RequestBuilder; import com.android.internal.telephony.uicc.euicc.apdu.RequestProvider; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; Loading Loading @@ -109,6 +111,10 @@ public class EuiccCard extends UiccCard { throws EuiccCardException, TagNotFoundException, InvalidAsn1DataException; } private interface ApduIntermediateResultHandler { boolean shouldContinue(IccIoResult intermediateResult); } private interface ApduExceptionHandler { void handleException(Throwable e); } Loading Loading @@ -230,7 +236,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES) .addChildAsBytes(Tags.TAG_TAG_LIST, Tags.EUICC_PROFILE_TAGS) .build().toHex())), (byte[] response) -> { response -> { List<Asn1Node> profileNodes = new Asn1Decoder(response).nextNode() .getChild(Tags.TAG_CTX_COMP_0).getChildren(Tags.TAG_PROFILE_INFO); int size = profileNodes.size(); Loading Loading @@ -274,7 +280,7 @@ public class EuiccCard extends UiccCard { .build()) .addChildAsBytes(Tags.TAG_TAG_LIST, Tags.EUICC_PROFILE_TAGS) .build().toHex())), (byte[] response) -> { response -> { List<Asn1Node> profileNodes = new Asn1Decoder(response).nextNode() .getChild(Tags.TAG_CTX_COMP_0).getChildren(Tags.TAG_PROFILE_INFO); if (profileNodes.isEmpty()) { Loading Loading @@ -310,7 +316,7 @@ public class EuiccCard extends UiccCard { .addChildAsBoolean(Tags.TAG_CTX_1, refresh) .build().toHex()); }), (byte[] response) -> { response -> { int result; // SGP.22 v2.0 DisableProfileResponse result = parseSimpleResult(response); Loading Loading @@ -349,7 +355,7 @@ public class EuiccCard extends UiccCard { .addChildAsBoolean(Tags.TAG_CTX_1, refresh) .build().toHex()); }), (byte[] response) -> { response -> { int result; // SGP.22 v2.0 EnableProfileResponse result = parseSimpleResult(response); Loading Loading @@ -393,7 +399,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_EID) .addChildAsBytes(Tags.TAG_TAG_LIST, new byte[] {Tags.TAG_EID}) .build().toHex())), (byte[] response) -> { response -> { String eid = IccUtils.bytesToHexString(parseResponse(response) .getChild(Tags.TAG_EID).asBytes()); synchronized (mLock) { Loading Loading @@ -421,7 +427,7 @@ public class EuiccCard extends UiccCard { IccUtils.bcdToBytes(padTrailingFs(iccid))) .addChildAsString(Tags.TAG_NICKNAME, nickname) .build().toHex())), (byte[] response) -> { response -> { // SGP.22 v2.0 SetNicknameResponse int result = parseSimpleResult(response); if (result != CODE_OK) { Loading @@ -448,7 +454,7 @@ public class EuiccCard extends UiccCard { .addChildAsBytes(Tags.TAG_ICCID, iccidBytes) .build().toHex()); }), (byte[] response) -> { response -> { // SGP.22 v2.0 DeleteProfileRequest int result = parseSimpleResult(response); if (result != CODE_OK) { Loading @@ -475,7 +481,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_EUICC_MEMORY_RESET) .addChildAsBits(Tags.TAG_CTX_2, options) .build().toHex())), (byte[] response) -> { response -> { int result = parseSimpleResult(response); if (result != CODE_OK && result != CODE_NOTHING_TO_DELETE) { throw new EuiccCardErrorException( Loading Loading @@ -535,7 +541,7 @@ public class EuiccCard extends UiccCard { Asn1Node.newBuilder(Tags.TAG_SET_DEFAULT_SMDP_ADDRESS) .addChildAsString(Tags.TAG_CTX_0, defaultSmdpAddress) .build().toHex())), (byte[] response) -> { response -> { // SGP.22 v2.0 SetDefaultDpAddressResponse int result = parseSimpleResult(response); if (result != CODE_OK) { Loading @@ -560,7 +566,7 @@ public class EuiccCard extends UiccCard { newRequestProvider((RequestBuilder requestBuilder) -> requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_RAT) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); List<Asn1Node> nodes = root.getChildren(Tags.TAG_CTX_COMP_0); EuiccRulesAuthTable.Builder builder = Loading Loading @@ -682,7 +688,7 @@ public class EuiccCard extends UiccCard { .addChild(ctxParams1Builder) .build().toHex()); }), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_CTX_COMP_1, Tags.TAG_UNI_2)) { throw new EuiccCardErrorException( Loading Loading @@ -720,7 +726,7 @@ public class EuiccCard extends UiccCard { builder.addChild(new Asn1Decoder(smdpCertificate).nextNode()) .build().toHex()); }), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_CTX_COMP_1, Tags.TAG_UNI_2)) { throw new EuiccCardErrorException( Loading Loading @@ -779,7 +785,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(elementSeqs.get(i).toHex()); } }), (byte[] response) -> { response -> { // SGP.22 v2.0 ErrorResult Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_PROFILE_INSTALLATION_RESULT_DATA, Loading @@ -793,6 +799,18 @@ public class EuiccCard extends UiccCard { } return root.toBytes(); }, intermediateResult -> { byte[] payload = intermediateResult.payload; if (payload != null && payload.length > 2) { int tag = (payload[0] & 0xFF) << 8 | (payload[1] & 0xFF); // Stops if the installation result has been returned if (tag == Tags.TAG_PROFILE_INSTALLATION_RESULT) { logd("loadBoundProfilePackage failed due to an early error."); return false; } } return true; }, callback, handler); } Loading Loading @@ -834,7 +852,7 @@ public class EuiccCard extends UiccCard { requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_LIST_NOTIFICATION) .addChildAsBits(Tags.TAG_CTX_1, events) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponseAndCheckSimpleError(response, EuiccCardErrorException.OPERATION_LIST_NOTIFICATIONS); List<Asn1Node> nodes = root.getChild(Tags.TAG_CTX_COMP_0).getChildren(); Loading Loading @@ -864,7 +882,7 @@ public class EuiccCard extends UiccCard { .addChild(Asn1Node.newBuilder(Tags.TAG_CTX_COMP_0) .addChildAsBits(Tags.TAG_CTX_1, events)) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponse(response); if (root.hasChild(Tags.TAG_CTX_1)) { // SGP.22 v2.0 RetrieveNotificationsListResponse Loading Loading @@ -905,7 +923,7 @@ public class EuiccCard extends UiccCard { .addChild(Asn1Node.newBuilder(Tags.TAG_CTX_COMP_0) .addChildAsInteger(Tags.TAG_CTX_0, seqNumber)) .build().toHex())), (byte[] response) -> { response -> { Asn1Node root = parseResponseAndCheckSimpleError(response, EuiccCardErrorException.OPERATION_RETRIEVE_NOTIFICATION); List<Asn1Node> nodes = root.getChild(Tags.TAG_CTX_COMP_0).getChildren(); Loading Loading @@ -933,7 +951,7 @@ public class EuiccCard extends UiccCard { Asn1Node.newBuilder(Tags.TAG_REMOVE_NOTIFICATION_FROM_LIST) .addChildAsInteger(Tags.TAG_CTX_0, seqNumber) .build().toHex())), (byte[] response) -> { response -> { // SGP.22 v2.0 NotificationSentResponse int result = parseSimpleResult(response); if (result != CODE_OK && result != CODE_NOTHING_TO_DELETE) { Loading Loading @@ -1111,7 +1129,7 @@ public class EuiccCard extends UiccCard { } /** * A wrapper on {@link ApduSender#send(RequestProvider, AsyncResultCallback, Handler)} to * A wrapper on {@link ApduSender#send(RequestProvider, ApduSenderResultCallback, Handler)} to * leverage lambda to simplify the sending APDU code.EuiccCardErrorException. * * @param requestBuilder Builds the request of APDU commands. Loading @@ -1123,7 +1141,16 @@ public class EuiccCard extends UiccCard { Handler handler) { sendApdu(requestBuilder, responseHandler, (e) -> callback.onException(new EuiccCardException("Cannot send APDU.", e)), callback, handler); null, callback, handler); } private <T> void sendApdu(RequestProvider requestBuilder, ApduResponseHandler<T> responseHandler, ApduIntermediateResultHandler intermediateResultHandler, AsyncResultCallback<T> callback, Handler handler) { sendApdu(requestBuilder, responseHandler, (e) -> callback.onException(new EuiccCardException("Cannot send APDU.", e)), intermediateResultHandler, callback, handler); } /** Loading @@ -1146,14 +1173,16 @@ public class EuiccCard extends UiccCard { } else { callback.onException(new EuiccCardException("Cannot send APDU.", e)); } }, callback, handler); }, null, callback, handler); } private <T> void sendApdu(RequestProvider requestBuilder, ApduResponseHandler<T> responseHandler, ApduExceptionHandler exceptionHandler, AsyncResultCallback<T> callback, ApduExceptionHandler exceptionHandler, @Nullable ApduIntermediateResultHandler intermediateResultHandler, AsyncResultCallback<T> callback, Handler handler) { mApduSender.send(requestBuilder, new AsyncResultCallback<byte[]>() { mApduSender.send(requestBuilder, new ApduSenderResultCallback() { @Override public void onResult(byte[] response) { try { Loading @@ -1166,6 +1195,14 @@ public class EuiccCard extends UiccCard { } } @Override public boolean shouldContinueOnIntermediateResult(IccIoResult result) { if (intermediateResultHandler == null) { return true; } return intermediateResultHandler.shouldContinue(result); } @Override public void onException(Throwable e) { exceptionHandler.handleException(e); Loading
src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java +14 −11 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ public class ApduSender { // Status code of APDU response private static final int STATUS_NO_ERROR = 0x9000; private static final int SW1_NO_ERROR = 0x91; private static void logv(String msg) { Rlog.v(LOG_TAG, msg); Loading Loading @@ -89,7 +90,7 @@ public class ApduSender { */ public void send( RequestProvider requestProvider, AsyncResultCallback<byte[]> resultCallback, ApduSenderResultCallback resultCallback, Handler handler) { synchronized (mChannelLock) { if (mChannelOpened) { Loading Loading @@ -146,7 +147,7 @@ public class ApduSender { private void sendCommand( List<ApduCommand> commands, int index, AsyncResultCallback<byte[]> resultCallback, ApduSenderResultCallback resultCallback, Handler handler) { ApduCommand command = commands.get(index); mTransmitApdu.invoke(command, new AsyncResultCallback<IccIoResult>() { Loading @@ -159,23 +160,25 @@ public class ApduSender { @Override public void onResult(IccIoResult fullResponse) { logv("Full APDU response: " + fullResponse); int status = (fullResponse.sw1 << 8) | fullResponse.sw2; if (status != STATUS_NO_ERROR) { if (status != STATUS_NO_ERROR && fullResponse.sw1 != SW1_NO_ERROR) { closeAndReturn(command.channel, null /* response */, new ApduException(status), resultCallback, handler); return; } // Last command if (index == commands.size() - 1) { boolean continueSendCommand = index < commands.size() - 1 // Checks intermediate APDU result except the last one && resultCallback.shouldContinueOnIntermediateResult( fullResponse); if (continueSendCommand) { // Sends the next command sendCommand(commands, index + 1, resultCallback, handler); } else { // Returns the result of the last command closeAndReturn(command.channel, fullResponse.payload, null /* exception */, resultCallback, handler); return; } // Sends the next command sendCommand(commands, index + 1, resultCallback, handler); } }, handler); } Loading Loading @@ -235,7 +238,7 @@ public class ApduSender { int channel, @Nullable byte[] response, @Nullable Throwable exception, AsyncResultCallback<byte[]> resultCallback, ApduSenderResultCallback resultCallback, Handler handler) { mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() { @Override Loading
src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderResultCallback.java 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony.uicc.euicc.apdu; import com.android.internal.telephony.uicc.IccIoResult; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; /** * Class to deliver the returned bytes from {@link ApduSender}. * * @hide */ public abstract class ApduSenderResultCallback extends AsyncResultCallback<byte[]> { /** * This will be called when a result to an intermediate APDU is returned. It will be called on * all APDU commands except the last one and executed in the {@link ApduSender}'s thread. * * @return True if next APDU command should be sent, otherwise the remaining APDU commands will * be skipped. */ public abstract boolean shouldContinueOnIntermediateResult(IccIoResult result); }
tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java +90 −1 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.asn1.Asn1Node; import com.android.internal.telephony.uicc.asn1.InvalidAsn1DataException; import com.android.internal.telephony.uicc.asn1.TagNotFoundException; import com.android.internal.telephony.uicc.euicc.apdu.ApduException; import com.android.internal.telephony.uicc.euicc.apdu.LogicalChannelMocker; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; Loading Loading @@ -724,7 +725,7 @@ public class EuiccCardTest extends TelephonyTest { } @Test public void testLoadBoundProfilePackage_Error() { public void testLoadBoundProfilePackage_ErrorAtEnd() { int channel = mockLogicalChannelResponses( // For boundProfilePackage head + initialiseSecureChannelRequest // (ES8+.InitialiseSecureChannel) Loading Loading @@ -770,6 +771,94 @@ public class EuiccCardTest extends TelephonyTest { verifyStoreData(channel, "86030A0B0C"); // ES8+.LoadProfileElements } @Test public void testLoadBoundProfilePackage_ErrorInMiddle() { int channel = mockLogicalChannelResponses( // For boundProfilePackage head + initialiseSecureChannelRequest // (ES8+.InitialiseSecureChannel) "9000", // For firstSequenceOf87 (ES8+.ConfigureISDP) "9000", // For head of sequenceOf88 (ES8+.StoreMetadata) "9000", // For body (element 1) of sequenceOf88 (ES8+.StoreMetadata) "BF370ABF2707A205A1038101039000", "9000", // For head of sequenceOf86 (ES8+.LoadProfileElements) "9000", // For body (element 1) of sequenceOf86 (ES8+.LoadProfileElements) "9000", // Profile installation result (element 2 of sequenceOf86) "9000"); ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>(); mEuiccCard.loadBoundProfilePackage( Asn1Node.newBuilder(0xBF36) .addChild(Asn1Node.newBuilder(0xBF23)) .addChild(Asn1Node.newBuilder(0xA0) .addChildAsBytes(0x87, new byte[] {1, 2, 3})) .addChild(Asn1Node.newBuilder(0xA1) .addChildAsBytes(0x88, new byte[] {4, 5, 6})) .addChild(Asn1Node.newBuilder(0xA2)) .addChild(Asn1Node.newBuilder(0xA3) .addChildAsBytes(0x86, new byte[] {7, 8, 9}) .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC})) .build().toBytes(), resultCaptor, mHandler); resultCaptor.await(); assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode()); verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel verifyStoreData(channel, "A0058703010203"); // ES8+.ConfigureISDP verifyStoreData(channel, "A105"); // ES8+.StoreMetadata verifyStoreData(channel, "8803040506"); // ES8+.StoreMetadata } @Test public void testLoadBoundProfilePackage_ErrorStatus() { int channel = mockLogicalChannelResponses( // For boundProfilePackage head + initialiseSecureChannelRequest // (ES8+.InitialiseSecureChannel) "9000", // For firstSequenceOf87 (ES8+.ConfigureISDP) "9000", // For head of sequenceOf88 (ES8+.StoreMetadata) "9000", // For body (element 1) of sequenceOf88 (ES8+.StoreMetadata) "6985", "9000", // For head of sequenceOf86 (ES8+.LoadProfileElements) "9000", // For body (element 1) of sequenceOf86 (ES8+.LoadProfileElements) "9000", // Profile installation result (element 2 of sequenceOf86) "9000"); ResultCaptor<byte[]> resultCaptor = new ResultCaptor<>(); mEuiccCard.loadBoundProfilePackage( Asn1Node.newBuilder(0xBF36) .addChild(Asn1Node.newBuilder(0xBF23)) .addChild(Asn1Node.newBuilder(0xA0) .addChildAsBytes(0x87, new byte[] {1, 2, 3})) .addChild(Asn1Node.newBuilder(0xA1) .addChildAsBytes(0x88, new byte[] {4, 5, 6})) .addChild(Asn1Node.newBuilder(0xA2)) .addChild(Asn1Node.newBuilder(0xA3) .addChildAsBytes(0x86, new byte[] {7, 8, 9}) .addChildAsBytes(0x86, new byte[] {0xA, 0xB, 0xC})) .build().toBytes(), resultCaptor, mHandler); resultCaptor.await(); EuiccCardException e = (EuiccCardException) resultCaptor.exception; assertEquals(0x6985, ((ApduException) e.getCause()).getApduStatus()); verifyStoreData(channel, "BF361FBF2300"); // ES8+.InitialiseSecureChannel verifyStoreData(channel, "A0058703010203"); // ES8+.ConfigureISDP verifyStoreData(channel, "A105"); // ES8+.StoreMetadata verifyStoreData(channel, "8803040506"); // ES8+.StoreMetadata } @Test public void testCancelSession() { int channel = mockLogicalChannelResponses("BF41009000"); Loading
tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java +43 −2 Original line number Diff line number Diff line Loading @@ -32,8 +32,8 @@ import android.os.HandlerThread; import com.android.internal.telephony.CommandException; import com.android.internal.telephony.CommandsInterface; import com.android.internal.telephony.uicc.IccIoResult; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback; import org.junit.After; import org.junit.Before; Loading @@ -47,9 +47,10 @@ import java.util.concurrent.TimeUnit; public class ApduSenderTest { private static final long WAIT_TIMEOUT_MLLIS = 5000; private static class ResponseCaptor extends AsyncResultCallback<byte[]> { private static class ResponseCaptor extends ApduSenderResultCallback { public byte[] response; public Throwable exception; public int stopApduIndex = -1; private final CountDownLatch mLatch = new CountDownLatch(1); Loading @@ -63,6 +64,18 @@ public class ApduSenderTest { } } @Override public boolean shouldContinueOnIntermediateResult(IccIoResult result) { if (stopApduIndex < 0) { return true; } if (stopApduIndex == 0) { return false; } stopApduIndex--; return true; } @Override public void onResult(byte[] bytes) { response = bytes; Loading Loading @@ -185,6 +198,34 @@ public class ApduSenderTest { eq(0), eq(2), eq("abcd"), any()); } @Test public void testSendMultiApdusStopEarly() throws InterruptedException { String aid = "B2C3D4"; ApduSender sender = new ApduSender(mMockCi, aid, false /* supportExtendedApdu */); int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000"); LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000", "A39000", "A49000"); LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel); mResponseCaptor.stopApduIndex = 2; sender.send((selectResponse, requestBuilder) -> { requestBuilder.addApdu(10, 1, 2, 3, 0, "a"); requestBuilder.addApdu(10, 1, 2, 3, "ab"); requestBuilder.addApdu(10, 1, 2, 3); requestBuilder.addStoreData("abcd"); }, mResponseCaptor, mHandler); mResponseCaptor.await(); assertEquals("A3", IccUtils.bytesToHexString(mResponseCaptor.response)); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), eq(3), eq(1), eq("ab"), any()); verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq(""), any()); } @Test public void testSendLongResponse() throws InterruptedException { String aid = "B2C3D4"; Loading