Updated on 2026-08-14
This commit is contained in:
parent
ff4c151e90
commit
bbdcad1410
18 changed files with 188 additions and 252 deletions
|
|
@ -4,7 +4,7 @@ import com.tangem.commands.CommandResponse
|
|||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.commands.SignCommand
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.crypto.initCrypto
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tasks.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ class CardManager(
|
|||
private val cardManagerExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
init {
|
||||
initCrypto()
|
||||
CryptoUtils.initCrypto()
|
||||
}
|
||||
|
||||
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
|
||||
|
|
@ -56,26 +56,21 @@ class CardManager(
|
|||
task.delegate = cardManagerDelegate
|
||||
|
||||
cardManagerExecutor.execute {
|
||||
task.run(environment) {
|
||||
when (it) {
|
||||
is TaskEvent.Event -> callback(it)
|
||||
is TaskEvent.Completion -> {
|
||||
isBusy = false
|
||||
callback(it)
|
||||
}
|
||||
}
|
||||
task.run(environment) { taskEvent ->
|
||||
if (taskEvent is TaskEvent.Completion) isBusy = false
|
||||
callback(taskEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : CommandResponse> runCommand(command: CommandSerializer<T>,
|
||||
cardId: String? = null,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
val task = SingleCommandTask(command)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
|
||||
return cardEnvironmentRepository[cardId] ?: CardEnvironment()
|
||||
}
|
||||
|
||||
fun <T : CommandResponse> runCommand(commandSerializer: CommandSerializer<T>,
|
||||
cardId: String? = null,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
val task = SingleCommandTask(commandSerializer)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,11 @@ import com.tangem.tasks.TaskError
|
|||
|
||||
interface CardManagerDelegate {
|
||||
|
||||
fun onTaskStarted()
|
||||
fun showSecurityDelay(ms: Int)
|
||||
fun onTaskCompleted()
|
||||
fun onTaskError(error: TaskError? = null)
|
||||
fun onNfcSessionStarted()
|
||||
fun onSecurityDelay(ms: Int)
|
||||
fun hideSecurityDelay()
|
||||
fun onNfcSessionCompleted()
|
||||
fun onError(error: TaskError? = null)
|
||||
|
||||
fun requestPin(callback: (result: CompletionResult<String>) -> Unit)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ import com.tangem.common.apdu.ResponseApdu
|
|||
interface CardReader {
|
||||
var readingActive: Boolean
|
||||
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
|
||||
fun setStartSession()
|
||||
fun startNfcSession()
|
||||
fun closeSession()
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem
|
||||
|
||||
interface DataStorage {
|
||||
|
||||
fun getTerminalPublicKey(): ByteArray?
|
||||
fun getTerminalPrivateKey(): ByteArray?
|
||||
fun getPin1(): String?
|
||||
fun getPin2(): String?
|
||||
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem
|
||||
//
|
||||
//data class SignResponse(
|
||||
// val cid: String,
|
||||
// val signature: ByteArray,
|
||||
// val remainingSignatures: Int,
|
||||
// val signedHashes: Int
|
||||
//)
|
||||
//
|
||||
//
|
||||
//data class CardError(
|
||||
// val code: Int = 0
|
||||
//)
|
||||
//
|
||||
|
||||
|
||||
|
|
@ -19,20 +19,19 @@ class CheckWalletResponse(
|
|||
|
||||
|
||||
class CheckWalletCommand(
|
||||
val pin1: String, val cid: String,
|
||||
val challenge: ByteArray, val publicKeyChallenge: ByteArray) : CommandSerializer<CheckWalletResponse>() {
|
||||
|
||||
override val instruction = Instruction.CheckWallet
|
||||
override val instructionCode = instruction.code
|
||||
private val pin1: String,
|
||||
private val cardId: String,
|
||||
private val challenge: ByteArray
|
||||
) : CommandSerializer<CheckWalletResponse>() {
|
||||
|
||||
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
|
||||
val tlvData = listOf(
|
||||
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
|
||||
Tlv(TlvTag.CardId, cid.hexToBytes()),
|
||||
Tlv(TlvTag.CardId, cardId.hexToBytes()),
|
||||
Tlv(TlvTag.Challenge, challenge)
|
||||
)
|
||||
|
||||
return CommandApdu(instructionCode, tlvData)
|
||||
return CommandApdu(Instruction.CheckWallet, tlvData)
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? {
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class SignResponse(
|
|||
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
|
||||
: CommandSerializer<SignResponse>() {
|
||||
|
||||
override val instruction = Instruction.Sign
|
||||
override val instructionCode = instruction.code
|
||||
|
||||
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
|
||||
private val dataToSign = flattenHashes()
|
||||
|
||||
|
|
@ -36,7 +33,7 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
|
|||
|
||||
private fun checkForErrors() {
|
||||
if (hashes.isEmpty()) throw TaskError.EmptyHashes()
|
||||
if (hashes.size > 10) throw TaskError.TooMuchHashes()
|
||||
if (hashes.size > 10) throw TaskError.TooMuchHashes()
|
||||
if (hashes.any { it.size != hashSizes }) throw TaskError.HashSizeMustBeEqual()
|
||||
}
|
||||
|
||||
|
|
@ -45,20 +42,20 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
|
|||
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.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())),
|
||||
Tlv(TlvTag.TransactionOutHash, dataToSign)
|
||||
)
|
||||
|
||||
addTerminalSignature(cardEnvironment, tlvData)
|
||||
|
||||
return CommandApdu(instructionCode, tlvData)
|
||||
return CommandApdu(Instruction.Sign, tlvData)
|
||||
}
|
||||
|
||||
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList<Tlv>) {
|
||||
cardEnvironment.terminalKeys?.let {
|
||||
val signedData = dataToSign.sign(it.privateKey)
|
||||
cardEnvironment.terminalKeys?.let { terminalKeyPair ->
|
||||
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
|
||||
tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, signedData))
|
||||
tlvData.add(Tlv(TlvTag.TerminalPublicKey, it.publicKey))
|
||||
tlvData.add(Tlv(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +64,7 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
|
|||
|
||||
val tlvMapper = TlvMapper(tlvData)
|
||||
return SignResponse(
|
||||
cardId= tlvMapper.map(TlvTag.CardId),
|
||||
cardId = tlvMapper.map(TlvTag.CardId),
|
||||
signature = tlvMapper.map(TlvTag.Signature),
|
||||
remainingSignatures = tlvMapper.map(TlvTag.RemainingSignatures),
|
||||
signedHashes = tlvMapper.map(TlvTag.SignedHashes)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import java.io.ByteArrayOutputStream
|
|||
|
||||
class CommandApdu(
|
||||
|
||||
private val instruction: Int,
|
||||
private val ins: Int,
|
||||
private val tlvList: List<Tlv>,
|
||||
|
||||
private val cla: Byte = ISO_CLA,
|
||||
|
|
@ -19,6 +19,18 @@ class CommandApdu(
|
|||
private val encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
private val encryptionKey: ByteArray? = null) {
|
||||
|
||||
constructor(
|
||||
instruction: Instruction,
|
||||
tlvList: List<Tlv>,
|
||||
encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
encryptionKey: ByteArray? = null
|
||||
) : this(
|
||||
instruction.code,
|
||||
tlvList,
|
||||
encryptionMode = encryptionMode,
|
||||
encryptionKey = encryptionKey
|
||||
)
|
||||
|
||||
val apduData: ByteArray
|
||||
|
||||
init {
|
||||
|
|
@ -39,7 +51,7 @@ class CommandApdu(
|
|||
|
||||
val byteStream = ByteArrayOutputStream()
|
||||
byteStream.write(cla.toInt())
|
||||
byteStream.write(instruction)
|
||||
byteStream.write(ins)
|
||||
byteStream.write(p1.toInt())
|
||||
byteStream.write(p2.toInt())
|
||||
if (lc != 0) {
|
||||
|
|
@ -58,7 +70,7 @@ class CommandApdu(
|
|||
|
||||
|
||||
private fun encrypt() {
|
||||
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ enum class StatusWord (val code: Int, val description: String){
|
|||
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
|
||||
ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"),
|
||||
InvalidState(0x6985, "SW_INVALID_STATE"),
|
||||
// PinsNotChanged(ProcessCompleted.code, ProcessCompleted.description),
|
||||
Pin1Changed(ProcessCompleted.code + 0x0001, "SW_PIN1_CHANGED"),
|
||||
Pin2Changed(ProcessCompleted.code + 0x0002, "SW_PIN2_CHANGED"),
|
||||
PinsChanged(ProcessCompleted.code + 0x0003, "SW_PINS_CHANGED"),
|
||||
InsNotSupported(0x6D00, "SW_INS_NOT_SUPPORTED"),
|
||||
NeedEncryption(0x6982, "SW_NEED_ENCRYPTION"),
|
||||
NeedPause(0x9789, "SW_NEED_PAUSE");
|
||||
NeedPause(0x9789, "SW_NEED_PAUSE"),
|
||||
Unknown(0x0000, "SW_UNKNOWN");
|
||||
|
||||
companion object {
|
||||
private val values = values()
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@ class Tlv {
|
|||
|
||||
val tag: TlvTag
|
||||
val value: ByteArray
|
||||
val tagCode: Int
|
||||
val tagRaw: Int
|
||||
|
||||
constructor(tagCode: Int, value: ByteArray = byteArrayOf()) {
|
||||
this.tag = TlvTag.byCode(tagCode)
|
||||
this.tagCode = tagCode
|
||||
this.tagRaw = tagCode
|
||||
this.value = value
|
||||
}
|
||||
|
||||
constructor(tag: TlvTag, value: ByteArray = byteArrayOf()) {
|
||||
this.tag = tag
|
||||
this.tagCode = tag.code
|
||||
this.tagRaw = tag.code
|
||||
this.value = value
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,19 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
enum class TlvValueType {
|
||||
HexString,
|
||||
Utf8String,
|
||||
IntValue,
|
||||
BoolValue,
|
||||
ByteArray,
|
||||
EllipticCurve,
|
||||
DateTime,
|
||||
ProductMask,
|
||||
SettingsMask,
|
||||
CardStatus,
|
||||
SigningMethod
|
||||
}
|
||||
|
||||
enum class TlvTag(val code: Int) {
|
||||
Unknown(0x00),
|
||||
CardId(0x01),
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
enum class TlvValueType {
|
||||
HexString,
|
||||
Utf8String,
|
||||
IntValue,
|
||||
BoolValue,
|
||||
ByteArray,
|
||||
EllipticCurve,
|
||||
DateTime,
|
||||
ProductMask,
|
||||
SettingsMask,
|
||||
CardStatus,
|
||||
SigningMethod
|
||||
}
|
||||
|
|
@ -5,35 +5,34 @@ import net.i2p.crypto.eddsa.EdDSASecurityProvider
|
|||
import java.security.SecureRandom
|
||||
import java.security.Security
|
||||
|
||||
fun generateRandomBytes(length: Int): ByteArray {
|
||||
val bytes = ByteArray(length)
|
||||
SecureRandom().nextBytes(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
|
||||
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> verifySecp256k1(publicKey, message, signature)
|
||||
EllipticCurve.Ed25519 -> verifyEd25519(publicKey, message, signature)
|
||||
object CryptoUtils {
|
||||
|
||||
fun initCrypto() {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
Security.addProvider(EdDSASecurityProvider())
|
||||
}
|
||||
|
||||
fun generateRandomBytes(length: Int): ByteArray {
|
||||
val bytes = ByteArray(length)
|
||||
SecureRandom().nextBytes(bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
|
||||
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> verifySecp256k1(publicKey, message, signature)
|
||||
EllipticCurve.Ed25519 -> verifyEd25519(publicKey, message, signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun initCrypto() {
|
||||
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
|
||||
Security.addProvider(EdDSASecurityProvider())
|
||||
}
|
||||
|
||||
|
||||
fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1): ByteArray {
|
||||
|
||||
return when (curve) {
|
||||
EllipticCurve.Secp256k1 -> signSecp256k1(this,privateKeyArray)
|
||||
EllipticCurve.Secp256k1 -> signSecp256k1(this, privateKeyArray)
|
||||
EllipticCurve.Ed25519 -> signEd25519(this, privateKeyArray)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package com.tangem.data
|
||||
|
||||
data class SettingsMask(val rawValue: Int) {
|
||||
|
||||
companion object{
|
||||
const val IsReusable = 0x0001
|
||||
const val UseActivation = 0x0002
|
||||
const val ForbidPurgeWallet = 0x0004
|
||||
const val UseBlock = 0x0008
|
||||
|
||||
const val AllowSwapPIN = 0x0010
|
||||
const val AllowSwapPIN2 = 0x0020
|
||||
const val UseCVC = 0x0040
|
||||
const val ForbidDefaultPIN = 0x0080
|
||||
|
||||
const val UseOneCommandAtTime = 0x0100
|
||||
const val UseNDEF = 0x0200
|
||||
const val UseDynamicNDEF = 0x0400
|
||||
const val SmartSecurityDelay = 0x0800
|
||||
|
||||
const val Protocol_AllowUnencrypted = 0x1000
|
||||
const val Protocol_AllowStaticEncryption = 0x2000
|
||||
|
||||
const val ProtectIssuerDataAgainstReplay = 0x4000
|
||||
|
||||
const val AllowSelectBlockchain = 0x8000
|
||||
|
||||
const val DisablePrecomputedNDEF = 0x00010000
|
||||
|
||||
const val SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000
|
||||
}
|
||||
}
|
||||
|
|
@ -3,11 +3,9 @@ package com.tangem.tasks
|
|||
import com.tangem.CardEnvironment
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CheckWalletCommand
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.commands.ReadCardCommand
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.crypto.generateRandomBytes
|
||||
import com.tangem.crypto.verify
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
|
||||
sealed class ScanEvent {
|
||||
data class OnReadEvent(val card: Card) : ScanEvent()
|
||||
|
|
@ -17,76 +15,67 @@ sealed class ScanEvent {
|
|||
|
||||
internal class ScanTask : Task<ScanEvent>() {
|
||||
|
||||
private lateinit var cardData: Card
|
||||
private lateinit var challenge: ByteArray
|
||||
private lateinit var curve: EllipticCurve
|
||||
private lateinit var walletPublickKey: ByteArray
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<ScanEvent>) -> Unit) {
|
||||
|
||||
val readCommand = ReadCardCommand()
|
||||
sendCommand(readCommand, cardEnvironment) { readEvent ->
|
||||
val readCommand = ReadCommand()
|
||||
sendCommand(readCommand, cardEnvironment) { readResult ->
|
||||
|
||||
when (readResult) {
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (readResult.error !is TaskError.UserCancelledError) {
|
||||
completeNfcSession(true, readResult.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(readResult.error))
|
||||
}
|
||||
|
||||
when (readEvent) {
|
||||
is CompletionResult.Success -> {
|
||||
cardData = readEvent.data
|
||||
val card = readResult.data
|
||||
|
||||
callback(TaskEvent.Event(ScanEvent.OnReadEvent(cardData)))
|
||||
callback(TaskEvent.Event(ScanEvent.OnReadEvent(card)))
|
||||
|
||||
if (cardData.curve != null && cardData.walletPublicKey != null) {
|
||||
curve = cardData.curve!!
|
||||
walletPublickKey = cardData.walletPublicKey!!
|
||||
} else {
|
||||
onTaskCompleted(true)
|
||||
if (card.curve == null || card.walletPublicKey == null) {
|
||||
completeNfcSession(true)
|
||||
callback(TaskEvent.Completion(TaskError.CardError()))
|
||||
return@sendCommand
|
||||
}
|
||||
|
||||
val checkWalletCommand = prepareCheckWalletCommand(cardEnvironment)
|
||||
val challenge = CryptoUtils.generateRandomBytes(16)
|
||||
val checkWalletCommand = CheckWalletCommand(
|
||||
cardEnvironment.pin1,
|
||||
card.cardId,
|
||||
challenge)
|
||||
|
||||
sendCommand(checkWalletCommand, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error !is TaskError.UserCancelledError) {
|
||||
completeNfcSession(true, result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
|
||||
sendCommand(checkWalletCommand, cardEnvironment) { checkWalletEvent ->
|
||||
when (checkWalletEvent) {
|
||||
is CompletionResult.Success -> {
|
||||
val checkWalletResponse = checkWalletEvent.data
|
||||
val verified = verify(walletPublickKey,
|
||||
completeNfcSession()
|
||||
val checkWalletResponse = result.data
|
||||
val verified = CryptoUtils.verify(
|
||||
card.walletPublicKey,
|
||||
challenge + checkWalletResponse.salt,
|
||||
checkWalletResponse.walletSignature,
|
||||
curve)
|
||||
card.curve)
|
||||
if (verified) {
|
||||
onTaskCompleted()
|
||||
callback(TaskEvent.Completion())
|
||||
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
|
||||
callback(TaskEvent.Completion())
|
||||
} else {
|
||||
onTaskCompleted(true)
|
||||
callback(TaskEvent.Completion(TaskError.VefificationFailed()))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (checkWalletEvent.error !is TaskError.UserCancelledError) {
|
||||
onTaskCompleted(true, checkWalletEvent.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(checkWalletEvent.error))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (readEvent.error !is TaskError.UserCancelledError) {
|
||||
onTaskCompleted(true, readEvent.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(readEvent.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareCheckWalletCommand(cardEnvironment: CardEnvironment): CheckWalletCommand {
|
||||
challenge = generateRandomBytes(16)
|
||||
return CheckWalletCommand(
|
||||
cardEnvironment.pin1,
|
||||
cardData.cardId,
|
||||
challenge,
|
||||
byteArrayOf())
|
||||
}
|
||||
}
|
||||
|
|
@ -6,22 +6,23 @@ import com.tangem.commands.CommandSerializer
|
|||
import com.tangem.common.CompletionResult
|
||||
|
||||
class SingleCommandTask<Event : CommandResponse>(
|
||||
private val commandSerializer: CommandSerializer<Event>) : Task<Event>() {
|
||||
private val command: CommandSerializer<Event>
|
||||
) : Task<Event>() {
|
||||
|
||||
override fun onRun(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<Event>) -> Unit) {
|
||||
sendCommand(commandSerializer, cardEnvironment) { completionResult ->
|
||||
when (completionResult) {
|
||||
sendCommand(command, cardEnvironment) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
onTaskCompleted()
|
||||
callback(TaskEvent.Event(completionResult.data))
|
||||
completeNfcSession()
|
||||
callback(TaskEvent.Event(result.data))
|
||||
callback(TaskEvent.Completion())
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (completionResult.error !is TaskError.UserCancelledError) {
|
||||
onTaskCompleted(true, completionResult.error)
|
||||
if (result.error !is TaskError.UserCancelledError) {
|
||||
completeNfcSession(true, result.error)
|
||||
}
|
||||
callback(TaskEvent.Completion(completionResult.error))
|
||||
callback(TaskEvent.Completion(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,40 @@ import com.tangem.Log
|
|||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.CommandSerializer
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.StatusWord
|
||||
|
||||
sealed class TaskError(description: String? = null) : Exception(description) {
|
||||
class UnknownStatus(sw: Int) : TaskError("Unknown StatusWord: $sw")
|
||||
class MappingError : TaskError()
|
||||
class GenericError(description: String? = null) : TaskError(description)
|
||||
class UserCancelledError() : TaskError()
|
||||
class Busy() : TaskError()
|
||||
class TagLost() : TaskError()
|
||||
|
||||
class ErrorProcessingCommand : TaskError()
|
||||
class InvalidState : TaskError()
|
||||
class InsNotSupported : TaskError()
|
||||
class InvalidParams : TaskError()
|
||||
class NeedEncryption : TaskError()
|
||||
class NeedPause : TaskError()
|
||||
|
||||
class VefificationFailed : TaskError()
|
||||
class CardError : TaskError()
|
||||
class ReaderError() : TaskError()
|
||||
class SerializeCommandError() : TaskError()
|
||||
|
||||
class CardIsMissing() : TaskError()
|
||||
class EmptyHashes() : TaskError()
|
||||
class TooMuchHashes() : TaskError()
|
||||
class HashSizeMustBeEqual() : TaskError()
|
||||
}
|
||||
|
||||
sealed class TaskEvent<T> {
|
||||
class Event<T>(val data: T) : TaskEvent<T>()
|
||||
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
|
||||
}
|
||||
|
||||
abstract class Task<T> {
|
||||
|
||||
var delegate: CardManagerDelegate? = null
|
||||
|
|
@ -16,18 +48,18 @@ abstract class Task<T> {
|
|||
|
||||
fun run(cardEnvironment: CardEnvironment,
|
||||
callback: (result: TaskEvent<T>) -> Unit) {
|
||||
delegate?.onTaskStarted()
|
||||
reader?.setStartSession()
|
||||
delegate?.onNfcSessionStarted()
|
||||
reader?.startNfcSession()
|
||||
Log.i(this::class.simpleName!!, "Nfc task is started")
|
||||
onRun(cardEnvironment, callback)
|
||||
}
|
||||
|
||||
protected fun onTaskCompleted(withError: Boolean = false, taskError: TaskError? = null) {
|
||||
protected fun completeNfcSession(withError: Boolean = false, taskError: TaskError? = null) {
|
||||
reader?.closeSession()
|
||||
if (withError) {
|
||||
delegate?.onTaskError(taskError)
|
||||
delegate?.onError(taskError)
|
||||
} else {
|
||||
delegate?.onTaskCompleted()
|
||||
delegate?.onNfcSessionCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,15 +67,22 @@ abstract class Task<T> {
|
|||
callback: (result: TaskEvent<T>) -> Unit)
|
||||
|
||||
protected fun <T : CommandResponse> sendCommand(
|
||||
commandSerializer: CommandSerializer<T>,
|
||||
command: CommandSerializer<T>,
|
||||
cardEnvironment: CardEnvironment,
|
||||
callback: (result: CompletionResult<T>) -> Unit) {
|
||||
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is initiated")
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated")
|
||||
|
||||
val commandApdu = command.serialize(cardEnvironment)
|
||||
sendRequest(command, commandApdu, cardEnvironment, callback)
|
||||
}
|
||||
|
||||
reader?.transceiveApdu(
|
||||
commandSerializer.serialize(cardEnvironment)) { result ->
|
||||
private fun <T : CommandResponse> sendRequest(command: CommandSerializer<T>,
|
||||
commandApdu: CommandApdu,
|
||||
cardEnvironment: CardEnvironment,
|
||||
callback: (result: CompletionResult<T>) -> Unit) {
|
||||
|
||||
reader?.transceiveApdu(commandApdu) { result ->
|
||||
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
|
|
@ -52,24 +91,26 @@ abstract class Task<T> {
|
|||
StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged
|
||||
-> {
|
||||
try {
|
||||
val responseData = commandSerializer.deserialize(cardEnvironment, responseApdu)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is completed")
|
||||
val responseData = command.deserialize(cardEnvironment, responseApdu)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is completed")
|
||||
callback(CompletionResult.Success(responseData as T))
|
||||
} catch (error: TaskError) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
}
|
||||
}
|
||||
StatusWord.InvalidParams -> callback(CompletionResult.Failure(TaskError.InvalidParams()))
|
||||
StatusWord.Unknown -> callback(CompletionResult.Failure(TaskError.UnknownStatus(result.data.sw)))
|
||||
StatusWord.ErrorProcessingCommand -> callback(CompletionResult.Failure(TaskError.ErrorProcessingCommand()))
|
||||
StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState()))
|
||||
|
||||
StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported()))
|
||||
StatusWord.NeedEncryption -> callback(CompletionResult.Failure(TaskError.NeedEncryption()))
|
||||
StatusWord.NeedPause -> {
|
||||
val remainingTime = commandSerializer.deserializeSecurityDelay(responseApdu, cardEnvironment)
|
||||
if (remainingTime != null) delegate?.showSecurityDelay(remainingTime)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
|
||||
sendCommand(commandSerializer, cardEnvironment, callback)
|
||||
// When NeedPause is returned from the card whenever security delay is triggered.
|
||||
val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment)
|
||||
if (remainingTime != null) delegate?.onSecurityDelay(remainingTime)
|
||||
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
|
||||
sendRequest(command, commandApdu, cardEnvironment, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,18 +121,9 @@ abstract class Task<T> {
|
|||
callback(CompletionResult.Failure(TaskError.UserCancelledError()))
|
||||
reader?.readingActive = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns in callback from tasks
|
||||
*/
|
||||
sealed class TaskEvent<T> {
|
||||
class Event<T>(val data: T) : TaskEvent<T>()
|
||||
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
/**
|
||||
* An error class that covers typical errors that may occur when performing Tangem SDK tasks.
|
||||
* Errors are got propagated back within callbacks.
|
||||
*/
|
||||
sealed class TaskError(description: String? = null) : Exception(description) {
|
||||
class UnknownStatus(sw: Int) : TaskError()
|
||||
class MappingError : TaskError()
|
||||
class GenericError(description: String? = null) : TaskError(description)
|
||||
class UserCancelledError() : TaskError()
|
||||
class Busy() : TaskError()
|
||||
|
||||
class ErrorProcessingCommand : TaskError()
|
||||
class InvalidState : TaskError()
|
||||
class InsNotSupported : TaskError()
|
||||
class InvalidParams : TaskError()
|
||||
class NeedEncryption : TaskError()
|
||||
class NeedPause : TaskError()
|
||||
|
||||
class VefificationFailed : TaskError()
|
||||
class CardError : TaskError()
|
||||
class ReaderError() : TaskError()
|
||||
class SerializeCommandError() : TaskError()
|
||||
|
||||
class CardIsMissing() : TaskError()
|
||||
class EmptyHashes() : TaskError()
|
||||
class TooMuchHashes() : TaskError()
|
||||
class HashSizeMustBeEqual() : TaskError()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue