diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt index 597a70d..5f399ea 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt @@ -1,7 +1,6 @@ package im.angry.openeuicc.core import im.angry.openeuicc.util.* -import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.LocalProfileAssistant interface EuiccChannel { @@ -29,10 +28,5 @@ interface EuiccChannel { */ val intrinsicChannelName: String? - /** - * The underlying APDU interface for this channel - */ - val apduInterface: ApduInterface - fun close() } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt index a56b1cc..3da829a 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt @@ -1,7 +1,6 @@ package im.angry.openeuicc.core -import im.angry.openeuicc.util.UiccPortInfoCompat -import im.angry.openeuicc.util.decodeHex +import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.Flow import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.LocalProfileAssistant @@ -12,7 +11,7 @@ class EuiccChannelImpl( override val type: String, override val port: UiccPortInfoCompat, override val intrinsicChannelName: String?, - override val apduInterface: ApduInterface, + private val apduInterface: ApduInterface, verboseLoggingFlow: Flow, ignoreTLSCertificateFlow: Flow ) : EuiccChannel { diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt index 09004d3..4204e82 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt @@ -1,7 +1,6 @@ package im.angry.openeuicc.core import im.angry.openeuicc.util.* -import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.LocalProfileAssistant class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { @@ -34,8 +33,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { get() = channel.valid override val intrinsicChannelName: String? get() = channel.intrinsicChannelName - override val apduInterface: ApduInterface - get() = channel.apduInterface override val atr: ByteArray? get() = channel.atr diff --git a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt index b3f42b5..c70669d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt @@ -7,6 +7,7 @@ import android.util.Log import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.single import kotlinx.coroutines.runBlocking import net.typeblog.lpac_jni.ApduInterface @@ -20,12 +21,7 @@ class OmapiApduInterface( } private lateinit var session: Session - private val channels = arrayOf( - null, - null, - null, - null, - ) + private lateinit var lastChannel: Channel override val valid: Boolean get() = service.isConnected && (this::session.isInitialized && !session.isClosed) @@ -42,24 +38,24 @@ class OmapiApduInterface( } override fun logicalChannelOpen(aid: ByteArray): Int { - val channel = session.openLogicalChannel(aid) - check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" } - val index = channels.indexOf(null) - check(index != -1) { "No free logical channel slots" } - synchronized(channels) { channels[index] = channel } - return index + check(!this::lastChannel.isInitialized) { + "Can only open one channel" + } + lastChannel = session.openLogicalChannel(aid)!! + return 1 } override fun logicalChannelClose(handle: Int) { - val channel = channels.getOrNull(handle) - check(channel != null) { "Invalid logical channel handle $handle" } - if (channel.isOpen) channel.close() - synchronized(channels) { channels[handle] = null } + check(handle == 1 && !this::lastChannel.isInitialized) { + "Unknown channel" + } + lastChannel.close() } - override fun transmit(handle: Int, tx: ByteArray): ByteArray { - val channel = channels.getOrNull(handle) - check(channel != null) { "Invalid logical channel handle $handle" } + override fun transmit(tx: ByteArray): ByteArray { + check(this::lastChannel.isInitialized) { + "Unknown channel" + } if (runBlocking { verboseLoggingFlow.first() }) { Log.d(TAG, "OMAPI APDU: ${tx.encodeHex()}") @@ -67,7 +63,7 @@ class OmapiApduInterface( try { for (i in 0..10) { - val res = channel.transmit(tx) + val res = lastChannel.transmit(tx) if (runBlocking { verboseLoggingFlow.first() }) { Log.d(TAG, "OMAPI APDU response: ${res.encodeHex()}") } diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt index f9e764b..624ef89 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt @@ -21,13 +21,10 @@ class UsbApduInterface( private lateinit var ccidDescription: UsbCcidDescription private lateinit var transceiver: UsbCcidTransceiver + private var channelId = -1 + override var atr: ByteArray? = null - override val valid: Boolean - get() = channels.isNotEmpty() - - private var channels = mutableSetOf() - override fun connect() { ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!! @@ -49,11 +46,11 @@ class UsbApduInterface( override fun disconnect() { conn.close() - - atr = null } override fun logicalChannelOpen(aid: ByteArray): Int { + check(channelId == -1) { "Logical channel already opened" } + // OPEN LOGICAL CHANNEL val req = manageChannelCmd(true, 0) @@ -69,7 +66,7 @@ class UsbApduInterface( return -1 } - val channelId = resp[0].toInt() + channelId = resp[0].toInt() Log.d(TAG, "channelId = $channelId") // Then, select AID @@ -81,32 +78,32 @@ class UsbApduInterface( return -1 } - channels.add(channelId) - return channelId } override fun logicalChannelClose(handle: Int) { - check(channels.contains(handle)) { - "Invalid logical channel handle $handle" - } + check(handle == channelId) { "Logical channel ID mismatch" } + check(channelId != -1) { "Logical channel is not opened" } + // CLOSE LOGICAL CHANNEL - val req = manageChannelCmd(false, handle.toByte()) - val resp = transmitApduByChannel(req, handle.toByte()) + val req = manageChannelCmd(false, channelId.toByte()) + val resp = transmitApduByChannel(req, channelId.toByte()) if (!isSuccessResponse(resp)) { Log.d(TAG, "CLOSE LOGICAL CHANNEL failed: ${resp.encodeHex()}") } - channels.remove(handle) + + channelId = -1 } - override fun transmit(handle: Int, tx: ByteArray): ByteArray { - check(channels.contains(handle)) { - "Invalid logical channel handle $handle" - } - return transmitApduByChannel(tx, handle.toByte()) + override fun transmit(tx: ByteArray): ByteArray { + check(channelId != -1) { "Logical channel is not opened" } + return transmitApduByChannel(tx, channelId.toByte()) } + override val valid: Boolean + get() = channelId != -1 + private fun isSuccessResponse(resp: ByteArray): Boolean = resp.size >= 2 && resp[resp.size - 2] == 0x90.toByte() && resp[resp.size - 1] == 0x00.toByte() diff --git a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt index f0b1909..6b09368 100644 --- a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt +++ b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt @@ -18,10 +18,12 @@ class TelephonyManagerApduInterface( const val TAG = "TelephonyManagerApduInterface" } - override val valid: Boolean - get() = channels.isNotEmpty() + private var lastChannel: Int = -1 - private var channels = mutableSetOf() + override val valid: Boolean + // TelephonyManager channels will never become truly "invalid", + // just that transactions might return errors or nonsense + get() = lastChannel != -1 override fun connect() { // Do nothing @@ -29,39 +31,52 @@ class TelephonyManagerApduInterface( override fun disconnect() { // Do nothing + lastChannel = -1 } override fun logicalChannelOpen(aid: ByteArray): Int { + check(lastChannel == -1) { "Already initialized" } val hex = aid.encodeHex() val channel = tm.iccOpenLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, hex, 0) if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) { - throw IllegalArgumentException("Cannot open logical channel $hex via TelephonyManager on slot ${port.card.physicalSlotIndex} port ${port.portIndex}") + throw IllegalArgumentException("Cannot open logical channel $hex via TelephonManager on slot ${port.card.physicalSlotIndex} port ${port.portIndex}") } - channels.add(channel.channel) - return channel.channel + lastChannel = channel.channel + return lastChannel } override fun logicalChannelClose(handle: Int) { - check(channels.contains(handle)) { - "Invalid logical channel handle $handle" - } + check(handle == lastChannel) { "Invalid channel handle " } tm.iccCloseLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, handle) - channels.remove(handle) + lastChannel = -1 } - override fun transmit(handle: Int, tx: ByteArray): ByteArray { - check(channels.contains(handle)) { - "Invalid logical channel handle $handle" - } + override fun transmit(tx: ByteArray): ByteArray { + check(lastChannel != -1) { "Uninitialized" } + if (runBlocking { verboseLoggingFlow.first() }) { Log.d(TAG, "TelephonyManager APDU: ${tx.encodeHex()}") } - val result = tm.iccTransmitApduLogicalChannelByPortCompat( - port.card.physicalSlotIndex, port.portIndex, handle, - tx, - ) - if (runBlocking { verboseLoggingFlow.first() }) - Log.d(TAG, "TelephonyManager APDU response: $result") - return result?.decodeHex() ?: byteArrayOf() + + val cla = tx[0].toUByte().toInt() + val instruction = tx[1].toUByte().toInt() + val p1 = tx[2].toUByte().toInt() + val p2 = tx[3].toUByte().toInt() + val p3 = tx[4].toUByte().toInt() + val p4 = tx.drop(5).toByteArray().encodeHex() + + return tm.iccTransmitApduLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, lastChannel, + cla, + instruction, + p1, + p2, + p3, + p4 + ).also { + if (runBlocking { verboseLoggingFlow.first() }) { + Log.d(TAG, "TelephonyManager APDU response: $it") + } + }?.decodeHex() ?: byteArrayOf() } + } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyCompat.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyCompat.kt index a9df18e..dbd39f2 100644 --- a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyCompat.kt +++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyCompat.kt @@ -111,26 +111,15 @@ fun TelephonyManager.iccCloseLogicalChannelByPortCompat( } fun TelephonyManager.iccTransmitApduLogicalChannelByPortCompat( - slotIndex: Int, - portIndex: Int, - channel: Int, - tx: ByteArray -): String? { - val cla = tx[0].toUByte().toInt() - val ins = tx[1].toUByte().toInt() - val p1 = tx[2].toUByte().toInt() - val p2 = tx[3].toUByte().toInt() - val p3 = tx[4].toUByte().toInt() - val p4 = tx.drop(5).toByteArray().encodeHex() - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + slotIndex: Int, portIndex: Int, channel: Int, + cla: Int, inst: Int, p1: Int, p2: Int, p3: Int, data: String? +): String? = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { iccTransmitApduLogicalChannelByPort( - slotIndex, portIndex, channel, - cla, ins, p1, p2, p3, p4 + slotIndex, portIndex, channel, cla, inst, p1, p2, p3, data ) } else { iccTransmitApduLogicalChannelBySlot( - slotIndex, channel, - cla, ins, p1, p2, p3, p4 + slotIndex, channel, cla, inst, p1, p2, p3, data ) } -} diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt index fe98b1a..dfa92df 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt @@ -8,7 +8,7 @@ interface ApduInterface { fun disconnect() fun logicalChannelOpen(aid: ByteArray): Int fun logicalChannelClose(handle: Int) - fun transmit(handle: Int, tx: ByteArray): ByteArray + fun transmit(tx: ByteArray): ByteArray /** * Is this APDU connection still valid? @@ -16,13 +16,4 @@ interface ApduInterface { * callers should further check with the LPA to fully determine the validity of a channel */ val valid: Boolean - - fun withLogicalChannel(aid: ByteArray, callback: ((ByteArray) -> ByteArray) -> T): T { - val handle = logicalChannelOpen(aid) - return try { - callback { transmit(handle, it) } - } finally { - logicalChannelClose(handle) - } - } -} +} \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index faf5312..8aafe94 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -28,9 +28,9 @@ class LocalProfileAssistantImpl( var lastApduResponse: ByteArray? = null var lastApduException: Exception? = null - override fun transmit(handle: Int, tx: ByteArray): ByteArray = + override fun transmit(tx: ByteArray): ByteArray = try { - apduInterface.transmit(handle, tx).also { + apduInterface.transmit(tx).also { lastApduException = null lastApduResponse = it } diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c index a61fc96..9059171 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c @@ -22,7 +22,7 @@ void interface_wrapper_init() { "([B)I"); method_apdu_logical_channel_close = (*env)->GetMethodID(env, apdu_class, "logicalChannelClose", "(I)V"); - method_apdu_transmit = (*env)->GetMethodID(env, apdu_class, "transmit", "(I[B)[B"); + method_apdu_transmit = (*env)->GetMethodID(env, apdu_class, "transmit", "([B)[B"); jclass http_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/HttpInterface"); method_http_transmit = (*env)->GetMethodID(env, http_class, "transmit", @@ -53,30 +53,24 @@ apdu_interface_logical_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, u jint ret = (*env)->CallIntMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, method_apdu_logical_channel_open, jbarr); LPAC_JNI_EXCEPTION_RETURN; - LPAC_JNI_CTX(ctx)->logical_channel_id = ret; return ret; } -static void apdu_interface_logical_channel_close(struct euicc_ctx *ctx, - __attribute__((unused)) uint8_t channel) { +static void apdu_interface_logical_channel_close(struct euicc_ctx *ctx, uint8_t channel) { LPAC_JNI_SETUP_ENV; - jint logical_channel_id = LPAC_JNI_CTX(ctx)->logical_channel_id; (*env)->CallVoidMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, - method_apdu_logical_channel_close, logical_channel_id); + method_apdu_logical_channel_close, channel); (*env)->ExceptionClear(env); } static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) { - const int logic_channel = LPAC_JNI_CTX(ctx)->logical_channel_id; LPAC_JNI_SETUP_ENV; jbyteArray txArr = (*env)->NewByteArray(env, tx_len); (*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx); - jbyteArray ret = (jbyteArray) (*env)->CallObjectMethod( - env, LPAC_JNI_CTX(ctx)->apdu_interface, - method_apdu_transmit, logic_channel, txArr - ); + jbyteArray ret = (jbyteArray) (*env)->CallObjectMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, + method_apdu_transmit, txArr); LPAC_JNI_EXCEPTION_RETURN; *rx_len = (*env)->GetArrayLength(env, ret); *rx = calloc(*rx_len, sizeof(uint8_t)); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index ca319db..6ea9d3e 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -28,7 +28,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) { string_constructor = (*env)->GetMethodID(env, string_class, "", "([BLjava/lang/String;)V"); - const jchar _unused[1]; + const char _unused[1]; empty_string = (*env)->NewString(env, _unused, 0); empty_string = (*env)->NewGlobalRef(env, empty_string); diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h index c2300be..a5d6262 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h @@ -8,7 +8,6 @@ _Static_assert(sizeof(void *) <= sizeof(jlong), "jlong must be big enough to hold a platform raw pointer"); struct lpac_jni_ctx { - jint logical_channel_id; jobject apdu_interface; jobject http_interface; };