refactor: usb ccid driver #149

Merged
PeterCxy merged 1 commit from septs/OpenEUICC:usb-ccid into master 2025-03-04 03:14:03 +01:00
5 changed files with 61 additions and 78 deletions

View file

@ -8,7 +8,8 @@ import android.se.omapi.SEService
import android.util.Log import android.util.Log
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.usb.UsbApduInterface import im.angry.openeuicc.core.usb.UsbApduInterface
import im.angry.openeuicc.core.usb.getIoEndpoints import im.angry.openeuicc.core.usb.bulkPair
import im.angry.openeuicc.core.usb.endpoints
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
@ -61,7 +62,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
} }
override fun tryOpenUsbEuiccChannel(usbDevice: UsbDevice, usbInterface: UsbInterface): EuiccChannel? { override fun tryOpenUsbEuiccChannel(usbDevice: UsbDevice, usbInterface: UsbInterface): EuiccChannel? {
val (bulkIn, bulkOut) = usbInterface.getIoEndpoints() val (bulkIn, bulkOut) = usbInterface.endpoints.bulkPair
if (bulkIn == null || bulkOut == null) return null if (bulkIn == null || bulkOut == null) return null
val conn = usbManager.openDevice(usbDevice) ?: return null val conn = usbManager.openDevice(usbDevice) ?: return null
if (!conn.claimInterface(usbInterface, true)) return null if (!conn.claimInterface(usbInterface, true)) return null

View file

@ -5,7 +5,8 @@ import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager import android.hardware.usb.UsbManager
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.util.Log import android.util.Log
import im.angry.openeuicc.core.usb.getSmartCardInterface import im.angry.openeuicc.core.usb.smartCard
import im.angry.openeuicc.core.usb.interfaces
import im.angry.openeuicc.di.AppContainer import im.angry.openeuicc.di.AppContainer
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -244,7 +245,7 @@ open class DefaultEuiccChannelManager(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
usbManager.deviceList.values.forEach { device -> usbManager.deviceList.values.forEach { device ->
Log.i(TAG, "Scanning USB device ${device.deviceId}:${device.vendorId}") Log.i(TAG, "Scanning USB device ${device.deviceId}:${device.vendorId}")
val iface = device.getSmartCardInterface() ?: return@forEach val iface = device.interfaces.smartCard ?: return@forEach
// If we don't have permission, tell UI code that we found a candidate device, but we // If we don't have permission, tell UI code that we found a candidate device, but we
// need permission to be able to do anything with it // need permission to be able to do anything with it
if (!usbManager.hasPermission(device)) return@withContext Pair(device, false) if (!usbManager.hasPermission(device)) return@withContext Pair(device, false)

View file

@ -20,12 +20,12 @@ data class UsbCcidDescription(
private const val FEATURE_EXCHANGE_LEVEL_TPDU = 0x10000 private const val FEATURE_EXCHANGE_LEVEL_TPDU = 0x10000
private const val FEATURE_EXCHANGE_LEVEL_SHORT_APDU = 0x20000 private const val FEATURE_EXCHANGE_LEVEL_SHORT_APDU = 0x20000
private const val FEATURE_EXCHAGE_LEVEL_EXTENDED_APDU = 0x40000 private const val FEATURE_EXCHANGE_LEVEL_EXTENDED_APDU = 0x40000
// bVoltageSupport Masks // bVoltageSupport Masks
private const val VOLTAGE_5V: Byte = 1 private const val VOLTAGE_5V0: Byte = 1
private const val VOLTAGE_3V: Byte = 2 private const val VOLTAGE_3V0: Byte = 2
private const val VOLTAGE_1_8V: Byte = 4 private const val VOLTAGE_1V8: Byte = 4
private const val SLOT_OFFSET = 4 private const val SLOT_OFFSET = 4
private const val FEATURES_OFFSET = 40 private const val FEATURES_OFFSET = 40
@ -71,30 +71,23 @@ data class UsbCcidDescription(
} }
enum class Voltage(powerOnValue: Int, mask: Int) { enum class Voltage(powerOnValue: Int, mask: Int) {
AUTO(0, 0), _5V(1, VOLTAGE_5V.toInt()), _3V(2, VOLTAGE_3V.toInt()), _1_8V( // @formatter:off
3, AUTO(0, 0),
VOLTAGE_1_8V.toInt() V50(1, VOLTAGE_5V0.toInt()),
); V30(2, VOLTAGE_3V0.toInt()),
V18(3, VOLTAGE_1V8.toInt());
// @formatter:on
val mask = powerOnValue.toByte() val mask = powerOnValue.toByte()
val powerOnValue = mask.toByte() val powerOnValue = mask.toByte()
} }
private fun hasFeature(feature: Int): Boolean = private fun hasFeature(feature: Int) = (dwFeatures and feature) != 0
(dwFeatures and feature) != 0
val voltages: Array<Voltage> val voltages: List<Voltage>
get() = get() {
if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) { if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) return listOf(Voltage.AUTO)
arrayOf(Voltage.AUTO) return Voltage.entries.filter { (it.mask.toInt() and bVoltageSupport.toInt()) != 0 }
} else {
Voltage.values().mapNotNull {
if ((it.mask.toInt() and bVoltageSupport.toInt()) != 0) {
it
} else {
null
}
}.toTypedArray()
} }
val hasAutomaticPps: Boolean val hasAutomaticPps: Boolean

View file

@ -95,6 +95,7 @@ class UsbCcidTransceiver(
data class UsbCcidErrorException(val msg: String, val errorResponse: CcidDataBlock) : data class UsbCcidErrorException(val msg: String, val errorResponse: CcidDataBlock) :
Exception(msg) Exception(msg)
@Suppress("ArrayInDataClass")
data class CcidDataBlock( data class CcidDataBlock(
val dwLength: Int, val dwLength: Int,
val bSlot: Byte, val bSlot: Byte,
@ -183,31 +184,26 @@ class UsbCcidTransceiver(
usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS
) )
if (runBlocking { verboseLoggingFlow.first() }) { if (runBlocking { verboseLoggingFlow.first() }) {
Log.d(TAG, "Received " + readBytes + " bytes: " + inputBuffer.encodeHex()) Log.d(TAG, "Received $readBytes bytes: ${inputBuffer.encodeHex()}")
} }
} while (readBytes <= 0 && attempts-- > 0) } while (readBytes <= 0 && attempts-- > 0)
if (readBytes < CCID_HEADER_LENGTH) { if (readBytes < CCID_HEADER_LENGTH) {
throw UsbTransportException("USB-CCID error - failed to receive CCID header") throw UsbTransportException("USB-CCID error - failed to receive CCID header")
} }
if (inputBuffer[0] != MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK.toByte()) { if (inputBuffer[0] != MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK.toByte()) {
throw UsbTransportException(buildString {
append("USB-CCID error - bad CCID header")
append(", type ")
append("%d (expected %d)".format(inputBuffer[0], MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK))
if (expectedSequenceNumber != inputBuffer[6]) { if (expectedSequenceNumber != inputBuffer[6]) {
throw UsbTransportException( append(", sequence number ")
((("USB-CCID error - bad CCID header, type " + inputBuffer[0]) + " (expected " + append("%d (expected %d)".format(inputBuffer[6], expectedSequenceNumber))
MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK) + "), sequence number " + inputBuffer[6]
) + " (expected " +
expectedSequenceNumber + ")"
)
} }
throw UsbTransportException( })
"USB-CCID error - bad CCID header type " + inputBuffer[0]
)
} }
var result = CcidDataBlock.parseHeaderFromBytes(inputBuffer) var result = CcidDataBlock.parseHeaderFromBytes(inputBuffer)
if (expectedSequenceNumber != result.bSeq) { if (expectedSequenceNumber != result.bSeq) {
throw UsbTransportException( throw UsbTransportException("USB-CCID error - expected sequence number $expectedSequenceNumber, got $result")
("USB-CCID error - expected sequence number " +
expectedSequenceNumber + ", got " + result)
)
} }
val dataBuffer = ByteArray(result.dwLength) val dataBuffer = ByteArray(result.dwLength)
@ -218,9 +214,7 @@ class UsbCcidTransceiver(
usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS
) )
if (readBytes < 0) { if (readBytes < 0) {
throw UsbTransportException( throw UsbTransportException("USB error - failed reading response data! Header: $result")
"USB error - failed reading response data! Header: $result"
)
} }
System.arraycopy(inputBuffer, 0, dataBuffer, bufferedBytes, readBytes) System.arraycopy(inputBuffer, 0, dataBuffer, bufferedBytes, readBytes)
bufferedBytes += readBytes bufferedBytes += readBytes
@ -285,7 +279,7 @@ class UsbCcidTransceiver(
} }
val ccidDataBlock = receiveDataBlock(sequenceNumber) val ccidDataBlock = receiveDataBlock(sequenceNumber)
val elapsedTime = SystemClock.elapsedRealtime() - startTime val elapsedTime = SystemClock.elapsedRealtime() - startTime
Log.d(TAG, "USB XferBlock call took " + elapsedTime + "ms") Log.d(TAG, "USB XferBlock call took ${elapsedTime}ms")
return ccidDataBlock return ccidDataBlock
} }
@ -293,13 +287,13 @@ class UsbCcidTransceiver(
val startTime = SystemClock.elapsedRealtime() val startTime = SystemClock.elapsedRealtime()
skipAvailableInput() skipAvailableInput()
var response: CcidDataBlock? = null var response: CcidDataBlock? = null
for (v in usbCcidDescription.voltages) { for (voltage in usbCcidDescription.voltages) {
Log.v(TAG, "CCID: attempting to power on with voltage $v") Log.v(TAG, "CCID: attempting to power on with voltage $voltage")
response = try { response = try {
iccPowerOnVoltage(v.powerOnValue) iccPowerOnVoltage(voltage.powerOnValue)
} catch (e: UsbCcidErrorException) { } catch (e: UsbCcidErrorException) {
if (e.errorResponse.bError.toInt() == 7) { // Power select error if (e.errorResponse.bError.toInt() == 7) { // Power select error
Log.v(TAG, "CCID: failed to power on with voltage $v") Log.v(TAG, "CCID: failed to power on with voltage $voltage")
iccPowerOff() iccPowerOff()
Log.v(TAG, "CCID: powered off") Log.v(TAG, "CCID: powered off")
continue continue
@ -314,8 +308,11 @@ class UsbCcidTransceiver(
val elapsedTime = SystemClock.elapsedRealtime() - startTime val elapsedTime = SystemClock.elapsedRealtime() - startTime
Log.d( Log.d(
TAG, TAG,
"Usb transport connected, took " + elapsedTime + "ms, ATR=" + buildString {
response.data?.encodeHex() append("Usb transport connected")
append(", took ", elapsedTime, "ms")
append(", ATR=", response.data?.encodeHex())
}
) )
return response return response
} }

View file

@ -6,31 +6,22 @@ import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbEndpoint import android.hardware.usb.UsbEndpoint
import android.hardware.usb.UsbInterface import android.hardware.usb.UsbInterface
class UsbTransportException(msg: String) : Exception(msg) class UsbTransportException(message: String) : Exception(message)
fun UsbInterface.getIoEndpoints(): Pair<UsbEndpoint?, UsbEndpoint?> { val UsbDevice.interfaces: Iterable<UsbInterface>
var bulkIn: UsbEndpoint? = null get() = (0 until interfaceCount).map(::getInterface)
var bulkOut: UsbEndpoint? = null
for (i in 0 until endpointCount) {
val endpoint = getEndpoint(i)
if (endpoint.type != UsbConstants.USB_ENDPOINT_XFER_BULK) {
continue
}
if (endpoint.direction == UsbConstants.USB_DIR_IN) {
bulkIn = endpoint
} else if (endpoint.direction == UsbConstants.USB_DIR_OUT) {
bulkOut = endpoint
}
}
return Pair(bulkIn, bulkOut)
}
fun UsbDevice.getSmartCardInterface(): UsbInterface? { val Iterable<UsbInterface>.smartCard: UsbInterface?
for (i in 0 until interfaceCount) { get() = find { it.interfaceClass == UsbConstants.USB_CLASS_CSCID }
val anInterface = getInterface(i)
if (anInterface.interfaceClass == UsbConstants.USB_CLASS_CSCID) { val UsbInterface.endpoints: Iterable<UsbEndpoint>
return anInterface get() = (0 until endpointCount).map(::getEndpoint)
val Iterable<UsbEndpoint>.bulkPair: Pair<UsbEndpoint?, UsbEndpoint?>
get() {
val endpoints = filter { it.type == UsbConstants.USB_ENDPOINT_XFER_BULK }
return Pair(
endpoints.find { it.direction == UsbConstants.USB_DIR_IN },
endpoints.find { it.direction == UsbConstants.USB_DIR_OUT },
)
} }
}
return null
}