Updated on 2026-08-14

This commit is contained in:
Tangem 2020-07-03 16:21:33 +03:00
parent 4a411286e8
commit 336ec4a1ed
10 changed files with 65 additions and 42 deletions

View file

@ -1,6 +1,8 @@
package com.tangem
import com.tangem.commands.*
import com.tangem.commands.CommandResponse
import com.tangem.commands.OpenSessionCommand
import com.tangem.commands.ReadCommand
import com.tangem.common.CompletionResult
import com.tangem.common.PinCode
import com.tangem.common.apdu.CommandApdu
@ -9,6 +11,7 @@ import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.getType
import com.tangem.crypto.EncryptionHelper
import com.tangem.crypto.pbkdf2Hash
import com.tangem.tasks.PinType
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@ -19,6 +22,8 @@ interface CardSessionRunnable<T : CommandResponse> {
val performPreflightRead: Boolean
val requiresPin2: Boolean
/**
* The starting point for custom business logic.
* Implement this interface and use [TangemSdk.startSessionWithRunnable] to run.
@ -28,10 +33,6 @@ interface CardSessionRunnable<T : CommandResponse> {
fun run(session: CardSession, callback: (result: CompletionResult<T>) -> Unit)
}
interface CardSessionStoppedListener {
fun onSessionStopped(environment: SessionEnvironment)
}
enum class CardSessionState {
Inactive,
Active
@ -64,7 +65,6 @@ class CardSession(
) {
var connectedTag: TagType? = null
var sessionStoppedListener: CardSessionStoppedListener? = null
/**
* True if some operation is still in progress.
@ -96,7 +96,7 @@ class CardSession(
return
}
if ((runnable as? Command<R>)?.requiresPin2 == true && environment.pin2 == null) {
if (runnable.requiresPin2 && environment.pin2 == null) {
viewDelegate.onSessionStarted(cardId)
viewDelegate.onPinRequested(PinType.Pin2) {
environment.pin2 = PinCode(it)
@ -231,7 +231,7 @@ class CardSession(
}
private fun stopSession() {
sessionStoppedListener?.onSessionStopped(environment)
environment.saveCardValues()
reader.stopSession()
state = CardSessionState.Inactive
scope.cancel()

View file

@ -2,7 +2,7 @@ package com.tangem
import com.tangem.commands.Card
import com.tangem.commands.EllipticCurve
import com.tangem.common.CardValuesService
import com.tangem.common.CardValuesStorage
import com.tangem.common.PinCode
import com.tangem.common.TerminalKeysService
import com.tangem.crypto.CryptoUtils.generatePublicKey
@ -12,14 +12,25 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey
* Contains data relating to a Tangem card. It is used in constructing all the commands,
* and commands can return modified [SessionEnvironment].
*
* @property card Current card, read by preflight [com.tangem.commands.ReadCommand].
* @property terminalKeys generated terminal keys used in Linked Terminal feature.
* @param cardId Card ID, if it is known before tapping the card.
* @property config sets a number of parameters for communication with Tangem cards.
* @param terminalKeysService is used to retrieve terminal keys used in Linked Terminal feature.
* @param cardValuesStorage is used to save and retrieve some values relating to a particular card.
*
* @property pin1 An access Code, required to get access to a card. A default value is set in [Config]
* @property pin2 A code, required to perform particular operations with a card. A default value is set in [Config]
* @property terminalKeys generated terminal keys used in Linked Terminal feature
* @property cardFilter a property that defines types of card that this SDK will be able to interact with
* @property handleErrors if true, the SDK parses internal card errors into concrete [TangemSdkError]
* @property encryptionMode preferred [EncryptionMode] for interaction with cards
* @property encryptionKey is used for encrypted communication with a card
*
*/
class SessionEnvironment(
cardId: String?,
private val config: Config,
private val terminalKeysService: TerminalKeysService?,
private val cardValuesService: CardValuesService?
terminalKeysService: TerminalKeysService?,
private val cardValuesStorage: CardValuesStorage?
) {
var pin1: PinCode?
@ -46,7 +57,7 @@ class SessionEnvironment(
encryptionMode = config.encryptionMode
val cardValues = cardId?.let { cardValuesService?.getValues(cardId) }
val cardValues = cardId?.let { cardValuesStorage?.getValues(cardId) }
cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified
cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified
@ -68,7 +79,7 @@ class SessionEnvironment(
}
fun restoreCardValues() {
val cardValues = this.card?.cardId?.let { cardValuesService?.getValues(it) }
val cardValues = this.card?.cardId?.let { cardValuesStorage?.getValues(it) }
cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified
cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified
codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified
@ -85,7 +96,7 @@ class SessionEnvironment(
card?.cardId?.let { cardId -> TangemSdk.pin2[cardId] = pin2 }
}
cardValuesService?.saveValues(this)
cardValuesStorage?.saveValues(this)
}
}
@ -106,5 +117,5 @@ class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) {
}
enum class VerificationState {
Passed, Offline, Failed, NotVerified
Passed, Offline, Failed, NotVerified, Cancelled
}

View file

@ -1,6 +1,6 @@
package com.tangem
import com.tangem.commands.PinType
import com.tangem.tasks.PinType
/**
* Allows interaction with users and shows visual elements.

View file

@ -11,12 +11,14 @@ import com.tangem.commands.personalization.entities.Issuer
import com.tangem.commands.personalization.entities.Manufacturer
import com.tangem.commands.verifycard.VerifyCardCommand
import com.tangem.commands.verifycard.VerifyCardResponse
import com.tangem.common.CardValuesDbService
import com.tangem.common.CardValuesDbStorage
import com.tangem.common.CompletionResult
import com.tangem.common.PinCode
import com.tangem.common.TerminalKeysService
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.ChangePinTask
import com.tangem.tasks.CreateWalletTask
import com.tangem.tasks.PinType
import com.tangem.tasks.ScanTask
/**
@ -369,7 +371,7 @@ class TangemSdk(
pin: ByteArray? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<SetPinResponse>) -> Unit) {
val command = ChangePinCommand(PinType.Pin1, pin)
val command = ChangePinTask(PinType.Pin1, pin)
startSessionWithRunnable(command, cardId, initialMessage, callback)
}
@ -377,7 +379,7 @@ class TangemSdk(
pin: ByteArray? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<SetPinResponse>) -> Unit) {
val command = ChangePinCommand(PinType.Pin2, pin)
val command = ChangePinTask(PinType.Pin2, pin)
startSessionWithRunnable(command, cardId, initialMessage, callback)
}
@ -385,7 +387,7 @@ class TangemSdk(
pin: ByteArray? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<SetPinResponse>) -> Unit) {
val command = ChangePinCommand(PinType.Pin3, pin)
val command = ChangePinTask(PinType.Pin3, pin)
startSessionWithRunnable(command, cardId, initialMessage, callback)
}
@ -407,18 +409,9 @@ class TangemSdk(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
callback: (result: CompletionResult<T>) -> Unit) {
val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage)
addSessionStoppedListener(cardSession)
Thread().run { cardSession.startWithRunnable(runnable, callback) }
}
private fun addSessionStoppedListener(session: CardSession) {
session.sessionStoppedListener = object : CardSessionStoppedListener {
override fun onSessionStopped(environment: SessionEnvironment) {
environment.saveCardValues()
}
}
}
/**
* Allows running a custom bunch of commands in one [CardSession] with lightweight closure syntax.
* Tangem SDK will start a card session and perform preflight [ReadCommand].
@ -434,7 +427,6 @@ class TangemSdk(
fun startSession(cardId: String? = null, initialMessage: Message? = null,
callback: (session: CardSession, error: TangemSdkError?) -> Unit) {
val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage)
addSessionStoppedListener(cardSession)
Thread().run { cardSession.start(callback = callback) }
}
@ -448,7 +440,7 @@ class TangemSdk(
private fun buildEnvironment(cardId: String?): SessionEnvironment {
return SessionEnvironment(
cardId, config, terminalKeysService, CardValuesDbService(sqlDriver)
cardId, config, terminalKeysService, CardValuesDbStorage(sqlDriver)
)
}

View file

@ -6,6 +6,7 @@ import com.tangem.common.PinCode
import com.tangem.common.apdu.*
import com.tangem.common.extensions.toInt
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.PinType
interface ApduSerializable<T : CommandResponse> {
@ -39,7 +40,7 @@ interface CommandResponse
abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRunnable<T> {
override val performPreflightRead: Boolean = true
open val requiresPin2: Boolean = false
override val requiresPin2: Boolean = false
override fun run(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
Log.i("Command", "Initializing ${this::class.java.simpleName}")

View file

@ -7,7 +7,7 @@ import com.tangem.CardValuesEntityQueries
import com.tangem.Database
import com.tangem.SessionEnvironment
interface CardValuesService {
interface CardValuesStorage {
fun saveValues(environment: SessionEnvironment)
@ -15,7 +15,7 @@ interface CardValuesService {
}
class CardValuesDbService(driver: SqlDriver) : CardValuesService {
class CardValuesDbStorage(driver: SqlDriver) : CardValuesStorage {
private val cardValuesQueries: CardValuesEntityQueries

View file

@ -1,8 +1,11 @@
package com.tangem.commands
package com.tangem.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.SessionEnvironment
import com.tangem.SessionViewDelegate
import com.tangem.commands.SetPinCommand
import com.tangem.commands.SetPinResponse
import com.tangem.common.CompletionResult
import com.tangem.common.PinCode
import com.tangem.common.extensions.calculateSha256
@ -17,15 +20,16 @@ enum class PinType {
;
}
class ChangePinCommand(
class ChangePinTask(
private val pinType: PinType,
private val pin: ByteArray? = null
) : CardSessionRunnable<SetPinResponse> {
override val performPreflightRead = true
override val requiresPin2 = false
override fun run(session: CardSession, callback: (result: CompletionResult<SetPinResponse>) -> Unit) {
session.scope.launch {
val pin = this@ChangePinCommand.pin
val pin = this@ChangePinTask.pin
?: requestNewPin(pinType, session.viewDelegate).calculateSha256()
runSetPin(pin, session, callback)
}
@ -61,7 +65,12 @@ class ChangePinCommand(
}
}
val command = SetPinCommand(pin1, pin2, pin3)
command.run(session, callback)
command.run(session) { result ->
when (result) {
is CompletionResult.Success -> savePin(pin, session.environment)
is CompletionResult.Failure -> callback(result)
}
}
}
private suspend fun requestPin(pinType: PinType, viewDelegate: SessionViewDelegate): String =
@ -78,5 +87,13 @@ class ChangePinCommand(
}
}
private fun savePin(pin: ByteArray, environment: SessionEnvironment) {
when (pinType) {
PinType.Pin1 -> environment.pin1 = PinCode(pin, false)
PinType.Pin2 -> environment.pin2 = PinCode(pin, false)
PinType.Pin3 -> {}
}
}
}

View file

@ -12,6 +12,7 @@ import com.tangem.common.CompletionResult
class CreateWalletTask : CardSessionRunnable<CreateWalletResponse> {
override val performPreflightRead = true
override val requiresPin2 = false
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
val curve = session.environment.card?.curve

View file

@ -16,6 +16,7 @@ import com.tangem.common.CompletionResult
internal class ScanTask : CardSessionRunnable<Card> {
override val performPreflightRead = false
override val requiresPin2 = false
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
@ -31,6 +32,7 @@ internal class ScanTask : CardSessionRunnable<Card> {
is CompletionResult.Success -> {
val card = readResult.data
session.environment.card = card
session.environment.restoreCardValues()
if (session.environment.pin1 != null && session.environment.pin2 != null) {
val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value)
checkPinCommand.run(session) { result ->
@ -39,7 +41,6 @@ internal class ScanTask : CardSessionRunnable<Card> {
session.environment.pin2 = null
}
}
session.environment.restoreCardValues()
runCheckWalletIfNeeded(card, session, callback)
}
} else {

View file

@ -2,9 +2,9 @@ package com.tangem.tangem_sdk_new
import androidx.fragment.app.FragmentActivity
import com.tangem.*
import com.tangem.commands.PinType
import com.tangem.tangem_sdk_new.nfc.NfcReader
import com.tangem.tangem_sdk_new.ui.NfcSessionDialog
import com.tangem.tasks.PinType
/**
* Default implementation of [SessionViewDelegate].