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 im.angry.openeuicc.common.R
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 java.lang.IllegalArgumentException
@ -61,7 +62,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
}
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
val conn = usbManager.openDevice(usbDevice) ?: 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.telephony.SubscriptionManager
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.util.*
import kotlinx.coroutines.Dispatchers
@ -244,7 +245,7 @@ open class DefaultEuiccChannelManager(
withContext(Dispatchers.IO) {
usbManager.deviceList.values.forEach { device ->
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
// need permission to be able to do anything with it
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_SHORT_APDU = 0x20000
private const val FEATURE_EXCHAGE_LEVEL_EXTENDED_APDU = 0x40000
private const val FEATURE_EXCHANGE_LEVEL_EXTENDED_APDU = 0x40000
// bVoltageSupport Masks
private const val VOLTAGE_5V: Byte = 1
private const val VOLTAGE_3V: Byte = 2
private const val VOLTAGE_1_8V: Byte = 4
private const val VOLTAGE_5V0: Byte = 1
private const val VOLTAGE_3V0: Byte = 2
private const val VOLTAGE_1V8: Byte = 4
private const val SLOT_OFFSET = 4
private const val FEATURES_OFFSET = 40
@ -71,30 +71,23 @@ data class UsbCcidDescription(
}
enum class Voltage(powerOnValue: Int, mask: Int) {
AUTO(0, 0), _5V(1, VOLTAGE_5V.toInt()), _3V(2, VOLTAGE_3V.toInt()), _1_8V(
3,
VOLTAGE_1_8V.toInt()
);
// @formatter:off
AUTO(0, 0),
V50(1, VOLTAGE_5V0.toInt()),
V30(2, VOLTAGE_3V0.toInt()),
V18(3, VOLTAGE_1V8.toInt());
// @formatter:on
val mask = powerOnValue.toByte()
val powerOnValue = mask.toByte()
}
private fun hasFeature(feature: Int): Boolean =
(dwFeatures and feature) != 0
private fun hasFeature(feature: Int) = (dwFeatures and feature) != 0
val voltages: Array<Voltage>
get() =
if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) {
arrayOf(Voltage.AUTO)
} else {
Voltage.values().mapNotNull {
if ((it.mask.toInt() and bVoltageSupport.toInt()) != 0) {
it
} else {
null
}
}.toTypedArray()
val voltages: List<Voltage>
get() {
if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) return listOf(Voltage.AUTO)
return Voltage.entries.filter { (it.mask.toInt() and bVoltageSupport.toInt()) != 0 }
}
val hasAutomaticPps: Boolean

View file

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

View file

@ -6,31 +6,22 @@ import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbEndpoint
import android.hardware.usb.UsbInterface
class UsbTransportException(msg: String) : Exception(msg)
class UsbTransportException(message: String) : Exception(message)
fun UsbInterface.getIoEndpoints(): Pair<UsbEndpoint?, UsbEndpoint?> {
var bulkIn: UsbEndpoint? = null
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)
}
val UsbDevice.interfaces: Iterable<UsbInterface>
get() = (0 until interfaceCount).map(::getInterface)
fun UsbDevice.getSmartCardInterface(): UsbInterface? {
for (i in 0 until interfaceCount) {
val anInterface = getInterface(i)
if (anInterface.interfaceClass == UsbConstants.USB_CLASS_CSCID) {
return anInterface
}
}
return null
val Iterable<UsbInterface>.smartCard: UsbInterface?
get() = find { it.interfaceClass == UsbConstants.USB_CLASS_CSCID }
val UsbInterface.endpoints: Iterable<UsbEndpoint>
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 },
)
}