Compare commits

...

2 commits

Author SHA1 Message Date
c62e8bcecd core: Implement TelephonyManagerApduInterface
Untested; need to build on AOSP and test on real eSIM device.
2023-11-25 21:58:08 -05:00
1c2ca55d51 app: build.gradle: Set versionCode to timestamp for debug builds 2023-11-25 21:56:11 -05:00
5 changed files with 57 additions and 68 deletions

View file

@ -67,6 +67,11 @@ android {
signingConfig signingConfigs.config
}
}
applicationVariants.all { variant ->
if (variant.name == "debug") {
variant.outputs.each { o -> o.versionCodeOverride = System.currentTimeSeconds() }
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8

View file

@ -21,7 +21,16 @@ abstract class EuiccChannel(
val removable = info.removable
abstract val lpa: LocalProfileAssistant
abstract val valid: Boolean
val valid: Boolean
get() {
try {
// Try to ping the eUICC card by reading the EID
lpa.eID
} catch (e: Exception) {
return false
}
return true
}
abstract fun close()
}

View file

@ -7,7 +7,6 @@ import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
import java.lang.IllegalStateException
class OmapiApduInterface(
private val service: SEService,
@ -25,23 +24,23 @@ class OmapiApduInterface(
}
override fun logicalChannelOpen(aid: ByteArray): Int {
if (this::lastChannel.isInitialized) {
throw IllegalStateException("Can only open one channel")
check(!this::lastChannel.isInitialized) {
"Can only open one channel"
}
lastChannel = session.openLogicalChannel(aid)!!;
return 0;
}
override fun logicalChannelClose(handle: Int) {
if (handle != 0 || !this::lastChannel.isInitialized) {
throw IllegalStateException("Unknown channel")
check(handle == 0 && !this::lastChannel.isInitialized) {
"Unknown channel"
}
lastChannel.close()
}
override fun transmit(tx: ByteArray): ByteArray {
if (!this::lastChannel.isInitialized) {
throw IllegalStateException("Unknown channel")
check(this::lastChannel.isInitialized) {
"Unknown channel"
}
return lastChannel.transmit(tx)
@ -53,30 +52,9 @@ class OmapiChannel(
service: SEService,
info: EuiccChannelInfo,
) : EuiccChannel(info) {
companion object {
private const val TAG = "OmapiChannel"
private val APPLET_ID = byteArrayOf(-96, 0, 0, 5, 89, 16, 16, -1, -1, -1, -1, -119, 0, 0, 1, 0)
/*fun tryConnect(service: SEService, info: EuiccChannelInfo): OmapiChannel? {
try {
val reader = service.getUiccReader(info.slotId + 1) // slotId from telephony starts from 0
val session = reader.openSession()
val channel = session.openLogicalChannel(APPLET_ID) ?: return null
return OmapiChannel(info, channel)
} catch (e: Exception) {
Log.e(TAG, "Unable to open eUICC channel for slot ${info.slotId}, skipping")
Log.e(TAG, Log.getStackTraceString(e))
return null
}
}*/
}
override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
OmapiApduInterface(service, info),
HttpInterfaceImpl())
override val valid: Boolean
get() = true // TODO: This has to be implemented properly
override fun close() = lpa.close()
}

View file

@ -1,6 +1,8 @@
package im.angry.openeuicc.core
import android.telephony.IccOpenLogicalChannelResponse
import android.telephony.TelephonyManager
import im.angry.openeuicc.util.*
import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
@ -10,24 +12,42 @@ class TelephonyManagerApduInterface(
private val info: EuiccChannelInfo,
private val tm: TelephonyManager
): ApduInterface {
private var lastChannel: Int = -1
override fun connect() {
TODO("Not yet implemented")
// Do nothing
}
override fun disconnect() {
TODO("Not yet implemented")
// Do nothing
}
override fun logicalChannelOpen(aid: ByteArray): Int {
TODO("Not yet implemented")
check(lastChannel == -1) { "Already initialized" }
val hex = aid.encodeHex()
val channel = tm.iccOpenLogicalChannelBySlot(info.slotId, hex, 0)
if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
throw IllegalArgumentException("Cannot open logical channel " + hex + " via TelephonManager on slot " + info.slotId);
}
return channel.channel
}
override fun logicalChannelClose(handle: Int) {
TODO("Not yet implemented")
tm.iccCloseLogicalChannelBySlot(info.slotId, handle)
}
override fun transmit(tx: ByteArray): ByteArray {
TODO("Not yet implemented")
check(lastChannel != -1) { "Uninitialized" }
val cla = tx[0].toInt()
val instruction = tx[1].toInt()
val p1 = tx[2].toInt()
val p2 = tx[3].toInt()
val p3 = tx[4].toInt()
val p4 = tx.drop(5).toByteArray().encodeHex()
return tm.iccTransmitApduLogicalChannelBySlot(info.slotId, lastChannel,
cla, instruction, p1, p2, p3, p4)?.decodeHex() ?: byteArrayOf()
}
}
@ -36,36 +56,10 @@ class TelephonyManagerChannel(
info: EuiccChannelInfo,
private val tm: TelephonyManager
) : EuiccChannel(info) {
companion object {
private const val TAG = "TelephonyManagerApduChannel"
private const val EUICC_APP_ID = "A0000005591010FFFFFFFF8900000100"
// TODO: On Tiramisu, we need to specify the portId also if we want MEP support
/*fun tryConnect(tm: TelephonyManager, info: EuiccChannelInfo): TelephonyManagerChannel? {
try {
val channel = tm.iccOpenLogicalChannelBySlot(info.slotId, EUICC_APP_ID, 0)
if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
Log.e(TAG, "Unable to open eUICC channel for slot ${info.slotId} via TelephonyManager: ${channel.status}")
return null
}
Log.d(TAG, "channel: ${channel.channel}")
return TelephonyManagerChannel(info, tm, channel.channel)
} catch (e: Exception) {
Log.e(TAG, "Unable to open eUICC channel for slot ${info.slotId} via TelephonyManager")
Log.e(TAG, Log.getStackTraceString(e))
return null
}
}*/
}
override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
TelephonyManagerApduInterface(info, tm),
HttpInterfaceImpl()
)
override val valid: Boolean
get() = true // TODO: Fix this
override fun close() = lpa.close()
}

View file

@ -1,19 +1,22 @@
package im.angry.openeuicc.util
fun hexStringToByteArray(str: String): ByteArray {
val length = str.length / 2
val out = ByteArray(length)
for (i in 0 until length) {
fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
val decodedLength = length / 2
val out = ByteArray(decodedLength)
for (i in 0 until decodedLength) {
val i2 = i * 2
out[i] = str.substring(i2, i2 + 2).toInt(16).toByte()
out[i] = substring(i2, i2 + 2).toInt(16).toByte()
}
return out
}
fun byteArrayToHex(arr: ByteArray): String {
fun ByteArray.encodeHex(): String {
val sb = StringBuilder()
val length = arr.size
val length = size
for (i in 0 until length) {
sb.append(String.format("%02X", arr[i]))
sb.append(String.format("%02X", this[i]))
}
return sb.toString()
}