Compare commits

..

2 commits

Author SHA1 Message Date
9ddfd62030
Merge branch 'master' into product-detecting 2025-02-26 21:43:07 +08:00
c8ecdee095 feat: supports for multi logical channel (#148)
Reviewed-on: PeterCxy/OpenEUICC#148
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-02-26 14:33:18 +01:00
8 changed files with 37 additions and 26 deletions

View file

@ -1,6 +1,7 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
interface EuiccChannel { interface EuiccChannel {
@ -28,5 +29,10 @@ interface EuiccChannel {
*/ */
val intrinsicChannelName: String? val intrinsicChannelName: String?
/**
* The underlying APDU interface for this channel
*/
val apduInterface: ApduInterface
fun close() fun close()
} }

View file

@ -1,6 +1,7 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.UiccPortInfoCompat
import im.angry.openeuicc.util.decodeHex
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
@ -11,7 +12,7 @@ class EuiccChannelImpl(
override val type: String, override val type: String,
override val port: UiccPortInfoCompat, override val port: UiccPortInfoCompat,
override val intrinsicChannelName: String?, override val intrinsicChannelName: String?,
private val apduInterface: ApduInterface, override val apduInterface: ApduInterface,
verboseLoggingFlow: Flow<Boolean>, verboseLoggingFlow: Flow<Boolean>,
ignoreTLSCertificateFlow: Flow<Boolean> ignoreTLSCertificateFlow: Flow<Boolean>
) : EuiccChannel { ) : EuiccChannel {

View file

@ -1,6 +1,7 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
@ -33,6 +34,8 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
get() = channel.valid get() = channel.valid
override val intrinsicChannelName: String? override val intrinsicChannelName: String?
get() = channel.intrinsicChannelName get() = channel.intrinsicChannelName
override val apduInterface: ApduInterface
get() = channel.apduInterface
override val atr: ByteArray? override val atr: ByteArray?
get() = channel.atr get() = channel.atr

View file

@ -7,9 +7,9 @@ import android.util.Log
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.ApduInterface
import java.util.concurrent.atomic.AtomicInteger
class OmapiApduInterface( class OmapiApduInterface(
private val service: SEService, private val service: SEService,
@ -21,8 +21,12 @@ class OmapiApduInterface(
} }
private lateinit var session: Session private lateinit var session: Session
private val channels = mutableMapOf<Int, Channel>() private val channels = arrayOf<Channel?>(
private val index = AtomicInteger(0) null,
null,
null,
null,
)
override val valid: Boolean override val valid: Boolean
get() = service.isConnected && (this::session.isInitialized && !session.isClosed) get() = service.isConnected && (this::session.isInitialized && !session.isClosed)
@ -41,23 +45,22 @@ class OmapiApduInterface(
override fun logicalChannelOpen(aid: ByteArray): Int { override fun logicalChannelOpen(aid: ByteArray): Int {
val channel = session.openLogicalChannel(aid) val channel = session.openLogicalChannel(aid)
check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" } check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" }
val id = index.addAndGet(1) val index = channels.indexOf(null)
channels[id] = channel check(index != -1) { "No free logical channel slots" }
return id synchronized(channels) { channels[index] = channel }
return index
} }
override fun logicalChannelClose(handle: Int) { override fun logicalChannelClose(handle: Int) {
val channel = channels[handle] val channel = channels.getOrNull(handle)
check(channel != null) { "Invalid logical channel handle $handle" } check(channel != null) { "Invalid logical channel handle $handle" }
channels.remove(handle)
if (channel.isOpen) channel.close() if (channel.isOpen) channel.close()
synchronized(channels) { channels[handle] = null }
} }
override fun transmit(handle: Int, tx: ByteArray): ByteArray { override fun transmit(handle: Int, tx: ByteArray): ByteArray {
val channel = channels[handle] val channel = channels.getOrNull(handle)
check(channel != null) { check(channel != null) { "Invalid logical channel handle $handle" }
"Invalid logical channel handle $handle"
}
if (runBlocking { verboseLoggingFlow.first() }) { if (runBlocking { verboseLoggingFlow.first() }) {
Log.d(TAG, "OMAPI APDU: ${tx.encodeHex()}") Log.d(TAG, "OMAPI APDU: ${tx.encodeHex()}")

View file

@ -17,18 +17,12 @@ interface ApduInterface {
*/ */
val valid: Boolean val valid: Boolean
fun <T> openChannel(aid: ByteArray, callback: (TransmitProvider) -> T): T { fun <T> withLogicalChannel(aid: ByteArray, callback: ((ByteArray) -> ByteArray) -> T): T {
val handle = logicalChannelOpen(aid) val handle = logicalChannelOpen(aid)
return try { return try {
callback(object : TransmitProvider { callback { tx -> transmit(handle, tx) }
override fun transmit(tx: ByteArray) = transmit(handle, tx)
})
} finally { } finally {
logicalChannelClose(handle) logicalChannelClose(handle)
} }
} }
} }
interface TransmitProvider {
fun transmit(tx: ByteArray): ByteArray
}

View file

@ -53,20 +53,23 @@ 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, jint ret = (*env)->CallIntMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface,
method_apdu_logical_channel_open, jbarr); method_apdu_logical_channel_open, jbarr);
LPAC_JNI_EXCEPTION_RETURN; LPAC_JNI_EXCEPTION_RETURN;
LPAC_JNI_CTX(ctx)->logical_channel_id = ret;
return ret; return ret;
} }
static void apdu_interface_logical_channel_close(struct euicc_ctx *ctx, uint8_t channel) { static void apdu_interface_logical_channel_close(struct euicc_ctx *ctx,
__attribute__((unused)) uint8_t channel) {
LPAC_JNI_SETUP_ENV; LPAC_JNI_SETUP_ENV;
jint logical_channel_id = LPAC_JNI_CTX(ctx)->logical_channel_id;
(*env)->CallVoidMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface, (*env)->CallVoidMethod(env, LPAC_JNI_CTX(ctx)->apdu_interface,
method_apdu_logical_channel_close, channel); method_apdu_logical_channel_close, logical_channel_id);
(*env)->ExceptionClear(env); (*env)->ExceptionClear(env);
} }
static int static int
apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx,
uint32_t tx_len) { uint32_t tx_len) {
const int logic_channel = ctx->apdu._internal.logic_channel; const int logic_channel = LPAC_JNI_CTX(ctx)->logical_channel_id;
LPAC_JNI_SETUP_ENV; LPAC_JNI_SETUP_ENV;
jbyteArray txArr = (*env)->NewByteArray(env, tx_len); jbyteArray txArr = (*env)->NewByteArray(env, tx_len);
(*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx); (*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx);

View file

@ -28,7 +28,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
string_constructor = (*env)->GetMethodID(env, string_class, "<init>", string_constructor = (*env)->GetMethodID(env, string_class, "<init>",
"([BLjava/lang/String;)V"); "([BLjava/lang/String;)V");
const char _unused[1]; const jchar _unused[1];
empty_string = (*env)->NewString(env, _unused, 0); empty_string = (*env)->NewString(env, _unused, 0);
empty_string = (*env)->NewGlobalRef(env, empty_string); empty_string = (*env)->NewGlobalRef(env, empty_string);

View file

@ -8,6 +8,7 @@ _Static_assert(sizeof(void *) <= sizeof(jlong),
"jlong must be big enough to hold a platform raw pointer"); "jlong must be big enough to hold a platform raw pointer");
struct lpac_jni_ctx { struct lpac_jni_ctx {
jint logical_channel_id;
jobject apdu_interface; jobject apdu_interface;
jobject http_interface; jobject http_interface;
}; };