[2/n] USB CCID Reader support
*cough* copied CCID driver from OpenKeychains
This commit is contained in:
parent
803b88f74e
commit
3667f578d7
6 changed files with 499 additions and 12 deletions
|
@ -91,6 +91,10 @@ open class DefaultEuiccChannelManager(
|
|||
override fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||
return@withContext usbChannel
|
||||
}
|
||||
|
||||
for (card in uiccCards) {
|
||||
for (port in card.ports) {
|
||||
if (port.logicalSlotIndex == logicalSlotId) {
|
||||
|
@ -106,6 +110,10 @@ open class DefaultEuiccChannelManager(
|
|||
override fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? =
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||
return@withContext usbChannel
|
||||
}
|
||||
|
||||
for (card in uiccCards) {
|
||||
if (card.physicalSlotIndex != physicalSlotId) continue
|
||||
for (port in card.ports) {
|
||||
|
@ -118,6 +126,10 @@ open class DefaultEuiccChannelManager(
|
|||
}
|
||||
|
||||
override suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List<EuiccChannel>? {
|
||||
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||
return usbChannel?.let { listOf(it) }
|
||||
}
|
||||
|
||||
for (card in uiccCards) {
|
||||
if (card.physicalSlotIndex != physicalSlotId) continue
|
||||
return card.ports.mapNotNull { tryOpenEuiccChannel(it) }
|
||||
|
@ -133,6 +145,10 @@ open class DefaultEuiccChannelManager(
|
|||
|
||||
override suspend fun findEuiccChannelByPort(physicalSlotId: Int, portId: Int): EuiccChannel? =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||
return@withContext usbChannel
|
||||
}
|
||||
|
||||
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
|
||||
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package im.angry.openeuicc.core.usb
|
|||
|
||||
import android.hardware.usb.UsbDeviceConnection
|
||||
import android.hardware.usb.UsbEndpoint
|
||||
import android.util.Log
|
||||
import im.angry.openeuicc.util.*
|
||||
import net.typeblog.lpac_jni.ApduInterface
|
||||
|
||||
class UsbApduInterface(
|
||||
|
@ -9,11 +11,30 @@ class UsbApduInterface(
|
|||
private val bulkIn: UsbEndpoint,
|
||||
private val bulkOut: UsbEndpoint
|
||||
): ApduInterface {
|
||||
companion object {
|
||||
private const val TAG = "UsbApduInterface"
|
||||
}
|
||||
|
||||
private lateinit var ccidDescription: UsbCcidDescription
|
||||
private lateinit var transceiver: UsbCcidTransceiver
|
||||
|
||||
private var channelId = -1
|
||||
|
||||
override fun connect() {
|
||||
ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!!
|
||||
ccidDescription.checkTransportProtocol()
|
||||
|
||||
if (!ccidDescription.hasT0Protocol) {
|
||||
throw IllegalArgumentException("Unsupported card reader; T=0 support is required")
|
||||
}
|
||||
|
||||
transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription)
|
||||
|
||||
try {
|
||||
transceiver.iccPowerOn()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
|
@ -21,17 +42,118 @@ class UsbApduInterface(
|
|||
}
|
||||
|
||||
override fun logicalChannelOpen(aid: ByteArray): Int {
|
||||
return 0
|
||||
check(channelId == -1) { "Logical channel already opened" }
|
||||
|
||||
// OPEN LOGICAL CHANNEL
|
||||
val req = manageChannelCmd(true, 0)
|
||||
Log.d(TAG, "OPEN LOGICAL CHANNEL: ${req.encodeHex()}")
|
||||
|
||||
val resp = try {
|
||||
transmitApduByChannel(req, 0)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return -1
|
||||
}
|
||||
Log.d(TAG, "OPEN LOGICAL CHANNEL response: ${resp.encodeHex()}")
|
||||
|
||||
return if (resp.size >= 2 && resp.sliceArray((resp.size - 2) until resp.size).contentEquals(
|
||||
byteArrayOf(0x90.toByte(), 0x00)
|
||||
)
|
||||
) {
|
||||
channelId = resp[0].toInt()
|
||||
Log.d(TAG, "channelId = $channelId")
|
||||
|
||||
// Then, select AID
|
||||
val selectAid = selectByDfCmd(aid, channelId.toByte())
|
||||
Log.d(TAG, "Select DF command: ${selectAid.encodeHex()}")
|
||||
val selectAidResp = transmitApduByChannel(selectAid, channelId.toByte())
|
||||
Log.d(TAG, "Select DF resp: ${selectAidResp.encodeHex()}")
|
||||
channelId
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
override fun logicalChannelClose(handle: Int) {
|
||||
check(handle == channelId) { "Logical channel ID mismatch" }
|
||||
check(channelId != -1) { "Logical channel is not opened" }
|
||||
|
||||
// CLOSE LOGICAL CHANNEL
|
||||
val req = manageChannelCmd(false, channelId.toByte())
|
||||
Log.d(TAG, "CLOSE LOGICAL CHANNEL: ${req.encodeHex()}")
|
||||
|
||||
val resp = transmitApduByChannel(req, channelId.toByte())
|
||||
Log.d(TAG, "CLOSE LOGICAL CHANNEL response: ${resp.encodeHex()}")
|
||||
|
||||
channelId = -1
|
||||
}
|
||||
|
||||
override fun transmit(tx: ByteArray): ByteArray {
|
||||
return byteArrayOf()
|
||||
check(channelId != -1) { "Logical channel is not opened" }
|
||||
Log.d(TAG, "USB APDU command: ${tx.encodeHex()}")
|
||||
val resp = transmitApduByChannel(tx, channelId.toByte())
|
||||
Log.d(TAG, "USB APDU response: ${resp.encodeHex()}")
|
||||
return resp
|
||||
}
|
||||
|
||||
override val valid: Boolean
|
||||
get() = true
|
||||
|
||||
private fun buildCmd(cla: Byte, ins: Byte, p1: Byte, p2: Byte, data: ByteArray?, le: Byte?) =
|
||||
byteArrayOf(cla, ins, p1, p2).let {
|
||||
if (data != null) {
|
||||
it + data.size.toByte() + data
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.let {
|
||||
if (le != null) {
|
||||
it + byteArrayOf(le)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
private fun manageChannelCmd(open: Boolean, channel: Byte) =
|
||||
if (open) {
|
||||
buildCmd(0x00, 0x70, 0x00, 0x00, null, 0x01)
|
||||
} else {
|
||||
buildCmd(channel, 0x70, 0x80.toByte(), channel, null, null)
|
||||
}
|
||||
|
||||
private fun selectByDfCmd(aid: ByteArray, channel: Byte) =
|
||||
buildCmd(channel, 0xA4.toByte(), 0x04, 0x00, aid, null)
|
||||
|
||||
private fun transmitApduByChannel(tx: ByteArray, channel: Byte): ByteArray {
|
||||
val realTx = tx.copyOf()
|
||||
// OR the channel mask into the CLA byte
|
||||
realTx[0] = ((realTx[0].toInt() and 0xFC) or channel.toInt()).toByte()
|
||||
|
||||
var resp = transceiver.sendXfrBlock(realTx)!!.data!!
|
||||
|
||||
if (resp.size >= 2) {
|
||||
var sw1 = resp[resp.size - 2].toInt() and 0xFF
|
||||
var sw2 = resp[resp.size - 1].toInt() and 0xFF
|
||||
|
||||
if (sw1 == 0x6C) {
|
||||
realTx[realTx.size - 1] = resp[resp.size - 1]
|
||||
resp = transceiver.sendXfrBlock(realTx)!!.data!!
|
||||
} else if (sw1 == 0x61) {
|
||||
do {
|
||||
val getResponseCmd = byteArrayOf(
|
||||
realTx[0], 0xC0.toByte(), 0x00, 0x00, sw2.toByte()
|
||||
)
|
||||
|
||||
val tmp = transceiver.sendXfrBlock(getResponseCmd)!!.data!!
|
||||
|
||||
resp = resp.sliceArray(0 until (resp.size - 2)) + tmp
|
||||
|
||||
sw1 = resp[resp.size - 2].toInt() and 0xFF
|
||||
sw2 = resp[resp.size - 1].toInt() and 0xFF
|
||||
} while (sw1 == 0x61)
|
||||
}
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
}
|
|
@ -96,11 +96,9 @@ data class UsbCcidDescription(
|
|||
}.toTypedArray()
|
||||
}
|
||||
|
||||
val hasAutomaticPps: Boolean = hasFeature(FEATURE_AUTOMATIC_PPS)
|
||||
val hasAutomaticPps: Boolean
|
||||
get() = hasFeature(FEATURE_AUTOMATIC_PPS)
|
||||
|
||||
fun checkTransportProtocol() {
|
||||
val hasT1Protocol = dwProtocols and MASK_T1_PROTO != 0
|
||||
val hasT0Protocol = dwProtocols and MASK_T0_PROTO != 0
|
||||
android.util.Log.d("CcidDescription", "hasT1Protocol = $hasT1Protocol, hasT0Protocol = $hasT0Protocol")
|
||||
}
|
||||
val hasT0Protocol: Boolean
|
||||
get() = (dwProtocols and MASK_T0_PROTO) != 0
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
package im.angry.openeuicc.core.usb
|
||||
|
||||
import android.hardware.usb.UsbDeviceConnection
|
||||
import android.hardware.usb.UsbEndpoint
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import im.angry.openeuicc.util.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
|
||||
/**
|
||||
* Provides raw, APDU-agnostic transmission to the CCID reader
|
||||
* Adapted from <https://github.com/open-keychain/open-keychain/blob/master/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java>
|
||||
*/
|
||||
class UsbCcidTransceiver(
|
||||
private val usbConnection: UsbDeviceConnection,
|
||||
private val usbBulkIn: UsbEndpoint,
|
||||
private val usbBulkOut: UsbEndpoint,
|
||||
private val usbCcidDescription: UsbCcidDescription
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "UsbCcidTransceiver"
|
||||
|
||||
private const val CCID_HEADER_LENGTH = 10
|
||||
|
||||
private const val MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK = 0x80
|
||||
private const val MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON = 0x62
|
||||
private const val MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF = 0x63
|
||||
private const val MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK = 0x6f
|
||||
|
||||
private const val COMMAND_STATUS_SUCCESS: Byte = 0
|
||||
private const val COMMAND_STATUS_TIME_EXTENSION_RQUESTED: Byte = 2
|
||||
|
||||
/**
|
||||
* Level Parameter: APDU is a single command.
|
||||
*
|
||||
* "the command APDU begins and ends with this command"
|
||||
* -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0
|
||||
* § 6.1.1.3
|
||||
*/
|
||||
const val LEVEL_PARAM_START_SINGLE_CMD_APDU: Short = 0x0000
|
||||
|
||||
/**
|
||||
* Level Parameter: First APDU in a multi-command APDU.
|
||||
*
|
||||
* "the command APDU begins with this command, and continue in the
|
||||
* next PC_to_RDR_XfrBlock"
|
||||
* -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0
|
||||
* § 6.1.1.3
|
||||
*/
|
||||
const val LEVEL_PARAM_START_MULTI_CMD_APDU: Short = 0x0001
|
||||
|
||||
/**
|
||||
* Level Parameter: Final APDU in a multi-command APDU.
|
||||
*
|
||||
* "this abData field continues a command APDU and ends the command APDU"
|
||||
* -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0
|
||||
* § 6.1.1.3
|
||||
*/
|
||||
const val LEVEL_PARAM_END_MULTI_CMD_APDU: Short = 0x0002
|
||||
|
||||
/**
|
||||
* Level Parameter: Next command in a multi-command APDU.
|
||||
*
|
||||
* "the abData field continues a command APDU and another block is to follow"
|
||||
* -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0
|
||||
* § 6.1.1.3
|
||||
*/
|
||||
const val LEVEL_PARAM_CONTINUE_MULTI_CMD_APDU: Short = 0x0003
|
||||
|
||||
/**
|
||||
* Level Parameter: Request the device continue sending APDU.
|
||||
*
|
||||
* "empty abData field, continuation of response APDU is expected in the next
|
||||
* RDR_to_PC_DataBlock"
|
||||
* -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0
|
||||
* § 6.1.1.3
|
||||
*/
|
||||
const val LEVEL_PARAM_CONTINUE_RESPONSE: Short = 0x0010
|
||||
|
||||
private const val SLOT_NUMBER = 0x00
|
||||
|
||||
private const val ICC_STATUS_SUCCESS: Byte = 0
|
||||
|
||||
private const val DEVICE_COMMUNICATE_TIMEOUT_MILLIS = 5000
|
||||
private const val DEVICE_SKIP_TIMEOUT_MILLIS = 100
|
||||
}
|
||||
|
||||
data class UsbCcidErrorException(val msg: String, val errorResponse: CcidDataBlock) :
|
||||
Exception(msg)
|
||||
|
||||
data class CcidDataBlock(
|
||||
val dwLength: Int,
|
||||
val bSlot: Byte,
|
||||
val bSeq: Byte,
|
||||
val bStatus: Byte,
|
||||
val bError: Byte,
|
||||
val bChainParameter: Byte,
|
||||
val data: ByteArray?
|
||||
) {
|
||||
companion object {
|
||||
fun parseHeaderFromBytes(headerBytes: ByteArray): CcidDataBlock {
|
||||
val buf = ByteBuffer.wrap(headerBytes)
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN)
|
||||
|
||||
val type = buf.get()
|
||||
require(type == MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK.toByte()) { "Header has incorrect type value!" }
|
||||
val dwLength = buf.int
|
||||
val bSlot = buf.get()
|
||||
val bSeq = buf.get()
|
||||
val bStatus = buf.get()
|
||||
val bError = buf.get()
|
||||
val bChainParameter = buf.get()
|
||||
|
||||
return CcidDataBlock(dwLength, bSlot, bSeq, bStatus, bError, bChainParameter, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun withData(d: ByteArray): CcidDataBlock {
|
||||
require(data == null) { "Cannot add data twice" }
|
||||
return CcidDataBlock(dwLength, bSlot, bSeq, bStatus, bError, bChainParameter, d)
|
||||
}
|
||||
|
||||
val iccStatus: Byte
|
||||
get() = (bStatus.toInt() and 0x03).toByte()
|
||||
|
||||
val commandStatus: Byte
|
||||
get() = ((bStatus.toInt() shr 6) and 0x03).toByte()
|
||||
|
||||
val isStatusTimeoutExtensionRequest: Boolean
|
||||
get() = commandStatus == COMMAND_STATUS_TIME_EXTENSION_RQUESTED
|
||||
|
||||
val isStatusSuccess: Boolean
|
||||
get() = iccStatus == ICC_STATUS_SUCCESS && commandStatus == COMMAND_STATUS_SUCCESS
|
||||
}
|
||||
|
||||
val hasAutomaticPps = usbCcidDescription.hasAutomaticPps
|
||||
|
||||
private val inputBuffer = ByteArray(usbBulkIn.maxPacketSize)
|
||||
|
||||
private var currentSequenceNumber: Byte = 0
|
||||
|
||||
private fun sendRaw(data: ByteArray, offset: Int, length: Int) {
|
||||
val tr1 = usbConnection.bulkTransfer(
|
||||
usbBulkOut, data, offset, length, DEVICE_COMMUNICATE_TIMEOUT_MILLIS
|
||||
)
|
||||
if (tr1 != length) {
|
||||
throw UsbTransportException(
|
||||
"USB error - failed to transmit data ($tr1/$length)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun receiveDataBlock(expectedSequenceNumber: Byte): CcidDataBlock? {
|
||||
var response: CcidDataBlock?
|
||||
do {
|
||||
response = receiveDataBlockImmediate(expectedSequenceNumber)
|
||||
} while (response!!.isStatusTimeoutExtensionRequest)
|
||||
if (!response.isStatusSuccess) {
|
||||
throw UsbCcidErrorException("USB-CCID error!", response)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
private fun receiveDataBlockImmediate(expectedSequenceNumber: Byte): CcidDataBlock? {
|
||||
/*
|
||||
* Some USB CCID devices (notably NitroKey 3) may time-out and need a subsequent poke to
|
||||
* carry on communications. No particular reason why the number 3 was chosen. If we get a
|
||||
* zero-sized reply (or a time-out), we try again. Clamped retries prevent an infinite loop
|
||||
* if things really turn sour.
|
||||
*/
|
||||
var attempts = 3
|
||||
Log.d(TAG, "Receive data block immediate seq=$expectedSequenceNumber")
|
||||
var readBytes: Int
|
||||
do {
|
||||
readBytes = usbConnection.bulkTransfer(
|
||||
usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS
|
||||
)
|
||||
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()) {
|
||||
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 + ")"
|
||||
)
|
||||
}
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
val dataBuffer = ByteArray(result.dwLength)
|
||||
var bufferedBytes = readBytes - CCID_HEADER_LENGTH
|
||||
System.arraycopy(inputBuffer, CCID_HEADER_LENGTH, dataBuffer, 0, bufferedBytes)
|
||||
while (bufferedBytes < dataBuffer.size) {
|
||||
readBytes = usbConnection.bulkTransfer(
|
||||
usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS
|
||||
)
|
||||
if (readBytes < 0) {
|
||||
throw UsbTransportException(
|
||||
"USB error - failed reading response data! Header: $result"
|
||||
)
|
||||
}
|
||||
System.arraycopy(inputBuffer, 0, dataBuffer, bufferedBytes, readBytes)
|
||||
bufferedBytes += readBytes
|
||||
}
|
||||
result = result.withData(dataBuffer)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private fun skipAvailableInput() {
|
||||
var ignoredBytes: Int
|
||||
do {
|
||||
ignoredBytes = usbConnection.bulkTransfer(
|
||||
usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_SKIP_TIMEOUT_MILLIS
|
||||
)
|
||||
if (ignoredBytes > 0) {
|
||||
Log.e(TAG, "Skipped $ignoredBytes bytes")
|
||||
}
|
||||
} while (ignoredBytes > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmits XfrBlock
|
||||
* 6.1.4 PC_to_RDR_XfrBlock
|
||||
*
|
||||
* @param payload payload to transmit
|
||||
*/
|
||||
fun sendXfrBlock(payload: ByteArray): CcidDataBlock? {
|
||||
return sendXfrBlock(payload, LEVEL_PARAM_START_SINGLE_CMD_APDU)
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a continued XfrBlock. Should be called when a multiblock response is indicated
|
||||
* 6.1.4 PC_to_RDR_XfrBlock
|
||||
*/
|
||||
fun receiveContinuedResponse(): CcidDataBlock? {
|
||||
return sendXfrBlock(ByteArray(0), LEVEL_PARAM_CONTINUE_RESPONSE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmits XfrBlock
|
||||
* 6.1.4 PC_to_RDR_XfrBlock
|
||||
*
|
||||
* @param payload payload to transmit
|
||||
* @param levelParam Level parameter
|
||||
*/
|
||||
private fun sendXfrBlock(payload: ByteArray, levelParam: Short): CcidDataBlock? {
|
||||
val startTime = SystemClock.elapsedRealtime()
|
||||
val l = payload.size
|
||||
val sequenceNumber: Byte = currentSequenceNumber++
|
||||
val headerData = byteArrayOf(
|
||||
MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK.toByte(),
|
||||
l.toByte(),
|
||||
(l shr 8).toByte(),
|
||||
(l shr 16).toByte(),
|
||||
(l shr 24).toByte(),
|
||||
SLOT_NUMBER.toByte(),
|
||||
sequenceNumber,
|
||||
0x00.toByte(),
|
||||
(levelParam.toInt() and 0x00ff).toByte(),
|
||||
(levelParam.toInt() shr 8).toByte()
|
||||
)
|
||||
val data: ByteArray = headerData + payload
|
||||
var sentBytes = 0
|
||||
while (sentBytes < data.size) {
|
||||
val bytesToSend = Math.min(usbBulkOut.maxPacketSize, data.size - sentBytes)
|
||||
sendRaw(data, sentBytes, bytesToSend)
|
||||
sentBytes += bytesToSend
|
||||
}
|
||||
val ccidDataBlock = receiveDataBlock(sequenceNumber)
|
||||
val elapsedTime = SystemClock.elapsedRealtime() - startTime
|
||||
Log.d(TAG, "USB XferBlock call took " + elapsedTime + "ms")
|
||||
return ccidDataBlock
|
||||
}
|
||||
|
||||
fun iccPowerOn(): CcidDataBlock {
|
||||
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")
|
||||
response = try {
|
||||
iccPowerOnVoltage(v.powerOnValue)
|
||||
} catch (e: UsbCcidErrorException) {
|
||||
if (e.errorResponse.bError.toInt() == 7) { // Power select error
|
||||
Log.v(TAG, "CCID: failed to power on with voltage $v")
|
||||
iccPowerOff()
|
||||
Log.v(TAG, "CCID: powered off")
|
||||
continue
|
||||
}
|
||||
throw e
|
||||
}
|
||||
break
|
||||
}
|
||||
if (response == null) {
|
||||
throw UsbTransportException("Couldn't power up ICC2")
|
||||
}
|
||||
val elapsedTime = SystemClock.elapsedRealtime() - startTime
|
||||
Log.d(
|
||||
TAG,
|
||||
"Usb transport connected, took " + elapsedTime + "ms, ATR=" +
|
||||
response.data?.encodeHex()
|
||||
)
|
||||
return response
|
||||
}
|
||||
|
||||
private fun iccPowerOnVoltage(voltage: Byte): CcidDataBlock? {
|
||||
val sequenceNumber = currentSequenceNumber++
|
||||
val iccPowerCommand = byteArrayOf(
|
||||
MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON.toByte(),
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
SLOT_NUMBER.toByte(),
|
||||
sequenceNumber,
|
||||
voltage,
|
||||
0x00, 0x00 // reserved for future use
|
||||
)
|
||||
sendRaw(iccPowerCommand, 0, iccPowerCommand.size)
|
||||
return receiveDataBlock(sequenceNumber)
|
||||
}
|
||||
|
||||
private fun iccPowerOff() {
|
||||
val sequenceNumber = currentSequenceNumber++
|
||||
val iccPowerCommand = byteArrayOf(
|
||||
MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF.toByte(),
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
sequenceNumber,
|
||||
0x00
|
||||
)
|
||||
sendRaw(iccPowerCommand, 0, iccPowerCommand.size)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ import android.hardware.usb.UsbDevice
|
|||
import android.hardware.usb.UsbEndpoint
|
||||
import android.hardware.usb.UsbInterface
|
||||
|
||||
class UsbTransportException(msg: String) : Exception(msg)
|
||||
|
||||
fun UsbInterface.getIoEndpoints(): Pair<UsbEndpoint?, UsbEndpoint?> {
|
||||
var bulkIn: UsbEndpoint? = null
|
||||
var bulkOut: UsbEndpoint? = null
|
||||
|
|
|
@ -74,11 +74,12 @@ fun SubscriptionManager.tryRefreshCachedEuiccInfo(cardId: Int) {
|
|||
}
|
||||
|
||||
// Every EuiccChannel we use here should be backed by a RealUiccPortInfoCompat
|
||||
// except when it is from a USB card reader
|
||||
val EuiccChannel.removable
|
||||
get() = (port as RealUiccPortInfoCompat).card.isRemovable
|
||||
get() = (port as? RealUiccPortInfoCompat)?.card?.isRemovable ?: true
|
||||
|
||||
val EuiccChannel.cardId
|
||||
get() = (port as RealUiccPortInfoCompat).card.cardId
|
||||
get() = (port as? RealUiccPortInfoCompat)?.card?.cardId ?: -1
|
||||
|
||||
val EuiccChannel.isMEP
|
||||
get() = (port as RealUiccPortInfoCompat).card.isMultipleEnabledProfilesSupported
|
||||
get() = (port as? RealUiccPortInfoCompat)?.card?.isMultipleEnabledProfilesSupported ?: false
|
Loading…
Add table
Reference in a new issue