diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Vendors.kt b/app-common/src/main/java/im/angry/openeuicc/util/Vendors.kt index 529f9ee..a1ff0a6 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Vendors.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Vendors.kt @@ -12,23 +12,16 @@ data class EuiccVendorInfo( val firmwareVersion: String?, ) -private val EUICC_VENDORS: Array = arrayOf(EstkMe(), SimLink()) +private val EUICC_VENDORS: Array = arrayOf(ESTKme(), SIMLink9(), Eastcompeace()) -fun EuiccChannel.tryParseEuiccVendorInfo(): EuiccVendorInfo? { - EUICC_VENDORS.forEach { vendor -> - vendor.tryParseEuiccVendorInfo(this@tryParseEuiccVendorInfo)?.let { - return it - } - } - - return null -} +fun EuiccChannel.tryParseEuiccVendorInfo(): EuiccVendorInfo? = + EUICC_VENDORS.firstNotNullOfOrNull { it.tryParseEuiccVendorInfo(this) } interface EuiccVendor { fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? } -private class EstkMe : EuiccVendor { +private class ESTKme : EuiccVendor { companion object { private val PRODUCT_AID = "A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex() private val PRODUCT_ATR_FPR = "estk.me".encodeToByteArray() @@ -47,12 +40,6 @@ private class EstkMe : EuiccVendor { return false } - private fun decodeAsn1String(b: ByteArray): String? { - if (b.size < 2) return null - if (b[b.size - 2] != 0x90.toByte() || b[b.size - 1] != 0x00.toByte()) return null - return b.sliceArray(0 until b.size - 2).decodeToString() - } - override fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? { if (!checkAtr(channel)) return null @@ -60,7 +47,8 @@ private class EstkMe : EuiccVendor { return try { iface.withLogicalChannel(PRODUCT_AID) { transmit -> fun invoke(p1: Byte) = - decodeAsn1String(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00))) + decodeResponse(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00))) + ?.decodeToString() EuiccVendorInfo( skuName = invoke(0x03), serialNumber = invoke(0x00), @@ -75,7 +63,7 @@ private class EstkMe : EuiccVendor { } } -private class SimLink : EuiccVendor { +private class SIMLink9 : EuiccVendor { companion object { private val EID_PATTERN = Regex("^89044045(84|21)67274948") } @@ -109,4 +97,42 @@ private class SimLink : EuiccVendor { firmwareVersion = null ) } +} + +@Suppress("SpellCheckingInspection") +private class Eastcompeace : EuiccVendor { + companion object { + private const val EID_PREFIX = "89086030" + private val PRODUCT_AID = "A000000533C000FF860000000427".decodeHex() + private val GET_SCID_COMMAND = "80CA000050".decodeHex() + } + + override fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? { + if (!channel.lpa.eID.startsWith(EID_PREFIX)) return null + return try { + channel.apduInterface.withLogicalChannel(PRODUCT_AID) { transmit -> + decodeResponse(transmit(GET_SCID_COMMAND))?.let(::parseSCID) + } + } catch (e: Exception) { + Log.d(TAG, "Failed to get EastcompeaceInfo", e) + null + } + } + + fun parseSCID(scid: ByteArray): EuiccVendorInfo { + // TODO: Some data needs to be accumulated to distinguish SKUs + Log.i(TAG, "Eastcompeace SCID: ${scid.encodeHex()}") + return EuiccVendorInfo( + skuName = "Eastcompeace", + serialNumber = null, + bootloaderVersion = null, + firmwareVersion = null, + ) + } +} + +private fun decodeResponse(b: ByteArray): ByteArray? { + if (b.size < 2) return null + if (b[b.size - 2] != 0x90.toByte() || b[b.size - 1] != 0x00.toByte()) return null + return b.sliceArray(0 until b.size - 2) } \ No newline at end of file