Updated on 2026-08-14

This commit is contained in:
Tangem 2019-11-01 10:51:08 +03:00
parent 509f585ceb
commit 005f7295b5
13 changed files with 99 additions and 130 deletions

View file

@ -1,22 +0,0 @@
package com.tangem
class CardEnvironmentRepository(val dataStorage: DataStorage?) {
var cardEnvironment: CardEnvironment = initCardEnvironment()
set(value) {
if (cardEnvironment != value) {
saveCardEnvirnoment(value)
}
}
private fun initCardEnvironment(): CardEnvironment {
return CardEnvironment(
dataStorage?.getPin1() ?: CardEnvironment.DEFAULT_PIN,
dataStorage?.getPin2() ?: CardEnvironment.DEFAULT_PIN2
)
}
private fun saveCardEnvirnoment(cardEnvironment: CardEnvironment) {
//TODO: logic for saving fields in persistence storage
}
}

View file

@ -6,15 +6,15 @@ import com.tangem.commands.SignCommand
import com.tangem.commands.SignResponse
import com.tangem.crypto.initCrypto
import com.tangem.tasks.*
import java.util.concurrent.Executors
class CardManager(
private val reader: CardReader,
private val cardManagerDelegate: CardManagerDelegate? = null) {
private var isBusy = false
private set
private val cardEnvironmentRepository = mutableMapOf<String, CardEnvironment>()
private val cardManagerExecutor = Executors.newSingleThreadExecutor()
init {
initCrypto()
@ -44,8 +44,6 @@ class CardManager(
fun <T> runTask(task: Task<T>, cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
// AppExecutors.diskIO().execute {
if (isBusy) {
callback(TaskEvent.Completion(TaskError.Busy()))
return
@ -57,16 +55,17 @@ class CardManager(
task.reader = reader
task.delegate = cardManagerDelegate
task.run(environment) {
when (it) {
is TaskEvent.Event -> callback(it)
is TaskEvent.Completion -> {
isBusy = false
callback(it)
cardManagerExecutor.execute {
task.run(environment) {
when (it) {
is TaskEvent.Event -> callback(it)
is TaskEvent.Completion -> {
isBusy = false
callback(it)
}
}
}
}
// }
}
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {

View file

@ -1,13 +1,15 @@
package com.tangem
import com.tangem.common.CompletionResult
import com.tangem.tasks.TaskError
interface CardManagerDelegate {
fun onTaskStarted()
fun showSecurityDelay(ms: Int)
fun requestPin(success: () -> String, error: (cardError: TaskError.CardError) -> TaskError.CardError)
fun onTaskCompleted()
fun onTaskError(error: TaskError? = null)
fun openNfcPopup()
fun closeNfcPopup()
fun requestPin(callback: (result: CompletionResult<String>) -> Unit)
}

View file

@ -8,4 +8,5 @@ interface CardReader {
var readingActive: Boolean
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
fun setStartSession()
fun closeSession()
}

View file

@ -1,20 +0,0 @@
package com.tangem
import java.util.concurrent.Executor
import java.util.concurrent.Executors
object AppExecutors {
private val diskIO = Executors.newSingleThreadExecutor()
private val networkIO = Executors.newFixedThreadPool(3)
fun diskIO(): Executor {
return diskIO
}
fun networkIO(): Executor {
return networkIO
}
}

View file

@ -41,9 +41,9 @@ class CheckWalletCommand(
return try {
val mapper = TlvMapper(tlvData)
CheckWalletResponse(
mapper.map(TlvTag.CardId),
mapper.map(TlvTag.Salt),
mapper.map(TlvTag.Signature)
cardId = mapper.map(TlvTag.CardId),
salt = mapper.map(TlvTag.Salt),
walletSignature = mapper.map(TlvTag.Signature)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()

View file

@ -67,7 +67,7 @@ class ReadCardResponse(
val issuerPublicKey: ByteArray?,
val curve: EllipticCurve?,
val maxSignatures: Int?,
val signingMethpod: SigningMethod?,
val signingMethod: SigningMethod?,
val pauseBeforePin2: Int?,
val walletPublicKey: ByteArray?,
val walletRemainingSignatures: Int?,
@ -116,38 +116,38 @@ class ReadCardCommand : CommandSerializer<ReadCardResponse>() {
val tlvMapper = TlvMapper(tlvData)
ReadCardResponse(
tlvMapper.map(TlvTag.CardId),
tlvMapper.map(TlvTag.ManufactureId),
tlvMapper.map(TlvTag.Status),
cardId = tlvMapper.map(TlvTag.CardId),
manufacturerName = tlvMapper.map(TlvTag.ManufactureId),
status = tlvMapper.map(TlvTag.Status),
tlvMapper.mapOptional(TlvTag.Firmware),
tlvMapper.mapOptional(TlvTag.CardPublicKey),
tlvMapper.mapOptional(TlvTag.SettingsMask),
tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey),
tlvMapper.mapOptional(TlvTag.CurveId),
tlvMapper.mapOptional(TlvTag.MaxSignatures),
tlvMapper.mapOptional(TlvTag.SigningMethod),
tlvMapper.mapOptional(TlvTag.PauseBeforePin2),
tlvMapper.mapOptional(TlvTag.WalletPublicKey),
tlvMapper.mapOptional(TlvTag.RemainingSignatures),
tlvMapper.mapOptional(TlvTag.SignedHashes),
tlvMapper.mapOptional(TlvTag.Health),
tlvMapper.map(TlvTag.IsActivated),
tlvMapper.mapOptional(TlvTag.ActivationSeed),
tlvMapper.mapOptional(TlvTag.PaymentFlowVersion),
tlvMapper.mapOptional(TlvTag.UserCounter),
tlvMapper.map(TlvTag.TerminalIsLinked),
firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware),
cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey),
settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask),
issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey),
curve = tlvMapper.mapOptional(TlvTag.CurveId),
maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures),
signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod),
pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2),
walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey),
walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures),
walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes),
health = tlvMapper.mapOptional(TlvTag.Health),
isActivated = tlvMapper.map(TlvTag.IsActivated),
activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed),
paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion),
userCounter = tlvMapper.mapOptional(TlvTag.UserCounter),
terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked),
tlvMapper.mapOptional(TlvTag.Batch),
tlvMapper.mapOptional(TlvTag.ManufactureDateTime),
tlvMapper.mapOptional(TlvTag.IssuerId),
tlvMapper.mapOptional(TlvTag.BlockchainId),
tlvMapper.mapOptional(TlvTag.ManufacturerSignature),
tlvMapper.mapOptional(TlvTag.ProductMask),
batchId = tlvMapper.mapOptional(TlvTag.Batch),
manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime),
issuerName = tlvMapper.mapOptional(TlvTag.IssuerId),
blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId),
manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature),
productMask = tlvMapper.mapOptional(TlvTag.ProductMask),
tlvMapper.mapOptional(TlvTag.TokenSymbol),
tlvMapper.mapOptional(TlvTag.TokenContractAddress),
tlvMapper.mapOptional(TlvTag.TokenDecimal)
tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol),
tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress),
tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()

View file

@ -67,10 +67,10 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
val tlvMapper = TlvMapper(tlvData)
return SignResponse(
tlvMapper.map(TlvTag.CardId),
tlvMapper.map(TlvTag.Signature),
tlvMapper.map(TlvTag.RemainingSignatures),
tlvMapper.map(TlvTag.SignedHashes)
cardId= tlvMapper.map(TlvTag.CardId),
signature = tlvMapper.map(TlvTag.Signature),
remainingSignatures = tlvMapper.map(TlvTag.RemainingSignatures),
signedHashes = tlvMapper.map(TlvTag.SignedHashes)
)
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.common.apdu
import com.tangem.common.tlv.Tlv
import com.tangem.enums.Status
import com.tangem.enums.StatusWord
class ResponseApdu(val data: ByteArray) {
@ -10,7 +10,7 @@ class ResponseApdu(val data: ByteArray) {
val sw: Int = sw1 shl 8 or sw2
val status: Status = Status.byCode(sw)
val statusWord: StatusWord = StatusWord.byCode(sw)
fun getTlvData(encryptionKey: ByteArray? = null): List<Tlv>? {
val tlvs = when {

View file

@ -1,6 +1,6 @@
package com.tangem.enums
enum class Status (val code: Int, val description: String){
enum class StatusWord (val code: Int, val description: String){
ProcessCompleted(0x9000, "SW_PROCESS_COMPLETED"),
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
@ -15,7 +15,7 @@ enum class Status (val code: Int, val description: String){
NeedPause(0x9789, "SW_NEED_PAUSE");
companion object {
fun byCode(code: Int): Status = values().find { it.code == code } ?: InvalidParams
fun byCode(code: Int): StatusWord = values().find { it.code == code } ?: InvalidParams
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.commands.CheckWalletCommand
import com.tangem.commands.EllipticCurve
import com.tangem.commands.ReadCardCommand
import com.tangem.commands.ReadCardResponse
import com.tangem.common.CompletionResult
import com.tangem.crypto.generateRandomBytes
import com.tangem.crypto.verify
@ -28,7 +29,7 @@ internal class ScanTask : Task<ScanEvent>() {
sendCommand(readCommand, cardEnvironment) { readEvent ->
when (readEvent) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
readCardData = readEvent.data
callback(TaskEvent.Event(ScanEvent.OnReadEvent(readCardData)))
@ -37,38 +38,41 @@ internal class ScanTask : Task<ScanEvent>() {
curve = readCardData.curve!!
walletPublickKey = readCardData.walletPublicKey!!
} else {
delegate?.closeNfcPopup()
delegate?.onTaskError()
callback(TaskEvent.Completion(TaskError.CardError()))
}
val checkWalletCommand = prepareCheckWalletCommand(cardEnvironment)
sendCommand(checkWalletCommand, cardEnvironment) { checkWalletEvent ->
delegate?.closeNfcPopup()
when (checkWalletEvent) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
val checkWalletResponse = checkWalletEvent.data
val verified = verify(walletPublickKey,
challenge + checkWalletResponse.salt,
checkWalletResponse.walletSignature,
curve)
if (verified) {
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
delegate?.onTaskCompleted()
callback(TaskEvent.Completion())
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
} else {
delegate?.onTaskError()
callback(TaskEvent.Completion(TaskError.VefificationFailed()))
}
}
is TaskEvent.Completion ->
if (checkWalletEvent.error != null)
callback(TaskEvent.Completion(checkWalletEvent.error))
is CompletionResult.Failure -> {
if (checkWalletEvent.error !is TaskError.UserCancelledError) delegate?.onTaskError()
callback(TaskEvent.Completion(checkWalletEvent.error))
}
}
}
}
is TaskEvent.Completion ->
if (readEvent.error != null) callback(TaskEvent.Completion(readEvent.error))
is CompletionResult.Failure -> {
if (readEvent.error !is TaskError.UserCancelledError) delegate?.onTaskError()
callback(TaskEvent.Completion(readEvent.error))
}
}
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CompletionResult
class SingleCommandTask<Event : CommandResponse>(
private val commandSerializer: CommandSerializer<Event>) : Task<Event>() {
@ -10,8 +11,17 @@ class SingleCommandTask<Event : CommandResponse>(
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<Event>) -> Unit) {
sendCommand(commandSerializer, cardEnvironment) {
delegate?.closeNfcPopup()
callback(it)
when (it) {
is CompletionResult.Success -> {
delegate?.onTaskCompleted()
callback(TaskEvent.Event(it.data))
callback(TaskEvent.Completion())
}
is CompletionResult.Failure -> {
if (it.error !is TaskError.UserCancelledError) delegate?.onTaskError()
callback(TaskEvent.Completion(it.error))
}
}
}
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.Log
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CompletionResult
import com.tangem.enums.Status
import com.tangem.enums.StatusWord
abstract class Task<T> {
@ -16,12 +16,10 @@ abstract class Task<T> {
fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<T>) -> Unit) {
delegate?.openNfcPopup()
delegate?.onTaskStarted()
reader?.setStartSession()
Log.i(this::class.simpleName!!, "Nfc task is started")
onRun(cardEnvironment, callback)
}
abstract fun onRun(cardEnvironment: CardEnvironment,
@ -30,7 +28,7 @@ abstract class Task<T> {
protected fun <T : CommandResponse> sendCommand(
commandSerializer: CommandSerializer<T>,
cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<T>) -> Unit) {
callback: (result: CompletionResult<T>) -> Unit) {
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is initiated")
@ -41,27 +39,24 @@ abstract class Task<T> {
when (result) {
is CompletionResult.Success -> {
val responseApdu = result.data
when (responseApdu.status) {
Status.ProcessCompleted, Status.Pin1Changed, Status.Pin2Changed, Status.PinsChanged
when (responseApdu.statusWord) {
StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged
-> {
// reader.readingActive = false
// reader.setStartSession()
try {
val responseData = commandSerializer.deserialize(cardEnvironment, responseApdu)
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is completed")
callback(TaskEvent.Event(responseData as T))
callback(TaskEvent.Completion())
callback(CompletionResult.Success(responseData as T))
} catch (error: TaskError) {
callback(TaskEvent.Completion(error))
callback(CompletionResult.Failure(error))
}
}
Status.InvalidParams -> callback(TaskEvent.Completion(TaskError.InvalidParams()))
Status.ErrorProcessingCommand -> callback(TaskEvent.Completion(TaskError.ErrorProcessingCommand()))
Status.InvalidState -> callback(TaskEvent.Completion(TaskError.InvalidState()))
StatusWord.InvalidParams -> callback(CompletionResult.Failure(TaskError.InvalidParams()))
StatusWord.ErrorProcessingCommand -> callback(CompletionResult.Failure(TaskError.ErrorProcessingCommand()))
StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState()))
Status.InsNotSupported -> callback(TaskEvent.Completion(TaskError.InsNotSupported()))
Status.NeedEncryption -> callback(TaskEvent.Completion(TaskError.NeedEncryption()))
Status.NeedPause -> {
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")
@ -71,7 +66,7 @@ abstract class Task<T> {
}
is CompletionResult.Failure ->
if (result.error is TaskError.UserCancelledError) {
callback(TaskEvent.Completion(TaskError.UserCancelledError()))
callback(CompletionResult.Failure(TaskError.UserCancelledError()))
reader?.readingActive = false
}
@ -85,7 +80,7 @@ sealed class TaskError(description: String? = null) : Exception(description) {
class MappingError : TaskError()
class GenericError(description: String? = null) : TaskError(description)
class UserCancelledError() : TaskError()
class Busy(): TaskError()
class Busy() : TaskError()
class ErrorProcessingCommand : TaskError()
class InvalidState : TaskError()