WIP: feat: add eastcompeace supports #185

Closed
septs wants to merge 9 commits from septs:scid into master

View file

@ -12,23 +12,16 @@ data class EuiccVendorInfo(
val firmwareVersion: String?, val firmwareVersion: String?,
) )
private val EUICC_VENDORS: Array<EuiccVendor> = arrayOf(EstkMe(), SimLink()) private val EUICC_VENDORS: Array<EuiccVendor> = arrayOf(ESTKme(), SIMLink9(), Eastcompeace())
fun EuiccChannel.tryParseEuiccVendorInfo(): EuiccVendorInfo? { fun EuiccChannel.tryParseEuiccVendorInfo(): EuiccVendorInfo? =
EUICC_VENDORS.forEach { vendor -> EUICC_VENDORS.firstNotNullOfOrNull { it.tryParseEuiccVendorInfo(this) }
vendor.tryParseEuiccVendorInfo(this@tryParseEuiccVendorInfo)?.let {
return it
}
}
return null
}
interface EuiccVendor { interface EuiccVendor {
fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo?
} }
private class EstkMe : EuiccVendor { private class ESTKme : EuiccVendor {
companion object { companion object {
private val PRODUCT_AID = "A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex() private val PRODUCT_AID = "A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex()
private val PRODUCT_ATR_FPR = "estk.me".encodeToByteArray() private val PRODUCT_ATR_FPR = "estk.me".encodeToByteArray()
@ -47,12 +40,6 @@ private class EstkMe : EuiccVendor {
return false 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? { override fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? {
if (!checkAtr(channel)) return null if (!checkAtr(channel)) return null
@ -60,7 +47,8 @@ private class EstkMe : EuiccVendor {
return try { return try {
iface.withLogicalChannel(PRODUCT_AID) { transmit -> iface.withLogicalChannel(PRODUCT_AID) { transmit ->
fun invoke(p1: Byte) = fun invoke(p1: Byte) =
decodeAsn1String(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00))) decodeResponse(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00)))
?.decodeToString()
EuiccVendorInfo( EuiccVendorInfo(
skuName = invoke(0x03), skuName = invoke(0x03),
serialNumber = invoke(0x00), serialNumber = invoke(0x00),
@ -75,7 +63,7 @@ private class EstkMe : EuiccVendor {
} }
} }
private class SimLink : EuiccVendor { private class SIMLink9 : EuiccVendor {
companion object { companion object {
private val EID_PATTERN = Regex("^89044045(84|21)67274948") private val EID_PATTERN = Regex("^89044045(84|21)67274948")
} }
@ -110,3 +98,41 @@ private class SimLink : EuiccVendor {
) )
} }
} }
@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)
}