Updated on 2026-08-14
This commit is contained in:
parent
509f585ceb
commit
005f7295b5
13 changed files with 99 additions and 130 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -8,4 +8,5 @@ interface CardReader {
|
|||
var readingActive: Boolean
|
||||
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
|
||||
fun setStartSession()
|
||||
fun closeSession()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue