Updated on 2026-08-14
This commit is contained in:
commit
903fc32bc9
51 changed files with 944 additions and 309 deletions
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.TerminalKeysService
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tasks.*
|
||||
import java.util.concurrent.Executors
|
||||
|
|
@ -16,10 +18,12 @@ import java.util.concurrent.Executors
|
|||
*/
|
||||
class CardManager(
|
||||
private val reader: CardReader,
|
||||
private val cardManagerDelegate: CardManagerDelegate? = null) {
|
||||
private val cardManagerDelegate: CardManagerDelegate? = null,
|
||||
private val config: Config = Config()
|
||||
) {
|
||||
|
||||
private var terminalKeysService: TerminalKeysService? = null
|
||||
private var isBusy = false
|
||||
private val cardEnvironmentRepository = mutableMapOf<String, CardEnvironment>()
|
||||
private val cardManagerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
init {
|
||||
|
|
@ -67,7 +71,7 @@ class CardManager(
|
|||
callback: (result: TaskEvent<SignResponse>) -> Unit) {
|
||||
val signCommand: SignCommand
|
||||
try {
|
||||
signCommand = SignCommand(hashes, cardId)
|
||||
signCommand = SignCommand(hashes)
|
||||
} catch (error: Exception) {
|
||||
if (error is TaskError) {
|
||||
callback(TaskEvent.Completion(error))
|
||||
|
|
@ -91,7 +95,7 @@ class CardManager(
|
|||
*/
|
||||
fun readIssuerData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
|
||||
val getIssuerDataCommand = ReadIssuerDataCommand(cardId)
|
||||
val getIssuerDataCommand = ReadIssuerDataCommand()
|
||||
val task = SingleCommandTask(getIssuerDataCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
|
@ -114,10 +118,10 @@ class CardManager(
|
|||
issuerDataCounter: Int? = null,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
val writeIssuerDataCommand = WriteIssuerDataCommand(
|
||||
cardId,
|
||||
issuerData,
|
||||
issuerDataSignature,
|
||||
issuerDataCounter)
|
||||
issuerData,
|
||||
issuerDataSignature,
|
||||
issuerDataCounter
|
||||
)
|
||||
val task = SingleCommandTask(writeIssuerDataCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
|
@ -134,7 +138,7 @@ class CardManager(
|
|||
*/
|
||||
fun createWallet(cardId: String,
|
||||
callback: (result: TaskEvent<CreateWalletResponse>) -> Unit) {
|
||||
val createWalletCommand = CreateWalletCommand(cardId)
|
||||
val createWalletCommand = CreateWalletCommand()
|
||||
val task = SingleCommandTask(createWalletCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
|
@ -148,7 +152,7 @@ class CardManager(
|
|||
*/
|
||||
fun purgeWallet(cardId: String,
|
||||
callback: (result: TaskEvent<PurgeWalletResponse>) -> Unit) {
|
||||
val purgeWalletCommand = PurgeWalletCommand(cardId)
|
||||
val purgeWalletCommand = PurgeWalletCommand()
|
||||
val task = SingleCommandTask(purgeWalletCommand)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
|
@ -163,7 +167,7 @@ class CardManager(
|
|||
return
|
||||
}
|
||||
|
||||
val environment = fetchCardEnvironment(cardId)
|
||||
val environment = prepareCardEnvironment(cardId)
|
||||
isBusy = true
|
||||
|
||||
task.reader = reader
|
||||
|
|
@ -187,7 +191,21 @@ class CardManager(
|
|||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
|
||||
return cardEnvironmentRepository[cardId] ?: CardEnvironment(cardId = cardId)
|
||||
/**
|
||||
* Allows to set a particular [TerminalKeysService] to retrieve terminal keys.
|
||||
* Default implementation is provided in tangem-sdk module: [TerminalKeysStorage].
|
||||
*/
|
||||
fun setTerminalKeysService(terminalKeysService: TerminalKeysService) {
|
||||
this.terminalKeysService = terminalKeysService
|
||||
}
|
||||
|
||||
private fun prepareCardEnvironment(cardId: String?): CardEnvironment {
|
||||
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
|
||||
return CardEnvironment(
|
||||
cardId = cardId,
|
||||
terminalKeys = terminalKeys
|
||||
)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -13,13 +13,13 @@ interface CardManagerDelegate {
|
|||
/**
|
||||
* It is called when user is expected to scan a Tangem Card with an Android device.
|
||||
*/
|
||||
fun onNfcSessionStarted()
|
||||
fun onNfcSessionStarted(cardId: String?)
|
||||
|
||||
/**
|
||||
* It is called when security delay is triggered by the card.
|
||||
* A user is expected to hold the card until the security delay is over.
|
||||
*/
|
||||
fun onSecurityDelay(ms: Int)
|
||||
fun onSecurityDelay(ms: Int, totalDurationSeconds: Int)
|
||||
|
||||
/**
|
||||
* It is called when user takes the card away from the Android device during the scanning
|
||||
|
|
|
|||
5
tangem-core/src/main/java/com/tangem/Config.kt
Normal file
5
tangem-core/src/main/java/com/tangem/Config.kt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem
|
||||
|
||||
data class Config(
|
||||
val linkedTerminal: Boolean = true
|
||||
)
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
|
|
@ -22,7 +24,16 @@ class CheckWalletResponse(
|
|||
val cardId: String,
|
||||
val salt: ByteArray,
|
||||
val walletSignature: ByteArray
|
||||
) : CommandResponse
|
||||
) : CommandResponse {
|
||||
|
||||
fun verify(curve: EllipticCurve, publicKey: ByteArray, challenge: ByteArray): Boolean {
|
||||
return CryptoUtils.verify(
|
||||
publicKey,
|
||||
challenge + salt,
|
||||
walletSignature,
|
||||
curve)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This command proves that the wallet private key from the card corresponds to the wallet public key.
|
||||
|
|
@ -32,18 +43,16 @@ class CheckWalletResponse(
|
|||
* @property cardId Unique Tangem card ID number
|
||||
* @property challenge Random challenge generated by application
|
||||
*/
|
||||
class CheckWalletCommand(
|
||||
private val cardId: String,
|
||||
private val challenge: ByteArray
|
||||
) : CommandSerializer<CheckWalletResponse>() {
|
||||
class CheckWalletCommand : CommandSerializer<CheckWalletResponse>() {
|
||||
|
||||
val challenge = CryptoUtils.generateRandomBytes(16)
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = listOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.Challenge, challenge)
|
||||
)
|
||||
return CommandApdu(Instruction.CheckWallet, tlvData)
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Challenge, challenge)
|
||||
return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.toInt
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
|
@ -37,21 +38,15 @@ class CreateWalletResponse(
|
|||
*
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class CreateWalletCommand(
|
||||
private val cardId: String
|
||||
) : CommandSerializer<CreateWalletResponse>() {
|
||||
class CreateWalletCommand : CommandSerializer<CreateWalletResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
|
||||
)
|
||||
if (cardEnvironment.cvc != null) {
|
||||
tlvData.add(Tlv(TlvTag.Cvc, cardEnvironment.cvc))
|
||||
}
|
||||
|
||||
return CommandApdu(Instruction.CreateWallet, tlvData)
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
|
||||
tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc)
|
||||
return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
|
@ -23,23 +24,20 @@ class PurgeWalletResponse(
|
|||
) : CommandResponse
|
||||
|
||||
/**
|
||||
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
|
||||
* This command deletes all wallet data. If Is_Reusable flag is enabled during personalization,
|
||||
|
||||
* If Is_Reusable flag is disabled, the card switches to ‘Purged’ state.
|
||||
* ‘Purged’ state is final, it makes the card useless.
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class PurgeWalletCommand(
|
||||
private val cardId: String
|
||||
) : CommandSerializer<PurgeWalletResponse>() {
|
||||
class PurgeWalletCommand : CommandSerializer<PurgeWalletResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256())
|
||||
)
|
||||
return CommandApdu(Instruction.PurgeWallet, tlvData)
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
|
||||
return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
|
@ -182,7 +183,7 @@ class Card(
|
|||
/**
|
||||
* Current status of the card.
|
||||
*/
|
||||
val status: CardStatus,
|
||||
val status: CardStatus?,
|
||||
|
||||
/**
|
||||
* Version of Tangem COS.
|
||||
|
|
@ -296,19 +297,16 @@ class Card(
|
|||
class ReadCommand : CommandSerializer<Card>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvBuilder = TlvBuilder()
|
||||
/**
|
||||
* [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain
|
||||
* default value of ‘000000’.
|
||||
* In order to obtain card’s data, [ReadCommand] should use the correct pin 1 value.
|
||||
* The card will not respond if wrong pin 1 has been submitted.
|
||||
*/
|
||||
val tlvData = mutableListOf(Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()))
|
||||
|
||||
cardEnvironment.terminalKeys?.let { terminalKeys ->
|
||||
Tlv(TlvTag.TerminalPublicKey, terminalKeys.publicKey)
|
||||
}
|
||||
|
||||
return CommandApdu(Instruction.Read, tlvData)
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey)
|
||||
return CommandApdu(Instruction.Read, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
|
||||
|
|
@ -318,9 +316,9 @@ class ReadCommand : CommandSerializer<Card>() {
|
|||
val tlvMapper = TlvMapper(tlvData)
|
||||
|
||||
Card(
|
||||
cardId = tlvMapper.map(TlvTag.CardId),
|
||||
manufacturerName = tlvMapper.map(TlvTag.ManufactureId),
|
||||
status = tlvMapper.map(TlvTag.Status),
|
||||
cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "",
|
||||
manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "",
|
||||
status = tlvMapper.mapOptional(TlvTag.Status),
|
||||
|
||||
firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware),
|
||||
cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey),
|
||||
|
|
@ -349,7 +347,7 @@ class ReadCommand : CommandSerializer<Card>() {
|
|||
|
||||
private fun deserializeCardData(tlvData: List<Tlv>): CardData? {
|
||||
val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let {
|
||||
Tlv.tlvListFromBytes(it.value)
|
||||
Tlv.deserialize(it.value)
|
||||
}
|
||||
if (cardDataTlvs.isNullOrEmpty()) return null
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
|
@ -49,16 +50,13 @@ class ReadIssuerDataResponse(
|
|||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
* @property cardId CID, Unique Tangem card ID number.
|
||||
*/
|
||||
class ReadIssuerDataCommand(
|
||||
private val cardId: String
|
||||
) : CommandSerializer<ReadIssuerDataResponse>() {
|
||||
class ReadIssuerDataCommand : CommandSerializer<ReadIssuerDataResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = listOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes())
|
||||
)
|
||||
return CommandApdu(Instruction.ReadIssuerData, tlvData)
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.crypto.sign
|
||||
|
|
@ -32,7 +33,7 @@ class SignResponse(
|
|||
* @property hashes Array of transaction hashes.
|
||||
* @property cardId CID, Unique Tangem card ID number
|
||||
*/
|
||||
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
|
||||
class SignCommand(private val hashes: Array<ByteArray>)
|
||||
: CommandSerializer<SignResponse>() {
|
||||
|
||||
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
|
||||
|
|
@ -50,17 +51,16 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
|
|||
}
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())),
|
||||
Tlv(TlvTag.TransactionOutHash, dataToSign)
|
||||
)
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte()))
|
||||
tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign)
|
||||
tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc)
|
||||
|
||||
addTerminalSignature(cardEnvironment, tlvData)
|
||||
|
||||
return CommandApdu(Instruction.Sign, tlvData)
|
||||
addTerminalSignature(cardEnvironment, tlvBuilder)
|
||||
return CommandApdu(Instruction.Sign, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,11 +70,11 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
|
|||
* TerminalTransactionSignature parameter containing a correct signature of raw data to be signed made with TerminalPrivateKey
|
||||
* (this key should be generated and securily stored by the application).
|
||||
*/
|
||||
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList<Tlv>) {
|
||||
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvBuilder: TlvBuilder) {
|
||||
cardEnvironment.terminalKeys?.let { terminalKeyPair ->
|
||||
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
|
||||
tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, signedData))
|
||||
tlvData.add(Tlv(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey))
|
||||
tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData)
|
||||
tlvBuilder.append(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.common.extensions.calculateSha256
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
|
@ -30,24 +31,20 @@ class WriteIssuerDataResponse(
|
|||
* @property issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
*/
|
||||
class WriteIssuerDataCommand(
|
||||
private val cardId: String,
|
||||
private val issuerData: ByteArray,
|
||||
private val issuerDataSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : CommandSerializer<WriteIssuerDataResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = mutableListOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.IssuerData, issuerData),
|
||||
Tlv(TlvTag.IssuerDataSignature, issuerDataSignature)
|
||||
)
|
||||
if (issuerDataCounter != null) {
|
||||
tlvData.add(Tlv(TlvTag.IssuerDataCounter, issuerDataCounter.toByteArray()))
|
||||
}
|
||||
val tlvBuilder = TlvBuilder()
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
|
||||
tlvBuilder.append(TlvTag.IssuerData, issuerData)
|
||||
tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature)
|
||||
tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter)
|
||||
|
||||
return CommandApdu(Instruction.WriteIssuerData, tlvData)
|
||||
return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize())
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem
|
||||
package com.tangem.common
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.common
|
||||
|
||||
/**
|
||||
* Interface for a service for managing Terminal keypair, used for Linked Terminal feature.
|
||||
* Its implementation Needs to be provided to [com.tangem.CardManager]
|
||||
* by calling [com.tangem.CardManager.setTerminalKeysService].
|
||||
* Default implementation is provided in tangem-sdk module: [TerminalKeysStorage].
|
||||
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
|
||||
*/
|
||||
interface TerminalKeysService {
|
||||
fun getKeys(): KeyPair
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.common.apdu
|
||||
|
||||
import com.tangem.EncryptionMode
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.toBytes
|
||||
import com.tangem.common.EncryptionMode
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
|
|
@ -15,7 +13,7 @@ import java.io.ByteArrayOutputStream
|
|||
class CommandApdu(
|
||||
|
||||
private val ins: Int,
|
||||
private val tlvList: List<Tlv>,
|
||||
private val tlvs: ByteArray,
|
||||
|
||||
private val cla: Byte = ISO_CLA,
|
||||
private val p1: Byte = 0x00,
|
||||
|
|
@ -28,12 +26,12 @@ class CommandApdu(
|
|||
|
||||
constructor(
|
||||
instruction: Instruction,
|
||||
tlvList: List<Tlv>,
|
||||
tlvs: ByteArray,
|
||||
encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
encryptionKey: ByteArray? = null
|
||||
) : this(
|
||||
instruction.code,
|
||||
tlvList,
|
||||
tlvs,
|
||||
encryptionMode = encryptionMode,
|
||||
encryptionKey = encryptionKey
|
||||
)
|
||||
|
|
@ -51,13 +49,7 @@ class CommandApdu(
|
|||
|
||||
private fun toBytes(): ByteArray {
|
||||
|
||||
val data = if (tlvList.isNotEmpty()) {
|
||||
tlvList.toBytes()
|
||||
} else {
|
||||
byteArrayOf()
|
||||
}
|
||||
|
||||
val lc = data.size
|
||||
val lc = tlvs.size
|
||||
|
||||
val byteStream = ByteArrayOutputStream()
|
||||
byteStream.write(cla.toInt())
|
||||
|
|
@ -66,7 +58,7 @@ class CommandApdu(
|
|||
byteStream.write(p2.toInt())
|
||||
if (lc != 0) {
|
||||
writeLength(byteStream, lc)
|
||||
byteStream.write(data)
|
||||
byteStream.write(tlvs)
|
||||
}
|
||||
return byteStream.toByteArray()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class ResponseApdu(private val data: ByteArray) {
|
|||
fun getTlvData(encryptionKey: ByteArray? = null): List<Tlv>? {
|
||||
return when {
|
||||
data.size <= 2 -> null
|
||||
else -> Tlv.tlvListFromBytes(data.copyOf(data.size - 2))
|
||||
else -> Tlv.deserialize(data.copyOf(data.size - 2))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,17 +53,17 @@ class Tlv {
|
|||
}
|
||||
|
||||
|
||||
fun tlvListFromBytes(mData: ByteArray): List<Tlv>? {
|
||||
fun deserialize(data: ByteArray, nfcV: Boolean = false): List<Tlv>? {
|
||||
val tlvList = mutableListOf<Tlv>()
|
||||
val stream = ByteArrayInputStream(mData)
|
||||
var tlv: Tlv? = null
|
||||
val stream = ByteArrayInputStream(data)
|
||||
var tlv: Tlv?
|
||||
do {
|
||||
try {
|
||||
tlv = Tlv.tlvFromBytes(stream)
|
||||
tlv = tlvFromBytes(stream)
|
||||
if (tlv != null) tlvList.add(tlv)
|
||||
} catch (e: IOException) {
|
||||
Log.e(this::class.java.simpleName,"TLVError: " + e.message)
|
||||
return null
|
||||
if (nfcV) break else return null
|
||||
}
|
||||
|
||||
} while (tlv != null)
|
||||
|
|
@ -73,10 +73,10 @@ class Tlv {
|
|||
|
||||
}
|
||||
|
||||
fun List<Tlv>.toBytes(): ByteArray =
|
||||
this.map { it.toBytes() }.reduce { arr1, arr2 -> arr1 + arr2 }
|
||||
fun List<Tlv>.serialize(): ByteArray =
|
||||
this.map { it.serialize() }.reduce { arr1, arr2 -> arr1 + arr2 }
|
||||
|
||||
fun Tlv.toBytes(): ByteArray {
|
||||
fun Tlv.serialize(): ByteArray {
|
||||
val tag = byteArrayOf(this.tag.code.toByte())
|
||||
val length = getLengthInBytes(this.value.size)
|
||||
val value = if (this.value.isNotEmpty()) this.value else byteArrayOf(0x00)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
class TlvBuilder {
|
||||
private val tlvs = mutableListOf<Tlv>()
|
||||
private val encoder = TlvEncoder()
|
||||
|
||||
internal inline fun <reified T> append(tag: TlvTag, value: T?) {
|
||||
if (value == null) return
|
||||
|
||||
tlvs.add(encoder.encode(tag, value))
|
||||
}
|
||||
|
||||
fun serialize(): ByteArray = tlvs.serialize()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extensions.*
|
||||
import com.tangem.tasks.TaskError
|
||||
import java.time.Year
|
||||
import java.util.*
|
||||
|
||||
class TlvEncoder {
|
||||
internal inline fun <reified T> encode(tag: TlvTag, value: T?): Tlv {
|
||||
if (value != null) {
|
||||
return Tlv(tag, encodeValue(value, tag))
|
||||
} else {
|
||||
throw TaskError.SerializeCommandError("Encoding error. Value for tag $tag is null")
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T> encodeValue(value: T, tag: TlvTag): ByteArray {
|
||||
return when (tag.valueType()) {
|
||||
TlvValueType.HexString -> {
|
||||
typeCheck<T, String>(tag)
|
||||
return if (tag == TlvTag.Pin || tag == TlvTag.Pin2) {
|
||||
(value as String).calculateSha256()
|
||||
} else {
|
||||
(value as String).hexToBytes()
|
||||
}
|
||||
}
|
||||
TlvValueType.Utf8String -> {
|
||||
typeCheck<T, String>(tag)
|
||||
(value as String).toByteArray()
|
||||
}
|
||||
TlvValueType.IntValue -> {
|
||||
typeCheck<T, Int>(tag)
|
||||
(value as Int).toByteArray()
|
||||
}
|
||||
TlvValueType.BoolValue -> {
|
||||
typeCheck<T, Boolean>(tag)
|
||||
throw ConversionException("Usopported operation: Boolean to ByteArray for tag $tag")
|
||||
}
|
||||
TlvValueType.ByteArray -> {
|
||||
typeCheck<T, ByteArray>(tag)
|
||||
value as ByteArray
|
||||
}
|
||||
TlvValueType.EllipticCurve -> {
|
||||
typeCheck<T, EllipticCurve>(tag)
|
||||
(value as EllipticCurve).curve.plus("\\0").toByteArray()
|
||||
}
|
||||
TlvValueType.DateTime -> {
|
||||
typeCheck<T, Date>(tag)
|
||||
val calendar = Calendar.getInstance().apply { time = (value as Date) }
|
||||
val year = calendar.get(Calendar.YEAR)
|
||||
val month = calendar.get(Calendar.MONTH) + 1
|
||||
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
return year.toByteArray() + month.toByteArray() + day.toByteArray()
|
||||
}
|
||||
TlvValueType.ProductMask -> {
|
||||
typeCheck<T, ProductMask>(tag)
|
||||
byteArrayOf(
|
||||
(value as ProductMask).code
|
||||
)
|
||||
}
|
||||
TlvValueType.SettingsMask -> {
|
||||
typeCheck<T, SettingsMask>(tag)
|
||||
(value as SettingsMask).rawValue.toByteArray()
|
||||
}
|
||||
TlvValueType.CardStatus -> {
|
||||
typeCheck<T, CardStatus>(tag)
|
||||
(value as CardStatus).code.toByteArray()
|
||||
}
|
||||
TlvValueType.SigningMethod -> {
|
||||
typeCheck<T, SigningMethod>(tag)
|
||||
(value as SigningMethod).rawValue.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified T, reified ExpectedT> typeCheck(tag: TlvTag) {
|
||||
if (T::class != ExpectedT::class)
|
||||
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
|
||||
}
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ enum class TlvTag(val code: Int) {
|
|||
*/
|
||||
fun valueType(): TlvValueType {
|
||||
return when (this) {
|
||||
CardId, Pin, Batch -> TlvValueType.HexString
|
||||
CardId, Pin, Pin2, Batch -> TlvValueType.HexString
|
||||
ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress ->
|
||||
TlvValueType.Utf8String
|
||||
CurveId -> TlvValueType.EllipticCurve
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CheckWalletCommand
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
|
||||
|
|
@ -31,64 +29,51 @@ sealed class ScanEvent {
|
|||
internal class ScanTask : Task<ScanEvent>() {
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<ScanEvent>) -> Unit) {
|
||||
|
||||
val readCommand = ReadCommand()
|
||||
sendCommand(readCommand, cardEnvironment) { readResult ->
|
||||
if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard)))
|
||||
|
||||
when (readResult) {
|
||||
if (currentCard == null) {
|
||||
completeNfcSession(true, TaskError.MissingPreflightRead())
|
||||
callback(TaskEvent.Completion(TaskError.MissingPreflightRead()))
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (readResult.error !is TaskError.UserCancelledError) {
|
||||
completeNfcSession(true, readResult.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(readResult.error))
|
||||
}
|
||||
} else if (currentCard.cardData?.productMask == ProductMask.Tag) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Completion())
|
||||
|
||||
is CompletionResult.Success -> {
|
||||
val card = readResult.data
|
||||
} else if (currentCard.status != CardStatus.Loaded) {
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Completion())
|
||||
|
||||
callback(TaskEvent.Event(ScanEvent.OnReadEvent(card)))
|
||||
} else if (currentCard.curve == null || currentCard.walletPublicKey == null) {
|
||||
completeNfcSession(true, TaskError.CardError())
|
||||
callback(TaskEvent.Completion(TaskError.CardError()))
|
||||
|
||||
if (card.curve == null || card.walletPublicKey == null) {
|
||||
completeNfcSession(true)
|
||||
callback(TaskEvent.Completion(TaskError.CardError()))
|
||||
return@sendCommand
|
||||
}
|
||||
} else {
|
||||
|
||||
val challenge = CryptoUtils.generateRandomBytes(16)
|
||||
val checkWalletCommand = CheckWalletCommand(
|
||||
card.cardId,
|
||||
challenge)
|
||||
val checkWalletCommand = CheckWalletCommand()
|
||||
|
||||
sendCommand(checkWalletCommand, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelledError) {
|
||||
completeNfcSession(true, result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
|
||||
is CompletionResult.Success -> {
|
||||
completeNfcSession()
|
||||
val checkWalletResponse = result.data
|
||||
val verified = CryptoUtils.verify(
|
||||
card.walletPublicKey,
|
||||
challenge + checkWalletResponse.salt,
|
||||
checkWalletResponse.walletSignature,
|
||||
card.curve)
|
||||
if (verified) {
|
||||
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
|
||||
callback(TaskEvent.Completion())
|
||||
} else {
|
||||
callback(TaskEvent.Completion(TaskError.VefificationFailed()))
|
||||
}
|
||||
}
|
||||
sendCommand(checkWalletCommand, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelledError) {
|
||||
completeNfcSession(true, result.error)
|
||||
}
|
||||
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
completeNfcSession()
|
||||
val verified = result.data.verify(
|
||||
currentCard.curve,
|
||||
currentCard.walletPublicKey,
|
||||
checkWalletCommand.challenge
|
||||
)
|
||||
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(verified)))
|
||||
callback(TaskEvent.Completion())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.common.CompletionResult
|
||||
|
|
@ -14,8 +15,11 @@ class SingleCommandTask<Event : CommandResponse>(
|
|||
private val command: CommandSerializer<Event>
|
||||
) : Task<Event>() {
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<Event>) -> Unit) {
|
||||
override fun onRun(
|
||||
cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<Event>) -> Unit
|
||||
) {
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.CardEnvironment
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.CardReader
|
||||
import com.tangem.Log
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.StatusWord
|
||||
|
|
@ -22,7 +24,7 @@ sealed class TaskError(description: String? = null) : Exception(description) {
|
|||
class Busy() : TaskError()
|
||||
class TagLost() : TaskError()
|
||||
|
||||
class ErrorProcessingCommand : TaskError()
|
||||
class ErrorProcessingCommand(description: String? = null) : TaskError(description)
|
||||
class InvalidState : TaskError()
|
||||
class InsNotSupported : TaskError()
|
||||
class InvalidParams : TaskError()
|
||||
|
|
@ -32,12 +34,15 @@ sealed class TaskError(description: String? = null) : Exception(description) {
|
|||
class VefificationFailed : TaskError()
|
||||
class CardError : TaskError()
|
||||
class ReaderError() : TaskError()
|
||||
class SerializeCommandError() : TaskError()
|
||||
class SerializeCommandError(description: String? = null) : TaskError(description)
|
||||
|
||||
class CardIsMissing() : TaskError()
|
||||
class EmptyHashes() : TaskError()
|
||||
class TooMuchHashes() : TaskError()
|
||||
class HashSizeMustBeEqual() : TaskError()
|
||||
|
||||
class WrongCard() : TaskError()
|
||||
class MissingPreflightRead() : TaskError()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -67,6 +72,8 @@ abstract class Task<T> {
|
|||
|
||||
var delegate: CardManagerDelegate? = null
|
||||
var reader: CardReader? = null
|
||||
var performPreflightRead: Boolean = true
|
||||
var securityDelayDuration: Int = 0
|
||||
|
||||
/**
|
||||
* This method should be called to run the [Task] and perform all its operations.
|
||||
|
|
@ -76,10 +83,15 @@ abstract class Task<T> {
|
|||
*/
|
||||
fun run(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
delegate?.onNfcSessionStarted()
|
||||
delegate?.onNfcSessionStarted(cardEnvironment.cardId)
|
||||
reader?.openSession()
|
||||
Log.i(this::class.simpleName!!, "Nfc task is started")
|
||||
onRun(cardEnvironment, callback)
|
||||
|
||||
if (performPreflightRead) {
|
||||
runWithPreflightRead(cardEnvironment, callback)
|
||||
} else {
|
||||
onRun(cardEnvironment, null, callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -101,6 +113,7 @@ abstract class Task<T> {
|
|||
* In this method the individual Tasks' logic should be implemented.
|
||||
*/
|
||||
protected abstract fun onRun(cardEnvironment: CardEnvironment,
|
||||
currentCard: Card?,
|
||||
callback: (result: TaskEvent<T>) -> Unit)
|
||||
|
||||
/**
|
||||
|
|
@ -149,7 +162,7 @@ abstract class Task<T> {
|
|||
StatusWord.NeedPause -> {
|
||||
// When NeedPause is returned from the card whenever security delay is triggered.
|
||||
val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment)
|
||||
if (remainingTime != null) delegate?.onSecurityDelay(remainingTime)
|
||||
if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
|
||||
sendRequest(command, commandApdu, cardEnvironment, callback)
|
||||
}
|
||||
|
|
@ -165,6 +178,35 @@ abstract class Task<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun runWithPreflightRead(
|
||||
environment: CardEnvironment, callback: (result: TaskEvent<T>) -> Unit) {
|
||||
sendCommand(ReadCommand(), environment) { readResult ->
|
||||
when (readResult) {
|
||||
is CompletionResult.Failure -> {
|
||||
completeNfcSession(true, readResult.error)
|
||||
callback(TaskEvent.Completion(readResult.error))
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
|
||||
val receivedCardId = readResult.data.cardId
|
||||
securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0
|
||||
|
||||
if (environment.cardId != null && environment.cardId != receivedCardId) {
|
||||
completeNfcSession(true, TaskError.WrongCard())
|
||||
callback(TaskEvent.Completion(TaskError.WrongCard()))
|
||||
return@sendCommand
|
||||
}
|
||||
|
||||
val newEnvironment = environment.copy(cardId = receivedCardId)
|
||||
onRun(newEnvironment, readResult.data, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue