Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-10 20:35:23 +03:00
commit b4e560e548
79 changed files with 1922 additions and 1929 deletions

16
.idea/dictionaries/tangem.xml generated Normal file
View file

@ -0,0 +1,16 @@
<component name="ProjectDictionaryState">
<dictionary name="tangem">
<words>
<w>binance</w>
<w>blockchain</w>
<w>cardano</w>
<w>ducatus</w>
<w>ethereum</w>
<w>litecoin</w>
<w>matic</w>
<w>tangem</w>
<w>testnet</w>
<w>tezos</w>
</words>
</dictionary>
</component>

View file

@ -4,24 +4,23 @@ import android.os.Bundle
import android.text.Editable
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardManager
import com.tangem.SessionError
import com.tangem.TangemSdk
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.Signer
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain_demo.databinding.ActivityBlockchainDemoBinding
import com.tangem.commands.Card
import com.tangem.common.CompletionResult
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
import kotlinx.coroutines.*
import java.math.BigDecimal
import kotlin.coroutines.CoroutineContext
class BlockchainDemoActivity : AppCompatActivity() {
private lateinit var cardManager: CardManager
private lateinit var tangemSdk: TangemSdk
private lateinit var signer: TransactionSigner
private lateinit var card: Card
private lateinit var walletManager: WalletManager
@ -47,8 +46,8 @@ class BlockchainDemoActivity : AppCompatActivity() {
val view = binding.root
setContentView(view)
cardManager = CardManager.init(this)
signer = Signer(cardManager)
tangemSdk = TangemSdk.init(this)
signer = Signer(tangemSdk)
binding.btnScan.setOnClickListener { scan() }
@ -59,27 +58,20 @@ class BlockchainDemoActivity : AppCompatActivity() {
private fun scan() {
cardManager.scanCard { taskEvent ->
when (taskEvent) {
is TaskEvent.Event -> {
when (taskEvent.data) {
is ScanEvent.OnReadEvent -> {
card = (taskEvent.data as ScanEvent.OnReadEvent).card
walletManager = WalletManagerFactory.makeWalletManager(card)!!
tangemSdk.scanCard { result ->
when (result) {
is CompletionResult.Success -> {
walletManager = WalletManagerFactory.makeWalletManager(result.data)!!
getInfo()
}
}
}
is TaskEvent.Completion -> {
if (taskEvent.error != null) {
if (taskEvent.error !is TaskError.UserCancelled) {
handleError(taskEvent.error.toString())
is CompletionResult.Failure -> {
if (result.error !is SessionError.UserCancelled) {
handleError(result.error.toString())
}
}
}
}
}
}
private fun getInfo() {
scope.launch {

View file

@ -8,8 +8,8 @@ import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
import com.tangem.tasks.TaskEvent
import java.math.BigDecimal
class BitcoinWalletManager(
@ -66,11 +66,11 @@ class BitcoinWalletManager(
is Result.Failure -> return SimpleResult.Failure(buildTransactionResult.error)
is Result.Success -> {
when (val signerResponse = signer.sign(buildTransactionResult.data.toTypedArray(), cardId)) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey)
return networkManager.sendTransaction(transactionToSend.toHexString())
}
is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error)
is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error)
}
}
}

View file

@ -8,7 +8,7 @@ import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.common.extensions.encodeBase64NoWrap
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.tasks.TaskEvent
import com.tangem.common.CompletionResult
class CardanoWalletManager(
private val cardId: String,
@ -47,11 +47,11 @@ class CardanoWalletManager(
val transactionHash = transactionBuilder.buildToSign(transactionData)
when (val signerResponse = signer.sign(arrayOf(transactionHash), cardId)) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey)
return networkManager.sendTransaction(transactionToSend.encodeBase64NoWrap())
}
is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error)
is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error)
}
}

View file

@ -4,7 +4,7 @@ import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.commands.SignResponse
import com.tangem.tasks.TaskEvent
import com.tangem.common.CompletionResult
interface WalletManager {
var wallet: CurrencyWallet
@ -18,7 +18,7 @@ interface TransactionSender {
}
interface TransactionSigner {
suspend fun sign(hashes: Array<ByteArray>, cardId: String): TaskEvent<SignResponse>
suspend fun sign(hashes: Array<ByteArray>, cardId: String): CompletionResult<SignResponse>
}
interface FeeProvider {

View file

@ -1,9 +1,9 @@
package com.tangem.blockchain.common.extensions
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.commands.SignResponse
import com.tangem.tasks.TaskEvent
import com.tangem.common.CompletionResult
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import java.io.IOException
@ -54,10 +54,10 @@ sealed class SimpleResult {
data class Failure(val error: Throwable?) : SimpleResult()
}
class Signer(private val cardManager: CardManager) : TransactionSigner {
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): TaskEvent<SignResponse> =
class Signer(private val tangemSdk: TangemSdk) : TransactionSigner {
override suspend fun sign(hashes: Array<ByteArray>, cardId: String): CompletionResult<SignResponse> =
suspendCancellableCoroutine { continuation ->
cardManager.sign(hashes, cardId) { result ->
tangemSdk.sign(hashes, cardId) { result ->
if (continuation.isActive) continuation.resume(result)
}
}

View file

@ -8,8 +8,8 @@ import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.ethereum.network.EthereumNetworkManager
import com.tangem.blockchain.ethereum.network.EthereumResponse
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toHexString
import com.tangem.tasks.TaskEvent
import org.kethereum.DEFAULT_GAS_LIMIT
import org.kethereum.crypto.api.ec.ECDSASignature
import org.kethereum.crypto.determineRecId
@ -81,11 +81,11 @@ class EthereumWalletManager(
val transactionToSign = builder.buildToSign(transactionData, txCount.toBigInteger())
?: return SimpleResult.Failure(Exception("Not enough data"))
when (val signerResponse = signer.sign(transactionToSign.hashes.toTypedArray(), cardId)) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
val transactionToSend = builder.buildToSend(signerResponse.data.signature, transactionToSign, walletPublicKey)
return networkManager.sendTransaction(String.format("0x%s", transactionToSend.toHexString()))
}
is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error)
is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error)
}
}

View file

@ -5,7 +5,7 @@ import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.extensions.Result
import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.tasks.TaskEvent
import com.tangem.common.CompletionResult
import java.math.BigDecimal
import java.util.*
@ -58,11 +58,11 @@ class StellarWalletManager(
override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult {
val hashes = builder.buildToSign(transactionData, sequence, baseFee.toStroops())
when (val signerResponse = signer.sign(hashes.toTypedArray(), cardId)) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
val transactionToSend = builder.buildToSend(signerResponse.data.signature)
return networkManager.sendTransaction(transactionToSend)
}
is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error)
is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error)
}
}

View file

@ -7,7 +7,7 @@ import com.tangem.blockchain.common.extensions.SimpleResult
import com.tangem.blockchain.wallets.CurrencyWallet
import com.tangem.blockchain.xrp.network.XrpInfoResponse
import com.tangem.blockchain.xrp.network.XrpNetworkManager
import com.tangem.tasks.TaskEvent
import com.tangem.common.CompletionResult
class XrpWalletManager(
private val cardId: String,
@ -68,11 +68,11 @@ class XrpWalletManager(
val transactionHash = transactionBuilder.buildToSign(transactionData)
when (val signerResponse = signer.sign(arrayOf(transactionHash), cardId)) {
is TaskEvent.Event -> {
is CompletionResult.Success -> {
val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature)
return networkManager.sendTransaction(transactionToSend)
}
is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error)
is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error)
}
}

View file

@ -1,7 +1,7 @@
package com.tangem.blockchain.common
import com.google.common.truth.Truth
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.blockchain.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.cardano.CardanoWalletManager
import com.tangem.blockchain.ethereum.EthereumWalletManager
@ -18,7 +18,7 @@ internal class WalletManagerFactoryTest {
fun createBitcoinWalletManager() {
val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
@ -29,7 +29,7 @@ internal class WalletManagerFactoryTest {
fun createEthereumWalletManager() {
val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
@ -40,7 +40,7 @@ internal class WalletManagerFactoryTest {
fun createStellarWalletManager() {
val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
@ -51,7 +51,7 @@ internal class WalletManagerFactoryTest {
fun createCardanoWalletManager() {
val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)
@ -62,7 +62,7 @@ internal class WalletManagerFactoryTest {
fun createXrpWalletManager() {
val data = "0108cb21000000002154200b534d4152542043415348000201028006322e31317200034104bdad63848f97c535da53cf8fd300d24fa33f0516d194aa78ec164a06994d00204bae243a424e316c6ec845e02d9b15eafae8c19018a926b0b7435e6e941cdadb0a0400007e210c5a81020028820407e30502830754414e47454d00840358525086400ed8734b877869722c7d0b37ffb154b9fef21c54bf2c6496feb1fb5c1fc28a2ac28e201dde84f27495fa7f08b3ca2be2fb4954bf0fe78af027d6cdc16c3eee923041048196aa4b410ac44a3b9cce18e7be226aea070acc83a9cf67540fac49af25129f6a538a28ad6341358e3c4f9963064f7e365372a651d374e5c23cdd37fd099bf2050a736563703235366b31000804000f4240070100090205dc604104d2b9fb288540d54e5b32ecaf0381cd571f97f6f1ecd036b66bb11aa52ffe9981110d883080e2e255c6b1640586f7765e6faa325d1340f49b56b83d9de56bc7ed6204000f42406304000000000f01009000"
val responseApdu = ResponseApdu(data.hexToBytes())
val card = ReadCommand().deserialize(CardEnvironment(), responseApdu)
val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu)
val walletManager = WalletManagerFactory.makeWalletManager(card!!)
Truth.assertThat(walletManager)

View file

@ -1,330 +0,0 @@
package com.tangem
import com.tangem.commands.*
import com.tangem.commands.personalization.DepersonalizeCommand
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.commands.personalization.PersonalizeCommand
import com.tangem.commands.personalization.entities.Acquirer
import com.tangem.commands.personalization.entities.CardConfig
import com.tangem.commands.personalization.entities.Issuer
import com.tangem.commands.personalization.entities.Manufacturer
import com.tangem.common.CardEnvironment
import com.tangem.common.TerminalKeysService
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.*
/**
* The main interface of Tangem SDK that allows your app to communicate with Tangem cards.
*
* @property reader is an interface that is responsible for NFC connection and
* transfer of data to and from the Tangem Card.
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
* @property cardManagerDelegate An interface that allows interaction with users and shows relevant UI.
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
*/
class CardManager(
private val reader: CardReader,
private val cardManagerDelegate: CardManagerDelegate? = null,
private val config: Config = Config()
) {
private var terminalKeysService: TerminalKeysService? = null
private var isBusy = false
init {
CryptoUtils.initCrypto()
}
/**
* To start using any card, you first need to read it using the scanCard() method.
* This method launches an NFC session, and once its connected with the card,
* it obtains the card data. Optionally, if the card contains a wallet (private and public key pair),
* it proves that the wallet owns a private key that corresponds to a public one.
*
* It launches on the new thread a [ScanTask] that will send the following events in a callback:
* [ScanEvent.OnReadEvent] after completing [com.tangem.commands.ReadCommand]
* [ScanEvent.OnVerifyEvent] after completing [com.tangem.commands.CheckWalletCommand]
* [TaskEvent.Completion] with an error field null after successful completion of a task or
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
* @param callback is triggered on events during a performance of the task,
* provides data in form of [ScanEvent] subclasses.
*/
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
val task = ScanTask()
runTask(task, callback = callback)
}
/**
* This method allows you to sign one or multiple hashes.
* Simultaneous signing of array of hashes in a single [SignCommand] is required to support
* Bitcoin-type multi-input blockchains (UTXO).
* The [SignCommand] will return a corresponding array of signatures.
*
* This method launches on the new thread [SignCommand] that will send the following events in a callback:
* [SignResponse] after completing [SignCommand]
* [TaskEvent.Completion] with an error field null after successful completion of a task or
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
* Please note that Tangem cards usually protect the signing with a security delay
* that may last up to 90 seconds, depending on a card.
* It is for [CardManagerDelegate] to notify users of security delay.
* @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length.
* @param cardId CID, Unique Tangem card ID number
* @param callback is triggered on the completion of the [SignCommand],
* provides card response in the form of [SignResponse].
*/
fun sign(hashes: Array<ByteArray>, cardId: String,
callback: (result: TaskEvent<SignResponse>) -> Unit) {
val signCommand = SignCommand(hashes)
val task = SingleCommandTask(signCommand)
runTask(task, cardId, callback)
}
/**
* This command returns 512-byte Issuer Data field and its issuers signature.
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
* format and payload of Issuer Data. For example, this field may contain information about
* wallet balance signed by the issuer or additional issuers attestation data.
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [ReadIssuerDataCommand],
* provides card response in the form of [ReadIssuerDataResponse].
*/
fun readIssuerData(cardId: String,
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
val task = ReadIssuerDataTask(config.issuerPublicKey)
runTask(task, cardId, callback)
}
/**
* This task retrieves Issuer Extra Data field and its issuers signature.
* Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
* format and payload of Issuer Data. . For example, this field may contain photo or
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [ReadIssuerExtraDataTask],
* provides card response in the form of [ReadIssuerExtraDataResponse].
*/
fun readIssuerExtraData(cardId: String,
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
val task = ReadIssuerExtraDataTask(config.issuerPublicKey)
runTask(task, cardId, callback)
}
/**
* This command writes 512-byte Issuer Data field and its issuers signature.
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
* format and payload of Issuer Data. For example, this field may contain information about
* wallet balance signed by the issuer or additional issuers attestation data.
* @param cardId CID, Unique Tangem card ID number.
* @param issuerData Data provided by issuer.
* @param issuerDataSignature Issuers signature of [issuerData] with Issuer Data Private Key.
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
* @param callback is triggered on the completion of the [WriteIssuerDataCommand],
* provides card response in the form of [WriteIssuerDataResponse].
*/
fun writeIssuerData(cardId: String,
issuerData: ByteArray,
issuerDataSignature: ByteArray,
issuerDataCounter: Int? = null,
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
val task = WriteIssuerDataTask(
issuerData,
issuerDataSignature,
issuerDataCounter,
config.issuerPublicKey
)
runTask(task, cardId, callback)
}
/**
* This task writes Issuer Extra Data field and its issuers signature.
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
* The issuer defines purpose of use, format and payload of Issuer Data.
* For example, this field may contain a photo or biometric information for ID card products.
* Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed
* to write entire Issuer_Extra_Data.
* @param cardId CID, Unique Tangem card ID number.
* @param issuerData Data provided by issuer.
* @param startingSignature Issuers signature with Issuer Data Private Key of [cardId],
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
* @param finalizingSignature Issuers signature with Issuer Data Private Key of [cardId],
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
* @param callback is triggered on the completion of the [WriteIssuerDataCommand],
* provides card response in the form of [WriteIssuerDataResponse].
*/
fun writeIssuerExtraData(cardId: String,
issuerData: ByteArray,
startingSignature: ByteArray,
finalizingSignature: ByteArray,
issuerDataCounter: Int? = null,
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
val task = WriteIssuerExtraDataTask(
issuerData,
startingSignature, finalizingSignature,
config.issuerPublicKey,
issuerDataCounter
)
runTask(task, cardId, callback)
}
/**
* This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields.
* User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS.
* The App defines purpose of use, format and it's payload. For example, this field may contain cashed information
* from blockchain to accelerate preparing new transaction.
* User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing
* of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use.
* For example, this fields may contain blockchain nonce value.
*
* Writing of User_Counter and User_Data protected only by PIN1.
* User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation.
*/
fun writeUserData(
cardId: String,
userData: ByteArray? = null,
userProtectedData: ByteArray? = null,
userCounter: Int? = null,
userProtectedCounter: Int? = null,
callback: (result: TaskEvent<WriteUserDataResponse>) -> Unit
) {
val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter)
val task = SingleCommandTask(writeUserDataCommand)
runTask(task, cardId, callback)
}
/**
* This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and
* User_Protected_Counter fields.
* User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS.
* The App defines purpose of use, format and it's payload. For example, this field may contain cashed information
* from blockchain to accelerate preparing new transaction.
* User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing
* of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use.
* For example, this fields may contain blockchain nonce value.
*/
fun readUserData(cardId: String, callback: (result: TaskEvent<ReadUserDataResponse>) -> Unit) {
val task = SingleCommandTask(ReadUserDataCommand())
runTask(task, cardId, callback)
}
/**
* This command will create a new wallet on the card having Empty state.
* A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card.
* App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand]
* and then transform it into an address of corresponding blockchain wallet
* according to a specific blockchain algorithm.
* WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand].
* RemainingSignature is set to MaxSignatures.
* @param cardId CID, Unique Tangem card ID number.
*/
fun createWallet(cardId: String,
callback: (result: TaskEvent<CreateWalletResponse>) -> Unit) {
val createWalletCommand = CreateWalletCommand()
val task = SingleCommandTask(createWalletCommand)
runTask(task, cardId, callback)
}
/**
* 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.
* @param cardId CID, Unique Tangem card ID number.
*/
fun purgeWallet(cardId: String,
callback: (result: TaskEvent<PurgeWalletResponse>) -> Unit) {
val purgeWalletCommand = PurgeWalletCommand()
val task = SingleCommandTask(purgeWalletCommand)
runTask(task, cardId, callback)
}
/**
* Command available on SDK cards only
*
* This command resets card to initial state,
* erasing all data written during personalization and usage.
* @param cardId CID, Unique Tangem card ID number.
*/
fun depersonalize(cardId: String,
callback: (result: TaskEvent<DepersonalizeResponse>) -> Unit) {
val depersonalizeCommand = DepersonalizeCommand()
val task = SingleCommandTask(depersonalizeCommand)
runTask(task, cardId, callback)
}
/**
* Command available on SDK cards only
*
* Personalization is an initialization procedure, required before starting using a card.
* During this procedure a card setting is set up.
* During this procedure all data exchange is encrypted.
* @param config is a configuration file with all the card settings that are written on the card
* during personalization.
* @param issuer Issuer is a third-party team or company wishing to use Tangem cards.
* @param manufacturer Tangem Card Manufacturer.
* @param acquirer Acquirer is a trusted third-party company that operates proprietary
* (non-EMV) POS terminal infrastructure and transaction processing back-end.
*/
fun personalize(config: CardConfig,
issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null,
callback: (result: TaskEvent<Card>) -> Unit) {
val personalizationCommand = PersonalizeCommand(config, issuer, manufacturer, acquirer)
val task = SingleCommandTask(personalizationCommand)
task.performPreflightRead = false
runTask(task, callback = callback)
}
/**
*/
fun <T> runTask(task: Task<T>, cardId: String? = null, callback: (result: TaskEvent<T>) -> Unit) {
if (isBusy) {
callback(TaskEvent.Completion(TaskError.Busy()))
return
}
val environment = prepareCardEnvironment(cardId)
isBusy = true
task.reader = reader
task.delegate = cardManagerDelegate
Thread().run {
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)
}
/**
* 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
}

View file

@ -0,0 +1,221 @@
package com.tangem
import com.tangem.commands.Card
import com.tangem.commands.CommandResponse
import com.tangem.commands.OpenSessionCommand
import com.tangem.commands.ReadCommand
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.extensions.calculateSha256
import com.tangem.crypto.EncryptionHelper
import com.tangem.crypto.FastEncryptionHelper
import com.tangem.crypto.StrongEncryptionHelper
import com.tangem.crypto.pbkdf2Hash
/**
* Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession]
*/
interface CardSessionRunnable<T : CommandResponse> {
/**
* The starting point for custom business logic.
* Implement this interface and use [TangemSdk.startSession] to run.
* @param session run commands in this [CardSession].
* @param callback trigger the callback to complete the task.
*/
fun run(session: CardSession, callback: (result: CompletionResult<T>) -> Unit)
}
/**
* Allows interaction with Tangem cards. Should be opened before sending commands.
*
* @property environment
* @property reader is an interface that is responsible for NFC connection and
* transfer of data to and from the Tangem Card.
* @property viewDelegate is an interface that allows interaction with users and shows relevant UI.
* @property cardId ID, Unique Tangem card ID number. If not null, the SDK will check that you the card
* with which you tapped a phone has this [cardId] and SDK will return
* the [SessionError.WrongCard] otherwise.
* @property initialMessage A custom description that will be shown at the beginning of the NFC session.
* If null, a default header and text body will be used.
*/
class CardSession(
val environment: SessionEnvironment,
private val reader: CardReader,
val viewDelegate: SessionViewDelegate,
private var cardId: String? = null,
private val initialMessage: Message? = null
) {
/**
* True if some operation is still in progress.
*/
private var isBusy = false
/**
* This metod starts a card session, performs preflight [ReadCommand],
* invokes [CardSessionRunnable.run] and closes the session.
* @param runnable [CardSessionRunnable] that will be performed in the session.
* @param callback will be triggered with a [CompletionResult] of a session.
*/
fun <T : CardSessionRunnable<R>, R : CommandResponse> startWithRunnable(
runnable: T, callback: (result: CompletionResult<R>) -> Unit) {
start { session, error ->
if (error != null) {
callback(CompletionResult.Failure(error))
return@start
}
if (runnable is ReadCommand) {
callback(CompletionResult.Success(environment.card as R))
return@start
}
runnable.run(this) { result ->
stop()
callback(result)
}
}
}
/**
* Starts a card session and performs preflight [ReadCommand].
* @param callback: callback with the card session. Can contain [SessionError] if something goes wrong.
*/
fun start(callback: (session: CardSession, error: SessionError?) -> Unit) {
try {
startSession()
} catch (error: SessionError) {
callback(this, error)
}
preflightRead() { result ->
when (result) {
is CompletionResult.Failure -> {
callback(this, result.error)
stopWithError(result.error)
}
is CompletionResult.Success -> {
callback(this, null)
}
}
}
}
private fun startSession() {
if (isBusy) throw SessionError.Busy()
isBusy = true
viewDelegate.onNfcSessionStarted(cardId, initialMessage)
reader.openSession()
}
private fun preflightRead(callback: (result: CompletionResult<Card>) -> Unit) {
val readCommand = ReadCommand()
readCommand.run(this) { result ->
when (result) {
is CompletionResult.Failure -> {
tryHandleError(result.error) { handleErrorResult ->
when (handleErrorResult) {
is CompletionResult.Success -> preflightRead(callback)
is CompletionResult.Failure -> {
stopWithError(result.error)
callback(CompletionResult.Failure(result.error))
}
}
}
}
is CompletionResult.Success -> {
val receivedCardId = result.data.cardId
if (cardId != null && receivedCardId != cardId) {
stopWithError(SessionError.WrongCard())
callback(CompletionResult.Failure(SessionError.WrongCard()))
return@run
}
environment.card = result.data
cardId = receivedCardId
callback(CompletionResult.Success(result.data))
}
}
}
}
/**
* Stops the current session with the text message.
* @param message If null, the default message will be shown.
*/
private fun stop(message: Message? = null) {
reader.closeSession()
viewDelegate.onNfcSessionCompleted(message)
isBusy = false
}
/**
* Stops the current session on error.
* @param error An error that will be shown.
*/
private fun stopWithError(error: Exception) {
reader.closeSession()
isBusy = false
val errorMessage = if (error is SessionError) {
"${error::class.simpleName}: ${error.code}"
} else {
error.localizedMessage
}
if (error !is SessionError.UserCancelled) viewDelegate.onError(errorMessage)
}
fun send(apdu: CommandApdu, callback: (result: CompletionResult<ResponseApdu>) -> Unit) {
reader.transceiveApdu(apdu, callback)
}
private fun tryHandleError(
error: SessionError, callback: (result: CompletionResult<Boolean>) -> Unit) {
when (error) {
is SessionError.NeedEncryption -> {
when (environment.encryptionMode) {
EncryptionMode.NONE -> {
environment.encryptionKey = null
environment.encryptionMode = EncryptionMode.FAST
}
EncryptionMode.FAST -> {
environment.encryptionKey = null
environment.encryptionMode = EncryptionMode.STRONG
}
EncryptionMode.STRONG -> {
Log.e(this::class.simpleName!!, "Encryption doesn't work")
callback(CompletionResult.Failure(SessionError.NeedEncryption()))
}
}
return establishEncryption(callback)
}
else -> callback(CompletionResult.Failure(SessionError.UnknownError()))
}
}
private fun establishEncryption(callback: (result: CompletionResult<Boolean>) -> Unit) {
val encryptionHelper: EncryptionHelper =
if (environment.encryptionMode == EncryptionMode.STRONG) {
StrongEncryptionHelper()
} else {
FastEncryptionHelper()
}
val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA)
openSesssionCommand.run(this) { result ->
when (result) {
is CompletionResult.Success -> {
val uid = result.data.uid
val protocolKey = environment.pin1.calculateSha256().pbkdf2Hash(uid, 50)
val secret = encryptionHelper.generateSecret(result.data.sessionKeyB)
val sessionKey = (secret + protocolKey).calculateSha256()
environment.encryptionKey = sessionKey
callback(CompletionResult.Success(true))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}

View file

@ -1,6 +1,33 @@
package com.tangem
class Config(
/**
* Enables or disables Linked Terminal feature.
App can optionally generate ECDSA key pair Terminal_PrivateKey / Terminal_PublicKey.
And then submit Terminal_PublicKey to the card in any SIGN command.
Once SIGN is successfully executed by COS (Card Operation System),
including PIN2 verification and/or completion of security delay, the submitted
Terminal_PublicKey key is stored by COS. After that, the App instance is deemed trusted
by COS and COS will allow skipping security delay for subsequent SIGN operations
thus improving convenience without sacrificing security.
In order to skip security delay, App should use Terminal_PrivateKey to compute the signature
of the data being submitted to SIGN command for signing and transmit this signature in
Terminal_Transaction_Signature parameter in the same SIGN command. COS will verify
the correctness of Terminal_Transaction_Signature using previously stored Terminal_PublicKey
and, if correct, will skip security delay for the current SIGN operation.
*/
val linkedTerminal: Boolean = true,
val issuerPublicKey: ByteArray? = null
/**
* If not null, it will be used to validate Issuer data and issuer extra data.
* If null, issuerPublicKey from current card will be used.
*/
val issuerPublicKey: ByteArray? = null,
/**
* Level of encryption used in communication with a Tangem Card.
*/
val encryptionMode: EncryptionMode = EncryptionMode.NONE
)

View file

@ -1,17 +1,22 @@
package com.tangem.common
package com.tangem
import com.tangem.commands.Card
import com.tangem.commands.EllipticCurve
import com.tangem.common.extensions.calculateSha256
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 [CardEnvironment].
* 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.
*/
data class CardEnvironment(
data class SessionEnvironment(
val pin1: String = DEFAULT_PIN,
val pin2: String = DEFAULT_PIN2,
val cardId: String? = null,
var card: Card? = null,
val terminalKeys: KeyPair? = null,
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
var encryptionKey: ByteArray? = null,
@ -24,6 +29,9 @@ data class CardEnvironment(
}
}
/**
* All possible encryption modes.
*/
enum class EncryptionMode(val code: Byte) {
NONE(0x0),
FAST(0x1),

View file

@ -0,0 +1,139 @@
package com.tangem
import com.tangem.commands.Card
import com.tangem.commands.ReadCommand
import com.tangem.common.apdu.StatusWord
import com.tangem.tasks.ScanTask
/**
* An error class that represent typical errors that may occur when performing Tangem SDK tasks.
* Errors are propagated back to the caller in callbacks.
*/
sealed class SessionError(val code: Int) : Exception() {
//Errors in serializing APDU
/**
* This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv]
* (this error is a wrapper around internal [com.tangem.common.tlv.TlvDecoder] errors).
*/
class SerializeCommandError : SessionError(1000)
class DeserializeApduFailed : SessionError(1001)
class EncodingFailedTypeMismatch : SessionError(1002)
class EncodingFailed : SessionError(1003)
class DecodingFailedMissingTag : SessionError(1004)
class DecodingFailedTypeMismatch : SessionError(1005)
class DecodingFailed : SessionError(1005)
/**
* This error is returned when unknown [StatusWord] is received from a card.
*/
class UnknownStatus : SessionError(2001)
/**
* This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand].
* The card sends this status in case of internal card error.
*/
class ErrorProcessingCommand : SessionError(2002)
/**
* This error is returned when a task (such as [ScanTask]) requires that [ReadCommand]
* is executed before performing other commands.
*/
class MissingPreflightRead : SessionError(2003)
/**
* This error is returned when a card's reply is [StatusWord.InvalidState].
* The card sends this status when command can not be executed in the current state of a card.
*/
class InvalidState : SessionError(2004)
/**
* This error is returned when a card's reply is [StatusWord.InsNotSupported].
* The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction].
*/
class InsNotSupported : SessionError(2005)
/**
* This error is returned when a card's reply is [StatusWord.InvalidParams].
* The card sends this status when there are wrong or not sufficient parameters in TLV request,
* or wrong PIN1/PIN2.
* The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer],
* mapping or serialization errors.
*/
class InvalidParams : SessionError(2006)
/**
* This error is returned when a card's reply is [StatusWord.NeedEncryption]
* and the encryption was not established by TangemSdk.
*/
class NeedEncryption : SessionError(2007)
//Scan errors
/**
* This error is returned when a [Task] checks unsuccessfully either
* a card's ability to sign with its private key, or the validity of issuer data.
*/
class VerificationFailed : SessionError(3000)
/**
* This error is returned when a [ScanTask] returns a [Card] without some of the essential fields.
*/
class CardError : SessionError(3001)
/**
* This error is returned when a [Task] expects a user to use a particular card,
* and a user tries to use a different card.
*/
class WrongCard : SessionError(3002)
/**
* Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand].
* This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign.
*/
class TooMuchHashesInOneTransaction : SessionError(3003)
/**
* This error is returned when a [com.tangem.commands.SignCommand]
* receives only empty hashes for signature.
*/
class EmptyHashes : SessionError(3004)
/**
* This error is returned when a [com.tangem.commands.SignCommand]
* receives hashes of different lengths for signature.
*/
class HashSizeMustBeEqual : SessionError(3005)
/**
* This error is returned when [com.tangem.TangemSdk] was called with a new [Task],
* while a previous [Task] is still in progress.
*/
class Busy : SessionError(4000)
/**
* This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog.
*/
class UserCancelled : SessionError(4001)
//NFC errors
class NfcReaderError : SessionError(5002)
/**
* This error is returned when Android NFC reader loses a tag
* (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress.
*/
class TagLost : SessionError(5003)
class UnknownError : SessionError(6000)
//Specific Command Errors
/**
* This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter
* (when the card's requires it), but the counter is missing.
*/
class MissingCounter : SessionError(7001)
class MissingIssuerPubicKey : SessionError(7002)
}

View file

@ -1,19 +1,18 @@
package com.tangem
import com.tangem.common.CompletionResult
import com.tangem.tasks.TaskError
/**
* Allows interaction with users and shows visual elements.
*
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
*/
interface CardManagerDelegate {
interface SessionViewDelegate {
/**
* It is called when user is expected to scan a Tangem Card with an Android device.
*/
fun onNfcSessionStarted(cardId: String?)
fun onNfcSessionStarted(cardId: String?, message: Message? = null)
/**
* It is called when security delay is triggered by the card.
@ -36,16 +35,21 @@ interface CardManagerDelegate {
/**
* It is called when NFC session was completed and a user can take the card away from the Android device.
*/
fun onNfcSessionCompleted()
fun onNfcSessionCompleted(message: Message? = null)
/**
* It is called when some error occur during NFC session.
*/
fun onError(error: TaskError)
fun onError(errorMessage: String)
/**
* It is called when a user is expected to enter pin code.
*/
fun onPinRequested(callback: (result: CompletionResult<String>) -> Unit)
}
}
/**
* Wrapper for a message that can be shown to user after a start of NFC session.
*/
data class Message(val header: String? = null, val body: String? = null)

View file

@ -0,0 +1,375 @@
package com.tangem
import com.tangem.commands.*
import com.tangem.commands.personalization.DepersonalizeCommand
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.commands.personalization.PersonalizeCommand
import com.tangem.commands.personalization.entities.Acquirer
import com.tangem.commands.personalization.entities.CardConfig
import com.tangem.commands.personalization.entities.Issuer
import com.tangem.commands.personalization.entities.Manufacturer
import com.tangem.common.CompletionResult
import com.tangem.common.TerminalKeysService
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.CreateWalletTask
import com.tangem.tasks.ScanTask
/**
* The main interface of Tangem SDK that allows your app to communicate with Tangem cards.
*
* @property reader is an interface that is responsible for NFC connection and
* transfer of data to and from the Tangem Card.
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
* @property viewDelegate An interface that allows interaction with users and shows relevant UI.
* Its default implementation, DefaultCardSessionViewDelegate, is in our tangem-sdk module.
* @property config allows to change a number of parameters for communication with Tangem cards.
* Do not change the default values unless you know what you are doing.
*/
class TangemSdk(
private val reader: CardReader,
private val viewDelegate: SessionViewDelegate,
var config: Config = Config()
) {
private var terminalKeysService: TerminalKeysService? = null
init {
CryptoUtils.initCrypto()
}
/**
* This method launches a [ScanTask] on a new thread.
*
* To start using any card, you first need to read it using the scanCard() method.
* This method launches an NFC session, and once its connected with the card,
* it obtains the card data. Optionally, if the card contains a wallet (private and public key pair),
* it proves that the wallet owns a private key that corresponds to a public one.
*
* @param callback is triggered on the completion of the [ScanTask] and provides card response
* in the form of [Card] if the task was performed successfully or [SessionError] in case of an error.
*/
fun scanCard(initialMessage: Message? = null, callback: (result: CompletionResult<Card>) -> Unit) {
startSession(ScanTask(), null, initialMessage, callback)
}
/**
* This method launches a [SignCommand] on a new thread.
*
* It allows you to sign one or multiple hashes.
* Simultaneous signing of array of hashes in a single [SignCommand] is required to support
* Bitcoin-type multi-input blockchains (UTXO).
* The [SignCommand] will return a corresponding array of signatures.
*
* Please note that Tangem cards usually protect the signing with a security delay
* that may last up to 90 seconds, depending on a card.
* It is for [SessionViewDelegate] to notify users of security delay.
*
* @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length.
* @param cardId CID, Unique Tangem card ID number
* @param callback is triggered on the completion of the [SignCommand] and provides card response
* in the form of [SignResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun sign(hashes: Array<ByteArray>, cardId: String, initialMessage: Message? = null,
callback: (result: CompletionResult<SignResponse>) -> Unit) {
startSession(SignCommand(hashes), cardId, initialMessage, callback)
}
/**
* This method launches a [ReadIssuerDataCommand] on a new thread.
* This command returns 512-byte Issuer Data field and its issuers signature.
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
* format and payload of Issuer Data. For example, this field may contain information about
* wallet balance signed by the issuer or additional issuers attestation data.
*
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [ReadIssuerDataCommand] and provides
* card response in the form of [ReadIssuerDataResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun readIssuerData(cardId: String, initialMessage: Message? = null,
callback: (result: CompletionResult<ReadIssuerDataResponse>) -> Unit) {
startSession(ReadIssuerDataCommand(config.issuerPublicKey), cardId, initialMessage, callback)
}
/**
* This method launches a [ReadIssuerExtraDataCommand] on a new thread.
*
* This command retrieves Issuer Extra Data field and its issuers signature.
* Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
* format and payload of Issuer Data. . For example, this field may contain photo or
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
*
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [ReadIssuerExtraDataCommand] and provides
* card response in the form of [ReadIssuerExtraDataResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun readIssuerExtraData(cardId: String,
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
startSession(ReadIssuerExtraDataCommand(config.issuerPublicKey), cardId, null, callback)
}
/**
* This method launches a [WriteIssuerDataCommand] on a new thread.
*
* This command writes 512-byte Issuer Data field and its issuers signature.
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
* format and payload of Issuer Data. For example, this field may contain information about
* wallet balance signed by the issuer or additional issuers attestation data.
*
* @param cardId CID, Unique Tangem card ID number.
* @param issuerData Data provided by issuer.
* @param issuerDataSignature Issuers signature of [issuerData] with Issuer Data Private Key.
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
* @param callback is triggered on the completion of the [WriteIssuerDataCommand] and provides
* card response in the form of [WriteIssuerDataResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun writeIssuerData(cardId: String,
issuerData: ByteArray,
issuerDataSignature: ByteArray,
issuerDataCounter: Int? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
val command = WriteIssuerDataCommand(
issuerData,
issuerDataSignature,
issuerDataCounter,
config.issuerPublicKey
)
startSession(command, cardId, initialMessage, callback)
}
/**
* This method launches a [WriteIssuerExtraDataCommand] on a new thread.
*
* This command writes Issuer Extra Data field and its issuers signature.
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
* The issuer defines purpose of use, format and payload of Issuer Data.
* For example, this field may contain a photo or biometric information for ID card products.
* Because of the large size of IssuerExtraData, a series of these commands have to be executed
* to write entire IssuerExtraData.
*
* @param cardId CID, Unique Tangem card ID number.
* @param issuerData Data provided by issuer.
* @param startingSignature Issuers signature with Issuer Data Private Key of [cardId],
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
* @param finalizingSignature Issuers signature with Issuer Data Private Key of [cardId],
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
* @param callback is triggered on the completion of the [WriteIssuerExtraDataCommand] and provides
* card response in the form of [WriteIssuerDataResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun writeIssuerExtraData(cardId: String,
issuerData: ByteArray,
startingSignature: ByteArray,
finalizingSignature: ByteArray,
issuerDataCounter: Int? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
val command = WriteIssuerExtraDataCommand(
issuerData,
startingSignature, finalizingSignature,
issuerDataCounter,
config.issuerPublicKey
)
startSession(command, cardId, initialMessage, callback)
}
/**
* This method launches a [WriteUserDataCommand] on a new thread.
*
* This command writes some of UserData, UserProtectedData, UserCounter and UserProtectedCounter fields.
* User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS.
* The App defines purpose of use, format and it's payload. For example, this field may contain cashed information
* from blockchain to accelerate preparing new transaction.
* User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing
* of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use.
* For example, this fields may contain blockchain nonce value.
*
* Writing of UserCounter and UserData is protected only by PIN1.
* UserProtectedCounter and UserProtectedData need additionally PIN2 to confirmation.
*/
fun writeUserData(
cardId: String,
userData: ByteArray? = null,
userProtectedData: ByteArray? = null,
userCounter: Int? = null,
userProtectedCounter: Int? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<WriteUserDataResponse>) -> Unit
) {
val command = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter)
startSession(command, cardId, initialMessage, callback)
}
/**
* This method launches a [ReadUserDataCommand] on a new thread.
*
* This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and
* User_Protected_Counter fields.
* User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS.
* The App defines purpose of use, format and it's payload. For example, this field may contain cashed information
* from blockchain to accelerate preparing new transaction.
* User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing
* of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use.
* For example, this fields may contain blockchain nonce value.
*
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [ReadUserDataCommand] and provides
* card response in the form of [ReadUserDataResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun readUserData(cardId: String, initialMessage: Message? = null,
callback: (result: CompletionResult<ReadUserDataResponse>) -> Unit) {
startSession(ReadUserDataCommand(), cardId, initialMessage, callback)
}
/**
* This method launches a [CreateWalletTask] on a new thread.
*
* This this will create a new wallet on the card having Empty state with [CreateWalletCommand]
* and will check the success of the operation by performing [CheckWalletCommand].
* A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card.
* App will need to obtain Wallet_PublicKey from the [CreateWalletResponse] or from the
* response of [ReadCommand] and then transform it into an address of corresponding
* blockchain wallet according to a specific blockchain algorithm.
* WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand].
* RemainingSignature is set to MaxSignatures.
*
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [CreateWalletTask] and provides
* card response in the form of [CreateWalletResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun createWallet(cardId: String, initialMessage: Message? = null,
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
startSession(CreateWalletTask(), cardId, initialMessage, callback)
}
/**
* This method launches a [PurgeWalletCommand] on a new thread.
*
* This command deletes all wallet data. If IsReusable flag is enabled during personalization,
* or [CreateWalletCommand].
* If IsReusable flag is disabled, the card switches to Purged state.
* Purged state is final, it makes the card useless.
*
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [PurgeWalletCommand] and provides
* card response in the form of [PurgeWalletResponse] if the task was performed successfully
* or [SessionError] in case of an error.
*/
fun purgeWallet(cardId: String, initialMessage: Message? = null,
callback: (result: CompletionResult<PurgeWalletResponse>) -> Unit) {
startSession(PurgeWalletCommand(), cardId, initialMessage, callback)
}
/**
* Command available on SDK cards only
*
* This method launches a [DepersonalizeCommand] on a new thread.
*
* This command resets card to initial state,
* erasing all data written during personalization and usage.
*
* @param cardId CID, Unique Tangem card ID number.
* @param callback is triggered on the completion of the [DepersonalizeCommand] and provides
* card response in the form of [DepersonalizeResponse] if the task was performed successfully
* or [SessionError] in case of an error.
* */
fun depersonalize(cardId: String, initialMessage: Message? = null,
callback: (result: CompletionResult<DepersonalizeResponse>) -> Unit) {
startSession(DepersonalizeCommand(), cardId, initialMessage, callback)
}
/**
* Command available on SDK cards only
*
* This method launches a [PersonalizeCommand] on a new thread.
*
* Personalization is an initialization procedure, required before starting using a card.
* During this procedure a card setting is set up.
* During this procedure all data exchange is encrypted.
* @param config is a configuration file with all the card settings that are written on the card
* during personalization.
* @param issuer Issuer is a third-party team or company wishing to use Tangem cards.
* @param manufacturer Tangem Card Manufacturer.
* @param acquirer Acquirer is a trusted third-party company that operates proprietary
* (non-EMV) POS terminal infrastructure and transaction processing back-end.
* @param callback is triggered on the completion of the [PersonalizeCommand] and provides
* card response in the form of [Card] if the command was performed successfully
* or [SessionError] in case of an error.
*/
fun personalize(config: CardConfig,
issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null,
initialMessage: Message? = null,
callback: (result: CompletionResult<Card>) -> Unit) {
val command = PersonalizeCommand(config, issuer, manufacturer, acquirer)
startSession(command, null, initialMessage, callback)
}
/**
* Allows running a custom bunch of commands in one [CardSession] by creating a custom task.
* [TangemSdk] will start a card session, perform preflight [ReadCommand],
* invoke [CardSessionRunnable.run] and close the session.
* You can find the current card in the [CardSession.environment].
* @runnable: A custom task, adopting [CardSessionRunnable] protocol
* @cardId: CID, Unique Tangem card ID number. If not null, the SDK will check that you the card
* with which you tapped a phone has this [cardId] and SDK will return
* the [SessionError.WrongCard] otherwise.
* @initialMessage: A custom description that shows at the beginning of the NFC session.
* If null, default message will be used.
* @callback: Standard [TangemSdk] callback.
*/
fun <T : CommandResponse> startSession(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
callback: (result: CompletionResult<T>) -> Unit) {
val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage)
Thread().run { cardSession.startWithRunnable(runnable, callback) }
}
/**
* Allows running a custom bunch of commands in one [CardSession] with lightweight closure syntax.
* Tangem SDK will start a card sesion and perform preflight [ReadCommand].
* @cardId: CID, Unique Tangem card ID number. If not null, the SDK will check that you the card
* with which you tapped a phone has this [cardId] and SDK will return
* the [SessionError.WrongCard] otherwise.
* @initialMessage: A custom description that shows at the beginning of the NFC session.
* If null, default message will be used.
* @callback: At first, you should check that the [SessionError] is not null,
* then you can use the [CardSession] to interact with a card.
*/
fun <T : CommandResponse> startSession(
cardId: String? = null, initialMessage: Message? = null,
callback: (session: CardSession, error: SessionError?) -> Unit) {
val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage)
Thread().run { cardSession.start(callback) }
}
/**
* 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 buildEnvironment(): SessionEnvironment {
val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null
return SessionEnvironment(
terminalKeys = terminalKeys
)
}
companion object
}

View file

@ -1,14 +1,16 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.CardSession
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.TaskError
/**
* Deserialized response from the Tangem card after [CheckWalletCommand].
@ -40,33 +42,54 @@ class CheckWalletResponse(
* @property cardId Unique Tangem card ID number
* @property challenge Random challenge generated by application
*/
class CheckWalletCommand : CommandSerializer<CheckWalletResponse>() {
class CheckWalletCommand(
private val curve: EllipticCurve, private val publicKey: ByteArray
) : Command<CheckWalletResponse>() {
val challenge = CryptoUtils.generateRandomBytes(16)
private val challenge = CryptoUtils.generateRandomBytes(16)
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun run(session: CardSession, callback: (result: CompletionResult<CheckWalletResponse>) -> Unit) {
transceive(session) { result ->
when (result) {
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
is CompletionResult.Success -> {
val verified = result.data.verify(
curve,
publicKey,
challenge
)
if (verified) {
callback(CompletionResult.Success(result.data))
} else {
callback(CompletionResult.Failure(SessionError.VerificationFailed()))
}
}
}
}
}
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Challenge, challenge)
return CommandApdu(
Instruction.CheckWallet, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CheckWalletResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
CheckWalletResponse(
cardId = mapper.map(TlvTag.CardId),
salt = mapper.map(TlvTag.Salt),
walletSignature = mapper.map(TlvTag.Signature)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return CheckWalletResponse(
cardId = decoder.decode(TlvTag.CardId),
salt = decoder.decode(TlvTag.Salt),
walletSignature = decoder.decode(TlvTag.Signature)
)
}
}

View file

@ -0,0 +1,116 @@
package com.tangem.commands
import com.tangem.*
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.apdu.StatusWord
import com.tangem.common.apdu.toSessionError
import com.tangem.common.extensions.toInt
import com.tangem.common.tlv.TlvTag
/**
* Basic interface for a parsed response from [Command].
*/
interface CommandResponse
/**
* Basic class for Tangem card commands
*/
abstract class Command<T : CommandResponse> : CardSessionRunnable<T> {
/**
* Serializes data into an array of [com.tangem.common.tlv.Tlv],
* then creates [CommandApdu] with this data.
* @param environment [SessionEnvironment] of the current card
* @return command data converted to [CommandApdu] that allows to convert it to [ByteArray]
* that can be sent to a Tangem card
*/
abstract fun serialize(environment: SessionEnvironment): CommandApdu
/**
* Deserializes data received from a card and stored in [ResponseApdu]
* into an array of [com.tangem.common.tlv.Tlv]. Then maps it into a [CommandResponse].
* @param environment [SessionEnvironment] of the current card.
* @param apdu received data.
* @return Card response converted to a [CommandResponse] of a type [T]
*/
abstract fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): T
override fun run(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
transceive(session, callback)
}
fun transceive(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
try {
val apdu = serialize(session.environment)
transceiveApdu(apdu, session) { result ->
when (result) {
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
is CompletionResult.Success -> {
val response = deserialize(session.environment, result.data)
callback(CompletionResult.Success(response))
}
}
}
} catch (error: SessionError) {
callback(CompletionResult.Failure(error))
}
}
private fun transceiveApdu(apdu: CommandApdu, session: CardSession, callback: (result: CompletionResult<ResponseApdu>) -> Unit) {
session.send(apdu) { result ->
when (result) {
is CompletionResult.Success -> {
val responseApdu = result.data
when (responseApdu.statusWord) {
StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged
-> callback(CompletionResult.Success(responseApdu))
StatusWord.NeedPause -> {
// NeedPause is returned from the card whenever security delay is triggered.
val remainingTime = deserializeSecurityDelay(responseApdu, session.environment)
if (remainingTime != null) {
session.viewDelegate.onSecurityDelay(
remainingTime,
session.environment.card?.pauseBeforePin2 ?: 0)
}
Log.i(this::class.simpleName!!, "Nfc command ${this::class.simpleName!!} " +
"triggered security delay of $remainingTime milliseconds")
transceiveApdu(apdu, session, callback)
}
else -> {
val error = responseApdu.statusWord.toSessionError()
if (error != null && !tryHandleError(error)) {
callback(CompletionResult.Failure(error))
} else {
callback(CompletionResult.Failure(SessionError.UnknownError()))
}
}
}
}
is CompletionResult.Failure ->
if (result.error == SessionError.TagLost()) {
session.viewDelegate.onTagLost()
} else {
callback(CompletionResult.Failure(result.error))
}
}
}
}
/**
* Helper method to parse security delay information received from a card.
*
* @return Remaining security delay in milliseconds.
*/
private fun deserializeSecurityDelay(responseApdu: ResponseApdu, environment: SessionEnvironment): Int? {
val tlv = responseApdu.getTlvData()
return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt()
}
private fun tryHandleError(error: SessionError): Boolean {
return false
}
}

View file

@ -1,44 +0,0 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.extensions.toInt
import com.tangem.common.tlv.TlvTag
/**
* Simple interface for responses received after sending commands to Tangem cards.
*/
interface CommandResponse
/**
* Abstract class for all Tangem card commands.
*/
abstract class CommandSerializer<T : CommandResponse> {
/**
* Serializes data into a [List] of [com.tangem.common.tlv.Tlv],
* then creates [CommandApdu] with this data.
*
* @return Command data that can be converted to raw bytes with a method [CommandApdu.toBytes].
*/
abstract fun serialize(cardEnvironment: CardEnvironment): CommandApdu
/**
* Deserializes data, received from a card and stored in [ResponseApdu],
* into a [List] of [com.tangem.common.tlv.Tlv]. Then this method maps it into a [CommandResponse].
*
* @return Card response, converted to a [CommandResponse] of a type [T].
*/
abstract fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): T?
/**
* Helper method to parse security delay information received from a card.
*
* @return Remaining security delay in milliseconds.
*/
fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? {
val tlv = responseApdu.getTlvData()
return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt()
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class CreateWalletResponse(
/**
@ -35,32 +35,29 @@ class CreateWalletResponse(
*
* @property cardId CID, Unique Tangem card ID number.
*/
class CreateWalletCommand : CommandSerializer<CreateWalletResponse>() {
class CreateWalletCommand : Command<CreateWalletResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun serialize(environment: SessionEnvironment): CommandApdu {
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)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Pin2, environment.pin2)
tlvBuilder.append(TlvTag.Cvc, environment.cvc)
return CommandApdu(
Instruction.CreateWallet, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
CreateWalletResponse(
cardId = mapper.map(TlvTag.CardId),
status = mapper.map(TlvTag.Status),
walletPublicKey = mapper.map(TlvTag.WalletPublicKey)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return CreateWalletResponse(
cardId = decoder.decode(TlvTag.CardId),
status = decoder.decode(TlvTag.Status),
walletPublicKey = decoder.decode(TlvTag.WalletPublicKey)
)
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class OpenSessionResponse(
val sessionKeyB: ByteArray,
@ -20,27 +20,24 @@ class OpenSessionResponse(
* to encrypt and decrypt commands payload.
*/
class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer<OpenSessionResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command<OpenSessionResponse>() {
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA)
return CommandApdu(
Instruction.OpenSession, tlvBuilder.serialize(),
encryptionMode = cardEnvironment.encryptionMode
encryptionMode = environment.encryptionMode
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? {
val tlvData = responseApdu.getTlvData() ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): OpenSessionResponse {
val tlvData = apdu.getTlvData()
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
OpenSessionResponse(
sessionKeyB = mapper.map(TlvTag.SessionKeyB),
uid = mapper.map(TlvTag.Uid)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return OpenSessionResponse(
sessionKeyB = decoder.decode(TlvTag.SessionKeyB),
uid = decoder.decode(TlvTag.Uid)
)
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class PurgeWalletResponse(
/**
@ -27,29 +27,26 @@ class PurgeWalletResponse(
* Purged state is final, it makes the card useless.
* @property cardId CID, Unique Tangem card ID number.
*/
class PurgeWalletCommand : CommandSerializer<PurgeWalletResponse>() {
class PurgeWalletCommand : Command<PurgeWalletResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Pin2, environment.pin2)
return CommandApdu(
Instruction.PurgeWallet, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PurgeWalletResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
PurgeWalletResponse(
cardId = mapper.map(TlvTag.CardId),
status = mapper.map(TlvTag.Status))
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return PurgeWalletResponse(
cardId = decoder.decode(TlvTag.CardId),
status = decoder.decode(TlvTag.Status))
}
}

View file

@ -1,14 +1,14 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
import java.util.*
/**
@ -73,7 +73,7 @@ data class SigningMethod(val rawValue: Int) {
if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData
if (signPos) signingMethod += 0x01 shl SigningMethod.signPos
}
return SigningMethod(signingMethod)
return SigningMethod(signingMethod)
}
}
}
@ -354,6 +354,13 @@ class Card(
*/
val userCounter: Int?,
/**
* This value can be initialized by App (with PIN2 confirmation) and will be increased by COS
* with the execution of each [SignCommand]. For example, this field can store blockchain nonce
* for a quick one-touch transaction on POS terminals. Returned only if [SigningMethod.SignPos].
*/
val userProtectedCounter: Int?,
/**
* When this value is true, it means that the application is linked to the card,
* and COS will not enforce security delay if [SignCommand] will be called
@ -373,58 +380,56 @@ class Card(
* This command receives from the Tangem Card all the data about the card and the wallet,
* including unique card number (CID or cardId) that has to be submitted while calling all other commands.
*/
class ReadCommand : CommandSerializer<Card>() {
class ReadCommand : Command<Card>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
/**
* [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain
* [SessionEnvironment] stores the pin1 value. If no pin1 value was set, it will contain
* default value of 000000.
* In order to obtain cards data, [ReadCommand] should use the correct pin 1 value.
* The card will not respond if wrong pin 1 has been submitted.
*/
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey)
return CommandApdu(
Instruction.Read, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val tlvMapper = TlvMapper(tlvData)
val decoder = TlvDecoder(tlvData)
Card(
cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "",
manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "",
status = tlvMapper.mapOptional(TlvTag.Status),
return Card(
cardId = decoder.decodeOptional(TlvTag.CardId) ?: "",
manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "",
status = decoder.decodeOptional(TlvTag.Status),
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),
firmwareVersion = decoder.decodeOptional(TlvTag.Firmware),
cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey),
settingsMask = decoder.decodeOptional(TlvTag.SettingsMask),
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
curve = decoder.decodeOptional(TlvTag.CurveId),
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),
walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes),
health = decoder.decodeOptional(TlvTag.Health),
isActivated = decoder.decode(TlvTag.IsActivated),
activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed),
paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion),
userCounter = decoder.decodeOptional(TlvTag.UserCounter),
userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter),
terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked),
cardData = deserializeCardData(tlvData)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
cardData = deserializeCardData(tlvData)
)
}
private fun deserializeCardData(tlvData: List<Tlv>): CardData? {
@ -433,18 +438,18 @@ class ReadCommand : CommandSerializer<Card>() {
}
if (cardDataTlvs.isNullOrEmpty()) return null
val tlvMapper = TlvMapper(cardDataTlvs)
val decoder = TlvDecoder(cardDataTlvs)
return CardData(
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),
batchId = decoder.decodeOptional(TlvTag.Batch),
manufactureDateTime = decoder.decodeOptional(TlvTag.ManufactureDateTime),
issuerName = decoder.decodeOptional(TlvTag.IssuerId),
blockchainName = decoder.decodeOptional(TlvTag.BlockchainId),
manufacturerSignature = decoder.decodeOptional(TlvTag.ManufacturerSignature),
productMask = decoder.decodeOptional(TlvTag.ProductMask),
tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol),
tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress),
tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal)
tokenSymbol = decoder.decodeOptional(TlvTag.TokenSymbol),
tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress),
tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal)
)
}
}

View file

@ -1,16 +1,19 @@
package com.tangem.commands
import com.tangem.CardSession
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.commands.common.DefaultIssuerDataVerifier
import com.tangem.commands.common.IssuerDataMode
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.commands.common.IssuerDataVerifier
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class ReadIssuerDataResponse(
@ -51,34 +54,63 @@ class ReadIssuerDataResponse(
* @property cardId CID, Unique Tangem card ID number.
*/
class ReadIssuerDataCommand(
val issuerPublicKey: ByteArray? = null,
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
) : CommandSerializer<ReadIssuerDataResponse>(),
IssuerDataVerifier by verifier {
) : Command<ReadIssuerDataResponse>(), IssuerDataVerifier by verifier {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun run(session: CardSession, callback: (result: CompletionResult<ReadIssuerDataResponse>) -> Unit) {
val card = session.environment.card
if (card == null) {
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
return
}
val publicKey = issuerPublicKey ?: card.issuerPublicKey
if (publicKey == null) {
callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey()))
return
}
super.run(session) { result ->
when (result) {
is CompletionResult.Failure -> callback(result)
is CompletionResult.Success -> {
if (result.data.issuerData.isEmpty()) {
callback(result)
return@run
}
val issuerDataToVerify = IssuerDataToVerify(
card.cardId, result.data.issuerData, result.data.issuerDataCounter
)
if (verify(publicKey, result.data.issuerDataSignature, issuerDataToVerify)) {
callback(result)
} else {
callback(CompletionResult.Failure(SessionError.VerificationFailed()))
}
}
}
}
}
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData)
return CommandApdu(
Instruction.ReadIssuerData, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
ReadIssuerDataResponse(
cardId = mapper.map(TlvTag.CardId),
issuerData = mapper.map(TlvTag.IssuerData),
issuerDataSignature = mapper.map(TlvTag.IssuerDataSignature),
issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return ReadIssuerDataResponse(
cardId = decoder.decode(TlvTag.CardId),
issuerData = decoder.decode(TlvTag.IssuerData),
issuerDataSignature = decoder.decode(TlvTag.IssuerDataSignature),
issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter)
)
}
}

View file

@ -1,16 +1,20 @@
package com.tangem.commands
import com.tangem.CardSession
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.commands.common.DefaultIssuerDataVerifier
import com.tangem.commands.common.IssuerDataMode
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.commands.common.IssuerDataVerifier
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
import java.io.ByteArrayOutputStream
class ReadIssuerExtraDataResponse(
@ -56,38 +60,113 @@ class ReadIssuerExtraDataResponse(
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
*/
class ReadIssuerExtraDataCommand(
private val issuerPublicKey: ByteArray? = null,
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
) : CommandSerializer<ReadIssuerExtraDataResponse>(), IssuerDataVerifier by verifier {
) : Command<ReadIssuerExtraDataResponse>(), IssuerDataVerifier by verifier {
var offset: Int = 0
private val issuerData = ByteArrayOutputStream()
private var offset: Int = 0
private var issuerDataSize: Int = 0
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun run(session: CardSession, callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
val card = session.environment.card
if (card == null) {
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
return
}
val publicKey = issuerPublicKey ?: card.issuerPublicKey
if (publicKey == null) {
callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey()))
return
}
readIssuerData(session, card.cardId, publicKey, callback)
}
private fun readIssuerData(
session: CardSession,
cardId: String, publicKey: ByteArray,
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
if (issuerDataSize != 0) {
session.viewDelegate.onDelay(
issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
)
}
transceive(session) { result ->
when (result) {
is CompletionResult.Success -> {
if (result.data.size != null) {
if (result.data.size == 0) {
callback(CompletionResult.Success(result.data))
return@transceive
}
issuerDataSize = result.data.size
}
issuerData.write(result.data.issuerData)
if (result.data.issuerDataSignature == null) {
offset = issuerData.size()
readIssuerData(session, cardId, publicKey, callback)
} else {
completeTask(result.data, cardId, publicKey, callback)
}
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
private fun completeTask(data: ReadIssuerExtraDataResponse,
cardId: String, publicKey: ByteArray,
callback: (result: CompletionResult<ReadIssuerExtraDataResponse>) -> Unit) {
val dataToVerify = IssuerDataToVerify(
cardId,
issuerData.toByteArray(),
data.issuerDataCounter
)
if (verify(publicKey, data.issuerDataSignature!!, dataToVerify)) {
val finalResult = ReadIssuerExtraDataResponse(
data.cardId,
issuerDataSize,
issuerData.toByteArray(),
data.issuerDataSignature,
data.issuerDataCounter
)
callback(CompletionResult.Success(finalResult))
} else {
callback(CompletionResult.Failure(SessionError.VerificationFailed()))
}
}
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData)
tlvBuilder.append(TlvTag.Offset, offset)
return CommandApdu(
Instruction.ReadIssuerData, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
ReadIssuerExtraDataResponse(
cardId = mapper.map(TlvTag.CardId),
size = mapper.mapOptional(TlvTag.Size),
issuerData = mapper.mapOptional(TlvTag.IssuerData) ?: byteArrayOf(),
issuerDataSignature = mapper.mapOptional(TlvTag.IssuerDataSignature),
issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return ReadIssuerExtraDataResponse(
cardId = decoder.decode(TlvTag.CardId),
size = decoder.decodeOptional(TlvTag.Size),
issuerData = decoder.decodeOptional(TlvTag.IssuerData) ?: byteArrayOf(),
issuerDataSignature = decoder.decodeOptional(TlvTag.IssuerDataSignature),
issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter)
)
}
companion object {

View file

@ -1,13 +1,13 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class ReadUserDataResponse(
/**
@ -47,33 +47,30 @@ class ReadUserDataResponse(
* of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use.
* For example, this fields may contain blockchain nonce value.
*/
class ReadUserDataCommand: CommandSerializer<ReadUserDataResponse>() {
class ReadUserDataCommand: Command<ReadUserDataResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun serialize(environment: SessionEnvironment): CommandApdu {
val builder = TlvBuilder()
builder.append(TlvTag.CardId, cardEnvironment.cardId)
builder.append(TlvTag.Pin, cardEnvironment.pin1)
builder.append(TlvTag.CardId, environment.card?.cardId)
builder.append(TlvTag.Pin, environment.pin1)
return CommandApdu(
Instruction.ReadUserData, builder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadUserDataResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
ReadUserDataResponse(
cardId = mapper.map(TlvTag.CardId),
userData = mapper.map(TlvTag.UserData),
userProtectedData = mapper.map(TlvTag.UserProtectedData),
userCounter = mapper.map(TlvTag.UserCounter),
userProtectedCounter = mapper.map(TlvTag.UserProtectedCounter)
val decoder = TlvDecoder(tlvData)
return ReadUserDataResponse(
cardId = decoder.decode(TlvTag.CardId),
userData = decoder.decode(TlvTag.UserData),
userProtectedData = decoder.decode(TlvTag.UserProtectedData),
userCounter = decoder.decode(TlvTag.UserCounter),
userProtectedCounter = decoder.decode(TlvTag.UserProtectedCounter)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
}
}

View file

@ -1,14 +1,14 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.crypto.sign
import com.tangem.tasks.TaskError
/**
* @param cardId CID, Unique Tangem card ID number
@ -31,10 +31,26 @@ class SignResponse(
* @property cardId CID, Unique Tangem card ID number
*/
class SignCommand(private val hashes: Array<ByteArray>)
: CommandSerializer<SignResponse>() {
: Command<SignResponse>() {
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
private val dataToSign = flattenHashes()
override fun serialize(environment: SessionEnvironment): CommandApdu {
val dataToSign = flattenHashes()
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.Pin2, environment.pin2)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte()))
tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign)
tlvBuilder.append(TlvTag.Cvc, environment.cvc)
addTerminalSignature(environment, dataToSign, tlvBuilder)
return CommandApdu(
Instruction.Sign, tlvBuilder.serialize(),
environment.encryptionMode, environment.encryptionKey
)
}
private fun flattenHashes(): ByteArray {
checkForErrors()
@ -42,25 +58,9 @@ class SignCommand(private val hashes: Array<ByteArray>)
}
private fun checkForErrors() {
if (hashes.isEmpty()) throw TaskError.EmptyHashes()
if (hashes.size > 10) throw TaskError.TooMuchHashesInOneTransaction()
if (hashes.any { it.size != hashSizes }) throw TaskError.HashSizeMustBeEqual()
}
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
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, tlvBuilder)
return CommandApdu(
Instruction.Sign, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
)
if (hashes.isEmpty()) throw SessionError.EmptyHashes()
if (hashes.size > 10) throw SessionError.TooMuchHashesInOneTransaction()
if (hashes.any { it.size != hashSizes }) throw SessionError.HashSizeMustBeEqual()
}
/**
@ -70,23 +70,25 @@ class SignCommand(private val hashes: Array<ByteArray>)
* 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, tlvBuilder: TlvBuilder) {
cardEnvironment.terminalKeys?.let { terminalKeyPair ->
private fun addTerminalSignature(
environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder) {
environment.terminalKeys?.let { terminalKeyPair ->
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData)
tlvBuilder.append(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey)
}
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SignResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
val tlvMapper = TlvMapper(tlvData)
val decoder = TlvDecoder(tlvData)
return SignResponse(
cardId = tlvMapper.map(TlvTag.CardId),
signature = tlvMapper.map(TlvTag.Signature),
walletRemainingSignatures = tlvMapper.map(TlvTag.RemainingSignatures),
walletSignedHashes = tlvMapper.map(TlvTag.SignedHashes)
cardId = decoder.decode(TlvTag.CardId),
signature = decoder.decode(TlvTag.Signature),
walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures),
walletSignedHashes = decoder.decode(TlvTag.SignedHashes)
)
}
}

View file

@ -1,16 +1,19 @@
package com.tangem.commands
import com.tangem.CardSession
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.commands.common.DefaultIssuerDataVerifier
import com.tangem.commands.common.IssuerDataMode
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.commands.common.IssuerDataVerifier
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class WriteIssuerDataResponse(
/**
@ -33,13 +36,50 @@ class WriteIssuerDataCommand(
private val issuerData: ByteArray,
private val issuerDataSignature: ByteArray,
private val issuerDataCounter: Int? = null,
private val issuerPublicKey: ByteArray? = null,
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
) : CommandSerializer<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
) : Command<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
val card = session.environment.card
if (card == null) {
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
return
}
val publicKey = issuerPublicKey ?: card.issuerPublicKey
if (publicKey == null) {
callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey()))
return
}
if (!isCounterValid(issuerDataCounter, card)) {
callback(CompletionResult.Failure(SessionError.MissingCounter()))
} else if (!verifySignature(publicKey, card.cardId)) {
callback(CompletionResult.Failure(SessionError.VerificationFailed()))
} else {
super.run(session, callback)
}
}
private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean =
if (isCounterRequired(card)) issuerDataCounter != null else true
private fun isCounterRequired(card: Card): Boolean =
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
private fun verifySignature(publicKey: ByteArray, cardId: String): Boolean {
return verify(
publicKey,
issuerDataSignature,
IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
)
}
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData)
tlvBuilder.append(TlvTag.IssuerData, issuerData)
tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature)
@ -47,20 +87,17 @@ class WriteIssuerDataCommand(
return CommandApdu(
Instruction.WriteIssuerData, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
WriteIssuerDataResponse(
cardId = mapper.map(TlvTag.CardId)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
val decoder = TlvDecoder(tlvData)
return WriteIssuerDataResponse(
cardId = decoder.decode(TlvTag.CardId)
)
}
}

View file

@ -1,16 +1,19 @@
package com.tangem.commands
import com.tangem.CardSession
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.commands.common.DefaultIssuerDataVerifier
import com.tangem.commands.common.IssuerDataMode
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.commands.common.IssuerDataVerifier
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
/**
* This command writes Issuer Extra Data field and its issuers signature.
@ -33,17 +36,94 @@ class WriteIssuerExtraDataCommand(
private val startingSignature: ByteArray,
private val finalizingSignature: ByteArray,
private val issuerDataCounter: Int? = null,
private val issuerPublicKey: ByteArray? = null,
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
) : CommandSerializer<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
) : Command<WriteIssuerDataResponse>(), IssuerDataVerifier by verifier {
var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData
var offset: Int = 0
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun run(session: CardSession, callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit) {
val card = session.environment.card
if (card == null) {
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
return
}
val publicKey = issuerPublicKey ?: card.issuerPublicKey
if (publicKey == null) {
callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey()))
return
}
if (!isCounterValid(issuerDataCounter, card)) {
callback(CompletionResult.Failure(SessionError.MissingCounter()))
} else if (!verifySignatures(card.cardId, publicKey)) {
callback(CompletionResult.Failure(SessionError.VerificationFailed()))
} else {
writeIssuerData(session, card.cardId, publicKey, callback)
}
}
private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean =
if (isCounterRequired(card)) issuerDataCounter != null else true
private fun isCounterRequired(card: Card): Boolean =
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
private fun verifySignatures(cardId: String, publicKey: ByteArray): Boolean {
val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size)
val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
return verify(publicKey, startingSignature, firstData) &&
verify(publicKey, finalizingSignature, secondData)
}
private fun writeIssuerData(
session: CardSession,
cardId: String, publicKey: ByteArray,
callback: (result: CompletionResult<WriteIssuerDataResponse>) -> Unit
) {
if (mode == IssuerDataMode.WriteExtraData) {
session.viewDelegate.onDelay(issuerData.size, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE)
}
transceive(session) { result ->
when (result) {
is CompletionResult.Success -> {
when (mode) {
IssuerDataMode.InitializeWritingExtraData -> {
mode = IssuerDataMode.WriteExtraData
writeIssuerData(session, cardId, publicKey, callback)
return@transceive
}
IssuerDataMode.WriteExtraData -> {
offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
if (offset >= issuerData.size) {
mode = IssuerDataMode.FinalizeExtraData
}
writeIssuerData(session, cardId, publicKey, callback)
return@transceive
}
IssuerDataMode.FinalizeExtraData -> {
callback(CompletionResult.Success(result.data))
}
}
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))
}
}
}
}
override fun serialize(environment: SessionEnvironment): CommandApdu {
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId)
tlvBuilder.append(TlvTag.Pin, environment.pin1)
tlvBuilder.append(TlvTag.CardId, environment.card?.cardId)
tlvBuilder.append(TlvTag.Mode, mode)
when (mode) {
@ -62,7 +142,7 @@ class WriteIssuerExtraDataCommand(
}
return CommandApdu(
Instruction.WriteIssuerData, tlvBuilder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
environment.encryptionMode, environment.encryptionKey
)
}
@ -74,17 +154,12 @@ class WriteIssuerExtraDataCommand(
return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val mapper = TlvMapper(tlvData)
WriteIssuerDataResponse(
cardId = mapper.map(TlvTag.CardId)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId)
)
}
companion object {

View file

@ -1,20 +1,20 @@
package com.tangem.commands
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.tasks.TaskError
class WriteUserDataResponse(
/**
* CID, Unique Tangem card ID number.
*/
val cardId: String
): CommandResponse
/**
* CID, Unique Tangem card ID number.
*/
val cardId: String
) : CommandResponse
/**
* This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields.
@ -29,33 +29,29 @@ class WriteUserDataResponse(
* User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation.
*/
class WriteUserDataCommand(private val userData: ByteArray? = null, private val userProtectedData: ByteArray? = null,
private val userCounter: Int? = null,
private val userProtectedCounter: Int? = null): CommandSerializer<WriteUserDataResponse>() {
private val userCounter: Int? = null,
private val userProtectedCounter: Int? = null) : Command<WriteUserDataResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val builder = TlvBuilder()
builder.append(TlvTag.CardId, cardEnvironment.cardId)
builder.append(TlvTag.Pin, cardEnvironment.pin1)
builder.append(TlvTag.UserData, userData)
builder.append(TlvTag.UserCounter, userCounter)
builder.append(TlvTag.UserProtectedData, userProtectedData)
builder.append(TlvTag.UserProtectedCounter, userProtectedCounter)
if (userProtectedCounter != null || userProtectedData != null)
builder.append(TlvTag.Pin2, cardEnvironment.pin2)
override fun serialize(environment: SessionEnvironment): CommandApdu {
val builder = TlvBuilder()
builder.append(TlvTag.CardId, environment.card?.cardId)
builder.append(TlvTag.Pin, environment.pin1)
builder.append(TlvTag.UserData, userData)
builder.append(TlvTag.UserCounter, userCounter)
builder.append(TlvTag.UserProtectedData, userProtectedData)
builder.append(TlvTag.UserProtectedCounter, userProtectedCounter)
if (userProtectedCounter != null || userProtectedData != null)
builder.append(TlvTag.Pin2, environment.pin2)
return CommandApdu(
Instruction.WriteUserData, builder.serialize(),
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? {
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
return try {
WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId))
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
return CommandApdu(
Instruction.WriteUserData, builder.serialize(),
environment.encryptionMode, environment.encryptionKey
)
}
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteUserDataResponse {
val tlvData = apdu.getTlvData(environment.encryptionKey)
?: throw SessionError.DeserializeApduFailed()
return WriteUserDataResponse(TlvDecoder(tlvData).decode(TlvTag.CardId))
}
}
}

View file

@ -1,8 +1,8 @@
package com.tangem.commands.personalization
import com.tangem.SessionEnvironment
import com.tangem.commands.Command
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
@ -14,17 +14,16 @@ data class DepersonalizeResponse(val success: Boolean) : CommandResponse
*
* This command resets card to initial state,
* erasing all data written during personalization and usage.
* @param cardId CID, Unique Tangem card ID number.
*/
class DepersonalizeCommand : CommandSerializer<DepersonalizeResponse>() {
class DepersonalizeCommand : Command<DepersonalizeResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun serialize(environment: SessionEnvironment): CommandApdu {
return CommandApdu(
Instruction.Depersonalize, byteArrayOf()
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): DepersonalizeResponse? {
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): DepersonalizeResponse {
return DepersonalizeResponse(true)
}
}

View file

@ -1,10 +1,11 @@
package com.tangem.commands.personalization
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.commands.Card
import com.tangem.commands.CardData
import com.tangem.commands.CommandSerializer
import com.tangem.commands.Command
import com.tangem.commands.personalization.entities.*
import com.tangem.common.CardEnvironment
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.Instruction
import com.tangem.common.apdu.ResponseApdu
@ -12,10 +13,9 @@ 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.TlvDecoder
import com.tangem.common.tlv.TlvTag
import com.tangem.crypto.sign
import com.tangem.tasks.TaskError
/**
* Command available on SDK cards only
@ -34,9 +34,9 @@ class PersonalizeCommand(
private val config: CardConfig,
private val issuer: Issuer, private val manufacturer: Manufacturer,
private val acquirer: Acquirer? = null
) : CommandSerializer<Card>() {
) : Command<Card>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
override fun serialize(environment: SessionEnvironment): CommandApdu {
return CommandApdu(
Instruction.Personalize,
serializePersonalizationData(config),
@ -44,39 +44,37 @@ class PersonalizeCommand(
)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
val tlvData = responseApdu.getTlvData(devPersonalizationKey) ?: return null
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card {
val tlvData = apdu.getTlvData(devPersonalizationKey)
?: throw SessionError.DeserializeApduFailed()
return try {
val tlvMapper = TlvMapper(tlvData)
Card(
cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "",
manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "",
status = tlvMapper.mapOptional(TlvTag.Status),
val decoder = TlvDecoder(tlvData)
return Card(
cardId = decoder.decodeOptional(TlvTag.CardId) ?: "",
manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "",
status = decoder.decodeOptional(TlvTag.Status),
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),
firmwareVersion = decoder.decodeOptional(TlvTag.Firmware),
cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey),
settingsMask = decoder.decodeOptional(TlvTag.SettingsMask),
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
curve = decoder.decodeOptional(TlvTag.CurveId),
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),
walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes),
health = decoder.decodeOptional(TlvTag.Health),
isActivated = decoder.decode(TlvTag.IsActivated),
activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed),
paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion),
userCounter = decoder.decodeOptional(TlvTag.UserCounter),
userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter),
terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked),
cardData = deserializeCardData(tlvData)
)
} catch (exception: Exception) {
throw TaskError.SerializeCommandError()
}
cardData = deserializeCardData(tlvData)
)
}
private fun deserializeCardData(tlvData: List<Tlv>): CardData? {
@ -85,23 +83,23 @@ class PersonalizeCommand(
}
if (cardDataTlvs.isNullOrEmpty()) return null
val tlvMapper = TlvMapper(cardDataTlvs)
val decoder = TlvDecoder(cardDataTlvs)
return CardData(
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),
batchId = decoder.decodeOptional(TlvTag.Batch),
manufactureDateTime = decoder.decodeOptional(TlvTag.ManufactureDateTime),
issuerName = decoder.decodeOptional(TlvTag.IssuerId),
blockchainName = decoder.decodeOptional(TlvTag.BlockchainId),
manufacturerSignature = decoder.decodeOptional(TlvTag.ManufacturerSignature),
productMask = decoder.decodeOptional(TlvTag.ProductMask),
tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol),
tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress),
tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal)
tokenSymbol = decoder.decodeOptional(TlvTag.TokenSymbol),
tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress),
tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal)
)
}
private fun serializePersonalizationData(config: CardConfig): ByteArray {
val cardId = config.createCardId() ?: throw TaskError.SerializeCommandError()
val cardId = config.createCardId() ?: throw SessionError.SerializeCommandError()
val tlvBuilder = TlvBuilder()
tlvBuilder.append(TlvTag.CardId, cardId)

View file

@ -1,6 +1,6 @@
package com.tangem.commands.personalization.entities
import com.tangem.common.KeyPair
import com.tangem.KeyPair
data class Acquirer(
val keyPair: KeyPair,

View file

@ -1,6 +1,6 @@
package com.tangem.commands.personalization.entities
import com.tangem.common.KeyPair
import com.tangem.KeyPair
data class Issuer(
val name: String,

View file

@ -1,6 +1,6 @@
package com.tangem.commands.personalization.entities
import com.tangem.common.KeyPair
import com.tangem.KeyPair
data class Manufacturer(
val keyPair: KeyPair,

View file

@ -1,7 +1,7 @@
package com.tangem.common
import com.tangem.SessionError
import com.tangem.common.CompletionResult.Success
import com.tangem.tasks.TaskError
/**
* Response class encapsulating successful and failed results.
@ -9,5 +9,5 @@ import com.tangem.tasks.TaskError
*/
sealed class CompletionResult<T> {
class Success<T>(val data: T) : CompletionResult<T>()
class Failure<T>(val error: TaskError) : CompletionResult<T>()
class Failure<T>(val error: SessionError) : CompletionResult<T>()
}

View file

@ -1,9 +1,11 @@
package com.tangem.common
import com.tangem.KeyPair
/**
* 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].
* Its implementation Needs to be provided to [com.tangem.TangemSdk]
* by calling [com.tangem.TangemSdk.setTerminalKeysService].
* Default implementation is provided in tangem-sdk module: [TerminalKeysStorage].
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
*/

View file

@ -1,6 +1,6 @@
package com.tangem.common.apdu
import com.tangem.common.EncryptionMode
import com.tangem.EncryptionMode
import com.tangem.common.extensions.calculateCrc16
import com.tangem.common.extensions.toByteArray
import com.tangem.crypto.encrypt

View file

@ -1,5 +1,7 @@
package com.tangem.common.apdu
import com.tangem.SessionError
/**
* Part of a response from the card, shows the status of the operation
*/
@ -21,5 +23,18 @@ enum class StatusWord(val code: Int, val description: String) {
private val values = values()
fun byCode(code: Int): StatusWord = values.find { it.code == code } ?: Unknown
}
}
fun StatusWord.toSessionError(): SessionError? {
return when (this) {
StatusWord.ProcessCompleted, StatusWord.Pin1Changed,
StatusWord.Pin2Changed, StatusWord.PinsChanged -> null
StatusWord.NeedPause -> null
StatusWord.InvalidParams -> SessionError.InvalidParams()
StatusWord.ErrorProcessingCommand -> SessionError.ErrorProcessingCommand()
StatusWord.InvalidState -> SessionError.InvalidState()
StatusWord.InsNotSupported -> SessionError.InsNotSupported()
StatusWord.NeedEncryption -> SessionError.NeedEncryption()
StatusWord.Unknown -> SessionError.UnknownStatus()
}
}

View file

@ -1,13 +1,13 @@
package com.tangem.common.tlv
import com.tangem.Log
import com.tangem.SessionError
import com.tangem.commands.*
import com.tangem.commands.common.IssuerDataMode
import com.tangem.common.extensions.toDate
import com.tangem.common.extensions.toHexString
import com.tangem.common.extensions.toInt
import com.tangem.common.extensions.toUtf8
import com.tangem.tasks.TaskError
import java.util.*
/**
@ -16,7 +16,7 @@ import java.util.*
*
* @property tlvList List of TLVs, which values are to be converted to particular classes.
*/
class TlvMapper(val tlvList: List<Tlv>) {
class TlvDecoder(val tlvList: List<Tlv>) {
/**
* Finds [Tlv] by its [TlvTag].
@ -26,10 +26,10 @@ class TlvMapper(val tlvList: List<Tlv>) {
*
* @return Value converted to a nullable type [T].
*/
inline fun <reified T> mapOptional(tag: TlvTag): T? =
inline fun <reified T> decodeOptional(tag: TlvTag): T? =
try {
map<T>(tag)
} catch (exception: TaskError.MissingTag) {
decode<T>(tag)
} catch (exception: SessionError.DecodingFailedMissingTag) {
null
}
@ -44,13 +44,13 @@ class TlvMapper(val tlvList: List<Tlv>) {
*
* @throws [TaskError.MissingTag] exception if no [Tlv] is found by the Tag.
*/
inline fun <reified T> map(tag: TlvTag): T {
inline fun <reified T> decode(tag: TlvTag): T {
val tlvValue: ByteArray = tlvList.find { it.tag == tag }?.value
?: if (tag.valueType() == TlvValueType.BoolValue && T::class == Boolean::class) {
return false as T
} else {
Log.e(this::class.simpleName!!, "Tag $tag not found")
throw TaskError.MissingTag()
throw SessionError.DecodingFailedMissingTag()
}
return when (tag.valueType()) {
@ -68,7 +68,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
tlvValue.toInt() as T
} catch (exception: IllegalArgumentException) {
Log.e(this::class.simpleName!!, exception.message ?: "")
throw TaskError.ConvertError()
throw SessionError.DecodingFailed()
}
}
TlvValueType.BoolValue -> {
@ -85,7 +85,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
EllipticCurve.byName(tlvValue.toUtf8()) as T
} catch (exception: Exception) {
logException(tag, tlvValue.toUtf8(), exception)
throw TaskError.ConvertError()
throw SessionError.DecodingFailed()
}
@ -96,7 +96,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
tlvValue.toDate() as T
} catch (exception: Exception) {
logException(tag, tlvValue.toHexString(), exception)
throw TaskError.ConvertError()
throw SessionError.DecodingFailed()
}
}
TlvValueType.ProductMask -> {
@ -113,7 +113,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
CardStatus.byCode(tlvValue.toInt()) as T
} catch (exception: Exception) {
logException(tag, tlvValue.toInt().toString(), exception)
throw TaskError.ConvertError()
throw SessionError.DecodingFailed()
}
}
TlvValueType.SigningMethod -> {
@ -122,7 +122,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
SigningMethod(tlvValue.toInt()) as T
} catch (exception: Exception) {
logException(tag, tlvValue.toInt().toString(), exception)
throw TaskError.ConvertError()
throw SessionError.DecodingFailed()
}
}
TlvValueType.IssuerDataMode -> {
@ -131,7 +131,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T
} catch (exception: Exception) {
logException(tag, tlvValue.toInt().toString(), exception)
throw TaskError.ConvertError()
throw SessionError.DecodingFailed()
}
}
}
@ -146,7 +146,7 @@ class TlvMapper(val tlvList: List<Tlv>) {
if (T::class != ExpectedT::class) {
Log.e(this::class.simpleName!!,
"Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
throw TaskError.WrongType()
throw SessionError.DecodingFailedTypeMismatch()
}
}

View file

@ -1,13 +1,12 @@
package com.tangem.common.tlv
import com.tangem.Log
import com.tangem.SessionError
import com.tangem.commands.*
import com.tangem.commands.common.IssuerDataMode
import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toByteArray
import com.tangem.common.extensions.toHexString
import com.tangem.tasks.TaskError
import java.util.*
/**
@ -25,7 +24,7 @@ class TlvEncoder {
return Tlv(tag, encodeValue(tag, value))
} else {
Log.e(this::class.simpleName!!, "Encoding error. Value for tag $tag is null")
throw TaskError.SerializeCommandError()
throw SessionError.EncodingFailed()
}
}
@ -107,7 +106,7 @@ class TlvEncoder {
if (T::class != ExpectedT::class) {
Log.e(this::class.simpleName!!,
"Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
throw TaskError.WrongType()
throw SessionError.EncodingFailedTypeMismatch()
}
}
}

View file

@ -0,0 +1,43 @@
package com.tangem.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.SessionError
import com.tangem.commands.CardStatus
import com.tangem.commands.CheckWalletCommand
import com.tangem.commands.CreateWalletCommand
import com.tangem.commands.CreateWalletResponse
import com.tangem.common.CompletionResult
class CreateWalletTask : CardSessionRunnable<CreateWalletResponse> {
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
val curve = session.environment.card?.curve
if (curve == null) {
callback(CompletionResult.Failure(SessionError.CardError()))
return
}
val command = CreateWalletCommand()
command.run(session) { createWalletResult ->
when (createWalletResult) {
is CompletionResult.Failure -> callback(createWalletResult)
is CompletionResult.Success -> {
if (createWalletResult.data.status != CardStatus.Loaded) {
callback(CompletionResult.Failure(SessionError.UnknownError()))
} else {
val checkWalletCommand = CheckWalletCommand(
curve, createWalletResult.data.walletPublicKey
)
checkWalletCommand.run(session) { result ->
when (result) {
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
is CompletionResult.Success -> callback(createWalletResult)
}
}
}
}
}
}
}
}

View file

@ -1,53 +0,0 @@
package com.tangem.tasks
import com.tangem.commands.Card
import com.tangem.commands.ReadIssuerDataCommand
import com.tangem.commands.ReadIssuerDataResponse
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
class ReadIssuerDataTask(private val issuerPublicKey: ByteArray? = null) : Task<ReadIssuerDataResponse>() {
override fun onRun(
cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
val command = ReadIssuerDataCommand()
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
val data = result.data
if (data.issuerData.isEmpty()) {
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
return@sendCommand
}
val publicKey = issuerPublicKey ?: currentCard!!.issuerPublicKey!!
val issuerDataToVerify = IssuerDataToVerify(
cardEnvironment.cardId!!,
data.issuerData,
data.issuerDataCounter
)
if (command.verify(publicKey, data.issuerDataSignature, issuerDataToVerify)) {
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
} else {
completeNfcSession(TaskError.VerificationFailed())
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
}
}
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelled) {
completeNfcSession(result.error)
}
callback(TaskEvent.Completion(result.error))
}
}
}
}
}

View file

@ -1,100 +0,0 @@
package com.tangem.tasks
import com.tangem.commands.Card
import com.tangem.commands.ReadIssuerExtraDataCommand
import com.tangem.commands.ReadIssuerExtraDataResponse
import com.tangem.commands.WriteIssuerExtraDataCommand
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
import java.io.ByteArrayOutputStream
/**
* This task performs [ReadIssuerExtraDataCommand] repeatedly until
* the issuer extra data is fully retrieved.
*/
internal class ReadIssuerExtraDataTask(
private val issuerPublicKey: ByteArray?) : Task<ReadIssuerExtraDataResponse>() {
private val issuerData = ByteArrayOutputStream()
private var issuerDataSize = 0
private lateinit var card: Card
private lateinit var cardEnvironment: CardEnvironment
override fun onRun(
cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit
) {
card = currentCard!!
this.cardEnvironment = cardEnvironment
val command = ReadIssuerExtraDataCommand()
readIssuerData(command, callback)
}
private fun readIssuerData(
command: ReadIssuerExtraDataCommand,
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
if (issuerDataSize != 0) {
delegate?.onDelay(
issuerDataSize, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
)
}
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
if (result.data.size != null) {
if (result.data.size == 0) {
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
return@sendCommand
}
issuerDataSize = result.data.size
}
issuerData.write(result.data.issuerData)
if (result.data.issuerDataSignature == null) {
command.offset = issuerData.size()
readIssuerData(command, callback)
} else {
completeTask(result.data, command, callback)
}
}
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelled) {
completeNfcSession(result.error)
}
callback(TaskEvent.Completion(result.error))
}
}
}
}
private fun completeTask(data: ReadIssuerExtraDataResponse, command: ReadIssuerExtraDataCommand,
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!
val dataToVerify = IssuerDataToVerify(
cardEnvironment.cardId!!,
issuerData.toByteArray(),
data.issuerDataCounter
)
if (command.verify(publicKey, data.issuerDataSignature!!, dataToVerify)) {
completeNfcSession()
val finalResult = ReadIssuerExtraDataResponse(
data.cardId,
issuerDataSize,
issuerData.toByteArray(),
data.issuerDataSignature,
data.issuerDataCounter
)
callback(TaskEvent.Event(finalResult))
callback(TaskEvent.Completion())
} else {
completeNfcSession(TaskError.VerificationFailed())
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
}
}
}

View file

@ -1,78 +1,42 @@
package com.tangem.tasks
import com.tangem.CardSession
import com.tangem.CardSessionRunnable
import com.tangem.SessionError
import com.tangem.commands.*
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
/**
* Events that [ScanTask] returns on completion of its commands.
*/
sealed class ScanEvent {
/**
* Contains data from a Tangem card after successful completion of [ReadCommand].
*/
data class OnReadEvent(val card: Card) : ScanEvent()
/**
* Shows whether the Tangem card was verified on completion of [CheckWalletCommand].
*/
data class OnVerifyEvent(val isGenuine: Boolean) : ScanEvent()
}
/**
* Task that allows to read Tangem card and verify its private key.
*
* It performs two commands, [ReadCommand] and [CheckWalletCommand], subsequently.
*/
internal class ScanTask : Task<ScanEvent>() {
internal class ScanTask : CardSessionRunnable<Card> {
override fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<ScanEvent>) -> Unit) {
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard)))
val card = session.environment.card
if (currentCard == null) {
completeNfcSession(TaskError.MissingPreflightRead())
callback(TaskEvent.Completion(TaskError.MissingPreflightRead()))
if (card == null) {
callback(CompletionResult.Failure(SessionError.MissingPreflightRead()))
} else if (currentCard.cardData?.productMask?.contains(ProductMask.tag) != false) {
completeNfcSession()
callback(TaskEvent.Completion())
} else if (card.cardData?.productMask?.contains(ProductMask.tag) != false) {
callback(CompletionResult.Success(card))
} else if (currentCard.status != CardStatus.Loaded) {
completeNfcSession()
callback(TaskEvent.Completion())
} else if (card.status != CardStatus.Loaded) {
callback(CompletionResult.Success(card))
} else if (currentCard.curve == null || currentCard.walletPublicKey == null) {
completeNfcSession(TaskError.CardError())
callback(TaskEvent.Completion(TaskError.CardError()))
} else if (card.curve == null || card.walletPublicKey == null) {
callback(CompletionResult.Failure(SessionError.CardError()))
} else {
val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey)
val checkWalletCommand = CheckWalletCommand()
sendCommand(checkWalletCommand, cardEnvironment) { result ->
checkWalletCommand.run(session) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelled) {
completeNfcSession(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())
}
is CompletionResult.Success -> callback(CompletionResult.Success(card))
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}

View file

@ -1,39 +0,0 @@
package com.tangem.tasks
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
/**
* Allows to perform a single command.
*
* @property command A command that will be performed.
*/
class SingleCommandTask<Event : CommandResponse>(
private val command: CommandSerializer<Event>
) : Task<Event>() {
override fun onRun(
cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<Event>) -> Unit
) {
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
}
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelled) {
completeNfcSession(result.error)
}
callback(TaskEvent.Completion(result.error))
}
}
}
}
}

View file

@ -1,358 +0,0 @@
package com.tangem.tasks
import com.tangem.CardManagerDelegate
import com.tangem.CardReader
import com.tangem.Log
import com.tangem.commands.*
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
import com.tangem.common.EncryptionMode
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.StatusWord
import com.tangem.common.extensions.calculateSha256
import com.tangem.crypto.EncryptionHelper
import com.tangem.crypto.FastEncryptionHelper
import com.tangem.crypto.StrongEncryptionHelper
import com.tangem.crypto.pbkdf2Hash
/**
* An error class that represent typical errors that may occur when performing Tangem SDK tasks.
* Errors are propagated back to the caller in callbacks.
*/
sealed class TaskError(val code: Int) : Exception() {
//Errors in serializing APDU
/**
* This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv]
* (this error is a wrapper around internal [com.tangem.common.tlv.TlvMapper] errors).
*/
class SerializeCommandError : TaskError(1001)
class EncodingError : TaskError(1002)
class MissingTag : TaskError(1003)
class WrongType : TaskError(1004)
class ConvertError : TaskError(1005)
/**
* This error is returned when unknown [StatusWord] is received from a card.
*/
class UnknownStatus : TaskError(2001)
/**
* This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand].
* The card sends this status in case of internal card error.
*/
class ErrorProcessingCommand : TaskError(2002)
/**
* This error is returned when a task (such as [ScanTask]) requires that [ReadCommand]
* is executed before performing other commands.
*/
class MissingPreflightRead : TaskError(2003)
/**
* This error is returned when a card's reply is [StatusWord.InvalidState].
* The card sends this status when command can not be executed in the current state of a card.
*/
class InvalidState : TaskError(2004)
/**
* This error is returned when a card's reply is [StatusWord.InsNotSupported].
* The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction].
*/
class InsNotSupported : TaskError(2005)
/**
* This error is returned when a card's reply is [StatusWord.InvalidParams].
* The card sends this status when there are wrong or not sufficient parameters in TLV request,
* or wrong PIN1/PIN2.
* The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer],
* mapping or serialization errors.
*/
class InvalidParams : TaskError(2006)
/**
* This error is returned when a card's reply is [StatusWord.NeedEncryption]
* and the encryption was not established by TangemSdk.
*/
class NeedEncryption : TaskError(2007)
//Scan errors
/**
* This error is returned when a [Task] checks unsuccessfully either
* a card's ability to sign with its private key, or the validity of issuer data.
*/
class VerificationFailed : TaskError(3000)
/**
* This error is returned when a [ScanTask] returns a [Card] without some of the essential fields.
*/
class CardError : TaskError(3001)
/**
* This error is returned when a [Task] expects a user to use a particular card,
* and a user tries to use a different card.
*/
class WrongCard : TaskError(3002)
/**
* Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand].
* This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign.
*/
class TooMuchHashesInOneTransaction : TaskError(3003)
/**
* This error is returned when a [com.tangem.commands.SignCommand]
* receives only empty hashes for signature.
*/
class EmptyHashes : TaskError(3004)
/**
* This error is returned when a [com.tangem.commands.SignCommand]
* receives hashes of different lengths for signature.
*/
class HashSizeMustBeEqual : TaskError(3005)
/**
* This error is returned when [com.tangem.CardManager] was called with a new [Task],
* while a previous [Task] is still in progress.
*/
class Busy : TaskError(4000)
/**
* This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog.
*/
class UserCancelled : TaskError(4001)
//NFC errors
class NfcReaderError : TaskError(5002)
/**
* This error is returned when Android NFC reader loses a tag
* (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress.
*/
class TagLost : TaskError(5003)
class UnknownError : TaskError(6000)
//Specific Command Errors
/**
* This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter
* (when the card's requires it), but the counter is missing.
*/
class MissingCounter : TaskError(7001)
}
/**
* Events that are are sent in callbacks from [Task].
*/
sealed class TaskEvent<T> {
/**
* A callback that is triggered by a Task.
*/
class Event<T>(val data: T) : TaskEvent<T>()
/**
* A callback that is triggered when a [Task] is completed.
*
* @param error is null if it's a successful completion of a [Task]
*/
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
}
/**
* Allows to perform a group of commands interacting between the card and the application.
* A task opens an NFC session, sends commands to the card and receives its responses,
* repeats the commands if needed, and closes session after receiving the last answer.
*/
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.
*
* @param cardEnvironment Relevant current version of a card environment
* @param callback It will be triggered during the performance of the [Task]
*/
fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<T>) -> Unit) {
delegate?.onNfcSessionStarted(cardEnvironment.cardId)
reader?.openSession()
Log.i(this::class.simpleName!!, "Nfc task is started")
if (performPreflightRead) {
runWithPreflightRead(cardEnvironment, callback)
} else {
onRun(cardEnvironment, null, callback)
}
}
/**
* Should be called on [Task] completion, whether it was successful or with failure.
*
* @param taskError The error to be shown by [CardManagerDelegate]
*/
protected fun completeNfcSession(taskError: TaskError? = null) {
reader?.closeSession()
if (taskError != null) {
delegate?.onError(taskError)
} else {
delegate?.onNfcSessionCompleted()
}
}
/**
* In this method the individual Tasks' logic should be implemented.
*/
protected abstract fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<T>) -> Unit)
/**
* This method should be called by Tasks in their [onRun] method wherever
* they need to communicate with the Tangem Card by launching commands.
*/
protected fun <T : CommandResponse> sendCommand(
command: CommandSerializer<T>,
cardEnvironment: CardEnvironment,
callback: (result: CompletionResult<T>) -> Unit) {
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated")
when (cardEnvironment.encryptionMode) {
EncryptionMode.NONE -> {
val commandApdu = command.serialize(cardEnvironment)
sendRequest(command, commandApdu, cardEnvironment, callback)
}
EncryptionMode.FAST, EncryptionMode.STRONG -> {
if (cardEnvironment.encryptionKey != null ) {
val commandApdu = command.serialize(cardEnvironment)
sendRequest(command, commandApdu, cardEnvironment, callback)
return
}
val encryptionHelper: EncryptionHelper =
if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) {
StrongEncryptionHelper()
} else {
FastEncryptionHelper()
}
val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA)
val openSessionApdu = openSessionCommand.serialize(cardEnvironment)
sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
val uid = result.data.uid
val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50)
val secret = encryptionHelper.generateSecret(result.data.sessionKeyB)
val sessionKey = (secret + protocolKey).calculateSha256()
cardEnvironment.encryptionKey = sessionKey
sendCommand(command, cardEnvironment, callback)
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}
}
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 -> {
val responseApdu = result.data
when (responseApdu.statusWord) {
StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged
-> {
try {
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 -> {
Log.e(this::class.simpleName!!, "Unknown status error: ${result.data.sw}")
callback(CompletionResult.Failure(TaskError.UnknownStatus()))
}
StatusWord.ErrorProcessingCommand -> callback(CompletionResult.Failure(TaskError.ErrorProcessingCommand()))
StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState()))
StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported()))
StatusWord.NeedEncryption -> {
when (cardEnvironment.encryptionMode) {
EncryptionMode.NONE -> {
cardEnvironment.encryptionKey = null
cardEnvironment.encryptionMode = EncryptionMode.FAST
}
EncryptionMode.FAST -> {
cardEnvironment.encryptionKey = null
cardEnvironment.encryptionMode = EncryptionMode.STRONG
}
EncryptionMode.STRONG -> {
Log.e(this::class.simpleName!!, "Encryption doesn't work")
callback(CompletionResult.Failure(TaskError.NeedEncryption()))
return@transceiveApdu
}
}
sendCommand(command, cardEnvironment, callback)
}
StatusWord.NeedPause -> {
// NeedPause is returned from the card whenever security delay is triggered.
val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment)
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)
}
}
}
is CompletionResult.Failure ->
if (result.error == TaskError.TagLost()) {
delegate?.onTagLost()
} else if (result.error is TaskError.UserCancelled) {
callback(CompletionResult.Failure(TaskError.UserCancelled()))
reader?.closeSession()
}
}
}
}
private fun runWithPreflightRead(
environment: CardEnvironment, callback: (result: TaskEvent<T>) -> Unit) {
sendCommand(ReadCommand(), environment) { readResult ->
when (readResult) {
is CompletionResult.Failure -> {
completeNfcSession(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(TaskError.WrongCard())
callback(TaskEvent.Completion(TaskError.WrongCard()))
return@sendCommand
}
val newEnvironment = environment.copy(cardId = receivedCardId)
onRun(newEnvironment, readResult.data, callback)
}
}
}
}
}

View file

@ -1,67 +0,0 @@
package com.tangem.tasks
import com.tangem.commands.Card
import com.tangem.commands.Settings
import com.tangem.commands.WriteIssuerDataCommand
import com.tangem.commands.WriteIssuerDataResponse
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
class WriteIssuerDataTask(
private val issuerData: ByteArray,
private val issuerDataSignature: ByteArray,
private val issuerDataCounter: Int? = null,
private val issuerPublicKey: ByteArray? = null
) : Task<WriteIssuerDataResponse>() {
private lateinit var card: Card
override fun onRun(
cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
card = currentCard!!
val command = WriteIssuerDataCommand(
issuerData, issuerDataSignature, issuerDataCounter
)
if (!isCounterValid(issuerDataCounter)) {
completeNfcSession(TaskError.MissingCounter())
callback(TaskEvent.Completion(TaskError.MissingCounter()))
} else if (!verifySignature(command, cardEnvironment.cardId!!)) {
completeNfcSession(TaskError.VerificationFailed())
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
}
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
}
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelled) {
completeNfcSession(result.error)
}
callback(TaskEvent.Completion(result.error))
}
}
}
}
private fun isCounterValid(issuerDataCounter: Int?): Boolean =
if (isCounterRequired()) issuerDataCounter != null else true
private fun isCounterRequired(): Boolean =
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean {
return command.verify(
issuerPublicKey ?: card.issuerPublicKey!!,
issuerDataSignature,
IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
)
}
}

View file

@ -1,110 +0,0 @@
package com.tangem.tasks
import com.tangem.commands.*
import com.tangem.commands.common.IssuerDataMode
import com.tangem.commands.common.IssuerDataToVerify
import com.tangem.common.CardEnvironment
import com.tangem.common.CompletionResult
/**
* This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully
* written on the card.
* @param issuerData Data provided by issuer.
* @param startingSignature Issuers signature with Issuer Data Private Key of [cardId],
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
* @param finalizingSignature Issuers signature with Issuer Data Private Key of [cardId],
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
*/
internal class WriteIssuerExtraDataTask(
private val issuerData: ByteArray,
private val startingSignature: ByteArray,
private val finalizingSignature: ByteArray,
private val issuerPublicKey: ByteArray? = null,
private val issuerDataCounter: Int? = null
) : Task<WriteIssuerDataResponse>() {
private lateinit var card: Card
private lateinit var cardEnvironment: CardEnvironment
override fun onRun(cardEnvironment: CardEnvironment,
currentCard: Card?,
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
card = currentCard!!
this.cardEnvironment = cardEnvironment
val command = WriteIssuerExtraDataCommand(
issuerData, startingSignature, finalizingSignature, issuerDataCounter
)
if (!isCounterValid(issuerDataCounter)) {
completeNfcSession(TaskError.MissingCounter())
callback(TaskEvent.Completion(TaskError.MissingCounter()))
} else if (!verifySignatures(command)) {
completeNfcSession(TaskError.VerificationFailed())
callback(TaskEvent.Completion(TaskError.VerificationFailed()))
}
writeIssuerData(command, callback)
}
private fun writeIssuerData(
command: WriteIssuerExtraDataCommand,
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
if (command.mode == IssuerDataMode.WriteExtraData) {
delegate?.onDelay(issuerData.size, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE)
}
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
when (command.mode) {
IssuerDataMode.InitializeWritingExtraData -> {
command.mode = IssuerDataMode.WriteExtraData
writeIssuerData(command, callback)
return@sendCommand
}
IssuerDataMode.WriteExtraData -> {
command.offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE
if (command.offset >= issuerData.size) {
command.mode = IssuerDataMode.FinalizeExtraData
}
writeIssuerData(command, callback)
return@sendCommand
}
IssuerDataMode.FinalizeExtraData -> {
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
}
}
}
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelled) {
completeNfcSession(result.error)
}
callback(TaskEvent.Completion(result.error))
}
}
}
}
private fun isCounterValid(issuerDataCounter: Int?): Boolean =
if (isCounterRequired()) issuerDataCounter != null else true
private fun isCounterRequired(): Boolean =
card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false
private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean {
val publicKey = issuerPublicKey ?: card.issuerPublicKey!!
val cardId = cardEnvironment.cardId!!
val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size)
val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter)
return command.verify(publicKey, startingSignature, firstData) &&
command.verify(publicKey, finalizingSignature, secondData)
}
}

View file

@ -1,69 +1,69 @@
package com.tangem.common.tlv
import com.google.common.truth.Truth.assertThat
import com.tangem.SessionError
import com.tangem.commands.*
import com.tangem.common.extensions.hexToBytes
import com.tangem.tasks.TaskError
import org.junit.Test
import org.junit.jupiter.api.assertThrows
import java.util.*
class TlvMapperTest {
class TlvDecoderTest {
private val rawData = byteArrayOf(1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 32, 11, 83, 77, 65, 82, 84, 32, 67, 65, 83, 72, 0, 2, 1, 2, -128, 6, 50, 46, 49, 49, 114, 0, 3, 65, 4, -49, 11, -50, -66, -121, -25, -2, 65, 65, -13, 14, 49, 27, -82, -33, -85, -113, 65, 20, 8, -39, -75, 57, 45, 65, -31, 35, 44, 38, 40, 63, -44, 113, -45, -75, -95, -118, 118, 29, 65, 117, -24, -53, 82, -72, 91, -20, -96, -77, -103, -14, -63, 52, -127, -123, -27, -16, -128, -67, -3, -104, -26, -22, 65, 10, 4, 0, 0, 126, 33, 12, 90, -127, 2, 0, 41, -126, 4, 7, -29, 5, 2, -125, 7, 84, 65, 78, 71, 69, 77, 0, -124, 3, 69, 84, 72, -122, 64, 111, -103, 48, -114, -40, 18, -103, 26, -102, -12, -38, -78, -90, -9, -98, 88, -47, -100, -24, 24, -105, -70, -72, 6, 94, -96, -77, 11, -123, -28, -118, 37, 63, 107, -55, -11, 23, -12, 13, -23, -121, -63, 36, -59, 70, 116, 91, -125, -34, -69, 23, -112, 6, 17, 4, -49, 68, -56, 29, -45, 81, 10, 97, 83, 48, 65, 4, -127, -106, -86, 75, 65, 10, -60, 74, 59, -100, -50, 24, -25, -66, 34, 106, -22, 7, 10, -52, -125, -87, -49, 103, 84, 15, -84, 73, -81, 37, 18, -97, 106, 83, -118, 40, -83, 99, 65, 53, -114, 60, 79, -103, 99, 6, 79, 126, 54, 83, 114, -90, 81, -45, 116, -27, -62, 60, -35, 55, -3, 9, -101, -14, 5, 10, 115, 101, 99, 112, 50, 53, 54, 107, 49, 0, 8, 4, 0, 15, 66, 64, 7, 1, 0, 9, 2, 11, -72, 96, 65, 4, -42, -5, -41, -84, -23, 88, 2, 86, -63, -118, -123, -10, -66, -82, -107, -68, -93, 111, 47, 93, -20, -86, 74, 28, 21, 81, 93, -21, -124, -57, -102, 55, 17, 84, -66, -68, -22, -128, 126, -99, -65, -54, -42, 59, -25, -21, -124, 5, 59, -16, -72, 73, 48, 16, -27, 103, -112, -73, 2, 96, -51, 41, -42, 116, 98, 4, 0, 15, 66, 52, 99, 4, 0, 0, 0, 13, 15, 1, 0)
private val tlvData = Tlv.deserialize(rawData)
private val tlvMapper = TlvMapper(tlvData!!)
private val tlvMapper = TlvDecoder(tlvData!!)
private val cardDataRaw: ByteArray = tlvMapper.map(TlvTag.CardData)
private val cardDataMapper = TlvMapper(Tlv.deserialize(cardDataRaw)!!)
private val cardDataRaw: ByteArray = tlvMapper.decode(TlvTag.CardData)
private val cardDataMapper = TlvDecoder(Tlv.deserialize(cardDataRaw)!!)
@Test
fun `map optional when value is present`() {
val settingsMask: SettingsMask? = tlvMapper.mapOptional(TlvTag.SettingsMask)
val settingsMask: SettingsMask? = tlvMapper.decodeOptional(TlvTag.SettingsMask)
assertThat(settingsMask)
.isNotNull()
}
@Test
fun `map optional when no tag returns null`() {
val tokenSymbol: String? = tlvMapper.mapOptional(TlvTag.TokenSymbol)
val tokenSymbol: String? = tlvMapper.decodeOptional(TlvTag.TokenSymbol)
assertThat(tokenSymbol)
.isNull()
}
@Test
fun `map when value is null throws MissingTagException`() {
assertThrows<TaskError.MissingTag> {
tlvMapper.map<String>(TlvTag.TokenSymbol)
assertThrows<SessionError.DecodingFailedMissingTag> {
tlvMapper.decode<String>(TlvTag.TokenSymbol)
}
}
@Test
fun `map optional to wrong type throws WrongTypeException`() {
assertThrows<TaskError.WrongType> {
tlvMapper.mapOptional<String?>(TlvTag.CardData)
assertThrows<SessionError.DecodingFailedTypeMismatch> {
tlvMapper.decodeOptional<String?>(TlvTag.CardData)
}
}
@Test
fun `map to wrong type throws WrongTypeException`() {
assertThrows<TaskError.WrongType> {
tlvMapper.map<String>(TlvTag.CardData)
assertThrows<SessionError.DecodingFailedTypeMismatch> {
tlvMapper.decode<String>(TlvTag.CardData)
}
}
@Test
fun `map boolean missing flag returns false`() {
val terminalIsLinked: Boolean = tlvMapper.map(TlvTag.TerminalIsLinked)
val terminalIsLinked: Boolean = tlvMapper.decode(TlvTag.TerminalIsLinked)
assertThat(terminalIsLinked)
.isFalse()
}
@Test
fun `map SettingsMask returns correct value`() {
val settingsMask: SettingsMask = tlvMapper.map(TlvTag.SettingsMask)
val settingsMask: SettingsMask = tlvMapper.decode(TlvTag.SettingsMask)
assertThat(settingsMask)
.isNotNull()
assertThat(settingsMask.rawValue)
@ -82,16 +82,16 @@ class TlvMapperTest {
@Test
fun `map SigningMethods single value returns correct value`() {
val signingMethods: SigningMethod = tlvMapper.map(TlvTag.SigningMethod)
val signingMethods: SigningMethod = tlvMapper.decode(TlvTag.SigningMethod)
assertThat(signingMethods.contains(SigningMethod.signHash))
.isTrue()
}
@Test
fun `map SigningMethods set of methods returns correct value`() {
val localMapper = TlvMapper(Tlv.deserialize("070195".hexToBytes())!!)
val localMapper = TlvDecoder(Tlv.deserialize("070195".hexToBytes())!!)
val signingMethod: SigningMethod = localMapper.map(TlvTag.SigningMethod)
val signingMethod: SigningMethod = localMapper.decode(TlvTag.SigningMethod)
assertThat(signingMethod.contains(SigningMethod.signHash))
.isTrue()
assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuer))
@ -110,38 +110,38 @@ class TlvMapperTest {
@Test
fun `map CardStatus returns correct value`() {
val cardStatus: CardStatus = tlvMapper.map(TlvTag.Status)
val cardStatus: CardStatus = tlvMapper.decode(TlvTag.Status)
assertThat(cardStatus)
.isEqualTo(CardStatus.Loaded)
}
@Test
fun `map ProductMask with raw value 5 returns correct value`() {
val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5))))
val productMask: ProductMask = localMapper.map(TlvTag.ProductMask)
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5))))
val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask)
assertThat(productMask.contains(ProductMask.note) && productMask.contains(ProductMask.idCard))
.isTrue()
}
@Test
fun `map ProductMask with raw value 1 returns correct value`() {
val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1))))
val productMask: ProductMask = localMapper.map(TlvTag.ProductMask)
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1))))
val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask)
assertThat(productMask.contains(ProductMask.note))
.isTrue()
}
@Test
fun `map Enum with unknown code throws ConversionException error`() {
val localMapper = TlvMapper(listOf(Tlv(TlvTag.CurveId, "test".toByteArray())))
assertThrows<TaskError.ConvertError> {
localMapper.map<EllipticCurve>(TlvTag.CurveId)
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.CurveId, "test".toByteArray())))
assertThrows<SessionError.DecodingFailed> {
localMapper.decode<EllipticCurve>(TlvTag.CurveId)
}
}
@Test
fun `map DateTime returns correct value`() {
val date: Date = cardDataMapper.map(TlvTag.ManufactureDateTime)
val date: Date = cardDataMapper.decode(TlvTag.ManufactureDateTime)
val expected = Calendar.getInstance().apply { this.set(2019, 4, 2, 0, 0, 0) }.time
assertThat(date.toString())
.isEqualTo(expected.toString())
@ -149,43 +149,43 @@ class TlvMapperTest {
@Test
fun `map EllipticCurve returns correct value`() {
val ellipticCurve: EllipticCurve = tlvMapper.map(TlvTag.CurveId)
val ellipticCurve: EllipticCurve = tlvMapper.decode(TlvTag.CurveId)
assertThat(ellipticCurve)
.isEqualTo(EllipticCurve.Secp256k1)
}
@Test
fun `map ByteArray returns correctly`() {
val cardPublicKey: ByteArray = tlvMapper.map(TlvTag.CardPublicKey)
val cardPublicKey: ByteArray = tlvMapper.decode(TlvTag.CardPublicKey)
assertThat(cardPublicKey)
.isInstanceOf(ByteArray::class.java)
}
@Test
fun `map Int returns correct value`() {
val signedHashes: Int = tlvMapper.map(TlvTag.SignedHashes)
val signedHashes: Int = tlvMapper.decode(TlvTag.SignedHashes)
assertThat(signedHashes)
.isEqualTo(13)
}
@Test
fun `map Int with wrong value throws ConversionException`() {
val localMapper = TlvMapper(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5))))
assertThrows<TaskError.ConvertError> {
localMapper.map<Int>(TlvTag.SignedHashes)
val localMapper = TlvDecoder(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5))))
assertThrows<SessionError.DecodingFailed> {
localMapper.decode<Int>(TlvTag.SignedHashes)
}
}
@Test
fun `map UTF8 returns correct value`() {
val blockchainId: String = cardDataMapper.map(TlvTag.BlockchainId)
val blockchainId: String = cardDataMapper.decode(TlvTag.BlockchainId)
assertThat(blockchainId)
.isEqualTo("ETH")
}
@Test
fun `map Hex returns correct value`() {
val cardId: String = tlvMapper.map(TlvTag.CardId)
val cardId: String = tlvMapper.decode(TlvTag.CardId)
assertThat(cardId)
.isEqualTo("cb22000000027374")
}

View file

@ -3,16 +3,14 @@ package com.tangem.tangemtest
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.common.CompletionResult
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
import kotlinx.android.synthetic.main.old_activity_main.*
class Old_MainActivity : AppCompatActivity() {
private lateinit var cardManager: CardManager
private lateinit var tangemSdk: TangemSdk
private lateinit var cardId: String
private lateinit var issuerData: ByteArray
private lateinit var issuerDataSignature: ByteArray
@ -22,66 +20,50 @@ class Old_MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.old_activity_main)
cardManager = CardManager.init(this)
tangemSdk = TangemSdk.init(this)
btn_scan?.setOnClickListener { _ ->
cardManager.scanCard { taskEvent ->
tangemSdk.scanCard { taskEvent ->
when (taskEvent) {
is TaskEvent.Event -> {
when (taskEvent.data) {
is ScanEvent.OnReadEvent -> {
// Handle returned card data
cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
runOnUiThread {
tv_card_cid?.text = cardId
btn_create_wallet.isEnabled = true
}
}
is ScanEvent.OnVerifyEvent -> {
//Handle card verification
runOnUiThread {
tv_card_cid?.text = cardId
btn_sign.isEnabled = true
btn_read_issuer_data.isEnabled = true
btn_read_issuer_extra_data.isEnabled = true
btn_write_issuer_data.isEnabled = true
btn_purge_wallet.isEnabled = true
btn_create_wallet.isEnabled = true
}
}
is CompletionResult.Success -> {
// Handle returned card data
val card = taskEvent.data
cardId = card.cardId
runOnUiThread {
tv_card_cid?.text = cardId
btn_create_wallet.isEnabled = true
tv_card_cid?.text = cardId
btn_sign.isEnabled = true
btn_read_issuer_data.isEnabled = true
btn_read_issuer_extra_data.isEnabled = true
btn_write_issuer_data.isEnabled = true
btn_purge_wallet.isEnabled = true
btn_create_wallet.isEnabled = true
}
}
is TaskEvent.Completion -> {
if (taskEvent.error != null) {
if (taskEvent.error is TaskError.UserCancelled) {
// Handle case when user cancelled manually
}
// Handle other errors
}
// Handle completion
}
}
}
}
btn_sign?.setOnClickListener { _ ->
cardManager.sign(
tangemSdk.sign(
createSampleHashes(),
cardId) {
when (it) {
is TaskEvent.Completion -> {
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
is CompletionResult.Failure -> {
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
}
is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." }
is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." }
}
}
}
btn_read_issuer_data?.setOnClickListener { _ ->
cardManager.readIssuerData(cardId) {
tangemSdk.readIssuerData(cardId) {
when (it) {
is TaskEvent.Completion -> {
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
is CompletionResult.Failure -> {
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
}
is TaskEvent.Event -> runOnUiThread {
is CompletionResult.Success -> runOnUiThread {
btn_write_issuer_data.isEnabled = true
tv_card_cid?.text = it.data.issuerData.contentToString()
issuerData = it.data.issuerData
@ -91,27 +73,27 @@ class Old_MainActivity : AppCompatActivity() {
}
}
btn_write_issuer_data?.setOnClickListener { _ ->
cardManager.writeIssuerData(
tangemSdk.writeIssuerData(
cardId,
issuerData,
issuerDataSignature) {
when (it) {
is TaskEvent.Completion -> {
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
is CompletionResult.Failure -> {
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
}
is TaskEvent.Event -> runOnUiThread {
is CompletionResult.Success -> runOnUiThread {
tv_card_cid?.text = it.data.cardId
}
}
}
}
btn_read_issuer_extra_data?.setOnClickListener { _ ->
cardManager.readIssuerExtraData(cardId) {
tangemSdk.readIssuerExtraData(cardId) {
when (it) {
is TaskEvent.Completion -> {
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
is CompletionResult.Failure -> {
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
}
is TaskEvent.Event -> runOnUiThread {
is CompletionResult.Success -> runOnUiThread {
issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1
btn_write_issuer_data.isEnabled = true
tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data."
@ -120,26 +102,26 @@ class Old_MainActivity : AppCompatActivity() {
}
}
btn_purge_wallet?.setOnClickListener { _ ->
cardManager.purgeWallet(
tangemSdk.purgeWallet(
cardId) {
when (it) {
is TaskEvent.Completion -> {
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
is CompletionResult.Failure -> {
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
}
is TaskEvent.Event -> runOnUiThread {
is CompletionResult.Success -> runOnUiThread {
tv_card_cid?.text = it.data.status.name
}
}
}
}
btn_create_wallet?.setOnClickListener { _ ->
cardManager.createWallet(
tangemSdk.createWallet(
cardId) {
when (it) {
is TaskEvent.Completion -> {
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
is CompletionResult.Failure -> {
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
}
is TaskEvent.Event -> runOnUiThread {
is CompletionResult.Success -> runOnUiThread {
tv_card_cid?.text = it.data.status.name
btn_sign.isEnabled = true
btn_read_issuer_data.isEnabled = true

View file

@ -5,14 +5,11 @@ import android.view.View
import android.widget.CompoundButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.tangem.CardManager
import com.tangem.commands.ReadUserDataResponse
import com.tangem.commands.WriteUserDataResponse
import com.tangem.common.CardEnvironment
import com.tangem.SessionEnvironment
import com.tangem.SessionError
import com.tangem.TangemSdk
import com.tangem.common.CompletionResult
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskError
import com.tangem.tasks.TaskEvent
import kotlinx.android.synthetic.main.activity_test_user_data.*
import java.nio.charset.StandardCharsets
@ -21,7 +18,7 @@ import java.nio.charset.StandardCharsets
*/
class TestUserDataActivity : AppCompatActivity() {
private lateinit var cardManager: CardManager
private lateinit var tangemSdk: TangemSdk
private lateinit var writeOptions: WriteOptions
override fun onCreate(savedInstanceState: Bundle?) {
@ -33,31 +30,15 @@ class TestUserDataActivity : AppCompatActivity() {
}
private fun init() {
cardManager = CardManager.init(this)
tangemSdk = TangemSdk.init(this)
btn_scan?.setOnClickListener { _ ->
cardManager.scanCard { taskEvent ->
tangemSdk.scanCard { taskEvent ->
when (taskEvent) {
is TaskEvent.Event -> {
when (taskEvent.data) {
is ScanEvent.OnReadEvent -> {
// Handle returned card data
writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
runOnUiThread { showReadWriteSection(true) }
}
is ScanEvent.OnVerifyEvent -> {
//Handle card verification
}
}
}
is TaskEvent.Completion -> {
if (taskEvent.error != null) {
if (taskEvent.error is TaskError.UserCancelled) {
// Handle case when user cancelled manually
}
// Handle other errors
}
// Handle completion
is CompletionResult.Success -> {
// Handle returned card data
writeOptions.cardId = taskEvent.data.cardId
runOnUiThread { showReadWriteSection(true) }
}
}
}
@ -66,7 +47,7 @@ class TestUserDataActivity : AppCompatActivity() {
btn_write.setOnClickListener {
if (writeOptions.cardId == null) return@setOnClickListener
cardManager.writeUserData(
tangemSdk.writeUserData(
writeOptions.cardId!!,
writeOptions.userData,
writeOptions.userProtectedData,
@ -74,16 +55,9 @@ class TestUserDataActivity : AppCompatActivity() {
writeOptions.userProtectedCounter
) {
when (it) {
is TaskEvent.Completion -> handleError(tv_write_result, it.error)
is TaskEvent.Event -> {
runOnUiThread {
val data = it.data as? WriteUserDataResponse
if (data == null) {
tv_write_result.text = "Response doesn't match"
return@runOnUiThread
}
tv_write_result?.text = "Success"
}
is CompletionResult.Failure -> handleError(tv_write_result, it.error)
is CompletionResult.Success -> {
runOnUiThread { tv_write_result?.text = "Success" }
}
}
}
@ -92,28 +66,24 @@ class TestUserDataActivity : AppCompatActivity() {
btn_read.setOnClickListener {
if (writeOptions.cardId == null) return@setOnClickListener
cardManager.readUserData(writeOptions.cardId!!) {
tangemSdk.readUserData(writeOptions.cardId!!) {
when (it) {
is TaskEvent.Completion -> handleError(tv_read_result, it.error)
is TaskEvent.Event -> {
is CompletionResult.Failure -> handleError(tv_write_result, it.error)
is CompletionResult.Success -> {
runOnUiThread {
val data = it.data as? ReadUserDataResponse
if (data == null) {
tv_read_result.text = "Response doesn't match"
return@runOnUiThread
}
tv_read_result?.text = "Success"
writeOptions.userData = data.userData
writeOptions.userProtectedData = data.userProtectedData
writeOptions.userCounter = data.userCounter
writeOptions.userProtectedCounter = data.userProtectedCounter
writeOptions.userData = it.data.userData
writeOptions.userProtectedData = it.data.userProtectedData
writeOptions.userCounter = it.data.userCounter
writeOptions.userProtectedCounter = it.data.userProtectedCounter
tv_card_cid.text = data.cardId
tv_data.text = String(data.userData, StandardCharsets.US_ASCII)
tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII)
tv_counter.text = data.userCounter.toString()
tv_protected_counter.text = data.userProtectedCounter.toString()
tv_card_cid.text = it.data.cardId
tv_data.text = String(it.data.userData, StandardCharsets.US_ASCII)
tv_protected_data.text = String(it.data.userProtectedData, StandardCharsets.US_ASCII)
tv_counter.text = it.data.userCounter.toString()
tv_protected_counter.text = it.data.userProtectedCounter.toString()
}
}
@ -122,11 +92,10 @@ class TestUserDataActivity : AppCompatActivity() {
}
}
private fun handleError(tv: TextView, error: TaskError?) {
val er = error ?: return
if (er is TaskError.UserCancelled) return
private fun handleError(tv: TextView, error: SessionError) {
if (error is SessionError.UserCancelled) return
runOnUiThread { tv.text = er::class.simpleName }
runOnUiThread { tv.text = error::class.simpleName }
}
private fun initWriteOptions() {
@ -174,7 +143,7 @@ class WriteOptions {
}
fun updatePin2(chbx: CompoundButton) {
val value = CardEnvironment.DEFAULT_PIN2
val value = SessionEnvironment.DEFAULT_PIN2
pin2 = if (chbx.isChecked) value else null
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.tangemtest._main
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.tangem.tasks.TaskEvent
import com.tangem.commands.CommandResponse
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
@ -11,16 +11,16 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
class MainViewModel : ViewModel() {
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
var responseEvent: TaskEvent<*>? = null
var commandResponse: CommandResponse? = null
fun switchToggled(state: Boolean) {
ldDescriptionSwitch.postValue(state)
}
fun changeResponseEvent(event: TaskEvent<*>?) {
fun changeResponseEvent(commandResponse: CommandResponse?) {
Log.d(this, "changeResponseEvent")
val reqEvent = event ?: return
val response = commandResponse ?: return
responseEvent = reqEvent
this.commandResponse = response
}
}

View file

@ -1,6 +1,7 @@
package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.PayloadHolder
@ -8,7 +9,6 @@ import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import com.tangem.tasks.TaskEvent
/**
[REDACTED_AUTHOR]
@ -17,7 +17,7 @@ import com.tangem.tasks.TaskEvent
* and then processing the response. It also allows you to extract the main action as a lambda expression
*/
data class AttrForAction(
val cardManager: CardManager,
val tangemSdk: TangemSdk,
val itemList: List<Item>,
val payload: Payload,
val consequence: ItemsChangeConsequence?
@ -31,17 +31,17 @@ interface Action {
abstract class BaseAction : Action {
protected open fun handleResult(
payload: PayloadHolder,
taskEvent: TaskEvent<*>,
commandResult: CompletionResult<*>,
modifier: AfterActionModification?,
attrs: AttrForAction,
callback: ActionCallback
) {
val modifiedItems = mutableListOf<Item>()
modifier?.modify(payload, taskEvent, attrs.itemList)?.forEach { item ->
modifier?.modify(payload, commandResult, attrs.itemList)?.forEach { item ->
modifiedItems.add(item)
attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) }
}
callback(taskEvent, modifiedItems)
callback(commandResult, modifiedItems)
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null

View file

@ -14,7 +14,7 @@ class DepersonalizeAction : BaseAction() {
val item = attrs.itemList.findItem(TlvId.CardId) ?: return
val cardId = item.viewModel.data as? String ?: return
attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {

View file

@ -40,7 +40,7 @@ class PersonalizeAction : BaseAction() {
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig())
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) {
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {
handleResult(payload, it, null, attrs, callback)
}
}

View file

@ -9,6 +9,6 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.Aft
*/
class ScanAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
attrs.cardManager.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) }
attrs.tangemSdk.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) }
}
}

View file

@ -16,7 +16,7 @@ class SignAction : BaseAction() {
val hash = dataForHashing.getData() as? ByteArray ?: return
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {

View file

@ -1,14 +1,14 @@
package com.tangem.tangemtest.ucase.domain.paramsManager
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import com.tangem.tasks.TaskEvent
typealias ActionResponse = TaskEvent<*>
typealias ActionResponse = CompletionResult<*>
typealias AffectedList = List<Item>
typealias AffectedItemsCallback = (AffectedList) -> Unit
typealias ActionCallback = (ActionResponse, AffectedList) -> Unit
@ -22,8 +22,8 @@ interface ItemsManager : PayloadHolder {
fun setItems(items: List<Item>)
fun getItems(): List<Item>
fun setItemChangeConsequences(consequence: ItemsChangeConsequence?)
fun invokeMainAction(cardManager: CardManager, callback: ActionCallback)
fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)?
fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback)
fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)?
fun attachPayload(payload: Payload)
}

View file

@ -1,7 +1,7 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import androidx.lifecycle.LifecycleObserver
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.abstraction.Item
@ -47,12 +47,12 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
this.changeConsequence = consequence
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
}
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(cardManager))
override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(tangemSdk))
}
override fun attachPayload(payload: Payload) {
@ -64,6 +64,6 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) }
}
protected fun getAttrsForAction(cardManager: CardManager)
: AttrForAction = AttrForAction(cardManager, itemList, payload, changeConsequence)
protected fun getAttrsForAction(tangemSdk: TangemSdk)
: AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence)
}

View file

@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.OnLifecycleEvent
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.tangemtest.commons.Store
import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
@ -23,8 +23,8 @@ class PersonalizationItemsManager(
setItems(converter.convert(config))
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)

View file

@ -1,8 +1,8 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tasks.TaskEvent
/**
[REDACTED_AUTHOR]
@ -12,5 +12,5 @@ import com.tangem.tasks.TaskEvent
* Returns a list of items that have been modified
*/
interface AfterActionModification {
fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List<Item>): List<Item>
fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List<Item>): List<Item>
}

View file

@ -2,6 +2,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findItem
@ -9,17 +10,16 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.CardError
import com.tangem.tangemtest.ucase.variants.TlvId
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskEvent
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
/**
[REDACTED_AUTHOR]
*/
class AfterScanModifier : AfterActionModification {
override fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List<Item>): List<Item> {
override fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List<Item>): List<Item> {
val foundItem = itemList.findItem(TlvId.CardId) ?: return listOf()
val card = smartCast(taskEvent)?.card ?: return listOf()
val card = smartCast(commandResult) ?: return listOf()
val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf()
return if (isNotPersonalized(card)) {
@ -34,8 +34,8 @@ class AfterScanModifier : AfterActionModification {
}
}
private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? {
return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent
private fun smartCast(commandResult: CompletionResult<*>): Card? {
return (commandResult as? CompletionResult.Success<Card>)?.data
}
private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized

View file

@ -1,12 +1,8 @@
package com.tangem.tangemtest.ucase.domain.responses
import com.google.gson.*
import com.tangem.commands.ProductMask
import com.tangem.commands.Settings
import com.tangem.commands.SettingsMask
import com.tangem.commands.SigningMethod
import com.tangem.commands.*
import com.tangem.common.extensions.toHexString
import com.tangem.tasks.TaskEvent
import java.lang.reflect.Type
import java.text.DateFormat
import java.util.*
@ -29,12 +25,7 @@ class ResponseJsonConverter {
return builder.create()
}
fun convertTaskEvent(task: TaskEvent<*>?): String = gson.toJson(task)
fun convertEvent(task: TaskEvent<*>?): String {
val event = task as? TaskEvent.Event ?: ""
return gson.toJson(event)
}
fun convertResponse(response: CommandResponse?): String = gson.toJson(response)
}
class ByteTypeAdapter : JsonSerializer<ByteArray> {

View file

@ -4,13 +4,16 @@ import android.view.View
import androidx.annotation.UiThread
import androidx.lifecycle.*
import com.google.gson.Gson
import com.tangem.CardManager
import com.tangem.SessionError
import com.tangem.TangemSdk
import com.tangem.commands.Card
import com.tangem.commands.CommandResponse
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.SingleLiveEvent
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest.commons.performAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.responses.ResponseJsonConverter
import com.tangem.tangemtest.ucase.resources.ActionType
@ -30,19 +33,19 @@ class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvi
class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver {
val seResponseEvent = SingleLiveEvent<TaskEvent<*>>()
val seReadResponse = SingleLiveEvent<String>()
val seResponse = SingleLiveEvent<String>()
val seResponse = SingleLiveEvent<CommandResponse>()
val seResponseData = SingleLiveEvent<CommandResponse>()
val seResponseCardData = SingleLiveEvent<Card>()
val ldItemList = MutableLiveData(itemsManager.getItems())
val seError: MutableLiveData<String> = SingleLiveEvent()
val seChangedItems: MutableLiveData<List<Item>> = SingleLiveEvent()
private val notifier: Notifier = Notifier(this)
private lateinit var cardManager: CardManager
private lateinit var tangemSdk: TangemSdk
fun setCardManager(cardManager: CardManager) {
this.cardManager = cardManager
fun setCardManager(tangemSdk: TangemSdk) {
this.tangemSdk = tangemSdk
}
@Deprecated("Events must be send directly from the Widget")
@ -56,15 +59,17 @@ class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), Lif
//invokes Scan, Sign etc...
fun invokeMainAction() {
performAction(itemsManager, cardManager, { paramsManager, cardManager ->
paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams ->
notifier.handleActionResult(response, listOfChangedParams)
}
})
if (!::tangemSdk.isInitialized) {
Log.e(this, "TangemSdk isn't initialized")
return
}
itemsManager.invokeMainAction(tangemSdk) { response, listOfChangedParams ->
notifier.handleActionResult(response, listOfChangedParams)
}
}
fun getItemAction(id: Id): (() -> Unit)? {
val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null
val itemFunction = itemsManager.getActionByTag(id, tangemSdk) ?: return null
return {
itemFunction { response, listOfChangedParams ->
@ -128,12 +133,12 @@ class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), Lif
internal class Notifier(private val vm: ActionViewModel) {
private var notShowedError: TaskError? = null
private var notShowedError: SessionError? = null
private val gson: Gson = ResponseJsonConverter().gson
fun handleActionResult(response: TaskEvent<*>, list: List<Item>) {
fun handleActionResult(result: CompletionResult<*>, list: List<Item>) {
if (list.isNotEmpty()) notifyItemsChanged(list)
handleResponse(response)
handleCompletionResult(result)
}
@UiThread
@ -141,47 +146,35 @@ internal class Notifier(private val vm: ActionViewModel) {
vm.seChangedItems.postValue(list)
}
fun handleResponse(response: TaskEvent<*>) {
val taskEvent = response as? TaskEvent<*> ?: return
fun handleCompletionResult(result: CompletionResult<*>) {
val commandResponse = result as? CompletionResult<CommandResponse> ?: return
when (taskEvent) {
is TaskEvent.Completion -> handleCompletionEvent(taskEvent)
is TaskEvent.Event -> {
handleDataEvent(taskEvent.data)
vm.seResponseEvent.postValue(taskEvent)
}
when (commandResponse) {
is CompletionResult.Success -> handleData(commandResponse.data)
is CompletionResult.Failure -> handleError(commandResponse.error)
}
}
private fun handleDataEvent(event: Any?) {
private fun handleData(event: CommandResponse?) {
vm.seResponse.postValue(event)
when (event) {
is ScanEvent.OnReadEvent -> vm.seReadResponse.postValue(gson.toJson(event))
is ScanEvent.OnVerifyEvent -> {
}
else -> vm.seResponse.postValue(gson.toJson(event))
is Card -> vm.seResponseCardData.postValue(event)
else -> vm.seResponseData.postValue(event)
}
}
private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) {
if (taskEvent.error == null) {
Log.d(this, "error = null")
if (notShowedError != null) {
vm.seError.postValue("${notShowedError!!::class.simpleName}")
notShowedError = null
}
} else {
Log.d(this, "error = ${taskEvent.error}")
when (taskEvent.error) {
is TaskError.UserCancelled -> {
if (notShowedError == null) {
vm.seError.postValue("User canceled the action")
} else {
vm.seError.postValue("${notShowedError!!::class.simpleName}")
notShowedError = null
}
private fun handleError(error: SessionError) {
Log.d(this, "error = $error")
when (error) {
is SessionError.UserCancelled -> {
if (notShowedError == null) {
vm.seError.postValue("User canceled the action")
} else {
vm.seError.postValue("${notShowedError!!::class.simpleName}")
notShowedError = null
}
else -> notShowedError = taskEvent.error
}
else -> notShowedError = error
}
}
}

View file

@ -9,7 +9,8 @@ import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.tangem.CardManager
import com.tangem.TangemSdk
import com.tangem.commands.Card
import com.tangem.tangem_sdk_new.extensions.init
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
@ -43,7 +44,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
bindViews()
viewLifecycleOwner.lifecycle.addObserver(actionVM)
actionVM.setCardManager(CardManager.init(requireActivity()))
actionVM.setCardManager(TangemSdk.init(requireActivity()))
actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
initFab()
@ -80,28 +81,37 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
protected open fun subscribeToViewModelChanges() {
Log.d(this, "subscribeToViewModelChanges")
listenEvent()
listenReadResponse()
listenResponse()
listenResponseData()
listenResponseCardData()
listenError()
listenChangedItems()
listenDescriptionSwitchChanges()
}
protected open fun listenEvent() {
actionVM.seResponseEvent.observe(viewLifecycleOwner, Observer {
private fun listenResponse() {
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen response: $it")
mainActivityVM.changeResponseEvent(it)
})
}
protected open fun listenReadResponse() {}
protected open fun listenResponse() {
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
protected open fun listenResponseData() {
actionVM.seResponseData.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen responseData: $it")
navigateTo(R.id.action_nav_card_action_to_response_screen)
})
}
protected open fun listenResponseCardData() {
actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer {
Log.d(this, "listen responseCardData: $it")
responseCardDataHandled(it)
})
}
protected open fun responseCardDataHandled(card: Card?) {}
protected open fun listenError() {
actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
}

View file

@ -1,9 +1,9 @@
package com.tangem.tangemtest.ucase.variants.personalize.dto
import com.tangem.KeyPair
import com.tangem.commands.personalization.entities.Acquirer
import com.tangem.commands.personalization.entities.Issuer
import com.tangem.commands.personalization.entities.Manufacturer
import com.tangem.common.KeyPair
/**
[REDACTED_AUTHOR]

View file

@ -5,6 +5,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.lifecycle.Observer
import com.tangem.commands.Card
import com.tangem.tangemtest.R
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.StringId
@ -78,20 +79,6 @@ class PersonalizationFragment : BaseCardActionFragment() {
})
}
override fun subscribeToViewModelChanges() {
Log.d(this, "subscribeToViewModelChanges")
listenEvent()
listenError()
listenDescriptionSwitchChanges()
}
override fun listenEvent() {
actionVM.seResponseEvent.observe(viewLifecycleOwner, Observer {
mainActivityVM.changeResponseEvent(it)
navigateTo(R.id.action_nav_card_action_to_response_screen)
})
}
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
super.showSnackbar(id) {
when (id) {
@ -101,4 +88,9 @@ class PersonalizationFragment : BaseCardActionFragment() {
}
}
}
override fun responseCardDataHandled(card: Card?) {
super.responseCardDataHandled(card)
navigateTo(R.id.action_nav_card_action_to_response_screen)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.variants.responses
import android.view.View
import androidx.lifecycle.ViewModel
import com.tangem.commands.Card
import com.tangem.commands.CommandResponse
import com.tangem.commands.SignResponse
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.tangemtest.R
@ -10,8 +11,6 @@ import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
import com.tangem.tangemtest._arch.structure.abstraction.iterate
import com.tangem.tangemtest.ucase.variants.responses.converter.ConvertersStore
import com.tangem.tasks.ScanEvent
import com.tangem.tasks.TaskEvent
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
@ -22,13 +21,13 @@ class ResponseViewModel : ViewModel() {
private val convertersHolder = ConvertersStore()
private var itemList: List<Item>? = null
fun createItemList(taskEvent: TaskEvent<*>?): List<Item> {
fun createItemList(response: CommandResponse?): List<Item> {
Log.d(this, "createItemList: itemList size: ${itemList?.size ?: 0}")
val event = taskEvent as? TaskEvent.Event<Any> ?: return emptyList()
val type = event.data::class.java
val responseEvent = response ?: return emptyList()
val type = responseEvent::class.java
val converter = convertersHolder.get(type) as? ModelToItems<Any> ?: return emptyList()
itemList = converter.convert(event.data)
itemList = converter.convert(responseEvent)
return itemList!!
}
@ -38,13 +37,12 @@ class ResponseViewModel : ViewModel() {
}
}
fun determineTitleId(taskEvent: TaskEvent<*>?): Int {
val event = taskEvent as? TaskEvent.Event<Any> ?: return R.string.unknown
fun determineTitleId(response: CommandResponse?): Int {
val responseEvent = response ?: return R.string.unknown
return when (event.data) {
is ScanEvent.OnReadEvent -> R.string.fg_name_response_scan
is SignResponse -> R.string.fg_name_response_sign
return when (responseEvent) {
is Card -> R.string.fg_name_response_personalization
is SignResponse -> R.string.fg_name_response_sign
is DepersonalizeResponse -> R.string.fg_name_response_depersonalization
else -> R.string.unknown
}

View file

@ -3,13 +3,12 @@ package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.SignResponse
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.tasks.ScanEvent
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
import java.lang.reflect.Type
class ConvertersStore : BaseTypedHolder<Type, Any>() {
init {
register(ScanEvent.OnReadEvent::class.java, ReadEventConverter())
// register(CompletionResult.Success<Card>::class.java, ReadEventConverter())
register(SignResponse::class.java, SignResponseConverter())
register(Card::class.java, CardConverter())
register(DepersonalizeResponse::class.java, DepersonalizeResponseConverter())

View file

@ -1,19 +1,20 @@
package com.tangem.tangemtest.ucase.variants.responses.converter
import com.tangem.commands.Card
import com.tangem.commands.SignResponse
import com.tangem.commands.personalization.DepersonalizeResponse
import com.tangem.common.CompletionResult
import com.tangem.tangemtest._arch.structure.StringId
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
import com.tangem.tangemtest._arch.structure.impl.TextItem
import com.tangem.tasks.ScanEvent
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
[REDACTED_AUTHOR]
*/
class ReadEventConverter : ModelToItems<ScanEvent.OnReadEvent> {
override fun convert(from: ScanEvent.OnReadEvent): List<Item> = CardConverter().convert(from.card)
class ReadEventConverter : ModelToItems<CompletionResult.Success<Card>> {
override fun convert(from: CompletionResult.Success<Card>): List<Item> = CardConverter().convert(from.data)
}
class SignResponseConverter : ModelToItems<SignResponse> {

View file

@ -33,7 +33,7 @@ open class ResponseFragment : BaseFragment() {
}
private fun setTittle() {
val titleId = selfVM.determineTitleId(mainActivityVM.responseEvent)
val titleId = selfVM.determineTitleId(mainActivityVM.commandResponse)
activity?.setTitle(titleId)
}
@ -47,7 +47,7 @@ open class ResponseFragment : BaseFragment() {
private fun buildWidgets() {
val builder = WidgetBuilder(ResponseItemBuilder())
val itemList = selfVM.createItemList(mainActivityVM.responseEvent)
val itemList = selfVM.createItemList(mainActivityVM.commandResponse)
itemList.forEach { builder.build(it, itemContainer) }
}
@ -67,7 +67,7 @@ open class ResponseFragment : BaseFragment() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_share -> {
shareText(ResponseJsonConverter().convertEvent(mainActivityVM.responseEvent))
shareText(ResponseJsonConverter().convertResponse(mainActivityVM.commandResponse))
}
}
return super.onOptionsItemSelected(item)

View file

@ -1,8 +1,6 @@
package com.tangem.tangemtest.ucase.variants.scan.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import com.tangem.commands.Card
import com.tangem.tangemtest.R
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
@ -17,14 +15,12 @@ class ScanActionFragment : BaseCardActionFragment() {
override fun getLayoutId(): Int = R.layout.fg_action_card_scan
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
enableActionFab(true)
override fun initFab() {
actionFab.setOnClickListener { actionVM.invokeMainAction() }
}
override fun listenReadResponse() {
actionVM.seReadResponse.observe(viewLifecycleOwner, Observer {
navigateTo(R.id.action_nav_card_action_to_response_screen)
})
override fun responseCardDataHandled(card: Card?) {
super.responseCardDataHandled(card)
navigateTo(R.id.action_nav_card_action_to_response_screen)
}
}

View file

@ -6,24 +6,24 @@ import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.fragment.app.FragmentActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.tangem.CardManagerDelegate
import com.tangem.Log
import com.tangem.LoggerInterface
import com.tangem.Message
import com.tangem.SessionViewDelegate
import com.tangem.common.CompletionResult
import com.tangem.tangem_sdk_new.extensions.hide
import com.tangem.tangem_sdk_new.extensions.show
import com.tangem.tangem_sdk_new.nfc.NfcReader
import com.tangem.tangem_sdk_new.ui.NfcEnableDialog
import com.tangem.tangem_sdk_new.ui.TouchCardAnimation
import com.tangem.tasks.TaskError
import kotlinx.android.synthetic.main.layout_touch_card.*
import kotlinx.android.synthetic.main.nfc_bottom_sheet.*
/**
* Default implementation of [CardManagerDelegate].
* Default implementation of [SessionViewDelegate].
* If no customisation is required, this is the preferred way to use Tangem SDK.
*/
class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDelegate {
class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDelegate {
lateinit var activity: FragmentActivity
private var readingDialog: BottomSheetDialog? = null
@ -33,13 +33,13 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
setLogger()
}
override fun onNfcSessionStarted(cardId: String?) {
override fun onNfcSessionStarted(cardId: String?, message: Message?) {
reader.readingCancelled = false
postUI { showReadingDialog(activity, cardId) }
postUI { showReadingDialog(activity, cardId, message) }
if (!reader.nfcEnabled) showNFCEnableDialog()
}
private fun showReadingDialog(activity: FragmentActivity, cardId: String?) {
private fun showReadingDialog(activity: FragmentActivity, cardId: String?, message: Message?) {
val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null)
readingDialog = BottomSheetDialog(activity)
readingDialog?.setContentView(dialogView)
@ -56,6 +56,10 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
readingDialog?.tvCardId?.visibility = View.VISIBLE
readingDialog?.tvCardId?.text = cardId
}
if (message != null) {
if (message.body != null) readingDialog?.tvTaskText?.text = message.body
if (message.header != null) readingDialog?.tvTaskTitle?.text = message.header
}
}
readingDialog?.setOnCancelListener {
reader.readingCancelled = true
@ -132,25 +136,29 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
}
}
override fun onNfcSessionCompleted() {
override fun onNfcSessionCompleted(message: Message?) {
postUI {
readingDialog?.lTouchCard?.hide()
readingDialog?.flSecurityDelay?.hide()
readingDialog?.flCompletion?.show()
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp))
if (message != null) {
if (message.body != null) readingDialog?.tvTaskText?.text = message.body
if (message.header != null) readingDialog?.tvTaskTitle?.text = message.header
}
performHapticFeedback()
}
postUI(300) { readingDialog?.dismiss() }
}
override fun onError(error: TaskError) {
override fun onError(errorMessage: String) {
postUI {
readingDialog?.lTouchCard?.hide()
readingDialog?.flSecurityDelay?.hide()
readingDialog?.flCompletion?.hide()
readingDialog?.flError?.show()
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error)
readingDialog?.tvTaskText?.text = "${error::class.simpleName}: ${error.code}"
readingDialog?.tvTaskText?.text = errorMessage
performHapticFeedback()
}
}

View file

@ -3,7 +3,7 @@ package com.tangem.tangem_sdk_new
import android.app.Application
import android.content.SharedPreferences
import at.favre.lib.armadillo.Armadillo
import com.tangem.common.KeyPair
import com.tangem.KeyPair
import com.tangem.common.TerminalKeysService
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
@ -12,7 +12,7 @@ import com.tangem.crypto.CryptoUtils
/**
* Service for managing Terminal keypair, used for Linked Terminal feature.
* Needs to be provided to [com.tangem.CardManager] by calling [com.tangem.CardManager.setTerminalKeysService]
* Needs to be provided to [com.tangem.TangemSdk] by calling [com.tangem.TangemSdk.setTerminalKeysService]
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
* @param applicationContext is required to retrieve an instance of [SharedPreferences]
*/

View file

@ -1,21 +1,21 @@
package com.tangem.tangem_sdk_new.extensions
import androidx.fragment.app.FragmentActivity
import com.tangem.CardManager
import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate
import com.tangem.TangemSdk
import com.tangem.tangem_sdk_new.DefaultSessionViewDelegate
import com.tangem.tangem_sdk_new.NfcLifecycleObserver
import com.tangem.tangem_sdk_new.TerminalKeysStorage
import com.tangem.tangem_sdk_new.nfc.NfcManager
fun CardManager.Companion.init(activity: FragmentActivity): CardManager {
fun TangemSdk.Companion.init(activity: FragmentActivity): TangemSdk {
val nfcManager = NfcManager().apply {
this.setCurrentActivity(activity)
activity.lifecycle.addObserver(NfcLifecycleObserver(this))
}
val cardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader).apply {
val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader).apply {
this.activity = activity
}
return CardManager(nfcManager.reader, cardManagerDelegate).apply {
return TangemSdk(nfcManager.reader, viewDelegate).apply {
this.setTerminalKeysService(TerminalKeysStorage(activity.application))
}
}

View file

@ -6,10 +6,10 @@ import android.nfc.tech.IsoDep
import android.nfc.tech.NfcV
import com.tangem.CardReader
import com.tangem.Log
import com.tangem.SessionError
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.ResponseApdu
import com.tangem.tasks.TaskError
/**
* Provides NFC communication between an Android application and Tangem card.
@ -38,7 +38,7 @@ class NfcReader : CardReader {
// Stops reading and sends failure callback to a task
// if reading is cancelled (when user closes nfc bottom sheet dialog).
closeSession()
callback?.invoke(CompletionResult.Failure(TaskError.UserCancelled()))
callback?.invoke(CompletionResult.Failure(SessionError.UserCancelled()))
}
}
@ -67,7 +67,7 @@ class NfcReader : CardReader {
private fun transceiveData() {
if (readingCancelled) {
callback?.invoke(CompletionResult.Failure(TaskError.UserCancelled()))
callback?.invoke(CompletionResult.Failure(SessionError.UserCancelled()))
return
}
if (data == null) return
@ -76,7 +76,7 @@ class NfcReader : CardReader {
try {
rawResponse = isoDep?.transceive(data)
} catch (exception: TagLostException) {
callback?.invoke(CompletionResult.Failure(TaskError.TagLost()))
callback?.invoke(CompletionResult.Failure(SessionError.TagLost()))
isoDep = null
return
} catch (exception: Exception) {
@ -110,7 +110,7 @@ class NfcReader : CardReader {
when (response) {
is SlixReadResult.Failure -> {
Log.e(this::class.simpleName!!, "${response.exception.message}")
callback?.invoke(CompletionResult.Failure(TaskError.ErrorProcessingCommand()))
callback?.invoke(CompletionResult.Failure(SessionError.ErrorProcessingCommand()))
}
is SlixReadResult.Success -> {
callback?.invoke(CompletionResult.Success(ResponseApdu(response.data)))

View file

@ -7,7 +7,7 @@ import com.tangem.common.apdu.StatusWord
import com.tangem.common.extensions.toByteArray
import com.tangem.common.extensions.toHexString
import com.tangem.common.tlv.Tlv
import com.tangem.common.tlv.TlvMapper
import com.tangem.common.tlv.TlvDecoder
import com.tangem.common.tlv.TlvTag
import java.io.ByteArrayOutputStream
import java.io.IOException
@ -64,9 +64,9 @@ class SlixTagReader() {
val areaBuf = readMultipleBlocks(1, blocksCount)
val tlvNdef = TlvMapper(Tlv.deserialize(areaBuf, true) ?: listOf())
val tlvNdef = TlvDecoder(Tlv.deserialize(areaBuf, true) ?: listOf())
return NdefMessage(tlvNdef.map<ByteArray>(TlvTag.CardPublicKey))
return NdefMessage(tlvNdef.decode<ByteArray>(TlvTag.CardPublicKey))
}
private fun readSingleBlock(blockNo: Int): ByteArray {