ソースを参照

core: Implement TelephonyManagerApduInterface

Untested; need to build on AOSP and test on real eSIM device.
Peter Cai 2 年 前
コミット
c62e8bcecd

+ 10 - 1
app/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt

@@ -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()
 }

+ 6 - 28
app/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt

@@ -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()
 }

+ 25 - 31
app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt

@@ -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()
 }

+ 11 - 8
app/src/main/java/im/angry/openeuicc/util/StringUtils.kt

@@ -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()
 }