From 9c7a2f3ab132c55cdd0ff9ce597523015da9ae78 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 21 May 2020 21:43:55 +0300 Subject: [PATCH 01/43] Updated on 2026-08-14 --- .../bitcoin/BitcoinAddressService.kt | 8 ++- .../bitcoin/BitcoinTransactionBuilder.kt | 4 +- .../bitcoin/BitcoinWalletManager.kt | 2 +- .../ducatus/DucatusMainNetParams.java | 69 +++++++++++++++++++ .../ducatus/DucatusWalletManager.kt | 40 +++++++++++ .../ducatus/network/DucatusNetworkManager.kt | 8 +++ .../ducatus/network/bitcore/BitcoreApi.kt | 21 ++++++ .../network/bitcore/BitcoreProvider.kt | 64 +++++++++++++++++ .../network/bitcore/BitcoreResponse.kt | 34 +++++++++ .../tangem/blockchain/common/Blockchain.kt | 10 +-- .../blockchain/common/WalletManagerFactory.kt | 10 ++- .../blockchain/network/RetrofitBuilder.kt | 3 +- 12 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusMainNetParams.java create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusWalletManager.kt create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/DucatusNetworkManager.kt create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreApi.kt create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreProvider.kt create mode 100644 blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreResponse.kt diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt index 1483284942..c3b5102b95 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt @@ -1,15 +1,18 @@ package com.tangem.blockchain.blockchains.bitcoin +import com.tangem.blockchain.blockchains.ducatus.DucatusMainNetParams import com.tangem.blockchain.blockchains.litecoin.LitecoinMainNetParams import com.tangem.blockchain.common.AddressService import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.calculateRipemd160 import com.tangem.common.extensions.calculateSha256 -import org.bitcoinj.core.* +import org.bitcoinj.core.Base58 +import org.bitcoinj.core.LegacyAddress +import org.bitcoinj.core.NetworkParameters +import org.bitcoinj.core.SegwitAddress import org.bitcoinj.params.MainNetParams import org.bitcoinj.params.TestNet3Params -import java.security.MessageDigest class BitcoinAddressService(private val blockchain: Blockchain) : AddressService { @@ -17,6 +20,7 @@ class BitcoinAddressService(private val blockchain: Blockchain) : AddressService Blockchain.Bitcoin -> MainNetParams() Blockchain.BitcoinTestnet -> TestNet3Params() Blockchain.Litecoin -> LitecoinMainNetParams() + Blockchain.Ducatus -> DucatusMainNetParams() else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}") } diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionBuilder.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionBuilder.kt index ede62d31a4..b829572b6f 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionBuilder.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionBuilder.kt @@ -1,5 +1,6 @@ package com.tangem.blockchain.blockchains.bitcoin +import com.tangem.blockchain.blockchains.ducatus.DucatusMainNetParams import com.tangem.blockchain.blockchains.litecoin.LitecoinMainNetParams import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData @@ -23,6 +24,7 @@ open class BitcoinTransactionBuilder( Blockchain.Bitcoin, Blockchain.BitcoinCash -> MainNetParams() Blockchain.BitcoinTestnet -> TestNet3Params() Blockchain.Litecoin -> LitecoinMainNetParams() + Blockchain.Ducatus -> DucatusMainNetParams() else -> throw Exception("${blockchain.fullName} blockchain is not supported by ${this::class.simpleName}") } var unspentOutputs: List? = null @@ -64,7 +66,7 @@ open class BitcoinTransactionBuilder( } fun calculateChange(transactionData: TransactionData, unspentOutputs: List): BigDecimal { - val fullAmount = unspentOutputs!!.map { it.amount }.reduce { acc, number -> acc + number } + val fullAmount = unspentOutputs.map { it.amount }.reduce { acc, number -> acc + number } return fullAmount - (transactionData.amount.value!! + (transactionData.fee?.value ?: 0.toBigDecimal())) } diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinWalletManager.kt index 2a723fe5f9..ac393ef37e 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinWalletManager.kt @@ -13,7 +13,7 @@ import java.math.BigDecimal open class BitcoinWalletManager( cardId: String, wallet: Wallet, - private val transactionBuilder: BitcoinTransactionBuilder, + protected val transactionBuilder: BitcoinTransactionBuilder, private val networkManager: BitcoinProvider ) : WalletManager(cardId, wallet), TransactionSender { diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusMainNetParams.java b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusMainNetParams.java new file mode 100644 index 0000000000..c4373b88df --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusMainNetParams.java @@ -0,0 +1,69 @@ +package com.tangem.blockchain.blockchains.ducatus; + +import org.bitcoinj.core.Utils; +import org.bitcoinj.params.AbstractBitcoinNetParams; +import org.bitcoinj.params.MainNetParams; +import org.spongycastle.util.encoders.Hex; + +import static com.google.common.base.Preconditions.checkState; + + +public class DucatusMainNetParams extends AbstractBitcoinNetParams { + public static final int MAINNET_MAJORITY_WINDOW = MainNetParams.MAINNET_MAJORITY_WINDOW; + public static final int MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED = MainNetParams.MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED; + public static final int MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE = MainNetParams.MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE; + + public DucatusMainNetParams() { + super(); + id = "org.bitcoinj.ducatus_mainnet"; + // Genesis hash is 12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2 + packetMagic = 0xfbc0b6db; + + maxTarget = Utils.decodeCompactBits(0x1e0fffffL); + port = 9333; + addressHeader = 49; + p2shHeader = 51; //TODO: is this right? Haven't seen Ducatus p2sh address ever + segwitAddressHrp = "duc"; //TODO: does Ducatus even have bech32 addresses? At least other blockchain's addresses won't pass + dumpedPrivateKeyHeader = 176; + + spendableCoinbaseDepth = 100; + subsidyDecreaseBlockCount = 840000; + + genesisBlock.setTime(1317972665L); + genesisBlock.setDifficultyTarget(0x1e0ffff0L); + genesisBlock.setNonce(2084524493); + + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("5155a7ed2219a75c0735c58b5d459c6d07d97917570e27b9d1d4546fb8431381")); + alertSigningKey = Hex.decode("040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9"); + + majorityEnforceBlockUpgrade = MAINNET_MAJORITY_ENFORCE_BLOCK_UPGRADE; + majorityRejectBlockOutdated = MAINNET_MAJORITY_REJECT_BLOCK_OUTDATED; + majorityWindow = MAINNET_MAJORITY_WINDOW; + + dnsSeeds = new String[]{ + "dnsseed.litecointools.com", + "dnsseed.litecoinpool.org", + "dnsseed.ltc.xurious.com", + "dnsseed.koin-project.com", + "dnsseed.weminemnc.com" + }; + bip32HeaderP2PKHpub = 0x0488B21E; + bip32HeaderP2PKHpriv = 0x0488ADE4; + } + + @Override + public String getPaymentProtocolId() { + return PAYMENT_PROTOCOL_ID_MAINNET; + } + + + private static DucatusMainNetParams instance; + + public static synchronized DucatusMainNetParams get() { + if (instance == null) { + instance = new DucatusMainNetParams(); + } + return instance; + } +} diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusWalletManager.kt new file mode 100644 index 0000000000..06ea687cb2 --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/DucatusWalletManager.kt @@ -0,0 +1,40 @@ +package com.tangem.blockchain.blockchains.ducatus + +import com.tangem.blockchain.blockchains.bitcoin.BitcoinTransactionBuilder +import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager +import com.tangem.blockchain.blockchains.ducatus.network.DucatusNetworkManager +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.TransactionData +import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.extensions.Result +import java.math.BigDecimal + +class DucatusWalletManager( + cardId: String, + wallet: Wallet, + transactionBuilder: BitcoinTransactionBuilder, + networkManager: DucatusNetworkManager +) : BitcoinWalletManager(cardId, wallet, transactionBuilder, networkManager), TransactionSender { + override suspend fun getFee(amount: Amount, destination: String): Result> { + val feeValue = BigDecimal.ONE.movePointLeft(blockchain.decimals()) + val sizeResult = transactionBuilder.getEstimateSize( + TransactionData(amount, Amount(amount, feeValue), wallet.address, destination) + ) + return when (sizeResult) { + is Result.Failure -> sizeResult + is Result.Success -> { + val transactionSize = sizeResult.data.toBigDecimal() + val minFee = BigDecimal.valueOf(0.00000089).multiply(transactionSize) + val normalFee = BigDecimal.valueOf(0.00000144).multiply(transactionSize) + val priorityFee = BigDecimal.valueOf(0.00000350).multiply(transactionSize) + val fees = listOf( + Amount(minFee, blockchain), + Amount(normalFee, blockchain), + Amount(priorityFee, blockchain) + ) + Result.Success(fees) + } + } + } +} \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/DucatusNetworkManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/DucatusNetworkManager.kt new file mode 100644 index 0000000000..7b08f99b72 --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/DucatusNetworkManager.kt @@ -0,0 +1,8 @@ +package com.tangem.blockchain.blockchains.ducatus.network + +import com.tangem.blockchain.blockchains.ducatus.network.bitcore.BitcoreApi +import com.tangem.blockchain.blockchains.ducatus.network.bitcore.BitcoreProvider +import com.tangem.blockchain.network.API_DUCATUS +import com.tangem.blockchain.network.createRetrofitInstance + +class DucatusNetworkManager() : BitcoreProvider(createRetrofitInstance(API_DUCATUS).create(BitcoreApi::class.java)) \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreApi.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreApi.kt new file mode 100644 index 0000000000..be7214c1ef --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreApi.kt @@ -0,0 +1,21 @@ +package com.tangem.blockchain.blockchains.ducatus.network.bitcore + +import com.squareup.moshi.JsonClass +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + +interface BitcoreApi { + @GET("api/DUC/mainnet/address/{address}/balance") + suspend fun getBalance(@Path("address") address: String): BitcoreBalance + + @GET("api/DUC/mainnet/address/{address}/?unspent=true") + suspend fun getUnspents(@Path("address") address: String): List + + @POST("api/DUC/mainnet/tx/send") + suspend fun sendTransaction(@Body body: BitcoreSendBody): BitcoreSendResponse +} + +@JsonClass(generateAdapter = true) +data class BitcoreSendBody(val rawTx: List) \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreProvider.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreProvider.kt new file mode 100644 index 0000000000..39fb7fa213 --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreProvider.kt @@ -0,0 +1,64 @@ +package com.tangem.blockchain.blockchains.ducatus.network.bitcore + +import com.tangem.blockchain.blockchains.bitcoin.BitcoinUnspentOutput +import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinAddressResponse +import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinFee +import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinProvider +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.extensions.Result +import com.tangem.blockchain.extensions.SimpleResult +import com.tangem.blockchain.extensions.retryIO +import com.tangem.common.extensions.hexToBytes +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope + +open class BitcoreProvider(private val api: BitcoreApi) : BitcoinProvider{ + private val decimals = Blockchain.Ducatus.decimals() + + override suspend fun getInfo(address: String): Result { + return try { + coroutineScope { + val balanceDeferred = retryIO { async { api.getBalance(address) } } + val unspentsDeferred = retryIO { async { api.getUnspents(address) } } + + val balanceData = balanceDeferred.await() + val unspents = unspentsDeferred.await() + + val unspentTransactions = unspents.map { + BitcoinUnspentOutput( + amount = it.amount!!.toBigDecimal().movePointLeft(decimals), + outputIndex = it.index!!.toLong(), + transactionHash = it.transactionHash!!.hexToBytes(), + outputScript = it.script!!.hexToBytes() + ) + } + + Result.Success(BitcoinAddressResponse( + balance = balanceData.confirmed!!.toBigDecimal().movePointLeft(decimals),//only confirmed balance is returned right + hasUnconfirmed = balanceData.unconfirmed != null, + unspentOutputs = unspentTransactions + )) + } + } catch (error: Exception) { + Result.Failure(error) + } + } + + override suspend fun getFee(): Result { + TODO("Not yet implemented")// bitcore is used only in ducatus and fee is hardcoded there + } + + + override suspend fun sendTransaction(transaction: String): SimpleResult { + return try { + val response = retryIO { api.sendTransaction(BitcoreSendBody(listOf(transaction))) } + if (response.txid != null) { + SimpleResult.Success + } else { + SimpleResult.Failure(Exception("Unknown send transaction error")) + } + } catch (error: Exception) { + SimpleResult.Failure(error) + } + } +} \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreResponse.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreResponse.kt new file mode 100644 index 0000000000..5b7cda90db --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ducatus/network/bitcore/BitcoreResponse.kt @@ -0,0 +1,34 @@ +package com.tangem.blockchain.blockchains.ducatus.network.bitcore + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class BitcoreBalance( + @Json(name = "confirmed") + var confirmed: Long? = null, + + @Json(name = "unconfirmed") + var unconfirmed: Long? = null +) + +@JsonClass(generateAdapter = true) +data class BitcoreUtxo( + @Json(name = "mintTxid") + var transactionHash: String? = null, + + @Json(name = "mintIndex") + var index: Int? = null, + + @Json(name = "value") + var amount: Long? = null, + + @Json(name = "script") + var script: String? = null +) + +@JsonClass(generateAdapter = true) +data class BitcoreSendResponse( + @Json(name = "txid") + var txid: String? = null +) \ No newline at end of file diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt b/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt index d7ba034407..f732d9d723 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/Blockchain.kt @@ -19,6 +19,7 @@ enum class Blockchain( BitcoinTestnet("BTC/test", "BTCt", "Bitcoin Testnet"), BitcoinCash("BCH", "BCH", "Bitcoin Cash"), Litecoin("LTC", "LTC", "Litecoin"), + Ducatus("DUC", "DUC", "Ducatus"), Ethereum("ETH", "ETH", "Ethereum"), RSK("RSK", "RBTC", "RSK"), Cardano("CARDANO", "ADA", "Cardano"), @@ -29,7 +30,7 @@ enum class Blockchain( Tezos("TEZOS", "XTZ", "Tezos"); fun decimals(): Int = when (this) { - Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet, Litecoin -> 8 + Bitcoin, BitcoinTestnet, BitcoinCash, Binance, BinanceTestnet, Litecoin, Ducatus -> 8 Cardano, XRP, Tezos -> 6 Ethereum, RSK -> 18 Stellar -> 7 @@ -45,8 +46,7 @@ enum class Blockchain( fun validateAddress(address: String): Boolean = getAddressService().validate(address) private fun getAddressService(): AddressService = when (this) { - Unknown -> throw Exception("unsupported blockchain") - Bitcoin, BitcoinTestnet, Litecoin -> BitcoinAddressService(this) + Bitcoin, BitcoinTestnet, Litecoin, Ducatus -> BitcoinAddressService(this) BitcoinCash -> BitcoinCashAddressService() Ethereum, RSK -> EthereumAddressService() Cardano -> CardanoAddressService() @@ -55,6 +55,7 @@ enum class Blockchain( BinanceTestnet -> BinanceAddressService(true) Stellar -> StellarAddressService() Tezos -> TezosAddressService() + Unknown -> throw Exception("unsupported blockchain") } fun getShareUri(address: String): String = when (this) { @@ -71,9 +72,10 @@ enum class Blockchain( BitcoinTestnet -> "https://live.blockcypher.com/btc-testnet/address/$address" BitcoinCash -> "https://blockchair.com/bitcoin-cash/address/$address" Litecoin -> "https://live.blockcypher.com/ltc/address/$address" + Ducatus -> "https://insight.ducatus.io/#/DUC/mainnet/address/$address" Cardano -> "https://cardanoexplorer.com/address/$address" Ethereum -> if (token == null) { - "https://etherscan.io/address/" + "https://etherscan.io/address/$address" } else { "https://etherscan.io/token/${token.contractAddress}?a=$address" } diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt index 1c7af4e354..c47a0b1f86 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManagerFactory.kt @@ -12,6 +12,8 @@ import com.tangem.blockchain.blockchains.bitcoincash.BitcoinCashWalletManager import com.tangem.blockchain.blockchains.cardano.CardanoTransactionBuilder import com.tangem.blockchain.blockchains.cardano.CardanoWalletManager import com.tangem.blockchain.blockchains.cardano.network.CardanoNetworkManager +import com.tangem.blockchain.blockchains.ducatus.DucatusWalletManager +import com.tangem.blockchain.blockchains.ducatus.network.DucatusNetworkManager import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionBuilder import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager @@ -19,7 +21,6 @@ import com.tangem.blockchain.blockchains.litecoin.LitecoinNetworkManager import com.tangem.blockchain.blockchains.litecoin.LitecoinWalletManager import com.tangem.blockchain.blockchains.stellar.StellarNetworkManager import com.tangem.blockchain.blockchains.stellar.StellarTransactionBuilder - import com.tangem.blockchain.blockchains.stellar.StellarWalletManager import com.tangem.blockchain.blockchains.tezos.TezosTransactionBuilder import com.tangem.blockchain.blockchains.tezos.TezosWalletManager @@ -72,6 +73,13 @@ object WalletManagerFactory { LitecoinNetworkManager() ) } + Blockchain.Ducatus -> { + return DucatusWalletManager( + cardId, wallet, + BitcoinTransactionBuilder(walletPublicKey, blockchain), + DucatusNetworkManager() + ) + } Blockchain.Ethereum, Blockchain.RSK -> { return EthereumWalletManager( cardId, wallet, diff --git a/blockchain/src/main/java/com/tangem/blockchain/network/RetrofitBuilder.kt b/blockchain/src/main/java/com/tangem/blockchain/network/RetrofitBuilder.kt index 8573dff8a7..97e13c5643 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/network/RetrofitBuilder.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/network/RetrofitBuilder.kt @@ -55,4 +55,5 @@ const val API_RIPPLED = "https://s1.ripple.com:51234/" const val API_RIPPLED_RESERVE = "https://s2.ripple.com:51234/" const val API_BLOCKCHAIR = "https://api.blockchair.com/" const val API_TEZOS = "https://teznode.letzbake.com" -const val API_TEZOS_RESERVE = "https://mainnet.tezrpc.me" \ No newline at end of file +const val API_TEZOS_RESERVE = "https://mainnet.tezrpc.me" +const val API_DUCATUS = "https://ducapi.rocknblock.io/" \ No newline at end of file From 77cd7c78ee5aba5db0f185699bbb382fc26f6d1b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 22 May 2020 13:56:17 +0300 Subject: [PATCH 02/43] Updated on 2026-08-14 --- .../bitcoin/BitcoinAddressService.kt | 1 + .../blockchains/ducatus/DucatusAddressTest.kt | 28 +++++++++++++++++++ .../common/WalletManagerFactoryTest.kt | 14 +++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 blockchain/src/test/java/com/tangem/blockchain/blockchains/ducatus/DucatusAddressTest.kt diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt index c3b5102b95..d05657d6ed 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinAddressService.kt @@ -56,6 +56,7 @@ class BitcoinAddressService(private val blockchain: Blockchain) : AddressService Blockchain.Bitcoin -> LegacyAddress.fromBase58(MainNetParams(), address) Blockchain.BitcoinTestnet -> LegacyAddress.fromBase58(TestNet3Params(), address) Blockchain.Litecoin -> LegacyAddress.fromBase58(LitecoinMainNetParams(), address) + Blockchain.Ducatus -> LegacyAddress.fromBase58(DucatusMainNetParams(), address) else -> return false } true diff --git a/blockchain/src/test/java/com/tangem/blockchain/blockchains/ducatus/DucatusAddressTest.kt b/blockchain/src/test/java/com/tangem/blockchain/blockchains/ducatus/DucatusAddressTest.kt new file mode 100644 index 0000000000..528a0a199b --- /dev/null +++ b/blockchain/src/test/java/com/tangem/blockchain/blockchains/ducatus/DucatusAddressTest.kt @@ -0,0 +1,28 @@ +package com.tangem.blockchain.blockchains.ducatus + +import com.google.common.truth.Truth +import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressService +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.hexToBytes +import org.junit.Test + +class DucatusAddressTest { + + private val addressService = BitcoinAddressService(Blockchain.Ducatus) + + @Test + fun makeAddressFromCorrectPublicKey() { + val walletPublicKey = "0485D520C8B907F0BC5E03FCBBAC212CCD270764BBFF4990A28653A2FB0D656C342DF143C4D52C43582289E20A81D5D014C1384A1FFFEA1D121903AD7ED35A01EA".hexToBytes() + val expected = "Ly3SZetcgr5gkZMwiNwVrts2z2r3jYieAG" + + Truth.assertThat(addressService.makeAddress(walletPublicKey)) + .isEqualTo(expected) + } + + @Test + fun validateCorrectAddress() { + val address = "Ly3SZetcgr5gkZMwiNwVrts2z2r3jYieAG" + Truth.assertThat(addressService.validate(address)) + .isTrue() + } +} \ No newline at end of file diff --git a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt index 33c6e97c89..e372153248 100644 --- a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt +++ b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.blockchains.binance.BinanceWalletManager import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager import com.tangem.blockchain.blockchains.bitcoincash.BitcoinCashWalletManager import com.tangem.blockchain.blockchains.cardano.CardanoWalletManager +import com.tangem.blockchain.blockchains.ducatus.DucatusWalletManager import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.litecoin.LitecoinWalletManager import com.tangem.blockchain.blockchains.stellar.StellarWalletManager @@ -107,7 +108,7 @@ internal class WalletManagerFactoryTest { } @Test - fun createLTezosWalletManager() { + fun createTezosWalletManager() { val data = "0108BB00000000000080200754414E47454D00020102800A322E3432642053444B0003410436CFC5D0A11353AE6AFEEDC84A2D02B2635C044DEEE47F99913072B8D166D14E557230AC5FB5272F1A0E523332CCE1A744B51DB53102FF7D3FDE023DC3477C460A04041E76310C638102FFFF8A0101820407E40514830B54414E47454D2053444B00840554455A4F538640C752685B29333CFB0DB0A7347579A0AE763F2B5C4BB09FD68E0B81A06CD01EC51347001732815A3ECFFCD78DDE4E53877581B9E4914B069570629D0C40A771B93041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050865643235353139000804000186A0070100602098E0E504F3A5FDE704400302ABB0A2EFB0DF0F95C166C91D7F207DEDCE10CBA362040001869F6304000000010F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) @@ -116,4 +117,15 @@ internal class WalletManagerFactoryTest { Truth.assertThat(walletManager) .isInstanceOf(TezosWalletManager::class.java) } + + @Test + fun createDucatusWalletManager() { + val data = "0108BB00000000000098200754414E47454D00020102800A322E3432642053444B000341041B5FD7C590938E836B388B996AE451FDED54F625FF2CF05E26E5AADF6F690AEF125E3D0F23CB6B8D1F78040DF6F71B40D098F2D8BE504DDEE2E1F99BEADD90500A04041E76310C618102FFFF8A0101820407E40515830B54414E47454D2053444B00840344554386401B453C10A092A3448FA83CAFD4FC3D7EB5EA1BBBDD6A020CAAC8CED36BB2661F41EF0B0C7418214F670DFE1200DCE18597158119BEF6CC52A4FF3B7E021A53B93041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A007010060410485D520C8B907F0BC5E03FCBBAC212CCD270764BBFF4990A28653A2FB0D656C342DF143C4D52C43582289E20A81D5D014C1384A1FFFEA1D121903AD7ED35A01EA62040001869D6304000000030F01009000" + val responseApdu = ResponseApdu(data.hexToBytes()) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val walletManager = WalletManagerFactory.makeWalletManager(card) + + Truth.assertThat(walletManager) + .isInstanceOf(DucatusWalletManager::class.java) + } } \ No newline at end of file From ace3760f4b35fcb38d439fc537d2a3841b922950 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Jun 2020 21:27:02 +0300 Subject: [PATCH 03/43] Updated on 2026-08-14 --- .../ethereum/EthereumWalletManager.kt | 8 +++++-- .../network/EthereumNetworkManager.kt | 24 +++++++++---------- dependencies.gradle | 2 +- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt index 5ac19d6370..006ca87f77 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumWalletManager.kt @@ -1,8 +1,8 @@ package com.tangem.blockchain.blockchains.ethereum import android.util.Log -import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager import com.tangem.blockchain.blockchains.ethereum.network.EthereumInfoResponse +import com.tangem.blockchain.blockchains.ethereum.network.EthereumNetworkManager import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -24,7 +24,11 @@ class EthereumWalletManager( override suspend fun update() { - val result = networkManager.getInfo(wallet.address, wallet.amounts[AmountType.Token]?.address) + val result = networkManager.getInfo( + wallet.address, + wallet.amounts[AmountType.Token]?.address, + wallet.amounts[AmountType.Token]?.decimals + ) when (result) { is Result.Failure -> updateError(result.error) is Result.Success -> updateWallet(result.data) diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt index b5da72dd74..87da3d438e 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/network/EthereumNetworkManager.kt @@ -10,7 +10,6 @@ import com.tangem.blockchain.network.createRetrofitInstance import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope -import org.kethereum.ETH_IN_WEI import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode @@ -35,6 +34,7 @@ class EthereumNetworkManager(blockchain: Blockchain) { } private val provider: EthereumProvider by lazy { EthereumProvider(api, apiKey) } + private val decimals = Blockchain.Ethereum.decimals() suspend fun sendTransaction(transaction: String): SimpleResult { return try { @@ -59,19 +59,20 @@ class EthereumNetworkManager(blockchain: Blockchain) { } } - suspend fun getInfo(address: String, contractAddress: String? = null): Result { + suspend fun getInfo(address: String, contractAddress: String? = null, tokenDecimals: Int? = null) + : Result { return try { coroutineScope { val balanceResponse = retryIO { async { provider.getBalance(address) } } val txCountResponse = retryIO { async { provider.getTxCount(address) } } val pendingTxCountResponse = retryIO { async { provider.getPendingTxCount(address) } } var tokenBalanceResponse: Deferred? = null - if (contractAddress != null) { + if (contractAddress != null && tokenDecimals != null) { tokenBalanceResponse = retryIO { async { provider.getTokenBalance(address, contractAddress) } } } Result.Success(EthereumInfoResponse( - balanceResponse.await().result!!.parseAmount(), - tokenBalanceResponse?.await()?.result?.parseAmount(), + balanceResponse.await().result!!.parseAmount(decimals), + tokenBalanceResponse?.await()?.result?.parseAmount(tokenDecimals!!), txCountResponse.await().result?.responseToNumber()?.toLong() ?: 0, pendingTxCountResponse.await().result?.responseToNumber()?.toLong() ?: 0 )) @@ -87,20 +88,19 @@ class EthereumNetworkManager(blockchain: Blockchain) { val normalFee = minFee.multiply(BigDecimal(1.2)).setScale(0, RoundingMode.HALF_UP) val priorityFee = minFee.multiply(BigDecimal(1.5)).setScale(0, RoundingMode.HALF_UP) return listOf( - minFee.convertFeeToEth(), - normalFee.convertFeeToEth(), - priorityFee.convertFeeToEth() + minFee.movePointLeft(decimals), + normalFee.movePointLeft(decimals), + priorityFee.movePointLeft(decimals) ) } private fun String.responseToNumber(): BigInteger = this.substring(2).toBigInteger(16) - private fun String.parseAmount(): BigDecimal = - this.responseToNumber().toBigDecimal().divide(ETH_IN_WEI.toBigDecimal()) + private fun String.parseAmount(decimals: Int): BigDecimal = + this.responseToNumber().toBigDecimal().movePointLeft(decimals) private fun BigDecimal.convertFeeToEth(): BigDecimal { - return this.divide(ETH_IN_WEI.toBigDecimal()) - .setScale(12, BigDecimal.ROUND_DOWN).stripTrailingZeros() + return this.movePointLeft(decimals).setScale(decimals, BigDecimal.ROUND_DOWN).stripTrailingZeros() } } diff --git a/dependencies.gradle b/dependencies.gradle index 3c7aaf12d8..f61f047e5a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ kotlin : '1.3.72', - build_gradle: '3.6.3', + build_gradle: '4.0.0', ] From 0355c3451498e2bef0b6d6ceea68ea9f139cf18e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Jun 2020 23:30:52 +0300 Subject: [PATCH 04/43] Updated on 2026-08-14 --- tangem-core/build.gradle | 2 + .../src/main/java/com/tangem/CardReader.kt | 16 +- .../src/main/java/com/tangem/CardSession.kt | 138 ++++++++------ .../java/com/tangem/SessionViewDelegate.kt | 8 +- .../src/main/java/com/tangem/TangemSdk.kt | 2 +- .../java/com/tangem/commands/ReadCommand.kt | 56 +----- .../commands/common/CardDeserializer.kt | 69 +++++++ .../personalization/PersonalizeCommand.kt | 55 +----- .../main/java/com/tangem/tasks/ScanTask.kt | 61 +++++-- .../com/tangem/common/apdu/CommandApduTest.kt | 48 ++++- .../personalize/dto/PersonalizationConfig.kt | 2 +- tangem-sdk/build.gradle | 19 +- .../DefaultSessionViewDelegate.kt | 143 +++------------ .../SessionViewDelegateState.kt | 17 ++ .../tangem/tangem_sdk_new/extensions/View.kt | 9 + .../tangem/tangem_sdk_new/nfc/NfcManager.kt | 19 +- .../tangem/tangem_sdk_new/nfc/NfcReader.kt | 170 ++++++++--------- .../tangem_sdk_new/ui/NfcSessionDialog.kt | 171 ++++++++++++++++++ .../main/res/drawable/pb_simple_circle.xml | 21 +++ .../src/main/res/layout/nfc_bottom_sheet.xml | 17 +- 20 files changed, 635 insertions(+), 408 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt create mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt create mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt create mode 100644 tangem-sdk/src/main/res/drawable/pb_simple_circle.xml diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index 220c949038..c64079660f 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -11,6 +11,8 @@ dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.7" // crypto implementation "com.madgag.spongycastle:core:1.58.0.0" diff --git a/tangem-core/src/main/java/com/tangem/CardReader.kt b/tangem-core/src/main/java/com/tangem/CardReader.kt index 2327e13fe6..356b14fd34 100644 --- a/tangem-core/src/main/java/com/tangem/CardReader.kt +++ b/tangem-core/src/main/java/com/tangem/CardReader.kt @@ -3,6 +3,8 @@ package com.tangem import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BroadcastChannel /** * Allows interaction between the phone or any other terminal and Tangem card. @@ -11,6 +13,9 @@ import com.tangem.common.apdu.ResponseApdu */ interface CardReader { + val tag: BroadcastChannel + var scope: CoroutineScope? + /** * Sends data to the card and receives the reply. * @@ -23,10 +28,17 @@ interface CardReader { /** * Signals to [CardReader] to become ready to transceive data. */ - fun openSession() + fun startSession() /** * Signals to [CardReader] that no further NFC transition is expected. */ - fun closeSession() + fun stopSession(cancelled: Boolean = false) + + fun readSlixTag(callback: (result: CompletionResult) -> Unit) + +} + +interface ReadingActiveListener { + var readingIsActive: Boolean } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index c7fa4559e7..f3a77df81d 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,6 +1,5 @@ package com.tangem -import com.tangem.commands.Card import com.tangem.commands.CommandResponse import com.tangem.commands.OpenSessionCommand import com.tangem.commands.ReadCommand @@ -13,6 +12,14 @@ import com.tangem.crypto.EncryptionHelper import com.tangem.crypto.FastEncryptionHelper import com.tangem.crypto.StrongEncryptionHelper import com.tangem.crypto.pbkdf2Hash +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.launch /** * Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession] @@ -30,6 +37,16 @@ interface CardSessionRunnable { fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) } +enum class CardSessionState { + Inactive, + Active +} + +enum class TagType { + Nfc, + Slix +} + /** * Allows interaction with Tangem cards. Should be opened before sending commands. * @@ -51,13 +68,16 @@ class CardSession( private val initialMessage: Message? = null ) { - private val tag = this.javaClass.simpleName + var connectedTag: TagType? = null + /** * True if some operation is still in progress. */ - private var isBusy = false + private var state = CardSessionState.Inactive - private var performPreflightRead = true + private val scope = CoroutineScope(Dispatchers.IO) + + private val tag = this.javaClass.simpleName /** * This metod starts a card session, performs preflight [ReadCommand], @@ -68,9 +88,7 @@ class CardSession( fun , R : CommandResponse> startWithRunnable( runnable: T, callback: (result: CompletionResult) -> Unit) { - performPreflightRead = runnable.performPreflightRead - - start { session, error -> + start(runnable.performPreflightRead) { session, error -> if (error != null) { callback(CompletionResult.Failure(error)) return@start @@ -103,49 +121,55 @@ class CardSession( * Starts a card session and performs preflight [ReadCommand]. * @param callback: callback with the card session. Can contain [TangemSdkError] if something goes wrong. */ - fun start(callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - try { - startSession() - } catch (error: TangemSdkError) { - callback(this, error) - } + fun start(performPreflightRead: Boolean = true, + callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - if (!performPreflightRead) { - callback(this, null) + if (state != CardSessionState.Inactive) { + callback(this, TangemSdkError.Busy()) return } + state = CardSessionState.Active + viewDelegate.onSessionStarted(cardId) - preflightRead() { result -> - when (result) { - is CompletionResult.Failure -> { - callback(this, result.error) - stopWithError(result.error) - } - is CompletionResult.Success -> { - callback(this, null) - } - } + scope.launch { + reader.tag + .asFlow() + .collect { tagType -> + if (tagType == null && connectedTag != null) { + handleTagLost() + } else if (tagType != null) { + connectedTag = tagType + viewDelegate.onTagConnected() + + if (tagType == TagType.Nfc && performPreflightRead) { + preflightCheck(callback) + } else { + callback(this@CardSession, null) + } + } + } } + reader.scope = scope + reader.startSession() } - private fun startSession() { - if (isBusy) throw TangemSdkError.Busy() - isBusy = true - viewDelegate.onNfcSessionStarted(cardId, initialMessage) - reader.openSession() + private fun handleTagLost() { + connectedTag = null + environment.encryptionKey = null + viewDelegate.onTagLost() } - private fun preflightRead(callback: (result: CompletionResult) -> Unit) { + private fun preflightCheck(callback: (session: CardSession, error: TangemSdkError?) -> 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.Success -> preflightCheck(callback) is CompletionResult.Failure -> { stopWithError(result.error) - callback(CompletionResult.Failure(result.error)) + callback(this, result.error) } } } @@ -153,32 +177,35 @@ class CardSession( is CompletionResult.Success -> { val receivedCardId = result.data.cardId if (cardId != null && receivedCardId != cardId) { - stopWithError(TangemSdkError.WrongCardNumber()) - callback(CompletionResult.Failure(TangemSdkError.WrongCardNumber())) + viewDelegate.onWrongCard() + preflightCheck(callback) return@run } val allowedCardTypes = environment.cardFilter.allowedCardTypes if (!allowedCardTypes.contains(result.data.getType())) { stopWithError(TangemSdkError.WrongCardType()) - callback(CompletionResult.Failure(TangemSdkError.WrongCardType())) + callback(this, TangemSdkError.WrongCardType()) return@run } environment.card = result.data cardId = receivedCardId - callback(CompletionResult.Success(result.data)) + callback(this, null) } } } } + fun readSlixTag(callback: (result: CompletionResult) -> Unit) { + reader.readSlixTag(callback) + } + /** * 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 + stopSession() + viewDelegate.onSessionStopped(message) } /** @@ -186,27 +213,34 @@ class CardSession( * @param error An error that will be shown. */ private fun stopWithError(error: TangemSdkError) { - if (!isBusy) return - - reader.closeSession() - isBusy = false - - val errorMessage = if (error is TangemSdkError) { - "${error::class.simpleName}: ${error.code}" - } else { - error.localizedMessage - } + stopSession() if (error !is TangemSdkError.UserCancelled) { - Log.e(tag, "Finishing with error: $errorMessage") + Log.e(tag, "Finishing with error: ${error::class.simpleName}: ${error.code}") viewDelegate.onError(error) } else { Log.i(tag, "User cancelled NFC session") } + } + private fun stopSession() { + reader.stopSession() + state = CardSessionState.Inactive + scope.cancel() } fun send(apdu: CommandApdu, callback: (result: CompletionResult) -> Unit) { - reader.transceiveApdu(apdu, callback) + val subscription = reader.tag.openSubscription() + + scope.launch { + subscription.consumeAsFlow() + .filterNotNull() + .collect { + reader.transceiveApdu(apdu) { result -> + subscription.cancel() + callback(result) + } + } + } } private fun tryHandleError( diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 969b147b8f..71311dcf00 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -12,7 +12,7 @@ interface SessionViewDelegate { /** * It is called when user is expected to scan a Tangem Card with an Android device. */ - fun onNfcSessionStarted(cardId: String?, message: Message? = null) + fun onSessionStarted(cardId: String?, message: Message? = null) /** * It is called when security delay is triggered by the card. @@ -32,10 +32,14 @@ interface SessionViewDelegate { */ fun onTagLost() + fun onTagConnected() + + fun onWrongCard() + /** * It is called when NFC session was completed and a user can take the card away from the Android device. */ - fun onNfcSessionCompleted(message: Message? = null) + fun onSessionStopped(message: Message? = null) /** * It is called when some error occur during NFC session. diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index dc8f8dbe06..c5ac313d34 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -374,7 +374,7 @@ class TangemSdk( fun startSession(cardId: String? = null, initialMessage: Message? = null, callback: (session: CardSession, error: TangemSdkError?) -> Unit) { val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) - Thread().run { cardSession.start(callback) } + Thread().run { cardSession.start(callback = callback) } } /** diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index c8bbda44ff..49bf955851 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -3,13 +3,12 @@ package com.tangem.commands import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError +import com.tangem.commands.common.CardDeserializer 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.Tlv import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import java.util.* @@ -398,57 +397,6 @@ class ReadCommand : Command() { } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - - return Card( - cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", - manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", - status = decoder.decodeOptional(TlvTag.Status), - - 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), - signingMethods = 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) - ) - } - - private fun deserializeCardData(tlvData: List): CardData? { - val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { - Tlv.deserialize(it.value) - } - if (cardDataTlvs.isNullOrEmpty()) return null - - val decoder = TlvDecoder(cardDataTlvs) - return CardData( - 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 = decoder.decodeOptional(TlvTag.TokenSymbol), - tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), - tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) - ) + return CardDeserializer.deserialize(apdu, environment) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt new file mode 100644 index 0000000000..ff29df7075 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt @@ -0,0 +1,69 @@ +package com.tangem.commands.common + +import com.tangem.SessionEnvironment +import com.tangem.TangemSdkError +import com.tangem.commands.Card +import com.tangem.commands.CardData +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvDecoder +import com.tangem.common.tlv.TlvTag + +class CardDeserializer() { + companion object { + fun deserialize(apdu: ResponseApdu, environment: SessionEnvironment): Card { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw TangemSdkError.DeserializeApduFailed() + + val decoder = TlvDecoder(tlvData) + + return Card( + cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", + manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", + status = decoder.decodeOptional(TlvTag.Status), + + 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), + signingMethods = 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) + ) + } + + private fun deserializeCardData(tlvData: List): CardData? { + val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { + Tlv.deserialize(it.value) + } + if (cardDataTlvs.isNullOrEmpty()) return null + + val decoder = TlvDecoder(cardDataTlvs) + return CardData( + 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 = decoder.decodeOptional(TlvTag.TokenSymbol), + tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), + tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) + ) + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index 52d85278fa..425d33180d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -7,6 +7,7 @@ import com.tangem.commands.Card import com.tangem.commands.CardData import com.tangem.commands.CardStatus import com.tangem.commands.Command +import com.tangem.commands.common.CardDeserializer import com.tangem.commands.personalization.entities.* import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu @@ -14,9 +15,7 @@ import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu 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.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.sign @@ -56,57 +55,7 @@ class PersonalizeCommand( } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - val tlvData = apdu.getTlvData(devPersonalizationKey) - ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return Card( - cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", - manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", - status = decoder.decodeOptional(TlvTag.Status), - - 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), - signingMethods = 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) - ) - } - - private fun deserializeCardData(tlvData: List): CardData? { - val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { - Tlv.deserialize(it.value) - } - if (cardDataTlvs.isNullOrEmpty()) return null - - val decoder = TlvDecoder(cardDataTlvs) - return CardData( - 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 = decoder.decodeOptional(TlvTag.TokenSymbol), - tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), - tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) - ) + return CardDeserializer.deserialize(apdu, environment) } private fun serializePersonalizationData(config: CardConfig): ByteArray { diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index c05384d5a1..1b203b28fe 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -2,8 +2,10 @@ package com.tangem.tasks import com.tangem.CardSession import com.tangem.CardSessionRunnable +import com.tangem.TagType import com.tangem.TangemSdkError import com.tangem.commands.* +import com.tangem.commands.common.CardDeserializer import com.tangem.common.CompletionResult /** @@ -13,31 +15,58 @@ import com.tangem.common.CompletionResult */ internal class ScanTask : CardSessionRunnable { - override val performPreflightRead = true + override val performPreflightRead = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) + if (session.connectedTag == TagType.Slix) { + readSlixTag(session, callback) + return + } - } else if (card.cardData?.productMask?.contains(Product.Tag) != false) { - callback(CompletionResult.Success(card)) + ReadCommand().run(session) { readResult -> + when(readResult) { + is CompletionResult.Failure -> callback(readResult) + is CompletionResult.Success -> { + val card = readResult.data + session.environment.card = card + if (card.cardData?.productMask?.contains(Product.Tag) != false) { + callback(CompletionResult.Success(card)) - } else if (card.status != CardStatus.Loaded) { - callback(CompletionResult.Success(card)) + } else if (card.status != CardStatus.Loaded) { + callback(CompletionResult.Success(card)) - } else if (card.curve == null || card.walletPublicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.CardError())) + } else if (card.curve == null || card.walletPublicKey == null) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) - } else { - val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) + } else { + val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) + checkWalletCommand.run(session) { result -> + when (result) { + is CompletionResult.Success -> callback(CompletionResult.Success(card)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + } + } + } + } - checkWalletCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> callback(CompletionResult.Success(card)) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + private fun readSlixTag(session: CardSession, callback: (result: CompletionResult) -> Unit) { + session.readSlixTag { result -> + when (result) { + is CompletionResult.Success -> { + try { + val card = CardDeserializer.deserialize(result.data, session.environment) + callback(CompletionResult.Success(card)) + } catch (error: TangemSdkError) { + callback(CompletionResult.Failure(error)) + } + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) } } } diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt index f879f65e6f..c03069b6da 100644 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt @@ -4,6 +4,10 @@ import com.google.common.truth.Truth.assertThat import com.tangem.SessionEnvironment import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvTag +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BroadcastChannel +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.collect import org.junit.Test @@ -19,11 +23,12 @@ class CommandApduTest { tlvBuilder.serialize() ) val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 34, 16, 32, -111, -76, -47, 66, -126, 63, 125, - 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, - -45, 19, -124, -123, -55, -94, 3) + 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, + -45, 19, -124, -123, -55, -94, 3) assertThat(commandApdu.apduData) .isEqualTo(expected) + assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1,2)) } @Test @@ -42,13 +47,42 @@ class CommandApduTest { ) val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 101, 16, 32, -111, -76, -47, 66, -126, 63, - 125, 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, - -10, -45, 19, -124, -123, -55, -94, 3, 92, 65, 4, 80, -122, 58, -42, 74, -121, -82, -118, - 47, -24, 60, 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, - 126, 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, - -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) + 125, 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, + -10, -45, 19, -124, -123, -55, -94, 3, 92, 65, 4, 80, -122, 58, -42, 74, -121, -82, -118, + 47, -24, 60, 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, + 126, 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, + -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) assertThat(commandApdu.apduData) .isEqualTo(expected) } + + @Test + fun testFlow() { + val channel = BroadcastChannel(1) + val flow = GlobalScope.launch { + delay(100) + channel.send(1) + delay(200) + channel.send(2) + } + + val scope = CoroutineScope(Dispatchers.IO) + val scope2 = CoroutineScope(Dispatchers.IO) + + val receiveChannel1 = BroadcastChannel(1) + val receiveChannel2 = BroadcastChannel(1) + + scope.launch { + channel.asFlow().collect { println(it) } + channel.asFlow().collect { println(it) } + } + scope.launch { channel.asFlow().collect { println(it) } } + + runBlocking { + delay(1000) + } + + + } } \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt index a833de5d24..c3bdaf8c09 100644 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt +++ b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt @@ -181,7 +181,7 @@ class PersonalizationConfig { signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData) } if (from.SigningMethod6) { - signingMethodMaskBuilder.add(SigningMethod.SignHash) + signingMethodMaskBuilder.add(SigningMethod.SignPos) } return signingMethodMaskBuilder.build() } diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index de05139eb5..085db2391b 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -39,22 +39,31 @@ android { } dependencies { + // internal implementation project(':tangem-core') - implementation fileTree(dir: 'libs', include: ['*.jar']) + + // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" + implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" + + // coroutines + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.7" + + // android implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.google.android.material:material:1.1.0' - implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - - implementation 'androidx.core:core-ktx:1.2.0' + implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "androidx.lifecycle:lifecycle-runtime:2.2.0" implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" - implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" + // misc + implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'at.favre.lib:armadillo:0.9.0' + // testing testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index d8ca2ad645..31144eae96 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -1,20 +1,10 @@ package com.tangem.tangem_sdk_new -import android.animation.ObjectAnimator -import android.app.Activity -import android.view.HapticFeedbackConstants -import android.view.View -import android.view.animation.DecelerateInterpolator -import com.google.android.material.bottomsheet.BottomSheetDialog +import androidx.fragment.app.FragmentActivity import com.tangem.* import com.tangem.common.CompletionResult -import com.tangem.tangem_sdk_new.extensions.hide -import com.tangem.tangem_sdk_new.extensions.localizedDescription -import com.tangem.tangem_sdk_new.extensions.show import com.tangem.tangem_sdk_new.nfc.NfcReader -import com.tangem.tangem_sdk_new.ui.TouchCardAnimation -import kotlinx.android.synthetic.main.layout_touch_card.* -import kotlinx.android.synthetic.main.nfc_bottom_sheet.* +import com.tangem.tangem_sdk_new.ui.NfcSessionDialog /** * Default implementation of [SessionViewDelegate]. @@ -22,148 +12,65 @@ import kotlinx.android.synthetic.main.nfc_bottom_sheet.* */ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDelegate { - lateinit var activity: Activity - private var readingDialog: BottomSheetDialog? = null + lateinit var activity: FragmentActivity + private var readingDialog: NfcSessionDialog? = null init { setLogger() } - override fun onNfcSessionStarted(cardId: String?, message: Message?) { - reader.readingCancelled = false + override fun onSessionStarted(cardId: String?, message: Message?) { postUI { showReadingDialog(activity, cardId, message) } } - private fun showReadingDialog(activity: Activity, cardId: String?, message: Message?) { + private fun showReadingDialog(activity: FragmentActivity, cardId: String?, message: Message?) { val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null) - readingDialog = BottomSheetDialog(activity) + readingDialog = NfcSessionDialog(activity) readingDialog?.setContentView(dialogView) readingDialog?.dismissWithAnimation = true readingDialog?.create() readingDialog?.setOnShowListener { - readingDialog?.rippleBackgroundNfc?.startRippleAnimation() - val nfcDeviceAntenna = TouchCardAnimation( - activity, readingDialog!!.ivHandCardHorizontal, - readingDialog!!.ivHandCardVertical, readingDialog!!.llHand, readingDialog!!.llNfc) - nfcDeviceAntenna.init() - if (cardId != null) { - readingDialog?.tvCard?.visibility = View.VISIBLE - 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 - reader.closeSession() + readingDialog?.show(SessionViewDelegateState.Ready(cardId, message)) } + readingDialog?.setOnCancelListener { reader.stopSession(true) } readingDialog?.show() } override fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) { postUI { - readingDialog?.lTouchCard?.hide() - readingDialog?.tvRemainingTime?.text = ms.div(100).toString() - readingDialog?.flSecurityDelay?.show() - readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_security_delay) - readingDialog?.tvTaskText?.text = - activity.getText(R.string.dialog_security_delay_description) - - performHapticFeedback() - - if (readingDialog?.pbSecurityDelay?.max != totalDurationSeconds) { - readingDialog?.pbSecurityDelay?.max = totalDurationSeconds - } - readingDialog?.pbSecurityDelay?.progress = totalDurationSeconds - ms + 100 - - val animation = ObjectAnimator.ofInt( - readingDialog?.pbSecurityDelay, - "progress", - totalDurationSeconds - ms, - totalDurationSeconds - ms + 100) - animation.duration = 500 - animation.interpolator = DecelerateInterpolator() - animation.start() + readingDialog?.show(SessionViewDelegateState.SecurityDelay(ms, totalDurationSeconds)) } } override fun onDelay(total: Int, current: Int, step: Int) { postUI { - readingDialog?.lTouchCard?.hide() - readingDialog?.flSecurityDelay?.show() - readingDialog?.tvRemainingTime?.text = (((total - current) / step) + 1).toString() - readingDialog?.tvTaskTitle?.text = "Operation in process" - readingDialog?.tvTaskText?.text = "Please hold the card firmly until the operation is completed…" - - performHapticFeedback() - - if (readingDialog?.pbSecurityDelay?.max != total) { - readingDialog?.pbSecurityDelay?.max = total - } - readingDialog?.pbSecurityDelay?.progress = current - - val animation = ObjectAnimator.ofInt( - readingDialog?.pbSecurityDelay, - "progress", - current, - current + step) - animation.duration = 300 - animation.interpolator = DecelerateInterpolator() - animation.start() + readingDialog?.show(SessionViewDelegateState.Delay(total, current, step)) } } override fun onTagLost() { - postUI { - readingDialog?.lTouchCard?.show() - readingDialog?.flSecurityDelay?.hide() - readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) - readingDialog?.tvTaskText?.text = activity.getText(R.string.dialog_scan_text) - } + postUI { readingDialog?.show(SessionViewDelegateState.TagLost) } + } - 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 onTagConnected() { + postUI { readingDialog?.show(SessionViewDelegateState.TagConnected) } + } + + override fun onWrongCard() { + postUI { readingDialog?.show(SessionViewDelegateState.WrongCard) } + } + + override fun onSessionStopped(message: Message?) { + postUI { readingDialog?.show(SessionViewDelegateState.Success(message)) } } override fun onError(error: TangemSdkError) { - 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 = activity.getString( - R.string.error_message, - error.code.toString(), activity.getString(error.localizedDescription()) - ) - performHapticFeedback() - } + postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } override fun onPinRequested(callback: (result: CompletionResult) -> Unit) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - - private fun performHapticFeedback() { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - readingDialog?.llHeader?.isHapticFeedbackEnabled = true - readingDialog?.llHeader?.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - } + postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } } private fun setLogger() { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt new file mode 100644 index 0000000000..04ecbd2557 --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -0,0 +1,17 @@ +package com.tangem.tangem_sdk_new + +import com.tangem.Message +import com.tangem.TangemSdkError +import com.tangem.common.CompletionResult + +sealed class SessionViewDelegateState() { + data class Error(val error: TangemSdkError) : SessionViewDelegateState() + data class Success(val message: Message?) : SessionViewDelegateState() + data class SecurityDelay(val ms: Int, val totalDurationSeconds: Int) : SessionViewDelegateState() + data class Delay(val total: Int, val current: Int, val step: Int) : SessionViewDelegateState() + data class Ready(val cardId: String?, val message: Message?) : SessionViewDelegateState() + data class PinRequested(val callback: (result: CompletionResult) -> Unit) : SessionViewDelegateState() + object TagLost : SessionViewDelegateState() + object TagConnected : SessionViewDelegateState() + object WrongCard : SessionViewDelegateState() +} \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt index 008f194709..277b729d24 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt @@ -8,4 +8,13 @@ internal fun View.show() { internal fun View.hide() { this.visibility = View.GONE +} + +internal fun View.show(show: Boolean) { + if (show) { + this.visibility = View.VISIBLE + } else { + this.visibility = View.GONE + } + } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt index 8c2a865be4..860c8d5f55 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt @@ -11,6 +11,7 @@ import android.nfc.tech.IsoDep import android.os.Build import android.os.Bundle import com.tangem.Log +import com.tangem.ReadingActiveListener import com.tangem.tangem_sdk_new.ui.NfcEnableDialog /** @@ -18,7 +19,16 @@ import com.tangem.tangem_sdk_new.ui.NfcEnableDialog * Launches [NfcAdapter], manages it with [Activity] lifecycle, * enables and disables Nfc Reading Mode, receives NFC [Tag]. */ -class NfcManager : NfcAdapter.ReaderCallback { +class NfcManager : NfcAdapter.ReaderCallback, ReadingActiveListener { + + override var readingIsActive: Boolean = false + set(value) { + if (value) { + disableReaderMode() + enableReaderMode() + } + field = value + } val reader = NfcReader() private var activity: Activity? = null @@ -42,7 +52,7 @@ class NfcManager : NfcAdapter.ReaderCallback { override fun onTagDiscovered(tag: Tag?) { Log.i(this::class.simpleName!!, "Nfc tag is discovered") - if (reader.readingActive) reader.onTagDiscovered(tag) else ignoreTag(tag) + if (readingIsActive) reader.onTagDiscovered(tag) else ignoreTag(tag) } @@ -50,11 +60,10 @@ class NfcManager : NfcAdapter.ReaderCallback { val filter = IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) activity?.registerReceiver(mBroadcastReceiver, filter) handleNfcEnabled(nfcAdapter?.isEnabled == true) - reader.manager = this + reader.listener = this } private fun handleNfcEnabled(nfcEnabled: Boolean) { - reader.nfcEnabled = nfcEnabled if (nfcEnabled) { enableReaderMode() nfcEnableDialog?.cancel() @@ -67,7 +76,7 @@ class NfcManager : NfcAdapter.ReaderCallback { fun onPause() { activity?.unregisterReceiver(mBroadcastReceiver) disableReaderMode() - reader.manager = null + reader.listener = null } fun onDestroy() { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt index 559adf3f80..26ec450462 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt @@ -4,127 +4,115 @@ import android.nfc.Tag import android.nfc.TagLostException import android.nfc.tech.IsoDep import android.nfc.tech.NfcV -import com.tangem.CardReader -import com.tangem.Log -import com.tangem.TangemSdkError +import com.tangem.* import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.toHexString +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.ConflatedBroadcastChannel +import kotlinx.coroutines.launch + + +data class NfcTag(val type: TagType, val isoDep: IsoDep?, val nfcV: NfcV? = null) /** * Provides NFC communication between an Android application and Tangem card. */ class NfcReader : CardReader { + override val tag = ConflatedBroadcastChannel() + override var scope: CoroutineScope? = null - var readingActive = false - private set - var nfcEnabled = false - var manager: NfcManager? = null - private var isoDep: IsoDep? = null - set(value) { - // don't reassign when there's an active tag already - if (field == null) { - field = value - // if tag is received, call connect first before transceiving data - if (value != null) connect() - } - if (value == null) field = value - } + var listener: ReadingActiveListener? = null - var readingCancelled = false + private var nfcTag: NfcTag? = null set(value) { field = value - if (value) { - // 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(TangemSdkError.UserCancelled())) - } + scope?.launch { tag.send(value?.type) } } - private var data: ByteArray? = null - private var callback: ((response: CompletionResult) -> Unit)? = null - - override fun openSession() { + override fun startSession() { Log.i(this::class.simpleName!!, "NFC reader is starting NFC session") - readingActive = true - readingCancelled = false - manager?.disableReaderMode() - manager?.enableReaderMode() - } - - override fun closeSession() { - isoDep = null - readingActive = false - } - - override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) { - data = apdu.apduData - this.callback = callback - if (isoDep != null) { - transceiveData() - } - } - - private fun transceiveData() { - if (readingCancelled) { - callback?.invoke(CompletionResult.Failure(TangemSdkError.UserCancelled())) - return - } - if (data == null) return - - val rawResponse: ByteArray? - try { - Log.i(this::class.simpleName!!, "Sending data to the card, size is ${data?.size}") - Log.v(this::class.simpleName!!, "Raw data that is to be sent to the card: ${data?.toHexString()}") - rawResponse = isoDep?.transceive(data) - Log.v(this::class.simpleName!!, "Raw data that was received from the card: ${rawResponse?.toHexString()}") - } catch (exception: TagLostException) { - callback?.invoke(CompletionResult.Failure(TangemSdkError.TagLost())) - isoDep = null - return - } catch (exception: Exception) { - Log.i(this::class.simpleName!!, exception.localizedMessage ?: "Error tranceiving data") - // The messages of errors can vary on different Android devices, - // but we try to identify it by parsing the message. - if (exception.message?.contains("length") == true) { - callback?.invoke(CompletionResult.Failure(TangemSdkError.ExtendedLengthNotSupported())) - } - isoDep = null - return - } - if (rawResponse != null) { - Log.i(this::class.simpleName!!, "Data from the card was received") - data = null - } - rawResponse?.let { callback?.invoke(CompletionResult.Success(ResponseApdu(it))) } + listener?.readingIsActive = true } fun onTagDiscovered(tag: Tag?) { - NfcV.get(tag)?.let { onNfcVDiscovered(it) } - isoDep = IsoDep.get(tag) - transceiveData() + NfcV.get(tag)?.let { + nfcTag = NfcTag(TagType.Slix, null, NfcV.get(tag)) + return + } + IsoDep.get(tag)?.let { isoDep -> + connect(isoDep) + nfcTag = NfcTag(TagType.Nfc, isoDep) + } } - - private fun connect() { - isoDep?.connect() - isoDep?.close() - isoDep?.connect() - isoDep?.timeout = 240000 + private fun connect(isoDep: IsoDep) { + isoDep.connect() + isoDep.close() + isoDep.connect() + isoDep.timeout = 240000 Log.i(this::class.simpleName!!, "NFC tag is connected") } - private fun onNfcVDiscovered(nfcV: NfcV) { + override fun stopSession(cancelled: Boolean) { + nfcTag = null + listener?.readingIsActive = false + //TODO: send user cancelled if (cancelled) + } + + override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) { + val data = apdu.apduData + + val rawResponse: ByteArray? = try { + transcieveAndLog(data, callback) + } catch (exception: TagLostException) { + callback.invoke(CompletionResult.Failure(TangemSdkError.TagLost())) + nfcTag = null + return + } catch (exception: Exception) { + tryHandleNfcError(exception, callback) + nfcTag = null + return + } + + if (rawResponse != null) { + Log.i(this::class.simpleName!!, "Data from the card was received") + } + rawResponse?.let { callback.invoke(CompletionResult.Success(ResponseApdu(it))) } + } + + private fun transcieveAndLog(data: ByteArray, callback: (response: CompletionResult) -> Unit): ByteArray? { + Log.i(this::class.simpleName!!, "Sending data to the card, size is ${data.size}") + Log.v(this::class.simpleName!!, "Raw data that is to be sent to the card: ${data.toHexString()}") + val rawResponse = nfcTag?.isoDep?.transceive(data) + Log.v(this::class.simpleName!!, "Raw data that was received from the card: ${rawResponse?.toHexString()}") + return rawResponse + } + + private fun tryHandleNfcError(exception: Exception, callback: (response: CompletionResult) -> Unit) { + Log.i(this::class.simpleName!!, exception.localizedMessage ?: "Error tranceiving data") + // The messages of errors can vary on different Android devices, + // but we try to identify it by parsing the message. + if (exception.message?.contains("length") == true) { + callback.invoke(CompletionResult.Failure(TangemSdkError.ExtendedLengthNotSupported())) + } + } + + override fun readSlixTag(callback: (result: CompletionResult) -> Unit) { + val nfcV = nfcTag?.nfcV + if (nfcV == null) { + callback.invoke(CompletionResult.Failure(TangemSdkError.ErrorProcessingCommand())) + return + } val response = SlixTagReader().transceive(nfcV) when (response) { is SlixReadResult.Failure -> { Log.e(this::class.simpleName!!, "${response.exception.message}") - callback?.invoke(CompletionResult.Failure(TangemSdkError.ErrorProcessingCommand())) + callback.invoke(CompletionResult.Failure(TangemSdkError.ErrorProcessingCommand())) } is SlixReadResult.Success -> { - callback?.invoke(CompletionResult.Success(ResponseApdu(response.data))) + callback.invoke(CompletionResult.Success(ResponseApdu(response.data))) } } } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt new file mode 100644 index 0000000000..4e7468f390 --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -0,0 +1,171 @@ +package com.tangem.tangem_sdk_new.ui + +import android.animation.ObjectAnimator +import android.view.HapticFeedbackConstants +import android.view.View +import android.view.animation.DecelerateInterpolator +import androidx.fragment.app.FragmentActivity +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tangem_sdk_new.R +import com.tangem.tangem_sdk_new.SessionViewDelegateState +import com.tangem.tangem_sdk_new.extensions.localizedDescription +import com.tangem.tangem_sdk_new.extensions.show +import com.tangem.tangem_sdk_new.postUI +import kotlinx.android.synthetic.main.layout_touch_card.* +import kotlinx.android.synthetic.main.nfc_bottom_sheet.* + +class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activity) { + + private var currentState: SessionViewDelegateState? = null + + fun show(state: SessionViewDelegateState) { + when (state) { + is SessionViewDelegateState.Ready -> onReady(state) + is SessionViewDelegateState.Success -> onSuccess(state) + is SessionViewDelegateState.Error -> onError(state) + is SessionViewDelegateState.SecurityDelay -> onSecurityDelay(state) + is SessionViewDelegateState.Delay -> onDelay(state) + is SessionViewDelegateState.PinRequested -> onPinRequested(state) + is SessionViewDelegateState.TagLost -> onTagLost() + is SessionViewDelegateState.TagConnected -> onTagConnected() + is SessionViewDelegateState.WrongCard -> onWrongCard() + } + currentState = state + } + + private fun onReady(state: SessionViewDelegateState.Ready) { + show(lTouchCard) + rippleBackgroundNfc?.startRippleAnimation() + val nfcDeviceAntenna = TouchCardAnimation( + activity, ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc + ) + nfcDeviceAntenna.init() + state.cardId?.let { cardId -> + tvCard?.show() + tvCardId?.show() + tvCardId?.text = cardId + } + state.message?.let { message -> + if (message.body != null) tvTaskText?.text = message.body + if (message.header != null) tvTaskTitle?.text = message.header + } + } + + private fun onSuccess(state: SessionViewDelegateState.Success) { + show(flCompletion) + ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp)) + state.message?.let { message -> + if (message.body != null) tvTaskText?.text = message.body + if (message.header != null) tvTaskTitle?.text = message.header + } + performHapticFeedback() + postUI(300) { dismiss() } + } + + private fun onError(state: SessionViewDelegateState.Error) { + show(flError) + tvTaskTitle?.text = activity.getText(R.string.dialog_error) + tvTaskText?.text = activity.getString( + R.string.error_message, + state.error.code.toString(), activity.getString(state.error.localizedDescription()) + ) + performHapticFeedback() + } + + private fun onSecurityDelay(state: SessionViewDelegateState.SecurityDelay) { + show(flSecurityDelay) + + tvRemainingTime?.text = state.ms.div(100).toString() + tvTaskTitle?.text = activity.getText(R.string.dialog_security_delay) + tvTaskText?.text = + activity.getText(R.string.dialog_security_delay_description) + + performHapticFeedback() + + if (pbSecurityDelay?.max != state.totalDurationSeconds) { + pbSecurityDelay?.max = state.totalDurationSeconds + } + pbSecurityDelay?.progress = state.totalDurationSeconds - state.ms + 100 + + val animation = ObjectAnimator.ofInt( + pbSecurityDelay, + "progress", + state.totalDurationSeconds - state.ms, + state.totalDurationSeconds - state.ms + 100) + animation.duration = 500 + animation.interpolator = DecelerateInterpolator() + animation.start() + } + + private fun onDelay(state: SessionViewDelegateState.Delay) { + show(flSecurityDelay) + tvRemainingTime?.text = (((state.total - state.current) / state.step) + 1).toString() + tvTaskTitle?.text = "Operation in process" + tvTaskText?.text = "Please hold the card firmly until the operation is completed…" + + performHapticFeedback() + + if (pbSecurityDelay?.max != state.total) { + pbSecurityDelay?.max = state.total + } + pbSecurityDelay?.progress = state.current + + val animation = ObjectAnimator.ofInt( + pbSecurityDelay, + "progress", + state.current, + state.current + state.step) + animation.duration = 300 + animation.interpolator = DecelerateInterpolator() + animation.start() + } + + private fun onPinRequested(state: SessionViewDelegateState.PinRequested) { + TODO("To be implemented") + } + + private fun onTagLost() { + if (currentState is SessionViewDelegateState.Success) return + show(lTouchCard) + tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) + tvTaskText?.text = activity.getText(R.string.dialog_scan_text) + } + + private fun onTagConnected() { + show(flReading) + } + + private fun onWrongCard() { + if (currentState !is SessionViewDelegateState.WrongCard) { + show(flError) + tvTaskTitle?.text = activity.getText(R.string.dialog_error) + tvTaskText?.text = activity.getString( + R.string.error_message, + "Wrong Card", activity.getString(R.string.error_wrong_card_number) + ) + performHapticFeedback() + + postUI (2000){ + show(lTouchCard) + tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) + tvTaskText?.text = activity.getText(R.string.dialog_scan_text) + } + + } + } + + private fun show(view: View) { + lTouchCard?.show(view.id == lTouchCard.id) + flSecurityDelay?.show(view.id == flSecurityDelay.id) + flReading?.show(view.id == flReading.id) + flError?.show(view.id == flError.id) + flCompletion?.show(view.id == flCompletion.id) + } + + private fun performHapticFeedback() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + llHeader?.isHapticFeedbackEnabled = true + llHeader?.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) + } + } +} \ No newline at end of file diff --git a/tangem-sdk/src/main/res/drawable/pb_simple_circle.xml b/tangem-sdk/src/main/res/drawable/pb_simple_circle.xml new file mode 100644 index 0000000000..1ae7f2d76a --- /dev/null +++ b/tangem-sdk/src/main/res/drawable/pb_simple_circle.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index 33bf9ad0a3..55317c459a 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -157,9 +157,24 @@ android:progressDrawable="@drawable/pb_circle" android:progressTint="#FF9D30" android:secondaryProgress="100" /> - + + + + + Date: Fri, 5 Jun 2020 12:35:29 +0300 Subject: [PATCH 05/43] Updated on 2026-08-14 --- dependencies.gradle | 2 +- gradlew | 0 .../src/main/java/com/tangem/CardReader.kt | 9 + .../src/main/java/com/tangem/CardSession.kt | 162 ++++++++---------- .../java/com/tangem/SessionEnvironment.kt | 2 +- .../src/main/java/com/tangem/TangemSdk.kt | 7 + .../main/java/com/tangem/TangemSdkError.kt | 1 + .../com/tangem/commands/CheckWalletCommand.kt | 8 +- .../main/java/com/tangem/commands/Command.kt | 120 +++++++++---- .../tangem/commands/CreateWalletCommand.kt | 7 +- .../com/tangem/commands/OpenSessionCommand.kt | 18 +- .../com/tangem/commands/PurgeWalletCommand.kt | 8 +- .../java/com/tangem/commands/ReadCommand.kt | 7 +- .../tangem/commands/ReadIssuerDataCommand.kt | 8 +- .../commands/ReadIssuerExtraDataCommand.kt | 9 +- .../tangem/commands/ReadUserDataCommand.kt | 8 +- .../java/com/tangem/commands/SignCommand.kt | 8 +- .../tangem/commands/WriteIssuerDataCommand.kt | 8 +- .../commands/WriteIssuerExtraDataCommand.kt | 8 +- .../tangem/commands/WriteUserDataCommand.kt | 9 +- .../commands/common/CardDeserializer.kt | 6 +- .../personalization/PersonalizeCommand.kt | 8 +- .../com/tangem/common/apdu/CommandApdu.kt | 64 +++---- .../com/tangem/common/apdu/ResponseApdu.kt | 29 ++-- .../main/java/com/tangem/tasks/ScanTask.kt | 2 +- .../.circleci/config.yml | 0 .../extensions/TangemSdkError.kt | 1 + .../tangem/tangem_sdk_new/nfc/NfcManager.kt | 4 +- .../tangem/tangem_sdk_new/nfc/NfcReader.kt | 15 +- .../src/main/res/values/strings_errors.xml | 1 + 30 files changed, 264 insertions(+), 275 deletions(-) mode change 100755 => 100644 gradlew mode change 100755 => 100644 tangem-sdk-android-config/.circleci/config.yml diff --git a/dependencies.gradle b/dependencies.gradle index 3c7aaf12d8..f61f047e5a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ kotlin : '1.3.72', - build_gradle: '3.6.3', + build_gradle: '4.0.0', ] diff --git a/gradlew b/gradlew old mode 100755 new mode 100644 diff --git a/tangem-core/src/main/java/com/tangem/CardReader.kt b/tangem-core/src/main/java/com/tangem/CardReader.kt index 356b14fd34..deb1f33f14 100644 --- a/tangem-core/src/main/java/com/tangem/CardReader.kt +++ b/tangem-core/src/main/java/com/tangem/CardReader.kt @@ -16,6 +16,15 @@ interface CardReader { val tag: BroadcastChannel var scope: CoroutineScope? + /** + * Sends data to the card and receives the reply in an asynchronous way using coroutines. + * + * @param apdu Data to be sent. [CommandApdu] serializes it to a [ByteArray] + * @param callback Returns response from the card, + * [ResponseApdu] Allows to convert raw data to [Tlv] + */ + suspend fun transceiveApdu(apdu: CommandApdu): CompletionResult + /** * Sends data to the card and receives the reply. * diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index f3a77df81d..e46be34484 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -12,14 +12,8 @@ import com.tangem.crypto.EncryptionHelper import com.tangem.crypto.FastEncryptionHelper import com.tangem.crypto.StrongEncryptionHelper import com.tangem.crypto.pbkdf2Hash -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.launch +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* /** * Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession] @@ -61,11 +55,11 @@ enum class TagType { * 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 + val environment: SessionEnvironment, + private val reader: CardReader, + val viewDelegate: SessionViewDelegate, + private var cardId: String? = null, + private val initialMessage: Message? = null ) { var connectedTag: TagType? = null @@ -75,7 +69,9 @@ class CardSession( */ private var state = CardSessionState.Inactive - private val scope = CoroutineScope(Dispatchers.IO) + val scope = CoroutineScope(Dispatchers.IO) + CoroutineExceptionHandler { _, ex -> + throw ex + } private val tag = this.javaClass.simpleName @@ -86,17 +82,14 @@ class CardSession( * @param callback will be triggered with a [CompletionResult] of a session. */ fun , R : CommandResponse> startWithRunnable( - runnable: T, callback: (result: CompletionResult) -> Unit) { + runnable: T, callback: (result: CompletionResult) -> Unit + ) { start(runnable.performPreflightRead) { 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 -> when (result) { @@ -121,8 +114,10 @@ class CardSession( * Starts a card session and performs preflight [ReadCommand]. * @param callback: callback with the card session. Can contain [TangemSdkError] if something goes wrong. */ - fun start(performPreflightRead: Boolean = true, - callback: (session: CardSession, error: TangemSdkError?) -> Unit) { + fun start( + performPreflightRead: Boolean = true, + callback: (session: CardSession, error: TangemSdkError?) -> Unit + ) { if (state != CardSessionState.Inactive) { callback(this, TangemSdkError.Busy()) @@ -133,21 +128,21 @@ class CardSession( scope.launch { reader.tag - .asFlow() - .collect { tagType -> - if (tagType == null && connectedTag != null) { - handleTagLost() - } else if (tagType != null) { - connectedTag = tagType - viewDelegate.onTagConnected() + .asFlow() + .collect { tagType -> + if (tagType == null && connectedTag != null) { + handleTagLost() + } else if (tagType != null) { + connectedTag = tagType + viewDelegate.onTagConnected() - if (tagType == TagType.Nfc && performPreflightRead) { - preflightCheck(callback) - } else { - callback(this@CardSession, null) - } + if (tagType == TagType.Nfc && performPreflightRead) { + preflightCheck(callback) + } else { + callback(this@CardSession, null) } } + } } reader.scope = scope reader.startSession() @@ -164,15 +159,8 @@ class CardSession( readCommand.run(this) { result -> when (result) { is CompletionResult.Failure -> { - tryHandleError(result.error) { handleErrorResult -> - when (handleErrorResult) { - is CompletionResult.Success -> preflightCheck(callback) - is CompletionResult.Failure -> { - stopWithError(result.error) - callback(this, result.error) - } - } - } + stopWithError(result.error) + callback(this, result.error) } is CompletionResult.Success -> { val receivedCardId = result.data.cardId @@ -230,65 +218,53 @@ class CardSession( fun send(apdu: CommandApdu, callback: (result: CompletionResult) -> Unit) { val subscription = reader.tag.openSubscription() - scope.launch { subscription.consumeAsFlow() - .filterNotNull() - .collect { - reader.transceiveApdu(apdu) { result -> - subscription.cancel() - callback(result) - } - } - } - } - - private fun tryHandleError( - error: TangemSdkError, callback: (result: CompletionResult) -> Unit) { - - when (error) { - is TangemSdkError.NeedEncryption -> { - Log.i(tag, "Establishing encryption") - 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(tag, "Encryption doesn't work") - callback(CompletionResult.Failure(TangemSdkError.NeedEncryption())) - } + .filterNotNull() + .map { establishEncryption() } + .map { apdu.encrypt(environment.encryptionMode, environment.encryptionKey) } + .map { encryptedApdu -> reader.transceiveApdu(encryptedApdu) } + .catch { error -> + if (error is TangemSdkError) callback( + CompletionResult.Failure( + error + ) + ) + } + .collect { result -> + subscription.cancel() + callback(result) } - return establishEncryption(callback) - } - else -> callback(CompletionResult.Failure(TangemSdkError.UnknownError())) } } - private fun establishEncryption(callback: (result: CompletionResult) -> Unit) { + private suspend fun establishEncryption(): CompletionResult { + if (environment.encryptionKey != null) return CompletionResult.Success(true) 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.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)) + when (environment.encryptionMode) { + EncryptionMode.NONE -> return CompletionResult.Success(true) + EncryptionMode.FAST -> FastEncryptionHelper() + EncryptionMode.STRONG -> StrongEncryptionHelper() } + val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) + val apdu = openSesssionCommand.serialize(environment) + + val response = reader.transceiveApdu(apdu) + when (response) { + is CompletionResult.Success -> { + val result = try { + openSesssionCommand.deserialize(environment, response.data) + } catch (error: TangemSdkError) { + return CompletionResult.Failure(error) + } + val uid = result.uid + val protocolKey = environment.pin1.pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + environment.encryptionKey = sessionKey + return CompletionResult.Success(true) + } + is CompletionResult.Failure -> return CompletionResult.Failure(response.error) } } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 5f8734f685..78b6d28715 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -42,7 +42,7 @@ data class SessionEnvironment( /** * All possible encryption modes. */ -enum class EncryptionMode(val code: Byte) { +enum class EncryptionMode(val code: Int) { NONE(0x0), FAST(0x1), STRONG(0x2) diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index c5ac313d34..a4d035d669 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -8,6 +8,8 @@ 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.commands.verifycard.VerifyCardCommand +import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils @@ -294,6 +296,11 @@ class TangemSdk( startSessionWithRunnable(PurgeWalletCommand(), cardId, initialMessage, callback) } + fun verify(cardId: String? = null, online: Boolean = true, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSessionWithRunnable(VerifyCardCommand(online), cardId, initialMessage, callback) + } + /** * Command available on SDK cards only * diff --git a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt index 4aabb3da6b..9e35b75edd 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt @@ -29,6 +29,7 @@ sealed class TangemSdkError(val code: Int) : Exception(code.toString()) { class DecodingFailedMissingTag : TangemSdkError(20005) class DecodingFailedTypeMismatch : TangemSdkError(20006) class DecodingFailed : TangemSdkError(20007) + class InvalidResponse : TangemSdkError(20008) /** * This error is returned when unknown [StatusWord] is received from a card. diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index e3cbdf970a..09b6f08b94 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -90,15 +90,11 @@ class CheckWalletCommand( tlvBuilder.append(TlvTag.Pin, environment.pin1) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu( - Instruction.CheckWallet, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CheckWalletResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return CheckWalletResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 0b17642735..96343045a1 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -2,13 +2,31 @@ 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.toTangemSdkError +import com.tangem.common.apdu.* import com.tangem.common.extensions.toInt import com.tangem.common.tlv.TlvTag + +interface ApduSerializable { + /** + * 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 + */ + 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] + */ + fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): T +} + /** * Basic interface for a parsed response from [Command]. */ @@ -17,28 +35,10 @@ interface CommandResponse /** * Basic class for Tangem card commands */ -abstract class Command : CardSessionRunnable { +abstract class Command : ApduSerializable, CardSessionRunnable { override val performPreflightRead: Boolean = true - /** - * 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) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") if (session.environment.handleErrors) { @@ -52,14 +52,18 @@ abstract class Command : CardSessionRunnable { } } - open fun performPreCheck(session: CardSession, - callback: (result: CompletionResult) -> Unit): Boolean { + open fun performPreCheck( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ): Boolean { return false } - open fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit): Boolean { + open fun performAfterCheck( + session: CardSession, + result: CompletionResult, + callback: (result: CompletionResult) -> Unit + ): Boolean { return false } @@ -80,25 +84,63 @@ abstract class Command : CardSessionRunnable { } } - private fun transceiveApdu(apdu: CommandApdu, session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.send(apdu) { result -> + private fun transceiveApdu( + apdu: CommandApdu, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + Log.i(this::class.simpleName!!, "transieve: ${Instruction.byCode(apdu.ins)}") + 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.ProcessCompleted, StatusWord.Pin1Changed, + StatusWord.Pin2Changed, StatusWord.PinsChanged -> { + try { + val decryptedResponseApdu = + responseApdu.decrypt(session.environment.encryptionKey) + callback(CompletionResult.Success(decryptedResponseApdu)) + } catch (error: TangemSdkError) { + callback(CompletionResult.Failure(error)) + } + } StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. - val remainingTime = deserializeSecurityDelay(responseApdu, session.environment) + val remainingTime = + deserializeSecurityDelay(responseApdu) if (remainingTime != null) { session.viewDelegate.onSecurityDelay( - remainingTime, - session.environment.card?.pauseBeforePin2 ?: 0) + 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) + } + StatusWord.NeedEncryption -> { + Log.i(this::class.simpleName!!, "Establishing encryption") + when (session.environment.encryptionMode) { + EncryptionMode.NONE -> { + session.environment.encryptionKey = null + session.environment.encryptionMode = EncryptionMode.FAST + } + EncryptionMode.FAST -> { + session.environment.encryptionKey = null + session.environment.encryptionMode = EncryptionMode.STRONG + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(TangemSdkError.NeedEncryption())) + return@send + } } - Log.i(this::class.simpleName!!, "Nfc command ${this::class.simpleName!!} " + - "triggered security delay of $remainingTime milliseconds") transceiveApdu(apdu, session, callback) } else -> { @@ -126,7 +168,9 @@ abstract class Command : CardSessionRunnable { * * @return Remaining security delay in milliseconds. */ - private fun deserializeSecurityDelay(responseApdu: ResponseApdu, environment: SessionEnvironment): Int? { + private fun deserializeSecurityDelay( + responseApdu: ResponseApdu + ): Int? { val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index e0bf433c99..dba1b085d9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -80,14 +80,11 @@ class CreateWalletCommand : Command() { 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(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt index 891e28f3c1..1301703b9f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -10,8 +10,8 @@ import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag class OpenSessionResponse( - val sessionKeyB: ByteArray, - val uid: ByteArray + val sessionKeyB: ByteArray, + val uid: ByteArray ) : CommandResponse /** @@ -25,19 +25,21 @@ class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command() { 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(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PurgeWalletResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return PurgeWalletResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 49bf955851..3c6aa19bff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -390,13 +390,10 @@ class ReadCommand : Command() { */ tlvBuilder.append(TlvTag.Pin, environment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) - return CommandApdu( - Instruction.Read, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.Read, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu, environment) + return CardDeserializer.deserialize(apdu) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index d01a3adbe8..b3015b0be3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -103,15 +103,11 @@ class ReadIssuerDataCommand( 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(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return ReadIssuerDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 82e148a05d..1f5c77b1da 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -152,16 +152,11 @@ class ReadIssuerExtraDataCommand( tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu( - Instruction.ReadIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() - + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return ReadIssuerExtraDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index b8ee2022d6..3f79960e93 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -68,15 +68,11 @@ class ReadUserDataCommand : Command() { builder.append(TlvTag.CardId, environment.card?.cardId) builder.append(TlvTag.Pin, environment.pin1) - return CommandApdu( - Instruction.ReadUserData, builder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.ReadUserData, builder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadUserDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return ReadUserDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index fc926be116..816a0cd373 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -99,10 +99,7 @@ class SignCommand(private val hashes: Array) tlvBuilder.append(TlvTag.Cvc, environment.cvc) addTerminalSignature(environment, dataToSign, tlvBuilder) - return CommandApdu( - Instruction.Sign, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) } private fun flattenHashes(): ByteArray { @@ -133,8 +130,7 @@ class SignCommand(private val hashes: Array) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SignResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return SignResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 2574a2f4a6..e9a23afac7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -114,15 +114,11 @@ class WriteIssuerDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu( - Instruction.WriteIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return WriteIssuerDataResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 84e0b50851..5fd85d01c3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -184,10 +184,7 @@ class WriteIssuerExtraDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) } } - return CommandApdu( - Instruction.WriteIssuerData, tlvBuilder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) } private fun getDataToWrite(): ByteArray = @@ -199,8 +196,7 @@ class WriteIssuerExtraDataCommand( } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) ) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index fc0d0467ae..1cdac8447a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -77,15 +77,12 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val if (userProtectedCounter != null || userProtectedData != null) builder.append(TlvTag.Pin2, environment.pin2) - return CommandApdu( - Instruction.WriteUserData, builder.serialize(), - environment.encryptionMode, environment.encryptionKey - ) + return CommandApdu(Instruction.WriteUserData, builder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteUserDataResponse { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() + return WriteUserDataResponse(TlvDecoder(tlvData).decode(TlvTag.CardId)) } diff --git a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt index ff29df7075..ccf809cf65 100644 --- a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt @@ -1,6 +1,5 @@ package com.tangem.commands.common -import com.tangem.SessionEnvironment import com.tangem.TangemSdkError import com.tangem.commands.Card import com.tangem.commands.CardData @@ -11,9 +10,8 @@ import com.tangem.common.tlv.TlvTag class CardDeserializer() { companion object { - fun deserialize(apdu: ResponseApdu, environment: SessionEnvironment): Card { - val tlvData = apdu.getTlvData(environment.encryptionKey) - ?: throw TangemSdkError.DeserializeApduFailed() + fun deserialize(apdu: ResponseApdu): Card { + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index 425d33180d..5648b69def 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -47,15 +47,11 @@ class PersonalizeCommand( } override fun serialize(environment: SessionEnvironment): CommandApdu { - return CommandApdu( - Instruction.Personalize, - serializePersonalizationData(config), - encryptionKey = devPersonalizationKey - ) + return CommandApdu(Instruction.Personalize, serializePersonalizationData(config)) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu, environment) + return CardDeserializer.deserialize(apdu) } private fun serializePersonalizationData(config: CardConfig): ByteArray { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index e2d37df4bb..a804704598 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -15,42 +15,27 @@ import java.io.ByteArrayOutputStream */ class CommandApdu( - private val ins: Int, - private val tlvs: ByteArray, + val ins: Int, + private val tlvs: ByteArray, - private val le: Int = 0x00, + private val p1: Int, + private val p2: Int, - private val encryptionMode: EncryptionMode = EncryptionMode.NONE, - private val encryptionKey: ByteArray? = null, + private val le: Int = 0x00, - private val cla: Int = ISO_CLA) { + private val cla: Int = ISO_CLA +) { constructor( - instruction: Instruction, - tlvs: ByteArray, - encryptionMode: EncryptionMode = EncryptionMode.NONE, - encryptionKey: ByteArray? = null + instruction: Instruction, + tlvs: ByteArray ) : this( - instruction.code, - tlvs, - encryptionMode = encryptionMode, - encryptionKey = encryptionKey + instruction.code, + tlvs, + 0, + 0 ) - private val p1: Int - private val p2: Int - - init { - if (ins == Instruction.OpenSession.code) { - p1 = 0x00 - p2 = encryptionMode.code.toInt() - } else { - p1 = encryptionMode.code.toInt() - p2 = 0x00 - } - } - - /** * Request converted to a raw data */ @@ -62,7 +47,7 @@ class CommandApdu( private fun toBytes(): ByteArray { - val data = if (encryptionKey != null) tlvs.encrypt() else tlvs + val data = tlvs val byteStream = ByteArrayOutputStream() byteStream.write(cla) @@ -83,14 +68,21 @@ class CommandApdu( } - private fun ByteArray.encrypt(): ByteArray { - val crc: ByteArray = tlvs.calculateCrc16() - val stream = ByteArrayOutputStream() - stream.write(this.size.toByteArray(2)) - stream.write(crc) - stream.write(this) - return stream.toByteArray().encrypt(encryptionKey!!) + fun encrypt( + encryptionMode: EncryptionMode, + encryptionKey: ByteArray? + ): CommandApdu { + if (encryptionMode == EncryptionMode.NONE + || encryptionKey == null || p1 != EncryptionMode.NONE.code + ) { + return this } + val crc: ByteArray = tlvs.calculateCrc16() + val dataToEncrypt = tlvs.size.toByteArray(2) + crc + tlvs + val encryptedData = dataToEncrypt.encrypt(encryptionKey) + + return CommandApdu(ins, encryptedData, encryptionMode.code, p2, le, cla) + } companion object { const val ISO_CLA = 0x00 diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index e7e8625384..bb947d074a 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -1,5 +1,6 @@ package com.tangem.common.apdu +import com.tangem.TangemSdkError import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.tlv.Tlv import com.tangem.crypto.decrypt @@ -27,40 +28,36 @@ class ResponseApdu(private val data: ByteArray) { * @param encryptionKey key to decrypt response. * (Encryption / decryption functionality is not implemented yet.) */ - fun getTlvData(encryptionKey: ByteArray? = null): List? { + fun getTlvData(): List? { return if (data.size <= 2) { null } else { - val responseData = data.copyOf(data.size - 2) - return if (encryptionKey != null) { - if (data.size >= 18) { - val decryptedData = decrypt(responseData, encryptionKey) - Tlv.deserialize(decryptedData) - } else { - null - } - } else { - Tlv.deserialize(responseData) - } + Tlv.deserialize(data.copyOf(data.size - 2)) } } - private fun decrypt(responseData: ByteArray, encryptionKey: ByteArray): ByteArray { + fun decrypt(encryptionKey: ByteArray?): ResponseApdu { + if (encryptionKey == null) return this + + if (data.size < 18) throw TangemSdkError.InvalidResponse() + + val responseData = data.copyOf(data.size - 2) + val decryptedData: ByteArray = responseData.decrypt(encryptionKey) val inputStream = ByteArrayInputStream(decryptedData) val baLength = ByteArray(2) inputStream.read(baLength) val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) - if (length > decryptedData.size - 4) throw Exception("Can't decrypt - data size invalid") + if (length > decryptedData.size - 4) throw TangemSdkError.InvalidResponse() val baCRC = ByteArray(2) inputStream.read(baCRC) val answerData = ByteArray(length) inputStream.read(answerData) val crc: ByteArray = answerData.calculateCrc16() - if (!baCRC.contentEquals(crc)) throw Exception("Can't decrypt - crc invalid") + if (!baCRC.contentEquals(crc)) throw TangemSdkError.InvalidResponse() - return answerData + return ResponseApdu(answerData + data[data.size - 2] + data[data.size - 1]) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index 1b203b28fe..8b9d8f2452 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -59,7 +59,7 @@ internal class ScanTask : CardSessionRunnable { when (result) { is CompletionResult.Success -> { try { - val card = CardDeserializer.deserialize(result.data, session.environment) + val card = CardDeserializer.deserialize(result.data) callback(CompletionResult.Success(card)) } catch (error: TangemSdkError) { callback(CompletionResult.Failure(error)) diff --git a/tangem-sdk-android-config/.circleci/config.yml b/tangem-sdk-android-config/.circleci/config.yml old mode 100755 new mode 100644 diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt index 09c84716e4..7ef49ed0e0 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt @@ -52,5 +52,6 @@ fun TangemSdkError.localizedDescription(): Int { is TangemSdkError.WrongCardNumber -> R.string.error_wrong_card_number is TangemSdkError.WrongCardType -> R.string.error_wrong_card_type is TangemSdkError.CardError -> R.string.error_card_error + is TangemSdkError.InvalidResponse -> R.string.error_invalid_response } } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt index 860c8d5f55..6c73836773 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt @@ -84,11 +84,11 @@ class NfcManager : NfcAdapter.ReaderCallback, ReadingActiveListener { nfcAdapter = null } - fun enableReaderMode() { + private fun enableReaderMode() { nfcAdapter?.enableReaderMode(activity, this, READER_FLAGS, Bundle()) } - fun disableReaderMode() { + private fun disableReaderMode() { nfcAdapter?.disableReaderMode(activity) } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt index 26ec450462..4c8834f807 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt @@ -12,6 +12,8 @@ import com.tangem.common.extensions.toHexString import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume data class NfcTag(val type: TagType, val isoDep: IsoDep?, val nfcV: NfcV? = null) @@ -61,11 +63,16 @@ class NfcReader : CardReader { //TODO: send user cancelled if (cancelled) } - override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) { - val data = apdu.apduData + override suspend fun transceiveApdu(apdu: CommandApdu): CompletionResult = + suspendCancellableCoroutine { continuation -> + transceiveApdu(apdu) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + override fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) { val rawResponse: ByteArray? = try { - transcieveAndLog(data, callback) + transcieveAndLog(apdu.apduData) } catch (exception: TagLostException) { callback.invoke(CompletionResult.Failure(TangemSdkError.TagLost())) nfcTag = null @@ -82,7 +89,7 @@ class NfcReader : CardReader { rawResponse?.let { callback.invoke(CompletionResult.Success(ResponseApdu(it))) } } - private fun transcieveAndLog(data: ByteArray, callback: (response: CompletionResult) -> Unit): ByteArray? { + private fun transcieveAndLog(data: ByteArray): ByteArray? { Log.i(this::class.simpleName!!, "Sending data to the card, size is ${data.size}") Log.v(this::class.simpleName!!, "Raw data that is to be sent to the card: ${data.toHexString()}") val rawResponse = nfcTag?.isoDep?.transceive(data) diff --git a/tangem-sdk/src/main/res/values/strings_errors.xml b/tangem-sdk/src/main/res/values/strings_errors.xml index b41a0e5d1f..393a84e554 100644 --- a/tangem-sdk/src/main/res/values/strings_errors.xml +++ b/tangem-sdk/src/main/res/values/strings_errors.xml @@ -29,4 +29,5 @@ You tapped a different card. Please match the in-app Card ID to the your physical Card ID to continue this process. This card is configured for a different app. Please see the information on your card and download the related app. Your card is missing essential data + Invalid Response \ No newline at end of file From 5b418812bb78945d19c51b57f860f0fb526552d0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Jun 2020 10:17:04 +0300 Subject: [PATCH 06/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt index 4c8834f807..8636b5f366 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt @@ -10,6 +10,7 @@ import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.toHexString import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine @@ -60,7 +61,7 @@ class NfcReader : CardReader { override fun stopSession(cancelled: Boolean) { nfcTag = null listener?.readingIsActive = false - //TODO: send user cancelled if (cancelled) + if (cancelled) scope?.cancel() } override suspend fun transceiveApdu(apdu: CommandApdu): CompletionResult = From 1edb4eddc06d52d4e90cbbd822fdb695746b4b2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Jun 2020 10:31:30 +0300 Subject: [PATCH 07/43] Updated on 2026-08-14 --- tangem-core/src/main/java/com/tangem/TangemSdk.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index a4d035d669..c5ac313d34 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -8,8 +8,6 @@ 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.commands.verifycard.VerifyCardCommand -import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils @@ -296,11 +294,6 @@ class TangemSdk( startSessionWithRunnable(PurgeWalletCommand(), cardId, initialMessage, callback) } - fun verify(cardId: String? = null, online: Boolean = true, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(VerifyCardCommand(online), cardId, initialMessage, callback) - } - /** * Command available on SDK cards only * From 6b25de35e577091add77f0fccc14ee4cf8b748f5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 8 Jun 2020 12:41:16 +0300 Subject: [PATCH 08/43] Updated on 2026-08-14 --- tangem-core/build.gradle | 4 +-- .../com/tangem/common/apdu/CommandApduTest.kt | 29 ------------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index c64079660f..bd00611a6a 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -23,8 +23,8 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.6' // tests - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2' - testImplementation "com.google.truth:truth:1.0" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' + testImplementation "com.google.truth:truth:1.0.1" } sourceCompatibility = "8" diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt index c03069b6da..b995c4de57 100644 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt @@ -56,33 +56,4 @@ class CommandApduTest { assertThat(commandApdu.apduData) .isEqualTo(expected) } - - @Test - fun testFlow() { - val channel = BroadcastChannel(1) - val flow = GlobalScope.launch { - delay(100) - channel.send(1) - delay(200) - channel.send(2) - } - - val scope = CoroutineScope(Dispatchers.IO) - val scope2 = CoroutineScope(Dispatchers.IO) - - val receiveChannel1 = BroadcastChannel(1) - val receiveChannel2 = BroadcastChannel(1) - - scope.launch { - channel.asFlow().collect { println(it) } - channel.asFlow().collect { println(it) } - } - scope.launch { channel.asFlow().collect { println(it) } } - - runBlocking { - delay(1000) - } - - - } } \ No newline at end of file From 2f125e24d9076747788bfcc71e05284e13c0d05a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Jun 2020 09:38:19 +0300 Subject: [PATCH 09/43] Updated on 2026-08-14 --- .../main/java/com/tangem/TangemSdkError.kt | 2 +- .../com/tangem/commands/CheckWalletCommand.kt | 17 +-- .../main/java/com/tangem/commands/Command.kt | 59 ++++---- .../tangem/commands/CreateWalletCommand.kt | 79 +++++----- .../com/tangem/commands/PurgeWalletCommand.kt | 44 +++--- .../java/com/tangem/commands/ReadCommand.kt | 18 +-- .../tangem/commands/ReadIssuerDataCommand.kt | 95 ++++++------ .../commands/ReadIssuerExtraDataCommand.kt | 136 +++++++++--------- .../tangem/commands/ReadUserDataCommand.kt | 16 +-- .../java/com/tangem/commands/SignCommand.kt | 94 +++++------- .../tangem/commands/WriteIssuerDataCommand.kt | 89 +++++------- .../commands/WriteIssuerExtraDataCommand.kt | 129 ++++++++--------- .../tangem/commands/WriteUserDataCommand.kt | 36 ++--- .../personalization/PersonalizeCommand.kt | 10 +- .../extensions/TangemSdkError.kt | 2 +- 15 files changed, 358 insertions(+), 468 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt index 9e35b75edd..d58771fc77 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt @@ -105,7 +105,7 @@ sealed class TangemSdkError(val code: Int) : Exception(code.toString()) { class TooManyHashesInOneTransaction : TangemSdkError(40906) //Write Extra Issuer Data Errors - class ExendedDataSizeTooLarge : TangemSdkError(41101) + class ExtendedDataSizeTooLarge : TangemSdkError(41101) //General Errors class NotPersonalized() : TangemSdkError(40001) diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index 09b6f08b94..672ca2da62 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -70,19 +70,14 @@ class CheckWalletCommand( } } - override fun performPreCheck( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.isActivated) { + return TangemSdkError.NotActivated() } - return false + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 96343045a1..860b954ebf 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -41,47 +41,46 @@ abstract class Command : ApduSerializable, CardSessionRu override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") - if (session.environment.handleErrors) { - if (performPreCheck(session, callback)) return - } - transceive(session) { result -> - if (session.environment.handleErrors) { - if (performAfterCheck(session, result, callback)) return@transceive - } - callback(result) - } + transceive(session, callback) } - open fun performPreCheck( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ): Boolean { - return false - } + open fun performPreCheck(card: Card): TangemSdkError? = null - open fun performAfterCheck( - session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit - ): Boolean { - return false - } + open fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? = error fun transceive(session: CardSession, callback: (result: CompletionResult) -> 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 card = session.environment.card + if (session.environment.handleErrors && card != null) { + performPreCheck(card)?.let { error -> + callback(CompletionResult.Failure(error)) + return + } + } + + val apdu = serialize(session.environment) + transceiveApdu(apdu, session) { result -> + when (result) { + is CompletionResult.Failure -> { + if (session.environment.handleErrors) { + performAfterCheck(session.environment.card, result.error)?.let { + callback(CompletionResult.Failure(it)) + return@transceiveApdu + } + } + callback(CompletionResult.Failure(result.error)) + } + is CompletionResult.Success -> { + try { val response = deserialize(session.environment, result.data) callback(CompletionResult.Success(response)) + } catch (error: TangemSdkError) { + callback(CompletionResult.Failure(error)) } } } - } catch (error: TangemSdkError) { - callback(CompletionResult.Failure(error)) } + } private fun transceiveApdu( diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index dba1b085d9..6d0bab7883 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -1,9 +1,7 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -12,18 +10,18 @@ import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag class CreateWalletResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - /** - * Current status of the card [1 - Empty, 2 - Loaded, 3- Purged] - */ - val status: CardStatus, - /** + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + /** + * Current status of the card [1 - Empty, 2 - Loaded, 3- Purged] + */ + val status: CardStatus, + /** - */ - val walletPublicKey: ByteArray + */ + val walletPublicKey: ByteArray ) : CommandResponse /** @@ -39,39 +37,25 @@ class CreateWalletResponse( */ class CreateWalletCommand : Command() { - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.isActivated) { + return TangemSdkError.NotActivated() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + + return when (card.status) { + CardStatus.Empty -> null + CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() + CardStatus.Loaded -> TangemSdkError.AlreadyCreated() + CardStatus.Purged -> TangemSdkError.CardIsPurged() + null -> TangemSdkError.CardError() } - if (session.environment.card?.status == CardStatus.Purged) { - callback(CompletionResult.Failure(TangemSdkError.CardIsPurged())) - return true - } - if (session.environment.card?.status == CardStatus.Loaded) { - callback(CompletionResult.Failure(TangemSdkError.AlreadyCreated())) - return true - } - return false } - override fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit): Boolean { - when (result) { - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.InvalidParams) { - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - return true - } - return false - } - else -> return false + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams) { + return TangemSdkError.Pin2OrCvcRequired() } + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { @@ -83,15 +67,18 @@ class CreateWalletCommand : Command() { return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) } - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse { + override fun deserialize( + environment: SessionEnvironment, + apdu: ResponseApdu + ): CreateWalletResponse { val tlvData = apdu.getTlvData() - ?: throw TangemSdkError.DeserializeApduFailed() + ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return CreateWalletResponse( - cardId = decoder.decode(TlvTag.CardId), - status = decoder.decode(TlvTag.Status), - walletPublicKey = decoder.decode(TlvTag.WalletPublicKey) + cardId = decoder.decode(TlvTag.CardId), + status = decoder.decode(TlvTag.Status), + walletPublicKey = decoder.decode(TlvTag.WalletPublicKey) ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index f1277e8220..3ec6ff7315 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -1,9 +1,7 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -31,37 +29,33 @@ class PurgeWalletResponse( */ class PurgeWalletCommand : Command() { - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.isActivated) { + return TangemSdkError.NotActivated() } - if (session.environment.card?.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true) { - callback(CompletionResult.Failure(TangemSdkError.PurgeWalletProhibited())) - return true + if (card.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true) { + return TangemSdkError.PurgeWalletProhibited() + } + + return when (card.status) { + CardStatus.Loaded -> null + CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() + CardStatus.Empty, CardStatus.Purged -> TangemSdkError.CardIsEmpty() + null -> TangemSdkError.CardError() } - return false } - override fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit): Boolean { - when (result) { - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.InvalidParams) { - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - return true - } - return false - } - else -> return false + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams) { + return TangemSdkError.Pin2OrCvcRequired() } + return null } + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.Pin, environment.pin1) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 3c6aa19bff..d0efbf78bd 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -1,10 +1,8 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError import com.tangem.commands.common.CardDeserializer -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -317,6 +315,8 @@ class Card( /** * Whether the card requires issuer’s confirmation of activation. + * is "true" if the card requires activation, + * is 'false" if the card is activated or does not require activation */ val isActivated: Boolean, @@ -367,17 +367,11 @@ class Card( */ class ReadCommand : Command() { - override fun performAfterCheck(session: CardSession, result: CompletionResult, callback: (result: CompletionResult) -> Unit): Boolean { - when (result) { - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.InvalidParams) { - callback(CompletionResult.Failure(TangemSdkError.Pin1Required())) - return true - } - return false - } - else -> return false + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams) { + return TangemSdkError.Pin1Required() } + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index b3015b0be3..97bc306083 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -17,32 +17,32 @@ import com.tangem.common.tlv.TlvTag class ReadIssuerDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, - /** - * Data defined by issuer. - */ - val issuerData: ByteArray, + /** + * Data defined by issuer. + */ + val issuerData: ByteArray, - /** - * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). - * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: - * SHA256([cardId] | [issuerData]). - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of - * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: - * SHA256([cardId] | [issuerData] | [issuerDataCounter]). - */ - val issuerDataSignature: ByteArray, + /** + * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). + * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: + * SHA256([cardId] | [issuerData]). + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of + * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: + * SHA256([cardId] | [issuerData] | [issuerDataCounter]). + */ + val issuerDataSignature: ByteArray, - /** - * An optional counter that protect issuer data against replay attack. - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] - * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. - */ - val issuerDataCounter: Int? + /** + * An optional counter that protect issuer data against replay attack. + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] + * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. + */ + val issuerDataCounter: Int? ) : CommandResponse @@ -54,21 +54,17 @@ class ReadIssuerDataResponse( * @property cardId CID, Unique Tangem card ID number. */ class ReadIssuerDataCommand( - val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() + val issuerPublicKey: ByteArray? = null, + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() ) : Command(), IssuerDataVerifier by verifier { - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return - } - val publicKey = issuerPublicKey ?: card.issuerPublicKey - if (publicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey())) - return - } + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + + val publicKey = issuerPublicKey ?: session.environment.card?.issuerPublicKey + super.run(session) { result -> when (result) { is CompletionResult.Failure -> callback(result) @@ -78,9 +74,9 @@ class ReadIssuerDataCommand( return@run } val issuerDataToVerify = IssuerDataToVerify( - card.cardId, result.data.issuerData, result.data.issuerDataCounter + result.data.cardId, result.data.issuerData, result.data.issuerDataCounter ) - if (verify(publicKey, result.data.issuerDataSignature, issuerDataToVerify)) { + if (verify(publicKey!!, result.data.issuerDataSignature, issuerDataToVerify)) { callback(result) } else { callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) @@ -90,12 +86,12 @@ class ReadIssuerDataCommand( } } - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - return false + issuerPublicKey ?: card.issuerPublicKey ?: return TangemSdkError.MissingIssuerPubicKey() + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { @@ -106,15 +102,18 @@ class ReadIssuerDataCommand( return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse { + override fun deserialize( + environment: SessionEnvironment, + apdu: ResponseApdu + ): ReadIssuerDataResponse { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() 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) + cardId = decoder.decode(TlvTag.CardId), + issuerData = decoder.decode(TlvTag.IssuerData), + issuerDataSignature = decoder.decode(TlvTag.IssuerDataSignature), + issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter) ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 1f5c77b1da..cb7bba09d0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -18,37 +18,37 @@ import java.io.ByteArrayOutputStream class ReadIssuerExtraDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, - /** - * Size of all Issuer_Extra_Data field. - */ - val size: Int?, + /** + * Size of all Issuer_Extra_Data field. + */ + val size: Int?, - /** - * Data defined by issuer. - */ - val issuerData: ByteArray, + /** + * Data defined by issuer. + */ + val issuerData: ByteArray, - /** - * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). - * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: - * SHA256([cardId] | [issuerData]). - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of - * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: - * SHA256([cardId] | [issuerData] | [issuerDataCounter]). - */ - val issuerDataSignature: ByteArray?, + /** + * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). + * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: + * SHA256([cardId] | [issuerData]). + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of + * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: + * SHA256([cardId] | [issuerData] | [issuerDataCounter]). + */ + val issuerDataSignature: ByteArray?, - /** - * An optional counter that protects issuer data against replay attack. - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] - * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. - */ - val issuerDataCounter: Int? + /** + * An optional counter that protects issuer data against replay attack. + * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] + * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. + */ + val issuerDataCounter: Int? ) : CommandResponse @@ -60,42 +60,38 @@ 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() + private val issuerPublicKey: ByteArray? = null, + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() ) : Command(), IssuerDataVerifier by verifier { private val issuerData = ByteArrayOutputStream() private var offset: Int = 0 private var issuerDataSize: Int = 0 - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - val publicKey = issuerPublicKey ?: card.issuerPublicKey - if (publicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey())) - return - } - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return - } - - readIssuerData(session, card.cardId, publicKey, callback) + issuerPublicKey ?: card.issuerPublicKey ?: return TangemSdkError.MissingIssuerPubicKey() + return null } + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + val publicKey = issuerPublicKey ?: session.environment.card?.issuerPublicKey + readIssuerData(session, publicKey!!, callback) + } private fun readIssuerData( - session: CardSession, - cardId: String, publicKey: ByteArray, - callback: (result: CompletionResult) -> Unit) { + session: CardSession, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit + ) { if (issuerDataSize != 0) { session.viewDelegate.onDelay( - issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE ) } @@ -112,9 +108,9 @@ class ReadIssuerExtraDataCommand( issuerData.write(result.data.issuerData) if (result.data.issuerDataSignature == null) { offset = issuerData.size() - readIssuerData(session, cardId, publicKey, callback) + readIssuerData(session, publicKey, callback) } else { - completeTask(result.data, cardId, publicKey, callback) + completeTask(result.data, publicKey, callback) } } is CompletionResult.Failure -> { @@ -124,21 +120,22 @@ class ReadIssuerExtraDataCommand( } } - private fun completeTask(data: ReadIssuerExtraDataResponse, - cardId: String, publicKey: ByteArray, - callback: (result: CompletionResult) -> Unit) { + private fun completeTask( + data: ReadIssuerExtraDataResponse, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit + ) { val dataToVerify = IssuerDataToVerify( - cardId, - issuerData.toByteArray(), - data.issuerDataCounter + data.cardId, + issuerData.toByteArray(), + data.issuerDataCounter ) if (verify(publicKey, data.issuerDataSignature!!, dataToVerify)) { val finalResult = ReadIssuerExtraDataResponse( - data.cardId, - issuerDataSize, - issuerData.toByteArray(), - data.issuerDataSignature, - data.issuerDataCounter + data.cardId, + issuerDataSize, + issuerData.toByteArray(), + data.issuerDataSignature, + data.issuerDataCounter ) callback(CompletionResult.Success(finalResult)) } else { @@ -155,16 +152,19 @@ class ReadIssuerExtraDataCommand( return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) } - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse { + override fun deserialize( + environment: SessionEnvironment, + apdu: ResponseApdu + ): ReadIssuerExtraDataResponse { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() 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) + 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) ) } diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 3f79960e93..2e68b631ef 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -1,9 +1,7 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -51,16 +49,14 @@ class ReadUserDataResponse( */ class ReadUserDataCommand : Command() { - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.isActivated) { + return TangemSdkError.NotActivated() } - return false + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index 816a0cd373..b5135be2b3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -1,9 +1,7 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -20,10 +18,10 @@ import com.tangem.crypto.sign * Sums up array elements within all SIGN commands */ class SignResponse( - val cardId: String, - val signature: ByteArray, - val walletRemainingSignatures: Int, - val walletSignedHashes: Int + val cardId: String, + val signature: ByteArray, + val walletRemainingSignatures: Int, + val walletSignedHashes: Int ) : CommandResponse /** @@ -32,60 +30,44 @@ class SignResponse( * @property hashes Array of transaction hashes. * @property cardId CID, Unique Tangem card ID number */ -class SignCommand(private val hashes: Array) - : Command() { +class SignCommand(private val hashes: Array) : Command() { + + //TODO: Allow signing more than 10 hashes private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.isActivated) { + return TangemSdkError.NotActivated() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.walletRemainingSignatures == 0) { + return TangemSdkError.NoRemainingSignatures() } - if (session.environment.card?.status == CardStatus.Purged) { - callback(CompletionResult.Failure(TangemSdkError.CardIsPurged())) - return true - } - if (session.environment.card?.status == CardStatus.Empty) { - callback(CompletionResult.Failure(TangemSdkError.CardIsEmpty())) - return true - } - if (session.environment.card?.walletRemainingSignatures == 0) { - callback(CompletionResult.Failure(TangemSdkError.NoRemainingSignatures())) - return true - } - if (session.environment.card?.signingMethods?.contains(SigningMethod.SignHash) != true) { - callback(CompletionResult.Failure(TangemSdkError.SignHashesNotAvailable())) - return true + if (card.signingMethods?.contains(SigningMethod.SignHash) != true) { + return TangemSdkError.SignHashesNotAvailable() } if (hashSizes == 0) { - callback(CompletionResult.Failure(TangemSdkError.EmptyHashes())) - return true + return TangemSdkError.EmptyHashes() } if (hashes.any { it.size != hashSizes }) { - callback(CompletionResult.Failure(TangemSdkError.HashSizeMustBeEqual())) - return true + return TangemSdkError.HashSizeMustBeEqual() + } + + return when (card.status) { + CardStatus.Loaded -> null + CardStatus.Empty -> TangemSdkError. + CardIsEmpty() + CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() + CardStatus.Purged -> TangemSdkError.CardIsPurged() + null -> TangemSdkError.CardError() } - return false } - override fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit): Boolean { - when (result) { - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.InvalidParams) { - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - return true - } - return false - } - else -> return false + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams) { + return TangemSdkError.Pin2OrCvcRequired() } + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { @@ -103,16 +85,9 @@ class SignCommand(private val hashes: Array) } private fun flattenHashes(): ByteArray { - checkForErrors() return hashes.reduce { arr1, arr2 -> arr1 + arr2 } } - private fun checkForErrors() { - if (hashes.isEmpty()) throw TangemSdkError.EmptyHashes() - if (hashes.size > 10) throw TangemSdkError.TooManyHashesInOneTransaction() - if (hashes.any { it.size != hashSizes }) throw TangemSdkError.HashSizeMustBeEqual() - } - /** * Application can optionally submit a public key Terminal_PublicKey in [SignCommand]. * Submitted key is stored by the Tangem card if it differs from a previous submitted Terminal_PublicKey. @@ -121,7 +96,8 @@ class SignCommand(private val hashes: Array) * (this key should be generated and securily stored by the application). */ private fun addTerminalSignature( - environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder) { + environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder + ) { environment.terminalKeys?.let { terminalKeyPair -> val signedData = dataToSign.sign(terminalKeyPair.privateKey) tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData) @@ -134,10 +110,10 @@ class SignCommand(private val hashes: Array) val decoder = TlvDecoder(tlvData) return SignResponse( - cardId = decoder.decode(TlvTag.CardId), - signature = decoder.decode(TlvTag.Signature), - walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures), - walletSignedHashes = decoder.decode(TlvTag.SignedHashes) + cardId = decoder.decode(TlvTag.CardId), + signature = decoder.decode(TlvTag.Signature), + walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decode(TlvTag.SignedHashes) ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index e9a23afac7..68b97dbf54 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -1,13 +1,11 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError 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.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -16,10 +14,10 @@ import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag class WriteIssuerDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String ) : CommandResponse /** @@ -33,75 +31,53 @@ class WriteIssuerDataResponse( * @property issuerDataCounter An optional counter that protect issuer data against replay attack. */ class WriteIssuerDataCommand( - private val issuerData: ByteArray, - private val issuerDataSignature: ByteArray, - private val issuerDataCounter: Int? = null, - private val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() + private val issuerData: ByteArray, + private val issuerDataSignature: ByteArray, + private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null, + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() ) : Command(), IssuerDataVerifier by verifier { - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return true - } + override fun performPreCheck(card: Card): TangemSdkError? { val publicKey = issuerPublicKey ?: card.issuerPublicKey - if (publicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey())) - return true + ?: return TangemSdkError.MissingIssuerPubicKey() + + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true - } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.isActivated) { + return TangemSdkError.NotActivated() } if (issuerData.size > MAX_SIZE) { - callback(CompletionResult.Failure(TangemSdkError.DataSizeTooLarge())) - return true + return TangemSdkError.DataSizeTooLarge() } if (!isCounterValid(issuerDataCounter, card)) { - callback(CompletionResult.Failure(TangemSdkError.MissingCounter())) - return true + return TangemSdkError.MissingCounter() } if (!verifySignature(publicKey, card.cardId)) { - callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) - return true + return TangemSdkError.VerificationFailed() } - return false + return null } - override fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit - ): Boolean { - when (result) { - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.InvalidParams && - isCounterRequired(session.environment.card)) { - callback(CompletionResult.Failure(TangemSdkError.DataCannotBeWritten())) - return true - } - return false - } - else -> return false + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) { + return TangemSdkError.DataCannotBeWritten() } + return null } private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = - if (isCounterRequired(card)) issuerDataCounter != null else true + if (isCounterRequired(card)) issuerDataCounter != null else true private fun isCounterRequired(card: Card?): Boolean = - card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false + card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true private fun verifySignature(publicKey: ByteArray, cardId: String): Boolean { return verify( - publicKey, - issuerDataSignature, - IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + publicKey, + issuerDataSignature, + IssuerDataToVerify(cardId, issuerData, issuerDataCounter) ) } @@ -117,12 +93,15 @@ class WriteIssuerDataCommand( return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) } - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { + override fun deserialize( + environment: SessionEnvironment, + apdu: ResponseApdu + ): WriteIssuerDataResponse { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() val decoder = TlvDecoder(tlvData) return WriteIssuerDataResponse( - cardId = decoder.decode(TlvTag.CardId) + cardId = decoder.decode(TlvTag.CardId) ) } diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 5fd85d01c3..ac71e3c40c 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -32,89 +32,64 @@ import com.tangem.common.tlv.TlvTag * @param issuerDataCounter An optional counter that protect issuer data against replay attack. */ class WriteIssuerExtraDataCommand( - private val issuerData: ByteArray, - private val startingSignature: ByteArray, - private val finalizingSignature: ByteArray, - private val issuerDataCounter: Int? = null, - private val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() + private val issuerData: ByteArray, + private val startingSignature: ByteArray, + private val finalizingSignature: ByteArray, + private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null, + verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() ) : Command(), IssuerDataVerifier by verifier { var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData var offset: Int = 0 - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return - } - val publicKey = issuerPublicKey ?: card.issuerPublicKey - if (publicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey())) - return - } - - writeIssuerData(session, card.cardId, publicKey) { response -> - when (response) { - is CompletionResult.Success -> callback(response) - is CompletionResult.Failure -> { - if (response.error is TangemSdkError.InvalidParams && isCounterRequired(card)) { - callback(CompletionResult.Failure(TangemSdkError.DataCannotBeWritten())) - return@writeIssuerData - } - if (response.error is TangemSdkError.InvalidState && - card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false) { - callback(CompletionResult.Failure(TangemSdkError.OverwritingDataIsProhibited())) - return@writeIssuerData - } - } - } - } + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + writeIssuerData(session, callback) } - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return true - } + override fun performPreCheck(card: Card): TangemSdkError? { val publicKey = issuerPublicKey ?: card.issuerPublicKey - if (publicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingIssuerPubicKey())) - return true - } + ?: return TangemSdkError.MissingIssuerPubicKey() - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.isActivated) { + return TangemSdkError.NotActivated() } if (issuerData.size > MAX_SIZE) { - callback(CompletionResult.Failure(TangemSdkError.ExendedDataSizeTooLarge())) - return true + return TangemSdkError.DataSizeTooLarge() } if (!isCounterValid(issuerDataCounter, card)) { - callback(CompletionResult.Failure(TangemSdkError.MissingCounter())) - return true + return TangemSdkError.MissingCounter() } - if (!verifySignatures(card.cardId, publicKey)) { - callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) - return true + if (!verifySignatures(publicKey, card.cardId)) { + return TangemSdkError.VerificationFailed() } - return false + return null + } + + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) { + return TangemSdkError.DataCannotBeWritten() + } + if (error is TangemSdkError.InvalidState && + card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true) { + return TangemSdkError.OverwritingDataIsProhibited() + } + return null } private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = - if (isCounterRequired(card)) issuerDataCounter != null else true + if (isCounterRequired(card)) issuerDataCounter != null else true - private fun isCounterRequired(card: Card): Boolean = - card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false + private fun isCounterRequired(card: Card?): Boolean = + card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true - private fun verifySignatures(cardId: String, publicKey: ByteArray): Boolean { + private fun verifySignatures(publicKey: ByteArray, cardId: String): Boolean { val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size) val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter) @@ -124,13 +99,16 @@ class WriteIssuerExtraDataCommand( } private fun writeIssuerData( - session: CardSession, - cardId: String, publicKey: ByteArray, - callback: (result: CompletionResult) -> Unit + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { if (mode == IssuerDataMode.WriteExtraData) { - session.viewDelegate.onDelay(issuerData.size, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE) + session.viewDelegate.onDelay( + issuerData.size, + offset, + SINGLE_WRITE_SIZE + ) } transceive(session) { result -> when (result) { @@ -138,7 +116,7 @@ class WriteIssuerExtraDataCommand( when (mode) { IssuerDataMode.InitializeWritingExtraData -> { mode = IssuerDataMode.WriteExtraData - writeIssuerData(session, cardId, publicKey, callback) + writeIssuerData(session, callback) return@transceive } IssuerDataMode.WriteExtraData -> { @@ -146,7 +124,7 @@ class WriteIssuerExtraDataCommand( if (offset >= issuerData.size) { mode = IssuerDataMode.FinalizeExtraData } - writeIssuerData(session, cardId, publicKey, callback) + writeIssuerData(session, callback) return@transceive } IssuerDataMode.FinalizeExtraData -> { @@ -155,6 +133,11 @@ class WriteIssuerExtraDataCommand( } } is CompletionResult.Failure -> { + if (session.environment.handleErrors) { + performAfterCheck(session.environment.card, result.error)?.let { + callback(CompletionResult.Failure(it)) + } + } callback(CompletionResult.Failure(result.error)) } } @@ -188,17 +171,21 @@ class WriteIssuerExtraDataCommand( } private fun getDataToWrite(): ByteArray = - issuerData.copyOfRange(offset, offset + calculatePartSize()) + issuerData.copyOfRange(offset, offset + calculatePartSize()) private fun calculatePartSize(): Int { val bytesLeft = issuerData.size - offset return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE } - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { + override fun deserialize( + environment: SessionEnvironment, + apdu: ResponseApdu + ): WriteIssuerDataResponse { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) + return WriteIssuerDataResponse( + cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) ) } diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 1cdac8447a..8402ad9266 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -1,9 +1,7 @@ package com.tangem.commands -import com.tangem.CardSession import com.tangem.SessionEnvironment import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -34,36 +32,24 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val private val userCounter: Int? = null, private val userProtectedCounter: Int? = null) : Command() { - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status == CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() } - if (session.environment.card?.isActivated == true) { - callback(CompletionResult.Failure(TangemSdkError.NotActivated())) - return true + if (card.isActivated) { + return TangemSdkError.NotActivated() } if (userData?.size ?: 0 > MAX_SIZE || userProtectedData?.size ?: 0 > MAX_SIZE) { - callback(CompletionResult.Failure(TangemSdkError.DataSizeTooLarge())) - return true + return TangemSdkError.DataSizeTooLarge() } - return false + return null } - override fun performAfterCheck(session: CardSession, - result: CompletionResult, - callback: (result: CompletionResult) -> Unit - ): Boolean { - when (result) { - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.InvalidParams) { - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - return true - } - return false - } - else -> return false + override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + if (error is TangemSdkError.InvalidParams) { + return TangemSdkError.Pin2OrCvcRequired() } + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index 5648b69def..d7099728b8 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -9,7 +9,6 @@ import com.tangem.commands.CardStatus import com.tangem.commands.Command import com.tangem.commands.common.CardDeserializer import com.tangem.commands.personalization.entities.* -import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -38,12 +37,11 @@ class PersonalizeCommand( private val acquirer: Acquirer? = null ) : Command() { - override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { - if (session.environment.card?.status != CardStatus.NotPersonalized) { - callback(CompletionResult.Failure(TangemSdkError.AlreadyPersonalized())) - return true + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status != CardStatus.NotPersonalized) { + return TangemSdkError.AlreadyPersonalized() } - return false + return null } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt index 7ef49ed0e0..29c26fa50f 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt @@ -40,7 +40,7 @@ fun TangemSdkError.localizedDescription(): Int { is TangemSdkError.Pin2OrCvcRequired -> R.string.error_operation is TangemSdkError.VerificationFailed -> R.string.error_verification_failed is TangemSdkError.DataSizeTooLarge -> R.string.error_data_size_too_large - is TangemSdkError.ExendedDataSizeTooLarge -> R.string.error_data_size_too_large_extended + is TangemSdkError.ExtendedDataSizeTooLarge -> R.string.error_data_size_too_large_extended is TangemSdkError.MissingCounter -> R.string.error_missing_counter is TangemSdkError.OverwritingDataIsProhibited -> R.string.error_data_cannot_be_written is TangemSdkError.DataCannotBeWritten -> R.string.error_data_cannot_be_written From 265cde188a08c0e51dfde249ab9c3b54f9647bae Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Jun 2020 09:42:02 +0300 Subject: [PATCH 10/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardSession.kt | 20 ++++++++++--------- .../personalization/PersonalizeCommand.kt | 5 +++-- .../com/tangem/common/apdu/CommandApdu.kt | 5 ++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index e46be34484..bd6b041932 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -5,6 +5,7 @@ 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.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.getType @@ -221,16 +222,10 @@ class CardSession( scope.launch { subscription.consumeAsFlow() .filterNotNull() - .map { establishEncryption() } + .map { establishEncryption(apdu.ins) } .map { apdu.encrypt(environment.encryptionMode, environment.encryptionKey) } .map { encryptedApdu -> reader.transceiveApdu(encryptedApdu) } - .catch { error -> - if (error is TangemSdkError) callback( - CompletionResult.Failure( - error - ) - ) - } + .catch { if (it is TangemSdkError) callback(CompletionResult.Failure(it)) } .collect { result -> subscription.cancel() callback(result) @@ -238,8 +233,15 @@ class CardSession( } } - private suspend fun establishEncryption(): CompletionResult { + private suspend fun establishEncryption(ins: Int): CompletionResult { + if (environment.encryptionKey != null) return CompletionResult.Success(true) + + if (ins == Instruction.Personalize.code) { + environment.encryptionKey = null + return CompletionResult.Success(true) + } + val encryptionHelper: EncryptionHelper = when (environment.encryptionMode) { EncryptionMode.NONE -> return CompletionResult.Success(true) diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index d7099728b8..2fd86d0e28 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -1,6 +1,6 @@ package com.tangem.commands.personalization -import com.tangem.CardSession +import com.tangem.EncryptionMode import com.tangem.SessionEnvironment import com.tangem.TangemSdkError import com.tangem.commands.Card @@ -46,10 +46,11 @@ class PersonalizeCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu(Instruction.Personalize, serializePersonalizationData(config)) + .encrypt(EncryptionMode.NONE, devPersonalizationKey) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu) + return CardDeserializer.deserialize(apdu.decrypt(devPersonalizationKey)) } private fun serializePersonalizationData(config: CardConfig): ByteArray { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index a804704598..a152a5f2d2 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -72,9 +72,8 @@ class CommandApdu( encryptionMode: EncryptionMode, encryptionKey: ByteArray? ): CommandApdu { - if (encryptionMode == EncryptionMode.NONE - || encryptionKey == null || p1 != EncryptionMode.NONE.code - ) { + + if (encryptionKey == null || p1 != EncryptionMode.NONE.code) { return this } val crc: ByteArray = tlvs.calculateCrc16() From d60a65a57d0f81683033643ab91d64cd909a3a2c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 9 Jun 2020 13:39:08 +0300 Subject: [PATCH 11/43] Updated on 2026-08-14 --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 1357ddf95da006586b05c7ba993eace917ce36c1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jun 2020 14:40:53 +0300 Subject: [PATCH 12/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardSession.kt | 38 +++++++++++-------- .../main/java/com/tangem/commands/Command.kt | 18 +++------ .../tangem/commands/CreateWalletCommand.kt | 4 +- .../com/tangem/commands/PurgeWalletCommand.kt | 4 +- .../java/com/tangem/commands/ReadCommand.kt | 4 +- .../java/com/tangem/commands/SignCommand.kt | 4 +- .../tangem/commands/WriteIssuerDataCommand.kt | 4 +- .../commands/WriteIssuerExtraDataCommand.kt | 6 +-- .../tangem/commands/WriteUserDataCommand.kt | 4 +- .../personalization/PersonalizeCommand.kt | 17 ++++++++- .../com/tangem/common/apdu/ResponseApdu.kt | 3 +- .../com/tangem/crypto/EncryptionHelper.kt | 11 ++++++ 12 files changed, 71 insertions(+), 46 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index bd6b041932..8047252521 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -5,13 +5,10 @@ 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.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.getType import com.tangem.crypto.EncryptionHelper -import com.tangem.crypto.FastEncryptionHelper -import com.tangem.crypto.StrongEncryptionHelper import com.tangem.crypto.pbkdf2Hash import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -222,9 +219,10 @@ class CardSession( scope.launch { subscription.consumeAsFlow() .filterNotNull() - .map { establishEncryption(apdu.ins) } + .map { establishEncryptionIfNeeded() } .map { apdu.encrypt(environment.encryptionMode, environment.encryptionKey) } .map { encryptedApdu -> reader.transceiveApdu(encryptedApdu) } + .map { responseApdu -> decrypt(responseApdu) } .catch { if (it is TangemSdkError) callback(CompletionResult.Failure(it)) } .collect { result -> subscription.cancel() @@ -233,21 +231,14 @@ class CardSession( } } - private suspend fun establishEncryption(ins: Int): CompletionResult { - - if (environment.encryptionKey != null) return CompletionResult.Success(true) - - if (ins == Instruction.Personalize.code) { - environment.encryptionKey = null + private suspend fun establishEncryptionIfNeeded(): CompletionResult { + if (environment.encryptionMode == EncryptionMode.NONE || environment.encryptionKey != null) { return CompletionResult.Success(true) } - val encryptionHelper: EncryptionHelper = - when (environment.encryptionMode) { - EncryptionMode.NONE -> return CompletionResult.Success(true) - EncryptionMode.FAST -> FastEncryptionHelper() - EncryptionMode.STRONG -> StrongEncryptionHelper() - } + val encryptionHelper = EncryptionHelper.create(environment.encryptionMode) + ?: return CompletionResult.Success(true) + val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) val apdu = openSesssionCommand.serialize(environment) @@ -269,4 +260,19 @@ class CardSession( is CompletionResult.Failure -> return CompletionResult.Failure(response.error) } } + + private fun decrypt(result: CompletionResult): CompletionResult { + return when (result) { + is CompletionResult.Success -> { + try { + CompletionResult.Success( + result.data.decrypt(environment.encryptionKey) + ) + } catch (error: TangemSdkError) { + return CompletionResult.Failure(error) + } + } + is CompletionResult.Failure -> result + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 860b954ebf..3b25619ce6 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -46,7 +46,7 @@ abstract class Command : ApduSerializable, CardSessionRu open fun performPreCheck(card: Card): TangemSdkError? = null - open fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? = error + open fun mapError(card: Card?, error: TangemSdkError): TangemSdkError = error fun transceive(session: CardSession, callback: (result: CompletionResult) -> Unit) { @@ -63,10 +63,10 @@ abstract class Command : ApduSerializable, CardSessionRu when (result) { is CompletionResult.Failure -> { if (session.environment.handleErrors) { - performAfterCheck(session.environment.card, result.error)?.let { - callback(CompletionResult.Failure(it)) - return@transceiveApdu - } + val error = mapError(session.environment.card, result.error) + callback(CompletionResult.Failure(error)) + return@transceiveApdu + } callback(CompletionResult.Failure(result.error)) } @@ -98,13 +98,7 @@ abstract class Command : ApduSerializable, CardSessionRu when (responseApdu.statusWord) { StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged -> { - try { - val decryptedResponseApdu = - responseApdu.decrypt(session.environment.encryptionKey) - callback(CompletionResult.Success(decryptedResponseApdu)) - } catch (error: TangemSdkError) { - callback(CompletionResult.Failure(error)) - } + callback(CompletionResult.Success(responseApdu)) } StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 6d0bab7883..87f9f6dcbe 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -51,11 +51,11 @@ class CreateWalletCommand : Command() { } } - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams) { return TangemSdkError.Pin2OrCvcRequired() } - return null + return error } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index 3ec6ff7315..743a93c68a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -48,11 +48,11 @@ class PurgeWalletCommand : Command() { } } - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams) { return TangemSdkError.Pin2OrCvcRequired() } - return null + return error } diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index d0efbf78bd..189588a080 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -367,11 +367,11 @@ class Card( */ class ReadCommand : Command() { - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams) { return TangemSdkError.Pin1Required() } - return null + return error } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index b5135be2b3..c5d743975b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -63,11 +63,11 @@ class SignCommand(private val hashes: Array) : Command( } } - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams) { return TangemSdkError.Pin2OrCvcRequired() } - return null + return error } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 68b97dbf54..0b563a018e 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -60,11 +60,11 @@ class WriteIssuerDataCommand( return null } - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) { return TangemSdkError.DataCannotBeWritten() } - return null + return error } private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index ac71e3c40c..115ebcd1dd 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -72,7 +72,7 @@ class WriteIssuerExtraDataCommand( return null } - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) { return TangemSdkError.DataCannotBeWritten() } @@ -80,7 +80,7 @@ class WriteIssuerExtraDataCommand( card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true) { return TangemSdkError.OverwritingDataIsProhibited() } - return null + return error } private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = @@ -134,7 +134,7 @@ class WriteIssuerExtraDataCommand( } is CompletionResult.Failure -> { if (session.environment.handleErrors) { - performAfterCheck(session.environment.card, result.error)?.let { + mapError(session.environment.card, result.error)?.let { callback(CompletionResult.Failure(it)) } } diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 8402ad9266..97a406a2d3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -45,11 +45,11 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val return null } - override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? { + override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { if (error is TangemSdkError.InvalidParams) { return TangemSdkError.Pin2OrCvcRequired() } - return null + return error } override fun serialize(environment: SessionEnvironment): CommandApdu { diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt index 2fd86d0e28..643b374a13 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt @@ -1,5 +1,6 @@ package com.tangem.commands.personalization +import com.tangem.CardSession import com.tangem.EncryptionMode import com.tangem.SessionEnvironment import com.tangem.TangemSdkError @@ -9,6 +10,7 @@ import com.tangem.commands.CardStatus import com.tangem.commands.Command import com.tangem.commands.common.CardDeserializer import com.tangem.commands.personalization.entities.* +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -37,6 +39,18 @@ class PersonalizeCommand( private val acquirer: Acquirer? = null ) : Command() { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val encryptionMode = session.environment.encryptionMode + val encryptionKey = session.environment.encryptionKey + session.environment.encryptionMode = EncryptionMode.NONE + session.environment.encryptionKey = devPersonalizationKey + super.run(session) { result -> + session.environment.encryptionMode = encryptionMode + session.environment.encryptionKey = encryptionKey + callback(result) + } + } + override fun performPreCheck(card: Card): TangemSdkError? { if (card.status != CardStatus.NotPersonalized) { return TangemSdkError.AlreadyPersonalized() @@ -46,11 +60,10 @@ class PersonalizeCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu(Instruction.Personalize, serializePersonalizationData(config)) - .encrypt(EncryptionMode.NONE, devPersonalizationKey) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu.decrypt(devPersonalizationKey)) + return CardDeserializer.deserialize(apdu) } private fun serializePersonalizationData(config: CardConfig): ByteArray { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index bb947d074a..afc869f64a 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -39,7 +39,8 @@ class ResponseApdu(private val data: ByteArray) { fun decrypt(encryptionKey: ByteArray?): ResponseApdu { if (encryptionKey == null) return this - if (data.size < 18) throw TangemSdkError.InvalidResponse() + //nothing to decrypt + if (data.size < 18) return this val responseData = data.copyOf(data.size - 2) diff --git a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt index ab3d97445c..1360914241 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt @@ -1,5 +1,6 @@ package com.tangem.crypto +import com.tangem.EncryptionMode import org.spongycastle.jce.interfaces.ECPublicKey import java.security.KeyPair import java.security.KeyPairGenerator @@ -11,6 +12,16 @@ interface EncryptionHelper { val keyA: ByteArray fun generateSecret(keyB: ByteArray): ByteArray + + companion object { + fun create(encryptionMode: EncryptionMode): EncryptionHelper? { + return when (encryptionMode) { + EncryptionMode.NONE -> null + EncryptionMode.FAST -> FastEncryptionHelper() + EncryptionMode.STRONG -> StrongEncryptionHelper() + } + } + } } class StrongEncryptionHelper : EncryptionHelper { From 7de4ad811d44f7f5cffc8fbf2d247ea3d3b89cd7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 18 Jun 2020 08:35:53 +0300 Subject: [PATCH 13/43] Updated on 2026-08-14 --- tangem-core/build.gradle | 9 + .../src/main/java/com/tangem/TangemSdk.kt | 23 +++ .../commands/common/network/NetworkUtils.kt | 39 +++++ .../common/network/RetrofitBuilder.kt | 30 ++++ .../commands/common/network/TangemApi.kt | 17 ++ .../commands/common/network/TangemService.kt | 22 +++ .../commands/verifycard/VerifyCardCommand.kt | 159 ++++++++++++++++++ .../commands/verifycard/VerifyCardRequest.kt | 60 +++++++ tangem-devkit/src/main/AndroidManifest.xml | 2 + 9 files changed, 361 insertions(+) create mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index bd00611a6a..8b71b5e8bf 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'org.jetbrains.dokka' apply plugin: 'com.github.dcendents.android-maven' apply from: '../dependencies.gradle' apply from: '../jitpack.gradle' +apply plugin: 'kotlin-kapt' group = "$jitpackSdk.group" version "$jitpackSdk.version" @@ -22,6 +23,14 @@ dependencies { // misc implementation 'com.google.code.gson:gson:2.8.6' + //network + implementation 'com.squareup.retrofit2:retrofit:2.8.1' + implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' + implementation 'com.squareup.moshi:moshi:1.9.2' + implementation "com.squareup.moshi:moshi-kotlin:1.9.2" + kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2") + implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' + // tests testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' testImplementation "com.google.truth:truth:1.0.1" diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index c5ac313d34..6508fb8c3f 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -8,6 +8,8 @@ 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.commands.verifycard.VerifyCardCommand +import com.tangem.commands.verifycard.VerifyCardResponse import com.tangem.common.CompletionResult import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils @@ -294,6 +296,27 @@ class TangemSdk( startSessionWithRunnable(PurgeWalletCommand(), cardId, initialMessage, callback) } + /** + * This method launches a [VerifyCardCommand] on a new thread. + * + * The command to ensures the card has not been counterfeited. + * By using standard challenge-response scheme, the card proves possession of CardPrivateKey + * that corresponds to CardPublicKey returned by [ReadCommand]. Then the data is sent + * to Tangem server to prove that this card was indeed issued by Tangem. + * The online part of the verification is unavailable for DevKit cards. + * + * + * @param cardId CID, Unique Tangem card ID number. + * @param online flag that allows disable online verification + * @param callback is triggered on the completion of the [VerifyCardCommand] and provides + * card response in the form of [VerifyCardResponse] if the task was performed successfully + * or [TangemSdkError] in case of an error. + */ + fun verify(cardId: String? = null, online: Boolean = true, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSessionWithRunnable(VerifyCardCommand(online), cardId, initialMessage, callback) + } + /** * Command available on SDK cards only * diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt new file mode 100644 index 0000000000..6ff62b1021 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt @@ -0,0 +1,39 @@ +package com.tangem.commands.common.network + +import com.tangem.Log +import kotlinx.coroutines.delay +import java.io.IOException + +suspend fun retryIO( + times: Int = 3, + initialDelay: Long = 100, + maxDelay: Long = 1000, + factor: Double = 2.0, + block: suspend () -> T): T +{ + var currentDelay = initialDelay + repeat(times - 1) { + try { + return block() + } catch (e: IOException) { + Log.i("Network", e.localizedMessage) + } + delay(currentDelay) + currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) + } + return block() +} + +sealed class Result { + data class Success(val data: T) : Result() + data class Failure(val error: Throwable?) : Result() +} + +suspend fun performRequest(block: suspend () -> T): Result { + return try { + val result = retryIO { block() } + Result.Success(result) + } catch (exception: Exception) { + Result.Failure(exception) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt new file mode 100644 index 0000000000..ffbf990666 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt @@ -0,0 +1,30 @@ +package com.tangem.commands.common.network + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + + +class ApiTangem { + companion object { + const val TANGEM_ENDPOINT: String = "https://verify.tangem.com/" + + const val VERIFY = "verify" + const val VERIFY_AND_GET_INFO = "card/verify-and-get-info" + const val ARTWORK = "card/artwork" + } + +} + +private val moshi: Moshi by lazy { + Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() +} + +fun createRetrofitInstance(baseUrl: String): Retrofit = + Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt new file mode 100644 index 0000000000..75507687c6 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt @@ -0,0 +1,17 @@ +package com.tangem.commands.common.network + + +import com.tangem.commands.common.network.ApiTangem.Companion.VERIFY_AND_GET_INFO +import com.tangem.commands.verifycard.CardVerifyAndGetInfo +import retrofit2.http.Body +import retrofit2.http.Headers +import retrofit2.http.POST + +interface TangemApi { + + @Headers("Content-Type: application/json") + @POST(VERIFY_AND_GET_INFO) + suspend fun getCardVerifyAndGetInfo(@Body requestBody: CardVerifyAndGetInfo.Request): CardVerifyAndGetInfo.Response + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt new file mode 100644 index 0000000000..295536233d --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt @@ -0,0 +1,22 @@ +package com.tangem.commands.common.network + +import com.tangem.commands.verifycard.CardVerifyAndGetInfo + +class TangemService { + + private val tangemApi: TangemApi by lazy { + createRetrofitInstance(ApiTangem.TANGEM_ENDPOINT).create(TangemApi::class.java) + } + + suspend fun verifyAndGetInfo( + cardId: String, + cardPublicKey: String + ): Result { + val requestsBody = CardVerifyAndGetInfo.Request() + requestsBody.requests = + listOf(CardVerifyAndGetInfo.Request.Item(cardId, cardPublicKey)) + + return performRequest { tangemApi.getCardVerifyAndGetInfo(requestsBody) } + } + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt new file mode 100644 index 0000000000..1eb97c4aa9 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt @@ -0,0 +1,159 @@ +package com.tangem.commands.verifycard + +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.TangemSdkError +import com.tangem.commands.Card +import com.tangem.commands.CardStatus +import com.tangem.commands.Command +import com.tangem.commands.CommandResponse +import com.tangem.commands.common.network.Result +import com.tangem.commands.common.network.TangemService +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.extensions.CardType +import com.tangem.common.extensions.getType +import com.tangem.common.extensions.toHexString +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvDecoder +import com.tangem.common.tlv.TlvTag +import com.tangem.crypto.CryptoUtils +import kotlinx.coroutines.launch + +class VerifyCardResponse( + val cardId: String, + val verificationState: VerifyCardState? = null, + val artworkInfo: ArtworkInfo? = null, + internal val salt: ByteArray, + internal val cardSignature: ByteArray +) : CommandResponse { + + fun verify(publicKey: ByteArray, challenge: ByteArray): Boolean { + return CryptoUtils.verify( + publicKey, + challenge + salt, + cardSignature + ) + } +} + +enum class VerifyCardState { + VerifiedOnline, + VerifiedOffline, +} + +class VerifyCardCommand(private val onlineVerification: Boolean) : Command() { + + private val challenge = CryptoUtils.generateRandomBytes(16) + private val tangemService = TangemService() + + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + val card = session.environment.card + val cardPublicKey = card?.cardPublicKey + if (cardPublicKey == null) { + callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) + return + } + super.run(session) { result -> + when (result) { + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + is CompletionResult.Success -> { + val response = result.data + val verified = response.verify(cardPublicKey, challenge) + if (!verified) { + callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) + return@run + } + if (!onlineVerification || card.getType() != CardType.Release) { + callback( + CompletionResult.Success( + VerifyCardResponse( + response.cardId, VerifyCardState.VerifiedOffline, null, + response.salt, response.cardSignature + ) + ) + ) + } else { + verify(result.data, card.cardId, cardPublicKey, session, callback) + } + } + } + } + } + + private fun verify( + response: VerifyCardResponse, cardId: String, cardPublicKey: ByteArray, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + session.scope.launch { + val result = tangemService.verifyAndGetInfo(cardId, cardPublicKey.toHexString()) + + when (result) { + is Result.Success -> { + if (result.data.results?.firstOrNull()?.passed == true) { + callback( + CompletionResult.Success( + VerifyCardResponse( + response.cardId, VerifyCardState.VerifiedOnline, response.artworkInfo, + response.salt, response.cardSignature + ) + ) + ) + } else { + callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) + } + } + is Result.Failure -> { + callback( + CompletionResult.Success( + VerifyCardResponse( + response.cardId, VerifyCardState.VerifiedOffline, null, + response.salt, response.cardSignature + ) + ) + ) + } + } + } + } + + override fun performPreCheck(card: Card): TangemSdkError? { + if (card.status == CardStatus.NotPersonalized) { + return TangemSdkError.NotPersonalized() + } + if (card.isActivated) { + return TangemSdkError.NotActivated() + } + return null + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.Challenge, challenge) + return CommandApdu(Instruction.VerifyCard, tlvBuilder.serialize()) + } + + override fun deserialize( + environment: SessionEnvironment, + apdu: ResponseApdu + ): VerifyCardResponse { + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() + + val decoder = TlvDecoder(tlvData) + return VerifyCardResponse( + cardId = decoder.decode(TlvTag.CardId), + salt = decoder.decode(TlvTag.Salt), + cardSignature = decoder.decode(TlvTag.CardSignature) + ) + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt new file mode 100644 index 0000000000..8bc7efd052 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt @@ -0,0 +1,60 @@ +package com.tangem.commands.verifycard + +import com.squareup.moshi.JsonClass +import java.text.SimpleDateFormat +import java.util.* + + +class CardVerifyAndGetInfo { + + @JsonClass(generateAdapter = true) + data class Request( + var requests: List? = null + ) { + @JsonClass(generateAdapter = true) + data class Item( + var CID: String = "", + var publicKey: String = "" + ) + } + + @JsonClass(generateAdapter = true) + data class Response( + var results: List? = null + ) { + @JsonClass(generateAdapter = true) + data class Item( + var error: String? = null, + var CID: String = "", + var passed: Boolean = false, + var batch: String = "", + var artwork: ArtworkInfo? = null, + var substitution: SubstitutionInfo? = null + ) { + + @JsonClass(generateAdapter = true) + data class SubstitutionInfo( + var data: String? = null, + var signature: String? = null + ) + + } + } +} + +@JsonClass(generateAdapter = true) +data class ArtworkInfo( + var id: String = "", + var hash: String = "", + var date: String = "" +) { + fun getUpdateDate(): Date? { + return try { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US).parse(date) + } catch (e: Exception) { + e.printStackTrace() + null + } + } + +} \ No newline at end of file diff --git a/tangem-devkit/src/main/AndroidManifest.xml b/tangem-devkit/src/main/AndroidManifest.xml index e0749468f0..b74aaf74df 100644 --- a/tangem-devkit/src/main/AndroidManifest.xml +++ b/tangem-devkit/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ android:name="android.hardware.nfc" android:required="true" /> + + Date: Thu, 18 Jun 2020 10:56:28 +0300 Subject: [PATCH 14/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardSession.kt | 8 +++++ .../java/com/tangem/SessionEnvironment.kt | 11 ++++-- .../java/com/tangem/SessionViewDelegate.kt | 5 +-- .../main/java/com/tangem/commands/Command.kt | 33 +++++++++++++++-- .../com/tangem/commands/OpenSessionCommand.kt | 3 +- .../com/tangem/commands/PurgeWalletCommand.kt | 3 +- .../java/com/tangem/common/apdu/StatusWord.kt | 35 ++++++++++++------- .../main/java/com/tangem/common/tlv/TlvTag.kt | 1 - .../DefaultSessionViewDelegate.kt | 3 +- .../SessionViewDelegateState.kt | 3 +- .../tangem_sdk_new/ui/NfcSessionDialog.kt | 33 +++++++++++++++-- .../src/main/res/layout/nfc_bottom_sheet.xml | 32 ++++++++++++++++- 12 files changed, 137 insertions(+), 33 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index 8047252521..bcaf32f1a7 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -231,6 +231,14 @@ class CardSession( } } + fun pause() { + reader.stopSession() + } + + fun resume() { + reader.startSession() + } + private suspend fun establishEncryptionIfNeeded(): CompletionResult { if (environment.encryptionMode == EncryptionMode.NONE || environment.encryptionKey != null) { return CompletionResult.Success(true) diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 78b6d28715..0534f85d0a 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -14,7 +14,6 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey * @property terminalKeys generated terminal keys used in Linked Terminal feature. */ data class SessionEnvironment( - var pin1: ByteArray = DEFAULT_PIN.calculateSha256(), var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(), var card: Card? = null, var terminalKeys: KeyPair? = null, @@ -25,8 +24,14 @@ data class SessionEnvironment( val handleErrors: Boolean = true ) { + var isDefaultPin1: Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) + + var pin1: ByteArray = SessionEnvironment.pin1 + private set + get() = SessionEnvironment.pin1 + fun setPin1(pin1: String) { - this.pin1 = pin1.calculateSha256() + SessionEnvironment.pin1 = pin1.calculateSha256() } fun setPin2(pin2: String) { @@ -36,6 +41,8 @@ data class SessionEnvironment( companion object { const val DEFAULT_PIN = "000000" const val DEFAULT_PIN2 = "000" + + var pin1: ByteArray = DEFAULT_PIN.calculateSha256() } } diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 71311dcf00..e3b2306bbf 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,7 +1,5 @@ package com.tangem -import com.tangem.common.CompletionResult - /** * Allows interaction with users and shows visual elements. * @@ -49,8 +47,7 @@ interface SessionViewDelegate { /** * It is called when a user is expected to enter pin code. */ - fun onPinRequested(callback: (result: CompletionResult) -> Unit) - + fun onPinRequested(callback: (pin: String?) -> Unit) } /** diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 3b25619ce6..57cc3dd0a2 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -64,6 +64,10 @@ abstract class Command : ApduSerializable, CardSessionRu is CompletionResult.Failure -> { if (session.environment.handleErrors) { val error = mapError(session.environment.card, result.error) + if (error is TangemSdkError.Pin1Required) { + handlePin1(session, callback) + return@transceiveApdu + } callback(CompletionResult.Failure(error)) return@transceiveApdu @@ -80,7 +84,6 @@ abstract class Command : ApduSerializable, CardSessionRu } } } - } private fun transceiveApdu( @@ -96,8 +99,10 @@ abstract class Command : ApduSerializable, CardSessionRu val responseApdu = result.data when (responseApdu.statusWord) { - StatusWord.ProcessCompleted, StatusWord.Pin1Changed, - StatusWord.Pin2Changed, StatusWord.PinsChanged -> { + StatusWord.ProcessCompleted, + StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.Pins12Changed, + StatusWord.Pin3Changed, StatusWord.Pins13Changed, StatusWord.Pins23Changed, + StatusWord.Pins123Changed -> { callback(CompletionResult.Success(responseApdu)) } StatusWord.NeedPause -> { @@ -172,4 +177,26 @@ abstract class Command : ApduSerializable, CardSessionRu return false } + private fun handlePin1( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (!session.environment.isDefaultPin1) { + session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) + transceive(session, callback) + return + } + session.pause() + session.viewDelegate.onPinRequested { pin1 -> + if (!pin1.isNullOrEmpty()) { + session.environment.setPin1(pin1) + session.resume() + transceive(session, callback) + } else { + session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) + session.resume() + transceive(session, callback) + } + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt index 1301703b9f..cb682706bb 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -18,9 +18,8 @@ class OpenSessionResponse( * In case of encrypted communication, App should setup a session before calling any further command. * [OpenSessionCommand] generates secret session_key that is used by both host and card * to encrypt and decrypt commands’ payload. - */ -class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command() { +class OpenSessionCommand(private val sessionKeyA: ByteArray) : ApduSerializable { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index 743a93c68a..c3765f4ccd 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -43,7 +43,8 @@ class PurgeWalletCommand : Command() { return when (card.status) { CardStatus.Loaded -> null CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() - CardStatus.Empty, CardStatus.Purged -> TangemSdkError.CardIsEmpty() + CardStatus.Empty -> TangemSdkError.CardIsEmpty() + CardStatus.Purged -> TangemSdkError.CardIsPurged() null -> TangemSdkError.CardError() } } diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt index 96daaa2826..0b60f3ff91 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt @@ -5,19 +5,27 @@ import com.tangem.TangemSdkError /** * Part of a response from the card, shows the status of the operation */ -enum class StatusWord(val code: Int, val description: String) { +enum class StatusWord(val code: Int) { - ProcessCompleted(0x9000, "SW_PROCESS_COMPLETED"), - InvalidParams(0x6A86, "SW_INVALID_PARAMS"), - ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"), - InvalidState(0x6985, "SW_INVALID_STATE"), - Pin1Changed(ProcessCompleted.code + 0x0001, "SW_PIN1_CHANGED"), - Pin2Changed(ProcessCompleted.code + 0x0002, "SW_PIN2_CHANGED"), - PinsChanged(ProcessCompleted.code + 0x0003, "SW_PINS_CHANGED"), - InsNotSupported(0x6D00, "SW_INS_NOT_SUPPORTED"), - NeedEncryption(0x6982, "SW_NEED_ENCRYPTION"), - NeedPause(0x9789, "SW_NEED_PAUSE"), - Unknown(0x0000, "SW_UNKNOWN"); + ProcessCompleted(0x9000), + InvalidParams(0x6A86), + ErrorProcessingCommand(0x6286), + InvalidState(0x6985), + + //PinsNotChanged(0x9000) is equal to ProcessCompleted(0x9000) + Pin1Changed(0x9001), + Pin2Changed(0x9002), + Pins12Changed(0x9003), + Pin3Changed(0x9004), + Pins13Changed(0x9005), + Pins23Changed(0x9006), + Pins123Changed(0x9007), + + + InsNotSupported(0x6D00), + NeedEncryption(0x6982), + NeedPause(0x9789), + Unknown(0x0000); companion object { private val values = values() @@ -28,7 +36,8 @@ enum class StatusWord(val code: Int, val description: String) { fun StatusWord.toTangemSdkError(): TangemSdkError? { return when (this) { StatusWord.ProcessCompleted, StatusWord.Pin1Changed, - StatusWord.Pin2Changed, StatusWord.PinsChanged -> null + StatusWord.Pin2Changed, StatusWord.Pins12Changed, StatusWord.Pin3Changed, + StatusWord.Pins13Changed, StatusWord.Pins23Changed, StatusWord.Pins123Changed -> null StatusWord.NeedPause -> null StatusWord.InvalidParams -> TangemSdkError.InvalidParams() StatusWord.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand() diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index e3c9122c59..944ee4cc24 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -125,7 +125,6 @@ enum class TlvTag(val code: Int) { fun valueType(): TlvValueType { return when (this) { CardId, Batch, CrExKey -> TlvValueType.HexString - NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> TlvValueType.Utf8String CurveId -> TlvValueType.EllipticCurve diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index 31144eae96..3cd49e1741 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -2,7 +2,6 @@ package com.tangem.tangem_sdk_new import androidx.fragment.app.FragmentActivity import com.tangem.* -import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog @@ -69,7 +68,7 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } - override fun onPinRequested(callback: (result: CompletionResult) -> Unit) { + override fun onPinRequested(callback: (pin: String?) -> Unit) { postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt index 04ecbd2557..d5e02a44bb 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -2,7 +2,6 @@ package com.tangem.tangem_sdk_new import com.tangem.Message import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult sealed class SessionViewDelegateState() { data class Error(val error: TangemSdkError) : SessionViewDelegateState() @@ -10,7 +9,7 @@ sealed class SessionViewDelegateState() { data class SecurityDelay(val ms: Int, val totalDurationSeconds: Int) : SessionViewDelegateState() data class Delay(val total: Int, val current: Int, val step: Int) : SessionViewDelegateState() data class Ready(val cardId: String?, val message: Message?) : SessionViewDelegateState() - data class PinRequested(val callback: (result: CompletionResult) -> Unit) : SessionViewDelegateState() + data class PinRequested(val callback: (pin: String?) -> Unit) : SessionViewDelegateState() object TagLost : SessionViewDelegateState() object TagConnected : SessionViewDelegateState() object WrongCard : SessionViewDelegateState() diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index 4e7468f390..3ece303dc9 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -1,11 +1,16 @@ package com.tangem.tangem_sdk_new.ui import android.animation.ObjectAnimator +import android.app.Activity import android.view.HapticFeedbackConstants +import android.view.KeyEvent import android.view.View import android.view.animation.DecelerateInterpolator +import android.view.inputmethod.InputMethodManager +import android.widget.TextView import androidx.fragment.app.FragmentActivity import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.Log import com.tangem.tangem_sdk_new.R import com.tangem.tangem_sdk_new.SessionViewDelegateState import com.tangem.tangem_sdk_new.extensions.localizedDescription @@ -121,11 +126,34 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ } private fun onPinRequested(state: SessionViewDelegateState.PinRequested) { - TODO("To be implemented") + + tvTaskText?.visibility = View.INVISIBLE + tvTaskTitle?.visibility = View.INVISIBLE + show(flPin) + + performHapticFeedback() + etPin?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> + postUI { + if (actionId == KeyEvent.KEYCODE_ENDCALL) { + show(lTouchCard) + tvTaskTitle?.show() + tvTaskText?.show() + val imm: InputMethodManager = + context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v?.windowToken, 0) + + v?.text?.toString()?.let { state.callback(it) } + } + } + true + } } private fun onTagLost() { - if (currentState is SessionViewDelegateState.Success) return + if (currentState is SessionViewDelegateState.Success || + currentState is SessionViewDelegateState.PinRequested) { + return + } show(lTouchCard) tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) tvTaskText?.text = activity.getText(R.string.dialog_scan_text) @@ -160,6 +188,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ flReading?.show(view.id == flReading.id) flError?.show(view.id == flError.id) flCompletion?.show(view.id == flCompletion.id) + flPin?.show(view.id == flPin.id) } private fun performHapticFeedback() { diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index 55317c459a..d572de2f85 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -57,7 +57,6 @@ - + + + + + + + + + + Date: Thu, 18 Jun 2020 13:19:18 +0300 Subject: [PATCH 15/43] Updated on 2026-08-14 --- .../java/com/tangem/SessionEnvironment.kt | 1 + .../main/java/com/tangem/commands/Command.kt | 38 ++++++++- .../java/com/tangem/commands/SetPinCommand.kt | 83 +++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 0534f85d0a..c2b744d811 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -25,6 +25,7 @@ data class SessionEnvironment( ) { var isDefaultPin1: Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) + var isDefaultPin2: Boolean = pin2.contentEquals(DEFAULT_PIN2.calculateSha256()) var pin1: ByteArray = SessionEnvironment.pin1 private set diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 57cc3dd0a2..2c7263ca52 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -41,7 +41,18 @@ abstract class Command : ApduSerializable, CardSessionRu override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") - transceive(session, callback) + + if (requiresPin2() && session.environment.isDefaultPin2) { + handlePin2(session, callback) + } else { + transceive(session, callback) + } + + } + + private fun Command<*>.requiresPin2(): Boolean { + return this is SignCommand || this is PurgeWalletCommand || + this is CreateWalletCommand || this is WriteUserDataCommand } open fun performPreCheck(card: Card): TangemSdkError? = null @@ -199,4 +210,29 @@ abstract class Command : ApduSerializable, CardSessionRu } } } + + private fun handlePin2( + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + val checkPinCommand = SetPinCommand(session.environment.pin1, session.environment.pin2) + checkPinCommand.run(session) { result -> + when (result) { + is CompletionResult.Failure -> { + session.viewDelegate.onPinRequested { pin2 -> + if (!pin2.isNullOrEmpty()) { + session.environment.setPin2(pin2) + transceive(session, callback) + } else { + session.environment.setPin2(SessionEnvironment.DEFAULT_PIN2) + transceive(session, callback) + } + } + } + is CompletionResult.Success -> { + transceive(session, callback) + } + } + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt new file mode 100644 index 0000000000..e079f82786 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt @@ -0,0 +1,83 @@ +package com.tangem.commands + +import com.tangem.SessionEnvironment +import com.tangem.TangemSdkError +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.apdu.StatusWord +import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvDecoder +import com.tangem.common.tlv.TlvTag + +enum class SetPinStatus { + PinsNotChanged, + Pin1Changed, + Pin2Changed, + Pin3Changed, + Pins12Changed, + Pins13Changed, + Pins23Changed, + Pins123Changed, + ; + + companion object { + fun fromStatusWord(statusWord: StatusWord): SetPinStatus? { + return when (statusWord) { + StatusWord.ProcessCompleted -> PinsNotChanged + StatusWord.Pin1Changed -> Pin1Changed + StatusWord.Pin2Changed -> Pin2Changed + StatusWord.Pins12Changed -> Pins12Changed + StatusWord.Pin3Changed -> Pin3Changed + StatusWord.Pins13Changed -> Pins13Changed + StatusWord.Pins23Changed -> Pins23Changed + StatusWord.Pins123Changed -> Pins123Changed + else -> null + } + } + } +} + +class SetPinResponse( + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + /** + * + */ + val status: SetPinStatus +) : CommandResponse + + +class SetPinCommand( + private val newPin1: ByteArray = SessionEnvironment.DEFAULT_PIN.calculateSha256(), + private val newPin2: ByteArray = SessionEnvironment.DEFAULT_PIN2.calculateSha256(), + private val newPin3: ByteArray? = null +) : Command() { + + override fun serialize(environment: SessionEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + 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) + tlvBuilder.append(TlvTag.NewPin, newPin1) + tlvBuilder.append(TlvTag.NewPin2, newPin2) + tlvBuilder.append(TlvTag.NewPin3, newPin3) + return CommandApdu(Instruction.SwapPIN, tlvBuilder.serialize()) + } + + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SetPinResponse { + val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() + + val status = SetPinStatus.fromStatusWord(apdu.statusWord) ?: throw TangemSdkError.DecodingFailed() + + val decoder = TlvDecoder(tlvData) + return SetPinResponse( + cardId = decoder.decode(TlvTag.CardId), + status = status + ) + } +} \ No newline at end of file From 4e37c808c4df779f564fcedd9a2e392f6999636c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 22 Jun 2020 14:27:23 +0300 Subject: [PATCH 16/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/common/extensions/ByteArray.kt | 2 +- .../com/tangem/common/extensions/ByteArrayExtensionsTest.kt | 2 +- .../src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt index ac201f5fcb..226018b9d1 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt @@ -12,7 +12,7 @@ import kotlin.experimental.xor * Extension functions for [ByteArray]. */ -fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) } +fun ByteArray.toHexString(): String = joinToString("") { "%02X".format(it) } fun ByteArray.toUtf8(): String = String(this).removeSuffix("\u0000") diff --git a/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt b/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt index 224144afdb..42cdecc708 100644 --- a/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt @@ -9,7 +9,7 @@ class ByteArrayExtensionsTest { @Test fun `card Id to Hex String`() { - val hex = "cb22000000027374" + val hex = "CB22000000027374" val bytes = byteArrayOf(-53, 34, 0, 0, 0, 2, 115, 116) assertThat(bytes.toHexString()) .matches(hex) diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt index 8f7bd0b91e..d0cc8a62e7 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt @@ -187,6 +187,6 @@ class TlvDecoderTest { fun `map Hex returns correct value`() { val cardId: String = tlvMapper.decode(TlvTag.CardId) assertThat(cardId) - .isEqualTo("cb22000000027374") + .isEqualTo("CB22000000027374") } } \ No newline at end of file From 11f0197caa72f30fce9b79e450983b3b6c10348c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Jun 2020 14:50:26 +0300 Subject: [PATCH 17/43] Updated on 2026-08-14 --- .../java/com/tangem/SessionEnvironment.kt | 6 ++--- .../main/java/com/tangem/commands/Command.kt | 23 ++++++++----------- .../tangem/commands/CreateWalletCommand.kt | 2 ++ .../com/tangem/commands/PurgeWalletCommand.kt | 2 ++ .../java/com/tangem/commands/SetPinCommand.kt | 2 +- .../java/com/tangem/commands/SignCommand.kt | 2 ++ .../tangem/commands/WriteUserDataCommand.kt | 2 ++ .../com/tangem/common/apdu/Instruction.kt | 2 +- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index c2b744d811..88050df353 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -24,13 +24,13 @@ data class SessionEnvironment( val handleErrors: Boolean = true ) { - var isDefaultPin1: Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) - var isDefaultPin2: Boolean = pin2.contentEquals(DEFAULT_PIN2.calculateSha256()) - var pin1: ByteArray = SessionEnvironment.pin1 private set get() = SessionEnvironment.pin1 + fun isCurrentPin1Default(): Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) + fun isCurrentPin2Default(): Boolean = pin2.contentEquals(DEFAULT_PIN2.calculateSha256()) + fun setPin1(pin1: String) { SessionEnvironment.pin1 = pin1.calculateSha256() } diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 2c7263ca52..ce21f6cdba 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -38,21 +38,11 @@ interface CommandResponse abstract class Command : ApduSerializable, CardSessionRunnable { override val performPreflightRead: Boolean = true + open val requiresPin2: Boolean = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") - - if (requiresPin2() && session.environment.isDefaultPin2) { - handlePin2(session, callback) - } else { - transceive(session, callback) - } - - } - - private fun Command<*>.requiresPin2(): Boolean { - return this is SignCommand || this is PurgeWalletCommand || - this is CreateWalletCommand || this is WriteUserDataCommand + transceive(session, callback) } open fun performPreCheck(card: Card): TangemSdkError? = null @@ -69,6 +59,11 @@ abstract class Command : ApduSerializable, CardSessionRu } } + if (requiresPin2 && session.environment.isCurrentPin2Default()) { + handlePin2(session, callback) + return + } + val apdu = serialize(session.environment) transceiveApdu(apdu, session) { result -> when (result) { @@ -192,7 +187,7 @@ abstract class Command : ApduSerializable, CardSessionRu session: CardSession, callback: (result: CompletionResult) -> Unit ) { - if (!session.environment.isDefaultPin1) { + if (!session.environment.isCurrentPin1Default()) { session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) transceive(session, callback) return @@ -225,7 +220,7 @@ abstract class Command : ApduSerializable, CardSessionRu transceive(session, callback) } else { session.environment.setPin2(SessionEnvironment.DEFAULT_PIN2) - transceive(session, callback) + callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) } } } diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 87f9f6dcbe..83a6cbd78d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -37,6 +37,8 @@ class CreateWalletResponse( */ class CreateWalletCommand : Command() { + override val requiresPin2 = true + override fun performPreCheck(card: Card): TangemSdkError? { if (card.isActivated) { return TangemSdkError.NotActivated() diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index c3765f4ccd..c4ca477ca3 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -29,6 +29,8 @@ class PurgeWalletResponse( */ class PurgeWalletCommand : Command() { + override val requiresPin2 = true + override fun performPreCheck(card: Card): TangemSdkError? { if (card.status == CardStatus.NotPersonalized) { return TangemSdkError.NotPersonalized() diff --git a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt index e079f82786..084e755cc1 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt @@ -66,7 +66,7 @@ class SetPinCommand( tlvBuilder.append(TlvTag.NewPin, newPin1) tlvBuilder.append(TlvTag.NewPin2, newPin2) tlvBuilder.append(TlvTag.NewPin3, newPin3) - return CommandApdu(Instruction.SwapPIN, tlvBuilder.serialize()) + return CommandApdu(Instruction.SetPin, tlvBuilder.serialize()) } override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SetPinResponse { diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index c5d743975b..b48263e2d1 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -32,6 +32,8 @@ class SignResponse( */ class SignCommand(private val hashes: Array) : Command() { + override val requiresPin2 = true + //TODO: Allow signing more than 10 hashes private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 97a406a2d3..380f18eaf1 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -32,6 +32,8 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val private val userCounter: Int? = null, private val userProtectedCounter: Int? = null) : Command() { + override val requiresPin2 = true + override fun performPreCheck(card: Card): TangemSdkError? { if (card.status == CardStatus.NotPersonalized) { return TangemSdkError.NotPersonalized() diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt index 46c8989535..02a5eabf7b 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt @@ -15,7 +15,7 @@ enum class Instruction(var code: Int) { ReadIssuerData(0xF7), CreateWallet(0xF8), CheckWallet(0xF9), - SwapPIN(0xFA), + SetPin(0xFA), Sign(0xFB), PurgeWallet(0xFC), Activate(0xFE), From 834f410d4afae2f08f69ae86225167a820cb8e35 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 Jun 2020 15:47:01 +0300 Subject: [PATCH 18/43] Updated on 2026-08-14 --- build.gradle | 3 +- tangem-core/build.gradle | 10 +- .../src/main/java/com/tangem/CardSession.kt | 30 +++++- .../src/main/java/com/tangem/Config.kt | 10 +- .../java/com/tangem/SessionEnvironment.kt | 93 +++++++++++++----- .../java/com/tangem/SessionViewDelegate.kt | 2 +- .../src/main/java/com/tangem/TangemSdk.kt | 38 +++++--- .../com/tangem/commands/CheckWalletCommand.kt | 2 +- .../main/java/com/tangem/commands/Command.kt | 95 ++++++++++--------- .../tangem/commands/CreateWalletCommand.kt | 4 +- .../com/tangem/commands/PurgeWalletCommand.kt | 4 +- .../java/com/tangem/commands/ReadCommand.kt | 2 +- .../tangem/commands/ReadIssuerDataCommand.kt | 2 +- .../commands/ReadIssuerExtraDataCommand.kt | 2 +- .../tangem/commands/ReadUserDataCommand.kt | 2 +- .../java/com/tangem/commands/SetPinCommand.kt | 35 ++++--- .../java/com/tangem/commands/SignCommand.kt | 4 +- .../tangem/commands/WriteIssuerDataCommand.kt | 2 +- .../commands/WriteIssuerExtraDataCommand.kt | 2 +- .../tangem/commands/WriteUserDataCommand.kt | 2 +- .../commands/verifycard/VerifyCardCommand.kt | 2 +- .../com/tangem/common/CardValuesService.kt | 46 +++++++++ .../main/java/com/tangem/common/PinCode.kt | 10 ++ .../main/java/com/tangem/tasks/ScanTask.kt | 48 +++++++--- .../sqldelight/com/tangem/CardValuesEntity.sq | 34 +++++++ .../com/tangem/common/apdu/CommandApduTest.kt | 13 +-- .../com/tangem/devkit/TestUserDataActivity.kt | 11 +-- tangem-sdk/build.gradle | 1 + .../DefaultSessionViewDelegate.kt | 13 ++- .../SessionViewDelegateState.kt | 2 +- .../tangem_sdk_new/extensions/TangemSdk.kt | 8 +- .../tangem_sdk_new/ui/NfcSessionDialog.kt | 25 +++-- 32 files changed, 390 insertions(+), 167 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/common/CardValuesService.kt create mode 100644 tangem-core/src/main/java/com/tangem/common/PinCode.kt create mode 100644 tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq diff --git a/build.gradle b/build.gradle index 40bc31028b..bbc8c94420 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,9 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' classpath 'com.google.firebase:perf-plugin:1.3.1' + classpath 'com.squareup.sqldelight:gradle-plugin:1.4.0' } } diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle index 8b71b5e8bf..ce4d4d4c3e 100644 --- a/tangem-core/build.gradle +++ b/tangem-core/build.gradle @@ -4,6 +4,7 @@ apply plugin: 'com.github.dcendents.android-maven' apply from: '../dependencies.gradle' apply from: '../jitpack.gradle' apply plugin: 'kotlin-kapt' +apply plugin: 'com.squareup.sqldelight' group = "$jitpackSdk.group" version "$jitpackSdk.version" @@ -11,7 +12,7 @@ version "$jitpackSdk.version" dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin" + implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlidn" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.7" @@ -22,6 +23,7 @@ dependencies { // misc implementation 'com.google.code.gson:gson:2.8.6' + implementation "com.squareup.sqldelight:sqlite-driver:1.4.0" //network implementation 'com.squareup.retrofit2:retrofit:2.8.1' @@ -36,6 +38,12 @@ dependencies { testImplementation "com.google.truth:truth:1.0.1" } +sqldelight { + Database { + packageName = "com.tangem" + } +} + sourceCompatibility = "8" targetCompatibility = "8" diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index bcaf32f1a7..4238ce7832 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,9 +1,11 @@ package com.tangem +import com.tangem.commands.Command import com.tangem.commands.CommandResponse import com.tangem.commands.OpenSessionCommand import com.tangem.commands.ReadCommand import com.tangem.common.CompletionResult +import com.tangem.common.PinCode import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu import com.tangem.common.extensions.calculateSha256 @@ -29,6 +31,10 @@ interface CardSessionRunnable { fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) } +interface CardSessionStoppedListener { + fun onSessionStopped(environment: SessionEnvironment) +} + enum class CardSessionState { Inactive, Active @@ -61,6 +67,7 @@ class CardSession( ) { var connectedTag: TagType? = null + var sessionStoppedListener: CardSessionStoppedListener? = null /** * True if some operation is still in progress. @@ -74,7 +81,7 @@ class CardSession( private val tag = this.javaClass.simpleName /** - * This metod starts a card session, performs preflight [ReadCommand], + * This method 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. @@ -83,6 +90,24 @@ class CardSession( runnable: T, callback: (result: CompletionResult) -> Unit ) { + if (environment.pin1 == null) { + viewDelegate.onSessionStarted(cardId) + viewDelegate.onPinRequested { + environment.pin1 = PinCode(it) + startWithRunnable(runnable, callback) + } + return + } + + if ((runnable as? Command)?.requiresPin2 == true && environment.pin2 == null) { + viewDelegate.onSessionStarted(cardId) + viewDelegate.onPinRequested { + environment.pin2 = PinCode(it) + startWithRunnable(runnable, callback) + } + return + } + start(runnable.performPreflightRead) { session, error -> if (error != null) { callback(CompletionResult.Failure(error)) @@ -209,6 +234,7 @@ class CardSession( } private fun stopSession() { + sessionStoppedListener?.onSessionStopped(environment) reader.stopSession() state = CardSessionState.Inactive scope.cancel() @@ -259,7 +285,7 @@ class CardSession( return CompletionResult.Failure(error) } val uid = result.uid - val protocolKey = environment.pin1.pbkdf2Hash(uid, 50) + val protocolKey = environment.pin1!!.value.pbkdf2Hash(uid, 50) val secret = encryptionHelper.generateSecret(result.sessionKeyB) val sessionKey = (secret + protocolKey).calculateSha256() environment.encryptionKey = sessionKey diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 45da4b6912..3cc58b5e6d 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -36,6 +36,14 @@ class Config( */ val cardFilter: CardFilter = CardFilter(), - var handleErrors: Boolean = true + var handleErrors: Boolean = true, + + var defaultPin1: String = "000000", + + var defaultPin2: String = "000", + + var savePin1InStaticField: Boolean = true, + + var savePin2InStaticField: Boolean = false ) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 88050df353..322d25f057 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -2,7 +2,9 @@ package com.tangem import com.tangem.commands.Card import com.tangem.commands.EllipticCurve -import com.tangem.common.extensions.calculateSha256 +import com.tangem.common.CardValuesService +import com.tangem.common.PinCode +import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils.generatePublicKey @@ -13,38 +15,79 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey * @property card Current card, read by preflight [com.tangem.commands.ReadCommand]. * @property terminalKeys generated terminal keys used in Linked Terminal feature. */ -data class SessionEnvironment( - var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(), - var card: Card? = null, - var terminalKeys: KeyPair? = null, - var encryptionMode: EncryptionMode = EncryptionMode.NONE, - var encryptionKey: ByteArray? = null, - var cvc: ByteArray? = null, - var cardFilter: CardFilter = CardFilter(), - val handleErrors: Boolean = true +class SessionEnvironment( + cardId: String?, + private val config: Config, + private val terminalKeysService: TerminalKeysService?, + private val cardValuesService: CardValuesService? ) { - var pin1: ByteArray = SessionEnvironment.pin1 - private set - get() = SessionEnvironment.pin1 + var pin1: PinCode? + var pin2: PinCode? + var cvc: ByteArray? = null - fun isCurrentPin1Default(): Boolean = Companion.pin1.contentEquals(DEFAULT_PIN.calculateSha256()) - fun isCurrentPin2Default(): Boolean = pin2.contentEquals(DEFAULT_PIN2.calculateSha256()) + var terminalKeys: KeyPair? = null + var cardFilter: CardFilter + val handleErrors: Boolean - fun setPin1(pin1: String) { - SessionEnvironment.pin1 = pin1.calculateSha256() + var encryptionMode: EncryptionMode + var encryptionKey: ByteArray? = null + + var cardVerification: VerificationState + var cardValidation: VerificationState + var codeVerification: VerificationState + + var card: Card? = null + + init { + terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + cardFilter = config.cardFilter + handleErrors = config.handleErrors + + encryptionMode = config.encryptionMode + + val cardValues = cardId?.let { cardValuesService?.getValues(cardId) } + + cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified + cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified + codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified + + pin1 = TangemSdk.pin1 + ?: if (cardValues?.isPin1Default != false) { + PinCode(config.defaultPin1, true) + } else { + null + } + + pin2 = cardId?.let { TangemSdk.pin2[it] } + ?: if (cardValues?.isPin2Default != false) { + PinCode(config.defaultPin2, true) + } else { + null + } } - fun setPin2(pin2: String) { - this.pin2 = pin2.calculateSha256() + fun restoreCardValues() { + val cardValues = this.card?.cardId?.let { cardValuesService?.getValues(it) } + cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified + cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified + codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified + + if (cardValues?.isPin1Default == false && pin1?.isDefault == true) pin1 = null + if (cardValues?.isPin2Default == false && pin1?.isDefault == true) pin2 = null } - companion object { - const val DEFAULT_PIN = "000000" - const val DEFAULT_PIN2 = "000" + fun saveCardValues() { + if (config.savePin1InStaticField) { + TangemSdk.pin1 = pin1 + } + if (config.savePin2InStaticField) { + card?.cardId?.let { cardId -> TangemSdk.pin2[cardId] = pin2 } + } - var pin1: ByteArray = DEFAULT_PIN.calculateSha256() + cardValuesService?.saveValues(this) } + } /** @@ -60,4 +103,8 @@ class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) { constructor(privateKey: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1) : this(generatePublicKey(privateKey, curve), privateKey) +} + +enum class VerificationState { + Passed, Offline, Failed, NotVerified } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index e3b2306bbf..7177c0a2b6 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -47,7 +47,7 @@ interface SessionViewDelegate { /** * It is called when a user is expected to enter pin code. */ - fun onPinRequested(callback: (pin: String?) -> Unit) + fun onPinRequested(callback: (pin: String) -> Unit) } /** diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index 6508fb8c3f..190c13f515 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -1,5 +1,6 @@ package com.tangem +import com.squareup.sqldelight.db.SqlDriver import com.tangem.commands.* import com.tangem.commands.personalization.DepersonalizeCommand import com.tangem.commands.personalization.DepersonalizeResponse @@ -10,7 +11,9 @@ import com.tangem.commands.personalization.entities.Issuer import com.tangem.commands.personalization.entities.Manufacturer import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse +import com.tangem.common.CardValuesDbService import com.tangem.common.CompletionResult +import com.tangem.common.PinCode import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils import com.tangem.tasks.CreateWalletTask @@ -30,7 +33,8 @@ import com.tangem.tasks.ScanTask class TangemSdk( private val reader: CardReader, private val viewDelegate: SessionViewDelegate, - var config: Config = Config() + var config: Config = Config(), + private val sqlDriver: SqlDriver ) { private var terminalKeysService: TerminalKeysService? = null @@ -203,7 +207,7 @@ class TangemSdk( initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit ) { - val command = WriteUserDataCommand(userData = userData,userCounter = userCounter) + val command = WriteUserDataCommand(userData = userData, userCounter = userCounter) startSessionWithRunnable(command, cardId, initialMessage, callback) } @@ -378,13 +382,22 @@ class TangemSdk( fun startSessionWithRunnable( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage) + addSessionStoppedListener(cardSession) Thread().run { cardSession.startWithRunnable(runnable, callback) } } + private fun addSessionStoppedListener(session: CardSession) { + session.sessionStoppedListener = object : CardSessionStoppedListener { + override fun onSessionStopped(environment: SessionEnvironment) { + environment.saveCardValues() + } + } + } + /** * Allows running a custom bunch of commands in one [CardSession] with lightweight closure syntax. - * Tangem SDK will start a card sesion and perform preflight [ReadCommand]. + * Tangem SDK will start a card session 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 @@ -395,8 +408,9 @@ class TangemSdk( * then you can use the [CardSession] to interact with a card. */ fun startSession(cardId: String? = null, initialMessage: Message? = null, - callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + callback: (session: CardSession, error: TangemSdkError?) -> Unit) { + val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage) + addSessionStoppedListener(cardSession) Thread().run { cardSession.start(callback = callback) } } @@ -408,14 +422,14 @@ class TangemSdk( this.terminalKeysService = terminalKeysService } - private fun buildEnvironment(): SessionEnvironment { - val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + private fun buildEnvironment(cardId: String?): SessionEnvironment { return SessionEnvironment( - terminalKeys = terminalKeys, - cardFilter = config.cardFilter, - handleErrors = config.handleErrors + cardId, config, terminalKeysService, CardValuesDbService(sqlDriver) ) } - companion object + companion object { + var pin1: PinCode? = null + val pin2: MutableMap = mutableMapOf() + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index 672ca2da62..7b7116ba44 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -82,7 +82,7 @@ class CheckWalletCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index ce21f6cdba..cad5effb77 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -2,6 +2,7 @@ package com.tangem.commands import com.tangem.* import com.tangem.common.CompletionResult +import com.tangem.common.PinCode import com.tangem.common.apdu.* import com.tangem.common.extensions.toInt import com.tangem.common.tlv.TlvTag @@ -50,7 +51,6 @@ abstract class Command : ApduSerializable, CardSessionRu open fun mapError(card: Card?, error: TangemSdkError): TangemSdkError = error fun transceive(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card if (session.environment.handleErrors && card != null) { performPreCheck(card)?.let { error -> @@ -58,12 +58,14 @@ abstract class Command : ApduSerializable, CardSessionRu return } } - - if (requiresPin2 && session.environment.isCurrentPin2Default()) { + if (requiresPin2 && session.environment.pin2?.isDefault == true) { handlePin2(session, callback) return } + transceiveInternal(session, callback) + } + private fun transceiveInternal(session: CardSession, callback: (result: CompletionResult) -> Unit) { val apdu = serialize(session.environment) transceiveApdu(apdu, session) { result -> when (result) { @@ -74,6 +76,10 @@ abstract class Command : ApduSerializable, CardSessionRu handlePin1(session, callback) return@transceiveApdu } + if (error is TangemSdkError.Pin2OrCvcRequired) { + handlePin2(session, callback) + return@transceiveApdu + } callback(CompletionResult.Failure(error)) return@transceiveApdu @@ -93,9 +99,9 @@ abstract class Command : ApduSerializable, CardSessionRu } private fun transceiveApdu( - apdu: CommandApdu, - session: CardSession, - callback: (result: CompletionResult) -> Unit + apdu: CommandApdu, + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { Log.i(this::class.simpleName!!, "transieve: ${Instruction.byCode(apdu.ins)}") @@ -114,17 +120,17 @@ abstract class Command : ApduSerializable, CardSessionRu StatusWord.NeedPause -> { // NeedPause is returned from the card whenever security delay is triggered. val remainingTime = - deserializeSecurityDelay(responseApdu) + deserializeSecurityDelay(responseApdu) if (remainingTime != null) { session.viewDelegate.onSecurityDelay( - remainingTime, - session.environment.card?.pauseBeforePin2 ?: 0 + remainingTime, + session.environment.card?.pauseBeforePin2 ?: 0 ) } Log.i( - this::class.simpleName!!, - "Nfc command ${this::class.simpleName!!} " + - "triggered security delay of $remainingTime milliseconds" + this::class.simpleName!!, + "Nfc command ${this::class.simpleName!!} " + + "triggered security delay of $remainingTime milliseconds" ) transceiveApdu(apdu, session, callback) } @@ -149,7 +155,7 @@ abstract class Command : ApduSerializable, CardSessionRu } else -> { val error = responseApdu.statusWord.toTangemSdkError() - if (error != null && !tryHandleError(error)) { + if (error != null) { callback(CompletionResult.Failure(error)) } else { callback(CompletionResult.Failure(TangemSdkError.UnknownError())) @@ -173,61 +179,56 @@ abstract class Command : ApduSerializable, CardSessionRu * @return Remaining security delay in milliseconds. */ private fun deserializeSecurityDelay( - responseApdu: ResponseApdu + responseApdu: ResponseApdu ): Int? { val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } - private fun tryHandleError(error: TangemSdkError): Boolean { - return false - } - private fun handlePin1( - session: CardSession, - callback: (result: CompletionResult) -> Unit + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { - if (!session.environment.isCurrentPin1Default()) { - session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) - transceive(session, callback) - return - } session.pause() session.viewDelegate.onPinRequested { pin1 -> - if (!pin1.isNullOrEmpty()) { - session.environment.setPin1(pin1) - session.resume() - transceive(session, callback) - } else { - session.environment.setPin1(SessionEnvironment.DEFAULT_PIN) - session.resume() - transceive(session, callback) - } + session.environment.pin1 = PinCode(pin1) + session.resume() + transceive(session, callback) } } private fun handlePin2( - session: CardSession, - callback: (result: CompletionResult) -> Unit + session: CardSession, + callback: (result: CompletionResult) -> Unit ) { - val checkPinCommand = SetPinCommand(session.environment.pin1, session.environment.pin2) + val currentPin1 = session.environment.pin1?.value ?: run { + callback(CompletionResult.Failure(TangemSdkError.Pin1Required())) + return + } + val currentPin2 = session.environment.pin2?.value ?: run { + callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) + return + } + + val checkPinCommand = SetPinCommand(currentPin1, currentPin2) checkPinCommand.run(session) { result -> when (result) { is CompletionResult.Failure -> { - session.viewDelegate.onPinRequested { pin2 -> - if (!pin2.isNullOrEmpty()) { - session.environment.setPin2(pin2) - transceive(session, callback) - } else { - session.environment.setPin2(SessionEnvironment.DEFAULT_PIN2) - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - } - } + session.environment.pin2 = null + getPin2FromDelegate(session, callback) } is CompletionResult.Success -> { - transceive(session, callback) + transceiveInternal(session, callback) } } } } + + private fun getPin2FromDelegate(session: CardSession, + callback: (result: CompletionResult) -> Unit) { + session.viewDelegate.onPinRequested { pin2 -> + session.environment.pin2 = PinCode(pin2) + transceive(session, callback) + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 83a6cbd78d..af6cce48b8 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -62,9 +62,9 @@ class CreateWalletCommand : Command() { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) tlvBuilder.append(TlvTag.Cvc, environment.cvc) return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index c4ca477ca3..3d0631f3df 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -61,9 +61,9 @@ class PurgeWalletCommand : Command() { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 189588a080..5e692f2e40 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -382,7 +382,7 @@ class ReadCommand : Command() { * In order to obtain card’s 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, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) return CommandApdu(Instruction.Read, tlvBuilder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index 97bc306083..4d2301675a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -96,7 +96,7 @@ class ReadIssuerDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index cb7bba09d0..88e5af5569 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -145,7 +145,7 @@ class ReadIssuerExtraDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 2e68b631ef..58baea5b08 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -62,7 +62,7 @@ class ReadUserDataCommand : Command() { 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.Pin, environment.pin1?.value) return CommandApdu(Instruction.ReadUserData, builder.serialize()) } diff --git a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt index 084e755cc1..6b7f12fa80 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt @@ -6,7 +6,6 @@ import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.apdu.StatusWord -import com.tangem.common.extensions.calculateSha256 import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag @@ -40,28 +39,27 @@ enum class SetPinStatus { } class SetPinResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - /** - * - */ - val status: SetPinStatus + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + /** + * + */ + val status: SetPinStatus ) : CommandResponse - class SetPinCommand( - private val newPin1: ByteArray = SessionEnvironment.DEFAULT_PIN.calculateSha256(), - private val newPin2: ByteArray = SessionEnvironment.DEFAULT_PIN2.calculateSha256(), - private val newPin3: ByteArray? = null + private var newPin1: ByteArray, + private var newPin2: ByteArray, + private var newPin3: ByteArray? = null ) : Command() { override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) tlvBuilder.append(TlvTag.Cvc, environment.cvc) tlvBuilder.append(TlvTag.NewPin, newPin1) tlvBuilder.append(TlvTag.NewPin2, newPin2) @@ -72,12 +70,13 @@ class SetPinCommand( override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SetPinResponse { val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - val status = SetPinStatus.fromStatusWord(apdu.statusWord) ?: throw TangemSdkError.DecodingFailed() + val status = SetPinStatus.fromStatusWord(apdu.statusWord) + ?: throw TangemSdkError.DecodingFailed() val decoder = TlvDecoder(tlvData) return SetPinResponse( - cardId = decoder.decode(TlvTag.CardId), - status = status + cardId = decoder.decode(TlvTag.CardId), + status = status ) } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index b48263e2d1..21c003b64b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -75,8 +75,8 @@ class SignCommand(private val hashes: Array) : Command( 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.Pin, environment.pin1?.value) + tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 0b563a018e..62c44269e2 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -83,7 +83,7 @@ class WriteIssuerDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData) tlvBuilder.append(TlvTag.IssuerData, issuerData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 115ebcd1dd..6859aff915 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -149,7 +149,7 @@ class WriteIssuerExtraDataCommand( override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, mode) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 380f18eaf1..fadb16731d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -57,7 +57,7 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val 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.Pin, environment.pin1?.value) builder.append(TlvTag.UserData, userData) builder.append(TlvTag.UserCounter, userCounter) builder.append(TlvTag.UserProtectedData, userProtectedData) diff --git a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt index 1eb97c4aa9..bbb3454e66 100644 --- a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt @@ -137,7 +137,7 @@ class VerifyCardCommand(private val onlineVerification: Boolean) : Command + cardValuesQueries.insertOrReplace( + cardId, + environment.pin1?.isDefault ?: false, + environment.pin2?.isDefault ?: false, + environment.cardVerification, environment.cardVerification, + environment.codeVerification + ) + } + } + + override fun getValues(cardId: String): CardValues? = + cardValuesQueries.selectByCardId(cardId).executeAsOneOrNull() + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/PinCode.kt b/tangem-core/src/main/java/com/tangem/common/PinCode.kt new file mode 100644 index 0000000000..0262089def --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/common/PinCode.kt @@ -0,0 +1,10 @@ +package com.tangem.common + +import com.tangem.common.extensions.calculateSha256 + +class PinCode( + val value: ByteArray, + val isDefault: Boolean +) { + constructor(value: String, isDefault: Boolean = false): this(value.calculateSha256(), isDefault) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index 8b9d8f2452..d957d2966c 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -31,24 +31,44 @@ internal class ScanTask : CardSessionRunnable { is CompletionResult.Success -> { val card = readResult.data session.environment.card = card - if (card.cardData?.productMask?.contains(Product.Tag) != false) { - callback(CompletionResult.Success(card)) - - } else if (card.status != CardStatus.Loaded) { - callback(CompletionResult.Success(card)) - - } else if (card.curve == null || card.walletPublicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.CardError())) - - } else { - val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) - checkWalletCommand.run(session) { result -> + if (session.environment.pin1 != null && session.environment.pin2 != null) { + val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value) + checkPinCommand.run(session) { result -> when (result) { - is CompletionResult.Success -> callback(CompletionResult.Success(card)) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + is CompletionResult.Failure -> { + session.environment.pin2 = null + } } + session.environment.restoreCardValues() } + } else { + runCheckWalletIfNeeded(card, session, callback) } + + } + } + } + } + + private fun runCheckWalletIfNeeded( + card: Card, session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (card.cardData?.productMask?.contains(Product.Tag) != false) { + callback(CompletionResult.Success(card)) + + } else if (card.status != CardStatus.Loaded) { + callback(CompletionResult.Success(card)) + + } else if (card.curve == null || card.walletPublicKey == null) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + + } else { + val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) + checkWalletCommand.run(session) { result -> + when (result) { + is CompletionResult.Success -> callback(CompletionResult.Success(card)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } } } diff --git a/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq b/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq new file mode 100644 index 0000000000..7ac4521e98 --- /dev/null +++ b/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq @@ -0,0 +1,34 @@ +import com.tangem.VerificationState; + +CREATE TABLE cardValues ( + cardId TEXT NOT NULL UNIQUE PRIMARY KEY, + isPin1Default INTEGER AS Boolean DEFAULT 1, + isPin2Default INTEGER AS Boolean DEFAULT 1, + cardVerification TEXT AS VerificationState, + cardValidation TEXT AS VerificationState, + codeVerification TEXT AS VerificationState +); + +insertOrReplace: +INSERT OR REPLACE INTO cardValues( + cardId, + isPin1Default, + isPin2Default, + cardVerification, + cardValidation, + codeVerification +) +VALUES (?, ?, ?, ?, ?, ?); + +selectByCardId: +SELECT * +FROM cardValues +WHERE cardId = ?; + +deleteAll: +DELETE FROM cardValues; + +deleteByCardId: +DELETE +FROM cardValues +WHERE cardId = ?; \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt index b995c4de57..05a5b3ca0b 100644 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt @@ -1,23 +1,21 @@ package com.tangem.common.apdu import com.google.common.truth.Truth.assertThat +import com.tangem.Config import com.tangem.SessionEnvironment import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvTag -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.BroadcastChannel -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.collect import org.junit.Test class CommandApduTest { + val sessionEnvironment = SessionEnvironment(null, Config(), null, null) + @Test fun `simple READ command to bytes`() { - val sessionEnvironment = SessionEnvironment() val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1) + tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1?.value) val commandApdu = CommandApdu( Instruction.Read, tlvBuilder.serialize() @@ -33,13 +31,12 @@ class CommandApduTest { @Test fun `READ with terminal key to bytes`() { - val sessionEnvironment = SessionEnvironment() val terminalPublicKey = byteArrayOf(4, 80, -122, 58, -42, 74, -121, -82, -118, 47, -24, 60, 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, 126, 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1) + tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1?.value) tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey) val commandApdu = CommandApdu( Instruction.Read, diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt b/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt index 5628c01f29..2be072959f 100644 --- a/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt +++ b/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt @@ -5,7 +5,6 @@ import android.view.View import android.widget.CompoundButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import com.tangem.SessionEnvironment import com.tangem.TangemSdk import com.tangem.TangemSdkError import com.tangem.common.CompletionResult @@ -103,7 +102,7 @@ class TestUserDataActivity : AppCompatActivity() { chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } - chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } +// chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } } private fun showReadWriteSection(show: Boolean) { @@ -140,8 +139,8 @@ class WriteOptions { userProtectedCounter = if (chbx.isChecked) value else null } - fun updatePin2(chbx: CompoundButton) { - val value = SessionEnvironment.DEFAULT_PIN2 - pin2 = if (chbx.isChecked) value else null - } +// fun updatePin2(chbx: CompoundButton) { +// val value = SessionEnvironment.pin1.DEFAULT_PIN2 +// pin2 = if (chbx.isChecked) value else null +// } } \ No newline at end of file diff --git a/tangem-sdk/build.gradle b/tangem-sdk/build.gradle index 7b4e3dd92f..bb868f952a 100644 --- a/tangem-sdk/build.gradle +++ b/tangem-sdk/build.gradle @@ -62,6 +62,7 @@ dependencies { // misc implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'at.favre.lib:armadillo:0.9.0' + implementation "com.squareup.sqldelight:android-driver:1.4.0" // testing testImplementation 'junit:junit:4.12' diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index 3cd49e1741..9308dffddc 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -19,20 +19,19 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel } override fun onSessionStarted(cardId: String?, message: Message?) { - postUI { showReadingDialog(activity, cardId, message) } + postUI { + if (readingDialog == null) createReadingDialog(activity) + readingDialog?.show(SessionViewDelegateState.Ready(cardId, message)) + } } - private fun showReadingDialog(activity: FragmentActivity, cardId: String?, message: Message?) { + private fun createReadingDialog(activity: FragmentActivity) { val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null) readingDialog = NfcSessionDialog(activity) readingDialog?.setContentView(dialogView) readingDialog?.dismissWithAnimation = true readingDialog?.create() - readingDialog?.setOnShowListener { - readingDialog?.show(SessionViewDelegateState.Ready(cardId, message)) - } readingDialog?.setOnCancelListener { reader.stopSession(true) } - readingDialog?.show() } override fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) { @@ -68,7 +67,7 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } - override fun onPinRequested(callback: (pin: String?) -> Unit) { + override fun onPinRequested(callback: (pin: String) -> Unit) { postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt index d5e02a44bb..1880e23a17 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -9,7 +9,7 @@ sealed class SessionViewDelegateState() { data class SecurityDelay(val ms: Int, val totalDurationSeconds: Int) : SessionViewDelegateState() data class Delay(val total: Int, val current: Int, val step: Int) : SessionViewDelegateState() data class Ready(val cardId: String?, val message: Message?) : SessionViewDelegateState() - data class PinRequested(val callback: (pin: String?) -> Unit) : SessionViewDelegateState() + data class PinRequested(val callback: (pin: String) -> Unit) : SessionViewDelegateState() object TagLost : SessionViewDelegateState() object TagConnected : SessionViewDelegateState() object WrongCard : SessionViewDelegateState() diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt index e9a6ab555c..0022ac2066 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt @@ -1,7 +1,9 @@ package com.tangem.tangem_sdk_new.extensions import androidx.fragment.app.FragmentActivity +import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.Config +import com.tangem.Database import com.tangem.SessionViewDelegate import com.tangem.TangemSdk import com.tangem.tangem_sdk_new.DefaultSessionViewDelegate @@ -15,7 +17,8 @@ fun TangemSdk.Companion.init(activity: FragmentActivity, config: Config = Config val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader) viewDelegate.activity = activity - val tangemSdk = TangemSdk(nfcManager.reader, viewDelegate, config) + val databaseDriver = AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") + val tangemSdk = TangemSdk(nfcManager.reader, viewDelegate, config, databaseDriver) tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application)) return tangemSdk @@ -32,7 +35,8 @@ fun TangemSdk.Companion.customInit( nfcManager.reader, viewDelegate ?: DefaultSessionViewDelegate(nfcManager.reader) .apply { this.activity = activity }, - config + config, + AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") ) tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application)) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index 3ece303dc9..e1ffec7f98 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -10,7 +10,6 @@ import android.view.inputmethod.InputMethodManager import android.widget.TextView import androidx.fragment.app.FragmentActivity import com.google.android.material.bottomsheet.BottomSheetDialog -import com.tangem.Log import com.tangem.tangem_sdk_new.R import com.tangem.tangem_sdk_new.SessionViewDelegateState import com.tangem.tangem_sdk_new.extensions.localizedDescription @@ -24,6 +23,9 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ private var currentState: SessionViewDelegateState? = null fun show(state: SessionViewDelegateState) { + if (!this.isShowing) { + this.show() + } when (state) { is SessionViewDelegateState.Ready -> onReady(state) is SessionViewDelegateState.Success -> onSuccess(state) @@ -135,14 +137,21 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ etPin?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> postUI { if (actionId == KeyEvent.KEYCODE_ENDCALL) { - show(lTouchCard) - tvTaskTitle?.show() - tvTaskText?.show() - val imm: InputMethodManager = - context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(v?.windowToken, 0) + if (v?.text.isNullOrBlank()) { + etPin?.error = "" + } else { + show(lTouchCard) + tvTaskTitle?.show() + tvTaskText?.show() + val imm: InputMethodManager = + context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v?.windowToken, 0) - v?.text?.toString()?.let { state.callback(it) } + v?.text.toString().let { pin -> + v?.text = "" + state.callback(pin) + } + } } } true From f70813e88d340c507ed94fb28bfe97cf6eaf9c3d Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 1 Jul 2020 21:27:13 +0300 Subject: [PATCH 19/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardSession.kt | 9 +- .../java/com/tangem/SessionViewDelegate.kt | 9 +- .../src/main/java/com/tangem/TangemSdk.kt | 24 ++++++ .../com/tangem/commands/ChangePinCommand.kt | 82 +++++++++++++++++++ .../main/java/com/tangem/commands/Command.kt | 4 +- .../main/java/com/tangem/tasks/ScanTask.kt | 1 + .../DefaultSessionViewDelegate.kt | 19 ++++- .../SessionViewDelegateState.kt | 3 +- .../tangem_sdk_new/ui/NfcSessionDialog.kt | 72 ++++++++++++++-- .../src/main/res/layout/nfc_bottom_sheet.xml | 54 +++++++++++- tangem-sdk/src/main/res/values/strings.xml | 9 ++ 11 files changed, 267 insertions(+), 19 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index 4238ce7832..8c67cffd39 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,9 +1,6 @@ package com.tangem -import com.tangem.commands.Command -import com.tangem.commands.CommandResponse -import com.tangem.commands.OpenSessionCommand -import com.tangem.commands.ReadCommand +import com.tangem.commands.* import com.tangem.common.CompletionResult import com.tangem.common.PinCode import com.tangem.common.apdu.CommandApdu @@ -92,7 +89,7 @@ class CardSession( if (environment.pin1 == null) { viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested { + viewDelegate.onPinRequested(PinType.Pin1) { environment.pin1 = PinCode(it) startWithRunnable(runnable, callback) } @@ -101,7 +98,7 @@ class CardSession( if ((runnable as? Command)?.requiresPin2 == true && environment.pin2 == null) { viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested { + viewDelegate.onPinRequested(PinType.Pin2) { environment.pin2 = PinCode(it) startWithRunnable(runnable, callback) } diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 7177c0a2b6..3405c7c676 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,5 +1,7 @@ package com.tangem +import com.tangem.commands.PinType + /** * Allows interaction with users and shows visual elements. * @@ -47,7 +49,12 @@ interface SessionViewDelegate { /** * It is called when a user is expected to enter pin code. */ - fun onPinRequested(callback: (pin: String) -> Unit) + fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) + + /** + * It is called when a user wants to change pin code. + */ + fun onPinChangeRequested(pinType: PinType, callback: (pin: String) -> Unit) } /** diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index 190c13f515..266301b737 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -365,6 +365,30 @@ class TangemSdk( startSessionWithRunnable(command, null, initialMessage, callback) } + fun changePin1(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinCommand(PinType.Pin1, pin) + startSessionWithRunnable(command, cardId, initialMessage, callback) + } + + fun changePin2(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinCommand(PinType.Pin2, pin) + startSessionWithRunnable(command, cardId, initialMessage, callback) + } + + fun changePin3(cardId: String? = null, + pin: ByteArray? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = ChangePinCommand(PinType.Pin3, pin) + startSessionWithRunnable(command, cardId, 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], diff --git a/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt new file mode 100644 index 0000000000..641fb6e3d2 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt @@ -0,0 +1,82 @@ +package com.tangem.commands + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.SessionViewDelegate +import com.tangem.common.CompletionResult +import com.tangem.common.PinCode +import com.tangem.common.extensions.calculateSha256 +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume + +enum class PinType { + Pin1, + Pin2, + Pin3, + ; +} + +class ChangePinCommand( + private val pinType: PinType, + private val pin: ByteArray? = null +) : CardSessionRunnable { + override val performPreflightRead = true + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + session.scope.launch { + val pin = this@ChangePinCommand.pin + ?: requestNewPin(pinType, session.viewDelegate).calculateSha256() + runSetPin(pin, session, callback) + } + } + + private suspend fun runSetPin(pin: ByteArray, session: CardSession, callback: (result: CompletionResult) -> Unit) { + val pin1: ByteArray + val pin2: ByteArray + val pin3: ByteArray? + + if (session.environment.pin1 == null) { + session.environment.pin1 = PinCode(requestPin(PinType.Pin1, session.viewDelegate)) + } + if (session.environment.pin2 == null) { + session.environment.pin2 = PinCode(requestPin(PinType.Pin2, session.viewDelegate)) + } + + when (pinType) { + PinType.Pin1 -> { + pin1 = pin + pin2 = session.environment.pin2!!.value + pin3 = null + } + PinType.Pin2 -> { + pin1 = session.environment.pin1!!.value + pin2 = pin + pin3 = null + } + PinType.Pin3 -> { + pin1 = session.environment.pin1!!.value + pin2 = session.environment.pin2!!.value + pin3 = pin + } + } + val command = SetPinCommand(pin1, pin2, pin3) + command.run(session, callback) + } + + private suspend fun requestPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = + suspendCancellableCoroutine { continuation -> + viewDelegate.onPinRequested(pinType) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + + private suspend fun requestNewPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = + suspendCancellableCoroutine { continuation -> + viewDelegate.onPinChangeRequested(pinType) { result -> + if (continuation.isActive) continuation.resume(result) + } + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index cad5effb77..27abf365a7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -190,7 +190,7 @@ abstract class Command : ApduSerializable, CardSessionRu callback: (result: CompletionResult) -> Unit ) { session.pause() - session.viewDelegate.onPinRequested { pin1 -> + session.viewDelegate.onPinRequested(PinType.Pin1) { pin1 -> session.environment.pin1 = PinCode(pin1) session.resume() transceive(session, callback) @@ -226,7 +226,7 @@ abstract class Command : ApduSerializable, CardSessionRu private fun getPin2FromDelegate(session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.viewDelegate.onPinRequested { pin2 -> + session.viewDelegate.onPinRequested(PinType.Pin2) { pin2 -> session.environment.pin2 = PinCode(pin2) transceive(session, callback) } diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index d957d2966c..512171113f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -40,6 +40,7 @@ internal class ScanTask : CardSessionRunnable { } } session.environment.restoreCardValues() + runCheckWalletIfNeeded(card, session, callback) } } else { runCheckWalletIfNeeded(card, session, callback) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index 9308dffddc..b2bbd8afe0 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -2,6 +2,7 @@ package com.tangem.tangem_sdk_new import androidx.fragment.app.FragmentActivity import com.tangem.* +import com.tangem.commands.PinType import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog @@ -67,8 +68,22 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel postUI { readingDialog?.show(SessionViewDelegateState.Error(error)) } } - override fun onPinRequested(callback: (pin: String) -> Unit) { - postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(callback)) } + override fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) { + val message = when (pinType) { + PinType.Pin1 -> activity.getString(R.string.pin_enter_pin_1) + PinType.Pin2 -> activity.getString(R.string.pin_enter_pin_2) + PinType.Pin3 -> activity.getString(R.string.pin_enter_pin_3) + } + postUI { readingDialog?.show(SessionViewDelegateState.PinRequested(message, callback)) } + } + + override fun onPinChangeRequested(pinType: PinType, callback: (pin: String) -> Unit) { + val message = when (pinType) { + PinType.Pin1 -> activity.getString(R.string.pin_change_pin_1) + PinType.Pin2 -> activity.getString(R.string.pin_change_pin_2) + PinType.Pin3 -> activity.getString(R.string.pin_change_pin_3) + } + postUI { readingDialog?.show(SessionViewDelegateState.PinChangeRequested(message, callback)) } } private fun setLogger() { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt index 1880e23a17..5552ea2d1a 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt @@ -9,7 +9,8 @@ sealed class SessionViewDelegateState() { data class SecurityDelay(val ms: Int, val totalDurationSeconds: Int) : SessionViewDelegateState() data class Delay(val total: Int, val current: Int, val step: Int) : SessionViewDelegateState() data class Ready(val cardId: String?, val message: Message?) : SessionViewDelegateState() - data class PinRequested(val callback: (pin: String) -> Unit) : SessionViewDelegateState() + data class PinRequested(val message: String, val callback: (pin: String) -> Unit) : SessionViewDelegateState() + data class PinChangeRequested(val message: String, val callback: (pin: String) -> Unit) : SessionViewDelegateState() object TagLost : SessionViewDelegateState() object TagConnected : SessionViewDelegateState() object WrongCard : SessionViewDelegateState() diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index e1ffec7f98..5b32b7e0a2 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -33,6 +33,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ is SessionViewDelegateState.SecurityDelay -> onSecurityDelay(state) is SessionViewDelegateState.Delay -> onDelay(state) is SessionViewDelegateState.PinRequested -> onPinRequested(state) + is SessionViewDelegateState.PinChangeRequested -> onPinChangeRequested(state) is SessionViewDelegateState.TagLost -> onTagLost() is SessionViewDelegateState.TagConnected -> onTagConnected() is SessionViewDelegateState.WrongCard -> onWrongCard() @@ -52,9 +53,17 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ tvCardId?.show() tvCardId?.text = cardId } - state.message?.let { message -> - if (message.body != null) tvTaskText?.text = message.body - if (message.header != null) tvTaskTitle?.text = message.header + + if (state.message?.header != null) { + tvTaskTitle?.text = state.message.header + } else { + tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) + } + if (state.message?.body != null) { + tvTaskText?.text = state.message.body + } else { + tvTaskText?.text = activity.getText(R.string.dialog_scan_text) + } } @@ -132,13 +141,14 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ tvTaskText?.visibility = View.INVISIBLE tvTaskTitle?.visibility = View.INVISIBLE show(flPin) + tilPin.hint = state.message performHapticFeedback() etPin?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> postUI { if (actionId == KeyEvent.KEYCODE_ENDCALL) { if (v?.text.isNullOrBlank()) { - etPin?.error = "" + etPin?.error = activity.getString(R.string.pin_enter_error_empty) } else { show(lTouchCard) tvTaskTitle?.show() @@ -158,9 +168,58 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ } } + private fun onPinChangeRequested(state: SessionViewDelegateState.PinChangeRequested) { + + tvTaskText?.visibility = View.INVISIBLE + tvTaskTitle?.visibility = View.INVISIBLE + show(llChangePin) + + tilChangePin.hint = state.message + tilChangePinConfirm.hint = activity.getString(R.string.pin_change_confirm) + + performHapticFeedback() + etPin?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> + postUI { + if (actionId == KeyEvent.KEYCODE_ENDCALL) { + if (v?.text.isNullOrBlank()) { + etChangePin?.error = activity.getString(R.string.pin_enter_error_empty) + } + } + } + true + } + + etChangePinConfirm?.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? -> + postUI { + if (actionId == KeyEvent.KEYCODE_ENDCALL) { + if (v?.text.isNullOrBlank()) { + etChangePin?.error = activity.getString(R.string.pin_enter_error_empty) + } else if (v?.text.toString() != etChangePin?.text.toString()) { + etChangePinConfirm?.error = activity.getString(R.string.pin_change_error) + etChangePin?.error = activity.getString(R.string.pin_change_error) + } else { + show(lTouchCard) + tvTaskTitle?.show() + tvTaskText?.show() + val imm: InputMethodManager = + context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(v?.windowToken, 0) + + v?.text.toString().let { pin -> + v?.text = "" + etChangePin?.setText("") + state.callback(pin) + } + } + } + } + true + } + } + private fun onTagLost() { if (currentState is SessionViewDelegateState.Success || - currentState is SessionViewDelegateState.PinRequested) { + currentState is SessionViewDelegateState.PinRequested) { return } show(lTouchCard) @@ -182,7 +241,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ ) performHapticFeedback() - postUI (2000){ + postUI(2000) { show(lTouchCard) tvTaskTitle?.text = activity.getText(R.string.dialog_ready_to_scan) tvTaskText?.text = activity.getText(R.string.dialog_scan_text) @@ -198,6 +257,7 @@ class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activ flError?.show(view.id == flError.id) flCompletion?.show(view.id == flCompletion.id) flPin?.show(view.id == flPin.id) + llChangePin?.show(view.id == llChangePin.id) } private fun performHapticFeedback() { diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index d572de2f85..3ded5704da 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -98,12 +98,64 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:backgroundTint="#f3f5f6" - android:hint="Enter PIN" android:inputType="textPassword" /> + + + + + + + + + + + + + + Ready to Scan Scan card with your phone as shown above Card + Enter New Access Code + Enter New Pass Code + Enter New PIN 3 + Confirm New Code + Codes do not match + Enter code + Enter Access Code + Enter Pass Code + Enter PIN 3 From 4a411286e8a18929d0cea7baaaf0b8dcb5828a04 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 2 Jul 2020 13:11:31 +0300 Subject: [PATCH 20/43] Updated on 2026-08-14 --- .../common/WalletManagerFactoryTest.kt | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt index e372153248..5edc1ef722 100644 --- a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt +++ b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt @@ -1,6 +1,7 @@ package com.tangem.blockchain.common import com.google.common.truth.Truth +import com.tangem.Config import com.tangem.SessionEnvironment import com.tangem.blockchain.blockchains.binance.BinanceWalletManager import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager @@ -19,11 +20,13 @@ import org.junit.Test internal class WalletManagerFactoryTest { + private val sessionEnvironment = SessionEnvironment(null, Config(), null, null) + @Test fun createBitcoinWalletManager() { val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -34,7 +37,7 @@ internal class WalletManagerFactoryTest { fun createEthereumWalletManager() { val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -45,7 +48,7 @@ internal class WalletManagerFactoryTest { fun createStellarWalletManager() { val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -56,7 +59,7 @@ internal class WalletManagerFactoryTest { fun createCardanoWalletManager() { val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -67,7 +70,7 @@ internal class WalletManagerFactoryTest { fun createXrpWalletManager() { val data = "0108cb21000000002154200b534d4152542043415348000201028006322e31317200034104bdad63848f97c535da53cf8fd300d24fa33f0516d194aa78ec164a06994d00204bae243a424e316c6ec845e02d9b15eafae8c19018a926b0b7435e6e941cdadb0a0400007e210c5a81020028820407e30502830754414e47454d00840358525086400ed8734b877869722c7d0b37ffb154b9fef21c54bf2c6496feb1fb5c1fc28a2ac28e201dde84f27495fa7f08b3ca2be2fb4954bf0fe78af027d6cdc16c3eee923041048196aa4b410ac44a3b9cce18e7be226aea070acc83a9cf67540fac49af25129f6a538a28ad6341358e3c4f9963064f7e365372a651d374e5c23cdd37fd099bf2050a736563703235366b31000804000f4240070100090205dc604104d2b9fb288540d54e5b32ecaf0381cd571f97f6f1ecd036b66bb11aa52ffe9981110d883080e2e255c6b1640586f7765e6faa325d1340f49b56b83d9de56bc7ed6204000f42406304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -78,7 +81,7 @@ internal class WalletManagerFactoryTest { fun createBinanceWalletManager() { val data = "0108BB00000000000015200754414E47454D00020102800A322E3432642053444B0003410446D4155890B08BE217F0B1FA7DCCB16138C24B3E825A27315D5E4BBD6CAF76A28C7902007052BC1347355A78D54BD73216C9431D555CED827B54FD9255EB3A830A04041E76310C658102FFFF8A0101820407E40410830B54414E47454D2053444B00840742494E414E4345864029F115878EDC7B0CB2A6F4A4009447DCB43BBE922D7629AEBD0C9A910AD1E3BF15AE409C4F579700951ED2FE4D775171A86CFA8E50009A05938CE210D6D4A2583041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A0070100604104E3F3BE3CE3D8284DB3BA073AD0291040093D83C11A277B905D5555C9EC41073E103F4D9D299EDEA8285C51C3356A8681A545618C174251B984DF841F49D2376F62040001869F6304000000010F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -89,7 +92,7 @@ internal class WalletManagerFactoryTest { fun createBitcoinCashWalletManager() { val data = "0108BB00000000000049200754414E47454D00020102800A322E3432642053444B00034104766A1586D164B436E5D420AED01FDAB41B2AE7EDF0C865D7AF1DA995D70AB297E5B94B761CFBB405084C21BC97C02B4A1EA9ED4F515576EAB4D83AD3A0DFAA8A0A04041E76310C618102FFFF8A0101820407E4041B830B54414E47454D2053444B00840342434886408058F0F628C2466B09ECEB13F2A8EFDD4558F5D2DBDA9BD0628EE8C8CC99A778FF0F1AECD35704B9F3518486EA5C1D20F9DFCBAA66184F4CCCD9282E2632882C3041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A0070100604104BE37CD5251C8999EDBBFC759D800EB41E4DCB718289601EB15819404E1B2F2ED90FE50C2A481D06EC790D1EF6184974EB655ABAE4BE56A6D1C9E1A17B1EFDF0262040001869A6304000000060F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -100,7 +103,7 @@ internal class WalletManagerFactoryTest { fun createLitecoinWalletManager() { val data = "0108BB00000000000023200754414E47454D00020102800A322E3432642053444B000341043539F86A40ADD04CE165764A761FD3E4D251028615D2A573B1C3AE652E60AFDBFAF02E3239E89EF2C43FA448A327557ADC5AF36376A0574570F6DBD20113514A0A04041E76310C618102FFFF8A0101820407E40414830B54414E47454D2053444B0084034C5443864004BDEAD0117544886346CB47F7CA84ABA8C34239502F23D28595A4B16CAD72F7DE506BA818B86A649C2BB945986D4574993B3B755B47CBEE31C4FB931F6748183041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A00701006041044A76C9A70422160F515F956D0F50C71BBBA4F9862A22913817D63F0B1EF7C2FAF512E1C91B1BE827560EFE24FB1652B47337E296C778DFB1014D080CDD35EF6562040001869D6304000000030F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -111,7 +114,7 @@ internal class WalletManagerFactoryTest { fun createTezosWalletManager() { val data = "0108BB00000000000080200754414E47454D00020102800A322E3432642053444B0003410436CFC5D0A11353AE6AFEEDC84A2D02B2635C044DEEE47F99913072B8D166D14E557230AC5FB5272F1A0E523332CCE1A744B51DB53102FF7D3FDE023DC3477C460A04041E76310C638102FFFF8A0101820407E40514830B54414E47454D2053444B00840554455A4F538640C752685B29333CFB0DB0A7347579A0AE763F2B5C4BB09FD68E0B81A06CD01EC51347001732815A3ECFFCD78DDE4E53877581B9E4914B069570629D0C40A771B93041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050865643235353139000804000186A0070100602098E0E504F3A5FDE704400302ABB0A2EFB0DF0F95C166C91D7F207DEDCE10CBA362040001869F6304000000010F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) @@ -122,7 +125,7 @@ internal class WalletManagerFactoryTest { fun createDucatusWalletManager() { val data = "0108BB00000000000098200754414E47454D00020102800A322E3432642053444B000341041B5FD7C590938E836B388B996AE451FDED54F625FF2CF05E26E5AADF6F690AEF125E3D0F23CB6B8D1F78040DF6F71B40D098F2D8BE504DDEE2E1F99BEADD90500A04041E76310C618102FFFF8A0101820407E40515830B54414E47454D2053444B00840344554386401B453C10A092A3448FA83CAFD4FC3D7EB5EA1BBBDD6A020CAAC8CED36BB2661F41EF0B0C7418214F670DFE1200DCE18597158119BEF6CC52A4FF3B7E021A53B93041045F16BD1D2EAFE463E62A335A09E6B2BBCBD04452526885CB679FC4D27AF1BD22F553C7DEEFB54FD3D4F361D14E6DC3F11B7D4EA183250A60720EBDF9E110CD26050A736563703235366B31000804000186A007010060410485D520C8B907F0BC5E03FCBBAC212CCD270764BBFF4990A28653A2FB0D656C342DF143C4D52C43582289E20A81D5D014C1384A1FFFEA1D121903AD7ED35A01EA62040001869D6304000000030F01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) + val card = ReadCommand().deserialize(sessionEnvironment, responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card) Truth.assertThat(walletManager) From 336ec4a1edb9bb6ec5d59fe8b9c0cdfc1daf2db2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 Jul 2020 16:21:33 +0300 Subject: [PATCH 21/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardSession.kt | 16 +++++----- .../java/com/tangem/SessionEnvironment.kt | 29 +++++++++++++------ .../java/com/tangem/SessionViewDelegate.kt | 2 +- .../src/main/java/com/tangem/TangemSdk.kt | 22 +++++--------- .../main/java/com/tangem/commands/Command.kt | 3 +- ...dValuesService.kt => CardValuesStorage.kt} | 4 +-- .../ChangePinTask.kt} | 25 +++++++++++++--- .../java/com/tangem/tasks/CreateWalletTask.kt | 1 + .../main/java/com/tangem/tasks/ScanTask.kt | 3 +- .../DefaultSessionViewDelegate.kt | 2 +- 10 files changed, 65 insertions(+), 42 deletions(-) rename tangem-core/src/main/java/com/tangem/common/{CardValuesService.kt => CardValuesStorage.kt} (93%) rename tangem-core/src/main/java/com/tangem/{commands/ChangePinCommand.kt => tasks/ChangePinTask.kt} (77%) diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index 8c67cffd39..a55d4757d2 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,6 +1,8 @@ package com.tangem -import com.tangem.commands.* +import com.tangem.commands.CommandResponse +import com.tangem.commands.OpenSessionCommand +import com.tangem.commands.ReadCommand import com.tangem.common.CompletionResult import com.tangem.common.PinCode import com.tangem.common.apdu.CommandApdu @@ -9,6 +11,7 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.getType import com.tangem.crypto.EncryptionHelper import com.tangem.crypto.pbkdf2Hash +import com.tangem.tasks.PinType import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -19,6 +22,8 @@ interface CardSessionRunnable { val performPreflightRead: Boolean + val requiresPin2: Boolean + /** * The starting point for custom business logic. * Implement this interface and use [TangemSdk.startSessionWithRunnable] to run. @@ -28,10 +33,6 @@ interface CardSessionRunnable { fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) } -interface CardSessionStoppedListener { - fun onSessionStopped(environment: SessionEnvironment) -} - enum class CardSessionState { Inactive, Active @@ -64,7 +65,6 @@ class CardSession( ) { var connectedTag: TagType? = null - var sessionStoppedListener: CardSessionStoppedListener? = null /** * True if some operation is still in progress. @@ -96,7 +96,7 @@ class CardSession( return } - if ((runnable as? Command)?.requiresPin2 == true && environment.pin2 == null) { + if (runnable.requiresPin2 && environment.pin2 == null) { viewDelegate.onSessionStarted(cardId) viewDelegate.onPinRequested(PinType.Pin2) { environment.pin2 = PinCode(it) @@ -231,7 +231,7 @@ class CardSession( } private fun stopSession() { - sessionStoppedListener?.onSessionStopped(environment) + environment.saveCardValues() reader.stopSession() state = CardSessionState.Inactive scope.cancel() diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 322d25f057..295995337e 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -2,7 +2,7 @@ package com.tangem import com.tangem.commands.Card import com.tangem.commands.EllipticCurve -import com.tangem.common.CardValuesService +import com.tangem.common.CardValuesStorage import com.tangem.common.PinCode import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils.generatePublicKey @@ -12,14 +12,25 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey * Contains data relating to a Tangem card. It is used in constructing all the commands, * and commands can return modified [SessionEnvironment]. * - * @property card Current card, read by preflight [com.tangem.commands.ReadCommand]. - * @property terminalKeys generated terminal keys used in Linked Terminal feature. + * @param cardId Card ID, if it is known before tapping the card. + * @property config sets a number of parameters for communication with Tangem cards. + * @param terminalKeysService is used to retrieve terminal keys used in Linked Terminal feature. + * @param cardValuesStorage is used to save and retrieve some values relating to a particular card. + * + * @property pin1 An access Code, required to get access to a card. A default value is set in [Config] + * @property pin2 A code, required to perform particular operations with a card. A default value is set in [Config] + * @property terminalKeys generated terminal keys used in Linked Terminal feature + * @property cardFilter a property that defines types of card that this SDK will be able to interact with + * @property handleErrors if true, the SDK parses internal card errors into concrete [TangemSdkError] + * @property encryptionMode preferred [EncryptionMode] for interaction with cards + * @property encryptionKey is used for encrypted communication with a card + * */ class SessionEnvironment( cardId: String?, private val config: Config, - private val terminalKeysService: TerminalKeysService?, - private val cardValuesService: CardValuesService? + terminalKeysService: TerminalKeysService?, + private val cardValuesStorage: CardValuesStorage? ) { var pin1: PinCode? @@ -46,7 +57,7 @@ class SessionEnvironment( encryptionMode = config.encryptionMode - val cardValues = cardId?.let { cardValuesService?.getValues(cardId) } + val cardValues = cardId?.let { cardValuesStorage?.getValues(cardId) } cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified @@ -68,7 +79,7 @@ class SessionEnvironment( } fun restoreCardValues() { - val cardValues = this.card?.cardId?.let { cardValuesService?.getValues(it) } + val cardValues = this.card?.cardId?.let { cardValuesStorage?.getValues(it) } cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified @@ -85,7 +96,7 @@ class SessionEnvironment( card?.cardId?.let { cardId -> TangemSdk.pin2[cardId] = pin2 } } - cardValuesService?.saveValues(this) + cardValuesStorage?.saveValues(this) } } @@ -106,5 +117,5 @@ class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) { } enum class VerificationState { - Passed, Offline, Failed, NotVerified + Passed, Offline, Failed, NotVerified, Cancelled } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 3405c7c676..a62716454b 100644 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,6 +1,6 @@ package com.tangem -import com.tangem.commands.PinType +import com.tangem.tasks.PinType /** * Allows interaction with users and shows visual elements. diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index 266301b737..834933b1c2 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -11,12 +11,14 @@ import com.tangem.commands.personalization.entities.Issuer import com.tangem.commands.personalization.entities.Manufacturer import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse -import com.tangem.common.CardValuesDbService +import com.tangem.common.CardValuesDbStorage import com.tangem.common.CompletionResult import com.tangem.common.PinCode import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils +import com.tangem.tasks.ChangePinTask import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.PinType import com.tangem.tasks.ScanTask /** @@ -369,7 +371,7 @@ class TangemSdk( pin: ByteArray? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - val command = ChangePinCommand(PinType.Pin1, pin) + val command = ChangePinTask(PinType.Pin1, pin) startSessionWithRunnable(command, cardId, initialMessage, callback) } @@ -377,7 +379,7 @@ class TangemSdk( pin: ByteArray? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - val command = ChangePinCommand(PinType.Pin2, pin) + val command = ChangePinTask(PinType.Pin2, pin) startSessionWithRunnable(command, cardId, initialMessage, callback) } @@ -385,7 +387,7 @@ class TangemSdk( pin: ByteArray? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - val command = ChangePinCommand(PinType.Pin3, pin) + val command = ChangePinTask(PinType.Pin3, pin) startSessionWithRunnable(command, cardId, initialMessage, callback) } @@ -407,18 +409,9 @@ class TangemSdk( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage) - addSessionStoppedListener(cardSession) Thread().run { cardSession.startWithRunnable(runnable, callback) } } - private fun addSessionStoppedListener(session: CardSession) { - session.sessionStoppedListener = object : CardSessionStoppedListener { - override fun onSessionStopped(environment: SessionEnvironment) { - environment.saveCardValues() - } - } - } - /** * Allows running a custom bunch of commands in one [CardSession] with lightweight closure syntax. * Tangem SDK will start a card session and perform preflight [ReadCommand]. @@ -434,7 +427,6 @@ class TangemSdk( fun startSession(cardId: String? = null, initialMessage: Message? = null, callback: (session: CardSession, error: TangemSdkError?) -> Unit) { val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage) - addSessionStoppedListener(cardSession) Thread().run { cardSession.start(callback = callback) } } @@ -448,7 +440,7 @@ class TangemSdk( private fun buildEnvironment(cardId: String?): SessionEnvironment { return SessionEnvironment( - cardId, config, terminalKeysService, CardValuesDbService(sqlDriver) + cardId, config, terminalKeysService, CardValuesDbStorage(sqlDriver) ) } diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index 27abf365a7..c5b5925cc8 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -6,6 +6,7 @@ import com.tangem.common.PinCode import com.tangem.common.apdu.* import com.tangem.common.extensions.toInt import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.PinType interface ApduSerializable { @@ -39,7 +40,7 @@ interface CommandResponse abstract class Command : ApduSerializable, CardSessionRunnable { override val performPreflightRead: Boolean = true - open val requiresPin2: Boolean = false + override val requiresPin2: Boolean = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { Log.i("Command", "Initializing ${this::class.java.simpleName}") diff --git a/tangem-core/src/main/java/com/tangem/common/CardValuesService.kt b/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt similarity index 93% rename from tangem-core/src/main/java/com/tangem/common/CardValuesService.kt rename to tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt index 80e26859e4..7976bab5aa 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardValuesService.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt @@ -7,7 +7,7 @@ import com.tangem.CardValuesEntityQueries import com.tangem.Database import com.tangem.SessionEnvironment -interface CardValuesService { +interface CardValuesStorage { fun saveValues(environment: SessionEnvironment) @@ -15,7 +15,7 @@ interface CardValuesService { } -class CardValuesDbService(driver: SqlDriver) : CardValuesService { +class CardValuesDbStorage(driver: SqlDriver) : CardValuesStorage { private val cardValuesQueries: CardValuesEntityQueries diff --git a/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt b/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt similarity index 77% rename from tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt rename to tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt index 641fb6e3d2..4a8519d548 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ChangePinCommand.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt @@ -1,8 +1,11 @@ -package com.tangem.commands +package com.tangem.tasks import com.tangem.CardSession import com.tangem.CardSessionRunnable +import com.tangem.SessionEnvironment import com.tangem.SessionViewDelegate +import com.tangem.commands.SetPinCommand +import com.tangem.commands.SetPinResponse import com.tangem.common.CompletionResult import com.tangem.common.PinCode import com.tangem.common.extensions.calculateSha256 @@ -17,15 +20,16 @@ enum class PinType { ; } -class ChangePinCommand( +class ChangePinTask( private val pinType: PinType, private val pin: ByteArray? = null ) : CardSessionRunnable { override val performPreflightRead = true + override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { session.scope.launch { - val pin = this@ChangePinCommand.pin + val pin = this@ChangePinTask.pin ?: requestNewPin(pinType, session.viewDelegate).calculateSha256() runSetPin(pin, session, callback) } @@ -61,7 +65,12 @@ class ChangePinCommand( } } val command = SetPinCommand(pin1, pin2, pin3) - command.run(session, callback) + command.run(session) { result -> + when (result) { + is CompletionResult.Success -> savePin(pin, session.environment) + is CompletionResult.Failure -> callback(result) + } + } } private suspend fun requestPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = @@ -78,5 +87,13 @@ class ChangePinCommand( } } + private fun savePin(pin: ByteArray, environment: SessionEnvironment) { + when (pinType) { + PinType.Pin1 -> environment.pin1 = PinCode(pin, false) + PinType.Pin2 -> environment.pin2 = PinCode(pin, false) + PinType.Pin3 -> {} + } + } + } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt index 317134af60..126ca73514 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt @@ -12,6 +12,7 @@ import com.tangem.common.CompletionResult class CreateWalletTask : CardSessionRunnable { override val performPreflightRead = true + override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { val curve = session.environment.card?.curve diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index 512171113f..4a3cfcadb5 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -16,6 +16,7 @@ import com.tangem.common.CompletionResult internal class ScanTask : CardSessionRunnable { override val performPreflightRead = false + override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { @@ -31,6 +32,7 @@ internal class ScanTask : CardSessionRunnable { is CompletionResult.Success -> { val card = readResult.data session.environment.card = card + session.environment.restoreCardValues() if (session.environment.pin1 != null && session.environment.pin2 != null) { val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value) checkPinCommand.run(session) { result -> @@ -39,7 +41,6 @@ internal class ScanTask : CardSessionRunnable { session.environment.pin2 = null } } - session.environment.restoreCardValues() runCheckWalletIfNeeded(card, session, callback) } } else { diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index b2bbd8afe0..662f286147 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -2,9 +2,9 @@ package com.tangem.tangem_sdk_new import androidx.fragment.app.FragmentActivity import com.tangem.* -import com.tangem.commands.PinType import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog +import com.tangem.tasks.PinType /** * Default implementation of [SessionViewDelegate]. From dedbcbf211a5a8bbed3b103107ac5b6c779f65fc Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 3 Jul 2020 17:18:27 +0300 Subject: [PATCH 22/43] Updated on 2026-08-14 --- .../src/main/java/com/tangem/blockchain/common/WalletManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt index 3271af9575..37cde3b700 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt @@ -32,7 +32,7 @@ abstract class WalletManager(val cardId: String, var wallet: Wallet) { } private fun validateAmount(amount: Amount): Boolean { - return !amount.isAboveZero() && + return amount.isAboveZero() && wallet.fundsAvailable(amount.type) >= amount.value } } From 53599d8a3e0ca04f7b3e7a2f8d9559689f3d18fa Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 8 Jul 2020 13:13:13 +0300 Subject: [PATCH 23/43] Updated on 2026-08-14 --- .../common/WalletManagerFactoryTest.kt | 3 +- .../src/main/java/com/tangem/CardSession.kt | 55 +++++++----- .../src/main/java/com/tangem/Config.kt | 12 ++- .../java/com/tangem/SessionEnvironment.kt | 84 +++---------------- .../com/tangem/SessionEnvironmentService.kt | 77 +++++++++++++++++ .../src/main/java/com/tangem/TangemSdk.kt | 33 +++----- .../main/java/com/tangem/commands/Command.kt | 4 +- .../personalization/DepersonalizeCommand.kt | 12 --- .../com/tangem/common/CardValuesStorage.kt | 38 +++++---- .../java/com/tangem/tasks/ChangePinTask.kt | 6 +- .../java/com/tangem/tasks/CreateWalletTask.kt | 1 - .../main/java/com/tangem/tasks/ScanTask.kt | 45 ++++------ .../com/tangem/common/apdu/CommandApduTest.kt | 5 +- .../tangem_sdk_new/extensions/TangemSdk.kt | 17 ++-- 14 files changed, 201 insertions(+), 191 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt diff --git a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt index 5edc1ef722..af0e844c61 100644 --- a/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt +++ b/blockchain/src/test/java/com/tangem/blockchain/common/WalletManagerFactoryTest.kt @@ -1,7 +1,6 @@ package com.tangem.blockchain.common import com.google.common.truth.Truth -import com.tangem.Config import com.tangem.SessionEnvironment import com.tangem.blockchain.blockchains.binance.BinanceWalletManager import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager @@ -20,7 +19,7 @@ import org.junit.Test internal class WalletManagerFactoryTest { - private val sessionEnvironment = SessionEnvironment(null, Config(), null, null) + private val sessionEnvironment = SessionEnvironment() @Test fun createBitcoinWalletManager() { diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt index a55d4757d2..7b0ec51200 100644 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -1,5 +1,6 @@ package com.tangem +import com.tangem.commands.Command import com.tangem.commands.CommandResponse import com.tangem.commands.OpenSessionCommand import com.tangem.commands.ReadCommand @@ -20,8 +21,6 @@ import kotlinx.coroutines.flow.* */ interface CardSessionRunnable { - val performPreflightRead: Boolean - val requiresPin2: Boolean /** @@ -57,7 +56,7 @@ enum class TagType { * If null, a default header and text body will be used. */ class CardSession( - val environment: SessionEnvironment, + private val environmentService: SessionEnvironmentService, private val reader: CardReader, val viewDelegate: SessionViewDelegate, private var cardId: String? = null, @@ -77,6 +76,11 @@ class CardSession( private val tag = this.javaClass.simpleName + private var performPreflightRead = true + private var pin2Required = false + + val environment = environmentService.createEnvironment(cardId) + /** * This method starts a card session, performs preflight [ReadCommand], * invokes [CardSessionRunnable.run] and closes the session. @@ -87,25 +91,10 @@ class CardSession( runnable: T, callback: (result: CompletionResult) -> Unit ) { - if (environment.pin1 == null) { - viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested(PinType.Pin1) { - environment.pin1 = PinCode(it) - startWithRunnable(runnable, callback) - } - return - } + if ((runnable as? Command<*>)?.performPreflightRead == false) performPreflightRead = false + pin2Required = runnable.requiresPin2 - if (runnable.requiresPin2 && environment.pin2 == null) { - viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested(PinType.Pin2) { - environment.pin2 = PinCode(it) - startWithRunnable(runnable, callback) - } - return - } - - start(runnable.performPreflightRead) { session, error -> + start() { session, error -> if (error != null) { callback(CompletionResult.Failure(error)) return@start @@ -135,7 +124,6 @@ class CardSession( * @param callback: callback with the card session. Can contain [TangemSdkError] if something goes wrong. */ fun start( - performPreflightRead: Boolean = true, callback: (session: CardSession, error: TangemSdkError?) -> Unit ) { @@ -143,6 +131,26 @@ class CardSession( callback(this, TangemSdkError.Busy()) return } + + + if (environment.pin1 == null) { + viewDelegate.onSessionStarted(cardId) + viewDelegate.onPinRequested(PinType.Pin1) { + environment.pin1 = PinCode(it) + start(callback) + } + return + } + + if (pin2Required && environment.pin2 == null) { + viewDelegate.onSessionStarted(cardId) + viewDelegate.onPinRequested(PinType.Pin2) { + environment.pin2 = PinCode(it) + start(callback) + } + return + } + state = CardSessionState.Active viewDelegate.onSessionStarted(cardId) @@ -196,6 +204,7 @@ class CardSession( return@run } environment.card = result.data + environmentService.updateEnvironment(environment, result.data.cardId) cardId = receivedCardId callback(this, null) } @@ -231,7 +240,7 @@ class CardSession( } private fun stopSession() { - environment.saveCardValues() + environmentService.saveEnvironmentValues(environment, cardId) reader.stopSession() state = CardSessionState.Inactive scope.cancel() diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt index 3cc58b5e6d..7b643c5e1f 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -38,12 +38,16 @@ class Config( var handleErrors: Boolean = true, - var defaultPin1: String = "000000", + var defaultPin1: String = DEFAULT_PIN_1, - var defaultPin2: String = "000", + var defaultPin2: String = DEFAULT_PIN_2, var savePin1InStaticField: Boolean = true, var savePin2InStaticField: Boolean = false - -) \ No newline at end of file +) { + companion object { + const val DEFAULT_PIN_1 = "000000" + const val DEFAULT_PIN_2 = "000" + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 295995337e..e8afbb33e5 100644 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -2,9 +2,7 @@ package com.tangem import com.tangem.commands.Card import com.tangem.commands.EllipticCurve -import com.tangem.common.CardValuesStorage import com.tangem.common.PinCode -import com.tangem.common.TerminalKeysService import com.tangem.crypto.CryptoUtils.generatePublicKey @@ -27,79 +25,23 @@ import com.tangem.crypto.CryptoUtils.generatePublicKey * */ class SessionEnvironment( - cardId: String?, - private val config: Config, - terminalKeysService: TerminalKeysService?, - private val cardValuesStorage: CardValuesStorage? -) { + var pin1: PinCode? = PinCode(Config.DEFAULT_PIN_1, true), + var pin2: PinCode? = PinCode(Config.DEFAULT_PIN_2, true), + var cvc: ByteArray? = null, - var pin1: PinCode? - var pin2: PinCode? - var cvc: ByteArray? = null + var terminalKeys: KeyPair? = null, + var cardFilter: CardFilter = CardFilter(), + val handleErrors: Boolean = true, - var terminalKeys: KeyPair? = null - var cardFilter: CardFilter - val handleErrors: Boolean + var encryptionMode: EncryptionMode = EncryptionMode.NONE, + var encryptionKey: ByteArray? = null, - var encryptionMode: EncryptionMode - var encryptionKey: ByteArray? = null + var cardVerification: VerificationState = VerificationState.NotVerified, + var cardValidation: VerificationState = VerificationState.NotVerified, + var codeVerification: VerificationState = VerificationState.NotVerified, - var cardVerification: VerificationState - var cardValidation: VerificationState - var codeVerification: VerificationState - - var card: Card? = null - - init { - terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null - cardFilter = config.cardFilter - handleErrors = config.handleErrors - - encryptionMode = config.encryptionMode - - val cardValues = cardId?.let { cardValuesStorage?.getValues(cardId) } - - cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified - cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified - codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified - - pin1 = TangemSdk.pin1 - ?: if (cardValues?.isPin1Default != false) { - PinCode(config.defaultPin1, true) - } else { - null - } - - pin2 = cardId?.let { TangemSdk.pin2[it] } - ?: if (cardValues?.isPin2Default != false) { - PinCode(config.defaultPin2, true) - } else { - null - } - } - - fun restoreCardValues() { - val cardValues = this.card?.cardId?.let { cardValuesStorage?.getValues(it) } - cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified - cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified - codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified - - if (cardValues?.isPin1Default == false && pin1?.isDefault == true) pin1 = null - if (cardValues?.isPin2Default == false && pin1?.isDefault == true) pin2 = null - } - - fun saveCardValues() { - if (config.savePin1InStaticField) { - TangemSdk.pin1 = pin1 - } - if (config.savePin2InStaticField) { - card?.cardId?.let { cardId -> TangemSdk.pin2[cardId] = pin2 } - } - - cardValuesStorage?.saveValues(this) - } - -} + var card: Card? = null +) /** * All possible encryption modes. diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt new file mode 100644 index 0000000000..d36c95c8ad --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt @@ -0,0 +1,77 @@ +package com.tangem + +import com.tangem.common.CardValuesStorage +import com.tangem.common.PinCode +import com.tangem.common.TerminalKeysService + +class SessionEnvironmentService( + private val config: Config, + private val terminalKeysService: TerminalKeysService?, + private val cardValuesStorage: CardValuesStorage? +) { + + fun createEnvironment(cardId: String?): SessionEnvironment { + val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + + val cardValues = cardId?.let { cardValuesStorage?.getValues(cardId) } + + val cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified + val cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified + val codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified + + val pin1 = TangemSdk.pin1 + ?: if (cardValues?.isPin1Default != false) { + PinCode(config.defaultPin1, true) + } else { + null + } + + val pin2 = cardId?.let { TangemSdk.pin2[it] } + ?: if (cardValues?.isPin2Default != false) { + PinCode(config.defaultPin2, true) + } else { + null + } + + return SessionEnvironment( + terminalKeys = terminalKeys, + cardFilter = config.cardFilter, + handleErrors = config.handleErrors, + + encryptionMode = config.encryptionMode, + + cardVerification = cardVerification, + cardValidation = cardValidation, + codeVerification = codeVerification, + + pin1 = pin1, + pin2 = pin2 + ) + } + + fun updateEnvironment(environment: SessionEnvironment, cardId: String) { + val cardValues = cardId.let { cardValuesStorage?.getValues(it) } + environment.cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified + environment.cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified + environment.codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified + + if (cardValues?.isPin1Default == false && environment.pin1?.isDefault == true) environment.pin1 = null + if (cardValues?.isPin2Default == false && environment.pin1?.isDefault == true) environment.pin2 = null + } + + fun saveEnvironmentValues(environment: SessionEnvironment, cardId: String?) { + if (config.savePin1InStaticField) { + TangemSdk.pin1 = environment.pin1 + } + if (config.savePin2InStaticField) { + cardId?.let { cardId -> TangemSdk.pin2[cardId] = environment.pin2 } + } + cardId?.let { cardId -> + cardValuesStorage?.saveValues(cardId, + environment.pin1?.isDefault ?: false, + environment.pin2?.isDefault ?: false, + environment.cardVerification, environment.cardVerification, + environment.codeVerification) + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt index 834933b1c2..40b0a79848 100644 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -1,6 +1,5 @@ package com.tangem -import com.squareup.sqldelight.db.SqlDriver import com.tangem.commands.* import com.tangem.commands.personalization.DepersonalizeCommand import com.tangem.commands.personalization.DepersonalizeResponse @@ -11,7 +10,7 @@ import com.tangem.commands.personalization.entities.Issuer import com.tangem.commands.personalization.entities.Manufacturer import com.tangem.commands.verifycard.VerifyCardCommand import com.tangem.commands.verifycard.VerifyCardResponse -import com.tangem.common.CardValuesDbStorage +import com.tangem.common.CardValuesStorage import com.tangem.common.CompletionResult import com.tangem.common.PinCode import com.tangem.common.TerminalKeysService @@ -31,15 +30,19 @@ import com.tangem.tasks.ScanTask * 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. + * @property terminalKeysService allows to retrieve saved terminal keys. */ class TangemSdk( private val reader: CardReader, private val viewDelegate: SessionViewDelegate, var config: Config = Config(), - private val sqlDriver: SqlDriver + cardValuesStorage: CardValuesStorage, + terminalKeysService: TerminalKeysService? = null ) { - private var terminalKeysService: TerminalKeysService? = null + private val environmentService = SessionEnvironmentService( + config, terminalKeysService, cardValuesStorage + ) init { CryptoUtils.initCrypto() @@ -369,8 +372,8 @@ class TangemSdk( fun changePin1(cardId: String? = null, pin: ByteArray? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { val command = ChangePinTask(PinType.Pin1, pin) startSessionWithRunnable(command, cardId, initialMessage, callback) } @@ -408,7 +411,7 @@ class TangemSdk( fun startSessionWithRunnable( runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage) + val cardSession = CardSession(environmentService, reader, viewDelegate, cardId, initialMessage) Thread().run { cardSession.startWithRunnable(runnable, callback) } } @@ -426,24 +429,10 @@ class TangemSdk( */ fun startSession(cardId: String? = null, initialMessage: Message? = null, callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - val cardSession = CardSession(buildEnvironment(cardId), reader, viewDelegate, cardId, initialMessage) + val cardSession = CardSession(environmentService, reader, viewDelegate, cardId, initialMessage) Thread().run { cardSession.start(callback = 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(cardId: String?): SessionEnvironment { - return SessionEnvironment( - cardId, config, terminalKeysService, CardValuesDbStorage(sqlDriver) - ) - } - companion object { var pin1: PinCode? = null val pin2: MutableMap = mutableMapOf() diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt index c5b5925cc8..870cf3b446 100644 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -39,7 +39,7 @@ interface CommandResponse */ abstract class Command : ApduSerializable, CardSessionRunnable { - override val performPreflightRead: Boolean = true + open val performPreflightRead: Boolean = true override val requiresPin2: Boolean = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { @@ -74,10 +74,12 @@ abstract class Command : ApduSerializable, CardSessionRu if (session.environment.handleErrors) { val error = mapError(session.environment.card, result.error) if (error is TangemSdkError.Pin1Required) { + session.environment.pin1 = null handlePin1(session, callback) return@transceiveApdu } if (error is TangemSdkError.Pin2OrCvcRequired) { + session.environment.pin2 = null handlePin2(session, callback) return@transceiveApdu } diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt index 00dccae6a1..912bb15e5f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt @@ -19,18 +19,6 @@ class DepersonalizeCommand : Command() { override val performPreflightRead = false -// override fun performPreCheck(session: CardSession, callback: (result: CompletionResult) -> Unit): Boolean { -// if (session.environment.card?.status == CardStatus.NotPersonalized) { -// callback(CompletionResult.Failure(TangemSdkError.NotPersonalized())) -// return true -// } -// if (session.environment.card?.firmwareVersion?.contains("SDK") == false) { -// callback(CompletionResult.Failure(TangemSdkError.CannotBeDepersonalized())) -// return true -// } -// return false -// } - override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu( Instruction.Depersonalize, byteArrayOf() diff --git a/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt b/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt index 7976bab5aa..26262ccddb 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt @@ -2,16 +2,21 @@ package com.tangem.common import com.squareup.sqldelight.EnumColumnAdapter import com.squareup.sqldelight.db.SqlDriver +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver import com.tangem.CardValues import com.tangem.CardValuesEntityQueries import com.tangem.Database -import com.tangem.SessionEnvironment +import com.tangem.VerificationState interface CardValuesStorage { - fun saveValues(environment: SessionEnvironment) + fun saveValues(cardId: String, + isPin1Default: Boolean, isPin2Default: Boolean, + cardVerification: VerificationState?, + cardValidation: VerificationState?, + codeVerification: VerificationState?) - fun getValues(cardId: String) : CardValues? + fun getValues(cardId: String): CardValues? } @@ -28,19 +33,24 @@ class CardValuesDbStorage(driver: SqlDriver) : CardValuesStorage { cardValuesQueries = database.cardValuesEntityQueries } - override fun saveValues(environment: SessionEnvironment) { - environment.card?.cardId?.let {cardId -> - cardValuesQueries.insertOrReplace( - cardId, - environment.pin1?.isDefault ?: false, - environment.pin2?.isDefault ?: false, - environment.cardVerification, environment.cardVerification, - environment.codeVerification - ) - } + override fun saveValues(cardId: String, + isPin1Default: Boolean, isPin2Default: Boolean, + cardVerification: VerificationState?, + cardValidation: VerificationState?, + codeVerification: VerificationState?) { + cardValuesQueries.insertOrReplace( + cardId, + isPin1Default, isPin2Default, + cardVerification, cardValidation, codeVerification + ) } - override fun getValues(cardId: String): CardValues? = + override fun getValues(cardId: String): CardValues? = cardValuesQueries.selectByCardId(cardId).executeAsOneOrNull() + companion object { + fun initJvm() = CardValuesDbStorage( + JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).also { Database.Schema.create(it) } + ) + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt index 4a8519d548..fed66d31b3 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt @@ -24,7 +24,6 @@ class ChangePinTask( private val pinType: PinType, private val pin: ByteArray? = null ) : CardSessionRunnable { - override val performPreflightRead = true override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { @@ -67,7 +66,10 @@ class ChangePinTask( val command = SetPinCommand(pin1, pin2, pin3) command.run(session) { result -> when (result) { - is CompletionResult.Success -> savePin(pin, session.environment) + is CompletionResult.Success -> { + savePin(pin, session.environment) + callback(result) + } is CompletionResult.Failure -> callback(result) } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt index 126ca73514..a4bb23d8b5 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt @@ -11,7 +11,6 @@ import com.tangem.common.CompletionResult class CreateWalletTask : CardSessionRunnable { - override val performPreflightRead = true override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index 4a3cfcadb5..d9702f5a6d 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -15,41 +15,32 @@ import com.tangem.common.CompletionResult */ internal class ScanTask : CardSessionRunnable { - override val performPreflightRead = false override val requiresPin2 = false override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - - if (session.connectedTag == TagType.Slix) { readSlixTag(session, callback) return } - - ReadCommand().run(session) { readResult -> - when(readResult) { - is CompletionResult.Failure -> callback(readResult) - is CompletionResult.Success -> { - val card = readResult.data - session.environment.card = card - session.environment.restoreCardValues() - if (session.environment.pin1 != null && session.environment.pin2 != null) { - val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value) - checkPinCommand.run(session) { result -> - when (result) { - is CompletionResult.Failure -> { - session.environment.pin2 = null - } - } - runCheckWalletIfNeeded(card, session, callback) - } - } else { - runCheckWalletIfNeeded(card, session, callback) - } - - } - } + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return } + if (session.environment.pin1 != null && session.environment.pin2 != null) { + val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value) + checkPinCommand.run(session) { result -> + when (result) { + is CompletionResult.Failure -> { + session.environment.pin2 = null + } + } + runCheckWalletIfNeeded(card, session, callback) + } + } else { + runCheckWalletIfNeeded(card, session, callback) + } + } private fun runCheckWalletIfNeeded( diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt index 05a5b3ca0b..67ac0cf109 100644 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt @@ -1,7 +1,6 @@ package com.tangem.common.apdu import com.google.common.truth.Truth.assertThat -import com.tangem.Config import com.tangem.SessionEnvironment import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvTag @@ -10,7 +9,7 @@ import org.junit.Test class CommandApduTest { - val sessionEnvironment = SessionEnvironment(null, Config(), null, null) + private val sessionEnvironment = SessionEnvironment() @Test fun `simple READ command to bytes`() { @@ -26,7 +25,7 @@ class CommandApduTest { assertThat(commandApdu.apduData) .isEqualTo(expected) - assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1,2)) + assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1, 2)) } @Test diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt index 0022ac2066..50ec4ce46c 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt @@ -6,6 +6,7 @@ import com.tangem.Config import com.tangem.Database import com.tangem.SessionViewDelegate import com.tangem.TangemSdk +import com.tangem.common.CardValuesDbStorage import com.tangem.tangem_sdk_new.DefaultSessionViewDelegate import com.tangem.tangem_sdk_new.NfcLifecycleObserver import com.tangem.tangem_sdk_new.TerminalKeysStorage @@ -18,10 +19,10 @@ fun TangemSdk.Companion.init(activity: FragmentActivity, config: Config = Config viewDelegate.activity = activity val databaseDriver = AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") - val tangemSdk = TangemSdk(nfcManager.reader, viewDelegate, config, databaseDriver) - tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application)) - - return tangemSdk + return TangemSdk( + nfcManager.reader, viewDelegate, config, + CardValuesDbStorage(databaseDriver), TerminalKeysStorage(activity.application) + ) } fun TangemSdk.Companion.customInit( @@ -30,17 +31,15 @@ fun TangemSdk.Companion.customInit( config: Config = Config() ): TangemSdk { val nfcManager = TangemSdk.initNfcManager(activity) + val databaseDriver = AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") - val tangemSdk = TangemSdk( + return TangemSdk( nfcManager.reader, viewDelegate ?: DefaultSessionViewDelegate(nfcManager.reader) .apply { this.activity = activity }, config, - AndroidSqliteDriver(Database.Schema, activity.applicationContext, "cards.db") + CardValuesDbStorage(databaseDriver), TerminalKeysStorage(activity.application) ) - tangemSdk.setTerminalKeysService(TerminalKeysStorage(activity.application)) - - return tangemSdk } fun TangemSdk.Companion.initNfcManager(activity: FragmentActivity): NfcManager { From 85a05f0bf6e16ad831775cfd601ea92cc65807f0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 8 Jul 2020 15:51:10 +0300 Subject: [PATCH 24/43] Updated on 2026-08-14 --- .../tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt | 6 +++--- .../com/tangem/tangem_sdk_new/extensions/TangemSdk.kt | 8 ++++---- .../java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt | 3 +-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index 662f286147..6367586908 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -1,6 +1,6 @@ package com.tangem.tangem_sdk_new -import androidx.fragment.app.FragmentActivity +import android.app.Activity import com.tangem.* import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcSessionDialog @@ -12,7 +12,7 @@ import com.tangem.tasks.PinType */ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDelegate { - lateinit var activity: FragmentActivity + lateinit var activity: Activity private var readingDialog: NfcSessionDialog? = null init { @@ -26,7 +26,7 @@ class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDel } } - private fun createReadingDialog(activity: FragmentActivity) { + private fun createReadingDialog(activity: Activity) { val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null) readingDialog = NfcSessionDialog(activity) readingDialog?.setContentView(dialogView) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt index 50ec4ce46c..7c11dc06c2 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt @@ -1,6 +1,6 @@ package com.tangem.tangem_sdk_new.extensions -import androidx.fragment.app.FragmentActivity +import androidx.activity.ComponentActivity import com.squareup.sqldelight.android.AndroidSqliteDriver import com.tangem.Config import com.tangem.Database @@ -12,7 +12,7 @@ import com.tangem.tangem_sdk_new.NfcLifecycleObserver import com.tangem.tangem_sdk_new.TerminalKeysStorage import com.tangem.tangem_sdk_new.nfc.NfcManager -fun TangemSdk.Companion.init(activity: FragmentActivity, config: Config = Config()): TangemSdk { +fun TangemSdk.Companion.init(activity: ComponentActivity, config: Config = Config()): TangemSdk { val nfcManager = TangemSdk.initNfcManager(activity) val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader) @@ -26,7 +26,7 @@ fun TangemSdk.Companion.init(activity: FragmentActivity, config: Config = Config } fun TangemSdk.Companion.customInit( - activity: FragmentActivity, + activity: ComponentActivity, viewDelegate: SessionViewDelegate? = null, config: Config = Config() ): TangemSdk { @@ -42,7 +42,7 @@ fun TangemSdk.Companion.customInit( ) } -fun TangemSdk.Companion.initNfcManager(activity: FragmentActivity): NfcManager { +fun TangemSdk.Companion.initNfcManager(activity: ComponentActivity): NfcManager { val nfcManager = NfcManager() nfcManager.setCurrentActivity(activity) activity.lifecycle.addObserver(NfcLifecycleObserver(nfcManager)) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt index 5b32b7e0a2..a0ad3666c8 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt @@ -8,7 +8,6 @@ import android.view.View import android.view.animation.DecelerateInterpolator import android.view.inputmethod.InputMethodManager import android.widget.TextView -import androidx.fragment.app.FragmentActivity import com.google.android.material.bottomsheet.BottomSheetDialog import com.tangem.tangem_sdk_new.R import com.tangem.tangem_sdk_new.SessionViewDelegateState @@ -18,7 +17,7 @@ import com.tangem.tangem_sdk_new.postUI import kotlinx.android.synthetic.main.layout_touch_card.* import kotlinx.android.synthetic.main.nfc_bottom_sheet.* -class NfcSessionDialog(val activity: FragmentActivity) : BottomSheetDialog(activity) { +class NfcSessionDialog(val activity: Activity) : BottomSheetDialog(activity) { private var currentState: SessionViewDelegateState? = null From 2f4b3fdbec4bdc48b095a93a52e808cf9930f088 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 9 Jul 2020 20:42:41 +0300 Subject: [PATCH 25/43] Updated on 2026-08-14 --- tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 189588a080..003b3c42b1 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -1,5 +1,6 @@ package com.tangem.commands +import com.google.gson.annotations.SerializedName import com.tangem.SessionEnvironment import com.tangem.TangemSdkError import com.tangem.commands.common.CardDeserializer @@ -64,7 +65,9 @@ class SigningMethodMaskBuilder() { * Elliptic curve used for wallet key operations. */ enum class EllipticCurve(val curve: String) { + @SerializedName(value = "secp256k1") Secp256k1("secp256k1"), + @SerializedName(value = "ed25519") Ed25519("ed25519"); companion object { From 0ace26a77fcb0c87ea1f21fb0ddc1367ae121974 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 10 Jul 2020 10:25:06 +0300 Subject: [PATCH 26/43] Updated on 2026-08-14 --- .../tangem/server_android/data/LocalStorage.kt | 4 +++- .../src/main/res/drawable/card_tg073.png | Bin 0 -> 70328 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 server-android/src/main/res/drawable/card_tg073.png diff --git a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt index ed9371767b..a858e25318 100644 --- a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt +++ b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt @@ -47,7 +47,7 @@ class LocalStorage artworks = HashMap() } - if (artworks.count() < 44) { + if (artworks.count() < 45) { // forceSave=true only on the last one putResourceArtworkToCatalog(R.drawable.card_default, false) putResourceArtworkToCatalog(R.drawable.card_default_nft, false) @@ -90,6 +90,7 @@ class LocalStorage putResourceArtworkToCatalog(R.drawable.card_tg061, false) putResourceArtworkToCatalog(R.drawable.card_tg062, false) putResourceArtworkToCatalog(R.drawable.card_tg063, false) + putResourceArtworkToCatalog(R.drawable.card_tg073, false) putResourceArtworkToCatalog(R.drawable.card_tgslix, false) putResourceArtworkToCatalog(R.drawable.card_bc00, false) putResourceArtworkToCatalog(R.drawable.card_ff32, true) @@ -305,6 +306,7 @@ class LocalStorage card.batch == "0050" -> R.drawable.card_tg061 card.batch == "0051" -> R.drawable.card_tg062 card.batch == "0052" -> R.drawable.card_tg063 + card.batch == "0060" -> R.drawable.card_tg073 card.batch == "FF32" -> R.drawable.card_ff32 else -> null diff --git a/server-android/src/main/res/drawable/card_tg073.png b/server-android/src/main/res/drawable/card_tg073.png new file mode 100644 index 0000000000000000000000000000000000000000..205f99d631ee847e7a2640204647d7207821eb0e GIT binary patch literal 70328 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 Date: Fri, 10 Jul 2020 11:22:49 +0300 Subject: [PATCH 27/43] Updated on 2026-08-14 --- .../personalization/entities/CardConfig.kt | 14 ++++++++++---- .../src/main/res/layout/nfc_bottom_sheet.xml | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt index 4417374a87..7241e6c4f8 100644 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt +++ b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt @@ -3,6 +3,7 @@ package com.tangem.commands.personalization.entities import com.tangem.commands.CardData import com.tangem.commands.EllipticCurve import com.tangem.commands.SigningMethodMask +import com.tangem.common.extensions.calculateSha256 data class NdefRecord( val type: Type, @@ -20,15 +21,15 @@ data class NdefRecord( * It is a configuration file with all the card settings that are written on the card * during [PersonalizeCommand]. */ -data class CardConfig( +class CardConfig( val issuerName: String? = null, val acquirerName: String? = null, val series: String? = null, val startNumber: Long = 0, val count: Int = 0, - val pin: String, - val pin2: String, - val pin3: String, + pin: String, + pin2: String, + pin3: String, val hexCrExKey: String?, val cvc: String, val pauseBeforePin2: Int, @@ -67,5 +68,10 @@ data class CardConfig( val cardData: CardData, val ndefRecords: List ) { + + val pin: ByteArray = pin.calculateSha256() + val pin2: ByteArray = pin2.calculateSha256() + val pin3: ByteArray = pin3.calculateSha256() + companion object } \ No newline at end of file diff --git a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml index 3ded5704da..d8afc2b9b7 100644 --- a/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml +++ b/tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml @@ -79,6 +79,7 @@ android:layout_width="match_parent" android:layout_height="200dp" android:layout_gravity="center" + android:theme="@style/Theme.MaterialComponents" android:visibility="gone"> Date: Mon, 13 Jul 2020 10:32:23 +0300 Subject: [PATCH 28/43] Updated on 2026-08-14 --- blockchain-demo/build.gradle | 5 +- blockchain/build.gradle | 3 +- build.gradle | 1 + settings.gradle | 2 +- tangem-core/.gitignore | 1 - tangem-core/build.gradle | 78 ---- .../src/main/java/com/tangem/CardFilter.kt | 14 - .../src/main/java/com/tangem/CardReader.kt | 53 --- .../src/main/java/com/tangem/CardSession.kt | 318 ------------- .../src/main/java/com/tangem/Config.kt | 53 --- tangem-core/src/main/java/com/tangem/Log.kt | 34 -- .../java/com/tangem/SessionEnvironment.kt | 63 --- .../com/tangem/SessionEnvironmentService.kt | 77 --- .../java/com/tangem/SessionViewDelegate.kt | 63 --- .../src/main/java/com/tangem/TangemSdk.kt | 440 ------------------ .../main/java/com/tangem/TangemSdkError.kt | 163 ------- .../com/tangem/commands/CheckWalletCommand.kt | 101 ---- .../main/java/com/tangem/commands/Command.kt | 237 ---------- .../tangem/commands/CreateWalletCommand.kt | 86 ---- .../com/tangem/commands/OpenSessionCommand.kt | 44 -- .../com/tangem/commands/PurgeWalletCommand.kt | 78 ---- .../java/com/tangem/commands/ReadCommand.kt | 396 ---------------- .../tangem/commands/ReadIssuerDataCommand.kt | 119 ----- .../commands/ReadIssuerExtraDataCommand.kt | 178 ------- .../tangem/commands/ReadUserDataCommand.kt | 82 ---- .../java/com/tangem/commands/SetPinCommand.kt | 82 ---- .../java/com/tangem/commands/SignCommand.kt | 121 ----- .../tangem/commands/WriteIssuerDataCommand.kt | 111 ----- .../commands/WriteIssuerExtraDataCommand.kt | 196 -------- .../tangem/commands/WriteUserDataCommand.kt | 80 ---- .../commands/common/CardDeserializer.kt | 67 --- .../tangem/commands/common/IssuerDataMode.kt | 44 -- .../commands/common/IssuerDataVerifier.kt | 40 -- .../commands/common/ResponseConverter.kt | 119 ----- .../commands/common/network/NetworkUtils.kt | 39 -- .../common/network/RetrofitBuilder.kt | 30 -- .../commands/common/network/TangemApi.kt | 17 - .../commands/common/network/TangemService.kt | 22 - .../personalization/DepersonalizeCommand.kt | 31 -- .../commands/personalization/NdefEncoder.kt | 88 ---- .../personalization/PersonalizeCommand.kt | 125 ----- .../personalization/entities/Acquirer.kt | 9 - .../personalization/entities/CardConfig.kt | 77 --- .../entities/CardConfigExtensions.kt | 81 ---- .../personalization/entities/Issuer.kt | 10 - .../personalization/entities/Manufacturer.kt | 8 - .../commands/verifycard/VerifyCardCommand.kt | 159 ------- .../commands/verifycard/VerifyCardRequest.kt | 60 --- .../com/tangem/common/CardValuesStorage.kt | 56 --- .../com/tangem/common/CompletionResult.kt | 13 - .../main/java/com/tangem/common/PinCode.kt | 10 - .../com/tangem/common/TerminalKeysService.kt | 14 - .../com/tangem/common/apdu/CommandApdu.kt | 89 ---- .../com/tangem/common/apdu/Instruction.kt | 32 -- .../com/tangem/common/apdu/ResponseApdu.kt | 64 --- .../java/com/tangem/common/apdu/StatusWord.kt | 49 -- .../tangem/common/extensions/BigDecimal.kt | 7 - .../com/tangem/common/extensions/ByteArray.kt | 74 --- .../java/com/tangem/common/extensions/Card.kt | 22 - .../tangem/common/extensions/IntExtensions.kt | 16 - .../java/com/tangem/common/extensions/List.kt | 13 - .../com/tangem/common/extensions/String.kt | 26 -- .../main/java/com/tangem/common/tlv/Tlv.kt | 105 ----- .../java/com/tangem/common/tlv/TlvBuilder.kt | 21 - .../java/com/tangem/common/tlv/TlvDecoder.kt | 162 ------- .../java/com/tangem/common/tlv/TlvEncoder.kt | 112 ----- .../main/java/com/tangem/common/tlv/TlvTag.kt | 150 ------ .../java/com/tangem/crypto/CryptoUtils.kt | 121 ----- .../main/java/com/tangem/crypto/Ed25519.kt | 55 --- .../com/tangem/crypto/EncryptionHelper.kt | 61 --- .../src/main/java/com/tangem/crypto/Pbkdf2.kt | 88 ---- .../main/java/com/tangem/crypto/Secp256k1.kt | 118 ----- .../java/com/tangem/tasks/ChangePinTask.kt | 101 ---- .../java/com/tangem/tasks/CreateWalletTask.kt | 45 -- .../main/java/com/tangem/tasks/ScanTask.kt | 87 ---- .../sqldelight/com/tangem/CardValuesEntity.sq | 34 -- .../com/tangem/common/apdu/CommandApduTest.kt | 55 --- .../tangem/common/apdu/ResponseApduTest.kt | 44 -- .../extensions/ByteArrayExtensionsTest.kt | 105 ----- .../common/extensions/IntExtensionsTest.kt | 24 - .../common/extensions/StringExtensionsTest.kt | 53 --- .../com/tangem/common/tlv/TlvDecoderTest.kt | 192 -------- .../java/com/tangem/common/tlv/TlvTest.kt | 111 ----- .../java/com/tangem/crypto/CryptoUtilsTest.kt | 50 -- tangem-devkit/.gitignore | 1 - tangem-devkit/build.gradle | 56 --- tangem-devkit/proguard-rules.pro | 21 - tangem-devkit/src/main/AndroidManifest.xml | 80 ---- .../src/main/ic_launcher-playstore.png | Bin 14393 -> 0 bytes .../java/com/tangem/devkit/AppTangemDemo.kt | 35 -- .../com/tangem/devkit/Old_MainActivity.kt | 143 ------ .../com/tangem/devkit/TestUserDataActivity.kt | 146 ------ .../tangem/devkit/_arch/SingleLiveEvent.kt | 44 -- .../devkit/_arch/structure/HelpAbstracts.kt | 16 - .../com/tangem/devkit/_arch/structure/Ids.kt | 25 - .../tangem/devkit/_arch/structure/Logger.kt | 36 -- .../_arch/structure/abstraction/Extension.kt | 29 -- .../_arch/structure/abstraction/Item.kt | 47 -- .../_arch/structure/abstraction/ItemGroup.kt | 56 --- .../structure/abstraction/ItemViewModel.kt | 115 ----- .../structure/abstraction/ModelConverter.kt | 20 - .../_arch/structure/abstraction/Plugin.kt | 10 - .../devkit/_arch/structure/impl/Items.kt | 39 -- .../devkit/_arch/widget/ItemWidgetBuilder.kt | 12 - .../devkit/_arch/widget/WidgetBuilder.kt | 36 -- .../_arch/widget/abstraction/ViewWidget.kt | 81 ---- .../_arch/widget/impl/LinearGroupWidget.kt | 18 - .../devkit/_arch/widget/impl/StubWidget.kt | 15 - .../com/tangem/devkit/_main/MainActivity.kt | 75 --- .../com/tangem/devkit/_main/MainViewModel.kt | 28 -- .../_main/entryPoint/ActionListFragment.kt | 78 ---- .../devkit/_main/entryPoint/RvActions.kt | 67 --- .../com/tangem/devkit/commons/Abstractions.kt | 16 - .../tangem/devkit/commons/DialogController.kt | 76 --- .../tangem/devkit/commons/GlobalFunctions.kt | 9 - .../java/com/tangem/devkit/commons/Logger.kt | 8 - .../devkit/commons/view/MultiActionView.kt | 85 ---- .../devkit/extensions/AndroidComponents.kt | 31 -- .../java/com/tangem/devkit/extensions/List.kt | 16 - .../devkit/extensions/view/Transitions.kt | 13 - .../com/tangem/devkit/ucase/NavigationOp.kt | 19 - .../devkit/ucase/domain/actions/Action.kt | 48 -- .../domain/actions/CreateWalletAction.kt | 23 - .../domain/actions/DepersonalizeAction.kt | 14 - .../ucase/domain/actions/PersonalizeAction.kt | 66 --- .../ucase/domain/actions/PurgeWalletAction.kt | 23 - .../domain/actions/ReadIssuerDataAction.kt | 23 - .../actions/ReadIssuerExtraDataAction.kt | 23 - .../domain/actions/ReadUserDataAction.kt | 23 - .../devkit/ucase/domain/actions/ScanAction.kt | 14 - .../devkit/ucase/domain/actions/SignAction.kt | 28 -- .../domain/actions/WriteIssuerDataAction.kt | 37 -- .../actions/WriteIssuerExtraDataAction.kt | 45 -- .../domain/actions/WriteUserDataAction.kt | 28 -- .../actions/WriteUserProtectedDataAction.kt | 29 -- .../domain/paramsManager/ItemsManager.kt | 39 -- .../managers/BaseItemsManager.kt | 74 --- .../managers/PersonalizationItemsManager.kt | 35 -- .../paramsManager/managers/SimpleManagers.kt | 107 ----- .../afterAction/AfterActionModifiers.kt | 16 - .../triggers/afterAction/AfterScanModifier.kt | 42 -- .../ParamChangeConsequence.kt | 30 -- .../com/tangem/devkit/ucase/resources/Ids.kt | 23 - .../ucase/resources/MainResourceHolder.kt | 48 -- .../devkit/ucase/resources/ResourseTypes.kt | 15 - .../resources/initializers/ActionResources.kt | 30 -- .../initializers/PersonalizationResources.kt | 128 ----- .../initializers/ResponseResources.kt | 114 ----- .../resources/initializers/TlvResources.kt | 24 - .../com/tangem/devkit/ucase/tunnel/Errors.kt | 17 - .../tangem/devkit/ucase/tunnel/ViewScreen.kt | 16 - .../tangem/devkit/ucase/ui/ActionViewModel.kt | 178 ------- .../devkit/ucase/ui/BaseCardActionFragment.kt | 162 ------- .../tangem/devkit/ucase/ui/BaseFragment.kt | 38 -- .../ucase/ui/widgets/ParameterWidget.kt | 119 ----- .../com/tangem/devkit/ucase/variants/Ids.kt | 15 - .../ui/CreateWalletActionFragment.kt | 10 - .../ui/DepersonalizeActionFragment.kt | 56 --- .../issuerdata/ui/ReadIssuerDataFragment.kt | 10 - .../ui/ReadIssuerExtraDataFragment.kt | 10 - .../issuerdata/ui/WriteIssuerDataFragment.kt | 10 - .../ui/WriteIssuerExtraDataFragment.kt | 10 - .../devkit/ucase/variants/personalize/Ids.kt | 110 ----- .../personalize/PersonalizationConfigStore.kt | 53 --- .../personalize/converter/DefaultValues.kt | 136 ------ .../personalize/converter/ItemTypes.kt | 118 ----- .../PersonalizationConfigConverter.kt | 301 ------------ .../converter/PersonalizationJsonConverter.kt | 204 -------- .../dto/DefaultPersonalizationParams.kt | 55 --- .../variants/personalize/dto/Manufacturer.kt | 53 --- .../personalize/dto/PersonalizationConfig.kt | 232 --------- .../personalize/dto/PersonalizationJson.kt | 148 ------ .../personalize/ui/PersonalizationFragment.kt | 271 ----------- .../presets/PersonalizationPresetManager.kt | 77 --- .../ui/presets/PersonalizationPresetView.kt | 9 - .../personalize/ui/presets/PresetRv.kt | 46 -- .../personalize/ui/widgets/BaseAppWidget.kt | 28 -- .../ui/widgets/DescriptionWidget.kt | 36 -- .../personalize/ui/widgets/EditTextWidget.kt | 48 -- .../personalize/ui/widgets/NumberWidget.kt | 59 --- .../ui/widgets/PersonalizationItemBuilder.kt | 23 - .../personalize/ui/widgets/SpinnerWidget.kt | 94 ---- .../personalize/ui/widgets/SwitchWidget.kt | 35 -- .../ui/widgets/TextHeaderWidget.kt | 19 - .../ui/PurgeWalletActionFragment.kt | 10 - .../devkit/ucase/variants/responses/Ids.kt | 90 ---- .../variants/responses/ResponseViewModel.kt | 60 --- .../converter/BaseResponseConverter.kt | 27 -- .../responses/converter/CardConverter.kt | 95 ---- .../responses/converter/ConvertersStore.kt | 22 - .../responses/converter/SimpleConverters.kt | 99 ---- .../ucase/variants/responses/item/TextItem.kt | 12 - .../variants/responses/ui/ResponseFragment.kt | 84 ---- .../responses/ui/widget/CheckBoxWidget.kt | 27 -- .../ui/widget/ResponseItemBuilder.kt | 23 - .../responses/ui/widget/ResponseTextWidget.kt | 45 -- .../responses/ui/widget/ResponseWidget.kt | 27 -- .../variants/scan/ui/ScanActionFragment.kt | 56 --- .../variants/sign/ui/SignActionFragment.kt | 13 - .../userdata/ui/ReadUserDataFragment.kt | 10 - .../ui/WriteProtectedUserDataFragment.kt | 10 - .../userdata/ui/WriteUserDataFragment.kt | 10 - tangem-devkit/src/main/res/anim/fade_in.xml | 22 - tangem-devkit/src/main/res/anim/fade_out.xml | 22 - .../src/main/res/anim/slide_in_left.xml | 10 - .../src/main/res/anim/slide_in_right.xml | 10 - .../src/main/res/anim/slide_out_left.xml | 10 - .../src/main/res/anim/slide_out_right.xml | 10 - .../res/color/menu_switch_color_thumb.xml | 5 - .../res/color/menu_switch_color_track.xml | 5 - .../res/color/mtrl_fab_bg_color_selector.xml | 6 - .../mtrl_fab_icon_text_color_selector.xml | 6 - .../drawable-v24/ic_launcher_foreground.xml | 34 -- .../src/main/res/drawable/ic_import.xml | 12 - .../res/drawable/ic_launcher_background.xml | 74 --- .../main/res/drawable/ic_share_white_18dp.xml | 9 - tangem-devkit/src/main/res/layout/a_main.xml | 23 - .../res/layout/activity_test_user_data.xml | 238 ---------- .../dlg_personalization_preset_load.xml | 21 - .../dlg_personalization_preset_save.xml | 25 - .../main/res/layout/fg_base_action_layout.xml | 21 - .../src/main/res/layout/fg_card_response.xml | 22 - .../src/main/res/layout/fg_entry_point.xml | 15 - .../res/layout/fg_personalization_footer.xml | 18 - .../src/main/res/layout/m_divider_h.xml | 6 - .../src/main/res/layout/m_divider_v.xml | 6 - .../src/main/res/layout/menu_item_switch.xml | 9 - .../src/main/res/layout/old_activity_main.xml | 132 ------ .../main/res/layout/v_content_container.xml | 20 - .../main/res/layout/v_export_import_json.xml | 37 -- .../main/res/layout/v_item_list_container.xml | 26 -- .../src/main/res/layout/vh_actions.xml | 35 -- .../layout/vh_personalization_preset_name.xml | 35 -- .../main/res/layout/vh_sp_dropdown_item.xml | 11 - .../src/main/res/layout/vh_sp_item.xml | 9 - .../res/layout/w_action_response_json.xml | 8 - .../main/res/layout/w_card_incoming_param.xml | 60 --- tangem-devkit/src/main/res/layout/w_empty.xml | 4 - .../main/res/layout/w_field_description.xml | 19 - .../layout/w_personalize_item_edit_text.xml | 47 -- .../res/layout/w_personalize_item_header.xml | 36 -- .../res/layout/w_personalize_item_number.xml | 48 -- .../res/layout/w_personalize_item_spinner.xml | 45 -- .../res/layout/w_personalize_item_switch.xml | 38 -- .../layout/w_personalize_widgets_together.xml | 21 - .../main/res/layout/w_personilize_block.xml | 5 - .../src/main/res/layout/w_response_item.xml | 45 -- .../res/layout/w_response_item_checkbox.xml | 41 -- .../res/layout/w_response_item_header.xml | 35 -- .../src/main/res/menu/menu_activity_main.xml | 11 - .../main/res/menu/menu_fg_peronalization.xml | 32 -- .../src/main/res/menu/menu_fg_response.xml | 11 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 1425 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 1728 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 3249 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 988 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 1101 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2059 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 1953 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 2465 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 4553 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 3061 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 4276 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 7425 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 4348 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 6231 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 10769 -> 0 bytes .../src/main/res/navigation/navigation.xml | 149 ------ tangem-devkit/src/main/res/values/colors.xml | 26 -- tangem-devkit/src/main/res/values/dimens.xml | 10 - .../main/res/values/dimens_item_widget.xml | 12 - .../res/values/ic_launcher_background.xml | 4 - tangem-devkit/src/main/res/values/strings.xml | 45 -- .../src/main/res/values/strings_action.xml | 35 -- .../res/values/strings_personalization.xml | 178 ------- .../src/main/res/values/strings_response.xml | 179 ------- .../src/main/res/values/strings_scan.xml | 7 - .../src/main/res/values/strings_tlv.xml | 18 - tangem-devkit/src/main/res/values/styles.xml | 23 - .../src/main/res/values/theme_debug.xml | 73 --- .../src/main/res/xml/nfc_tech_filter.xml | 8 - tangem-sdk/.gitignore | 1 - tangem-sdk/build.gradle | 71 --- tangem-sdk/consumer-rules.pro | 0 tangem-sdk/proguard-rules.pro | 21 - tangem-sdk/src/main/AndroidManifest.xml | 5 - .../DefaultSessionViewDelegate.kt | 106 ----- .../tangem_sdk_new/NfcLifecycleObserver.kt | 28 -- .../SessionViewDelegateState.kt | 17 - .../tangem_sdk_new/TerminalKeysStorage.kt | 56 --- .../java/com/tangem/tangem_sdk_new/Utils.kt | 10 - .../tangem_sdk_new/extensions/TangemSdk.kt | 50 -- .../extensions/TangemSdkError.kt | 57 --- .../tangem/tangem_sdk_new/extensions/View.kt | 20 - .../tangem/tangem_sdk_new/nfc/NfcManager.kt | 111 ----- .../tangem/tangem_sdk_new/nfc/NfcReader.kt | 127 ----- .../tangem_sdk_new/nfc/SlixTagReader.kt | 123 ----- .../tangem_sdk_new/ui/NfcEnableDialog.kt | 29 -- .../tangem/tangem_sdk_new/ui/NfcLocation.kt | 245 ---------- .../tangem_sdk_new/ui/NfcSessionDialog.kt | 268 ----------- .../tangem_sdk_new/ui/TouchCardAnimation.kt | 99 ---- .../res/drawable/hand_card_horizontal.png | Bin 17362 -> 0 bytes .../main/res/drawable/hand_card_vertical.png | Bin 15582 -> 0 bytes .../main/res/drawable/ic_action_nfc_gray.png | Bin 536 -> 0 bytes .../src/main/res/drawable/ic_done_135dp.xml | 9 - tangem-sdk/src/main/res/drawable/ic_error.xml | 6 - .../res/drawable/ic_error_outline_135dp.xml | 4 - tangem-sdk/src/main/res/drawable/ic_nfc.xml | 10 - .../src/main/res/drawable/pb_circle.xml | 45 -- .../main/res/drawable/pb_simple_circle.xml | 21 - .../src/main/res/drawable/smartphone.png | Bin 4876 -> 0 bytes .../src/main/res/layout/layout_touch_card.xml | 70 --- .../src/main/res/layout/nfc_bottom_sheet.xml | 276 ----------- tangem-sdk/src/main/res/values/colors.xml | 11 - tangem-sdk/src/main/res/values/dimens.xml | 4 - tangem-sdk/src/main/res/values/strings.xml | 23 - .../src/main/res/values/strings_errors.xml | 33 -- 319 files changed, 6 insertions(+), 17138 deletions(-) delete mode 100644 tangem-core/.gitignore delete mode 100644 tangem-core/build.gradle delete mode 100644 tangem-core/src/main/java/com/tangem/CardFilter.kt delete mode 100644 tangem-core/src/main/java/com/tangem/CardReader.kt delete mode 100644 tangem-core/src/main/java/com/tangem/CardSession.kt delete mode 100644 tangem-core/src/main/java/com/tangem/Config.kt delete mode 100644 tangem-core/src/main/java/com/tangem/Log.kt delete mode 100644 tangem-core/src/main/java/com/tangem/SessionEnvironment.kt delete mode 100644 tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt delete mode 100644 tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt delete mode 100644 tangem-core/src/main/java/com/tangem/TangemSdk.kt delete mode 100644 tangem-core/src/main/java/com/tangem/TangemSdkError.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/Command.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/SignCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/ResponseConverter.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt delete mode 100644 tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/CompletionResult.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/PinCode.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/BigDecimal.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/Card.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/List.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/extensions/String.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt delete mode 100644 tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt delete mode 100644 tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt delete mode 100644 tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt delete mode 100644 tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt delete mode 100644 tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt delete mode 100644 tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt delete mode 100644 tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt delete mode 100644 tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt delete mode 100644 tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt delete mode 100644 tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq delete mode 100644 tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/common/apdu/ResponseApduTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/common/extensions/StringExtensionsTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt delete mode 100644 tangem-core/src/test/java/com/tangem/crypto/CryptoUtilsTest.kt delete mode 100644 tangem-devkit/.gitignore delete mode 100644 tangem-devkit/build.gradle delete mode 100644 tangem-devkit/proguard-rules.pro delete mode 100644 tangem-devkit/src/main/AndroidManifest.xml delete mode 100644 tangem-devkit/src/main/ic_launcher-playstore.png delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/AppTangemDemo.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/Old_MainActivity.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/SingleLiveEvent.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/HelpAbstracts.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Ids.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Logger.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Extension.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Item.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemGroup.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemViewModel.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ModelConverter.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Plugin.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/impl/Items.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/ItemWidgetBuilder.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/WidgetBuilder.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/abstraction/ViewWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/LinearGroupWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/StubWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_main/MainActivity.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_main/MainViewModel.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/ActionListFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/RvActions.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/commons/Abstractions.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/commons/DialogController.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/commons/GlobalFunctions.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/commons/Logger.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/commons/view/MultiActionView.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/extensions/AndroidComponents.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/extensions/List.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/extensions/view/Transitions.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/NavigationOp.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/Action.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/CreateWalletAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/DepersonalizeAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PersonalizeAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PurgeWalletAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerExtraDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadUserDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ScanAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/SignAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerExtraDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserProtectedDataAction.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/ItemsManager.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/BaseItemsManager.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/SimpleManagers.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/Ids.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/MainResourceHolder.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/ResourseTypes.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ActionResources.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/PersonalizationResources.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ResponseResources.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/TlvResources.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/Errors.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/ViewScreen.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/ActionViewModel.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseCardActionFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/widgets/ParameterWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/Ids.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/createwallet/ui/CreateWalletActionFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerDataFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerExtraDataFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerDataFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerExtraDataFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/Ids.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/PersonalizationConfigStore.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/DefaultValues.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/ItemTypes.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationJsonConverter.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/Manufacturer.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationJson.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/PersonalizationFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/presets/PersonalizationPresetManager.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/presets/PersonalizationPresetView.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/presets/PresetRv.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/BaseAppWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/DescriptionWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/EditTextWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/NumberWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/PersonalizationItemBuilder.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/SpinnerWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/SwitchWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/widgets/TextHeaderWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/purgewallet/ui/PurgeWalletActionFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/Ids.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/ResponseViewModel.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/converter/BaseResponseConverter.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/converter/CardConverter.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/converter/ConvertersStore.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/converter/SimpleConverters.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/item/TextItem.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/ui/ResponseFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/ui/widget/CheckBoxWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/ui/widget/ResponseItemBuilder.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/ui/widget/ResponseTextWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/responses/ui/widget/ResponseWidget.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/scan/ui/ScanActionFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/sign/ui/SignActionFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/userdata/ui/ReadUserDataFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/userdata/ui/WriteProtectedUserDataFragment.kt delete mode 100644 tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/userdata/ui/WriteUserDataFragment.kt delete mode 100644 tangem-devkit/src/main/res/anim/fade_in.xml delete mode 100644 tangem-devkit/src/main/res/anim/fade_out.xml delete mode 100644 tangem-devkit/src/main/res/anim/slide_in_left.xml delete mode 100644 tangem-devkit/src/main/res/anim/slide_in_right.xml delete mode 100644 tangem-devkit/src/main/res/anim/slide_out_left.xml delete mode 100644 tangem-devkit/src/main/res/anim/slide_out_right.xml delete mode 100644 tangem-devkit/src/main/res/color/menu_switch_color_thumb.xml delete mode 100644 tangem-devkit/src/main/res/color/menu_switch_color_track.xml delete mode 100644 tangem-devkit/src/main/res/color/mtrl_fab_bg_color_selector.xml delete mode 100644 tangem-devkit/src/main/res/color/mtrl_fab_icon_text_color_selector.xml delete mode 100644 tangem-devkit/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 tangem-devkit/src/main/res/drawable/ic_import.xml delete mode 100644 tangem-devkit/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 tangem-devkit/src/main/res/drawable/ic_share_white_18dp.xml delete mode 100644 tangem-devkit/src/main/res/layout/a_main.xml delete mode 100644 tangem-devkit/src/main/res/layout/activity_test_user_data.xml delete mode 100644 tangem-devkit/src/main/res/layout/dlg_personalization_preset_load.xml delete mode 100644 tangem-devkit/src/main/res/layout/dlg_personalization_preset_save.xml delete mode 100644 tangem-devkit/src/main/res/layout/fg_base_action_layout.xml delete mode 100644 tangem-devkit/src/main/res/layout/fg_card_response.xml delete mode 100644 tangem-devkit/src/main/res/layout/fg_entry_point.xml delete mode 100644 tangem-devkit/src/main/res/layout/fg_personalization_footer.xml delete mode 100644 tangem-devkit/src/main/res/layout/m_divider_h.xml delete mode 100644 tangem-devkit/src/main/res/layout/m_divider_v.xml delete mode 100644 tangem-devkit/src/main/res/layout/menu_item_switch.xml delete mode 100644 tangem-devkit/src/main/res/layout/old_activity_main.xml delete mode 100644 tangem-devkit/src/main/res/layout/v_content_container.xml delete mode 100644 tangem-devkit/src/main/res/layout/v_export_import_json.xml delete mode 100644 tangem-devkit/src/main/res/layout/v_item_list_container.xml delete mode 100644 tangem-devkit/src/main/res/layout/vh_actions.xml delete mode 100644 tangem-devkit/src/main/res/layout/vh_personalization_preset_name.xml delete mode 100644 tangem-devkit/src/main/res/layout/vh_sp_dropdown_item.xml delete mode 100644 tangem-devkit/src/main/res/layout/vh_sp_item.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_action_response_json.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_card_incoming_param.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_empty.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_field_description.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personalize_item_edit_text.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personalize_item_header.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personalize_item_number.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personalize_item_spinner.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personalize_item_switch.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personalize_widgets_together.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_personilize_block.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_response_item.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_response_item_checkbox.xml delete mode 100644 tangem-devkit/src/main/res/layout/w_response_item_header.xml delete mode 100644 tangem-devkit/src/main/res/menu/menu_activity_main.xml delete mode 100644 tangem-devkit/src/main/res/menu/menu_fg_peronalization.xml delete mode 100644 tangem-devkit/src/main/res/menu/menu_fg_response.xml delete mode 100644 tangem-devkit/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 tangem-devkit/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 tangem-devkit/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 tangem-devkit/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 tangem-devkit/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 tangem-devkit/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 tangem-devkit/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 tangem-devkit/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 tangem-devkit/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 tangem-devkit/src/main/res/navigation/navigation.xml delete mode 100644 tangem-devkit/src/main/res/values/colors.xml delete mode 100644 tangem-devkit/src/main/res/values/dimens.xml delete mode 100644 tangem-devkit/src/main/res/values/dimens_item_widget.xml delete mode 100644 tangem-devkit/src/main/res/values/ic_launcher_background.xml delete mode 100644 tangem-devkit/src/main/res/values/strings.xml delete mode 100644 tangem-devkit/src/main/res/values/strings_action.xml delete mode 100644 tangem-devkit/src/main/res/values/strings_personalization.xml delete mode 100644 tangem-devkit/src/main/res/values/strings_response.xml delete mode 100644 tangem-devkit/src/main/res/values/strings_scan.xml delete mode 100644 tangem-devkit/src/main/res/values/strings_tlv.xml delete mode 100644 tangem-devkit/src/main/res/values/styles.xml delete mode 100644 tangem-devkit/src/main/res/values/theme_debug.xml delete mode 100644 tangem-devkit/src/main/res/xml/nfc_tech_filter.xml delete mode 100644 tangem-sdk/.gitignore delete mode 100644 tangem-sdk/build.gradle delete mode 100644 tangem-sdk/consumer-rules.pro delete mode 100644 tangem-sdk/proguard-rules.pro delete mode 100644 tangem-sdk/src/main/AndroidManifest.xml delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/NfcLifecycleObserver.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/SessionViewDelegateState.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/Utils.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdkError.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/View.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcEnableDialog.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcLocation.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/NfcSessionDialog.kt delete mode 100644 tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/ui/TouchCardAnimation.kt delete mode 100644 tangem-sdk/src/main/res/drawable/hand_card_horizontal.png delete mode 100644 tangem-sdk/src/main/res/drawable/hand_card_vertical.png delete mode 100644 tangem-sdk/src/main/res/drawable/ic_action_nfc_gray.png delete mode 100644 tangem-sdk/src/main/res/drawable/ic_done_135dp.xml delete mode 100644 tangem-sdk/src/main/res/drawable/ic_error.xml delete mode 100644 tangem-sdk/src/main/res/drawable/ic_error_outline_135dp.xml delete mode 100644 tangem-sdk/src/main/res/drawable/ic_nfc.xml delete mode 100644 tangem-sdk/src/main/res/drawable/pb_circle.xml delete mode 100644 tangem-sdk/src/main/res/drawable/pb_simple_circle.xml delete mode 100644 tangem-sdk/src/main/res/drawable/smartphone.png delete mode 100644 tangem-sdk/src/main/res/layout/layout_touch_card.xml delete mode 100644 tangem-sdk/src/main/res/layout/nfc_bottom_sheet.xml delete mode 100644 tangem-sdk/src/main/res/values/colors.xml delete mode 100644 tangem-sdk/src/main/res/values/dimens.xml delete mode 100644 tangem-sdk/src/main/res/values/strings.xml delete mode 100644 tangem-sdk/src/main/res/values/strings_errors.xml diff --git a/blockchain-demo/build.gradle b/blockchain-demo/build.gradle index 6567a8b453..934f45b26c 100644 --- a/blockchain-demo/build.gradle +++ b/blockchain-demo/build.gradle @@ -45,10 +45,11 @@ android { } dependencies { - implementation project(':tangem-core') - implementation project(':tangem-sdk') implementation project(':blockchain') + implementation 'com.tangem:core:0.10.4' + implementation 'com.tangem:sdk:0.10.4' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.2.0' diff --git a/blockchain/build.gradle b/blockchain/build.gradle index 24ee17af7f..31c5286f39 100644 --- a/blockchain/build.gradle +++ b/blockchain/build.gradle @@ -40,8 +40,7 @@ android { dependencies { // implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':tangem-core') - implementation project(':tangem-sdk') + implementation 'com.tangem:core:1.13' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" implementation 'androidx.appcompat:appcompat:1.1.0' diff --git a/build.gradle b/build.gradle index bbc8c94420..f1609ce70f 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ allprojects { google() jcenter() maven { url 'https://jitpack.io' } + maven { url "https://api.bitbucket.org/2.0/repositories/tangem/maven_repository/src/releases" } } } diff --git a/settings.gradle b/settings.gradle index 9a87107347..239e3ea7e9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':tangem-sdk-old', ':server-android', ':tangem-card-old', ':tangem-core', ':tangem-sdk', ':tangem-devkit', ':blockchain', ':blockchain-demo' \ No newline at end of file +include ':app', ':tangem-sdk-old', ':server-android', ':tangem-card-old', ':blockchain', ':blockchain-demo' \ No newline at end of file diff --git a/tangem-core/.gitignore b/tangem-core/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/tangem-core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/tangem-core/build.gradle b/tangem-core/build.gradle deleted file mode 100644 index ce4d4d4c3e..0000000000 --- a/tangem-core/build.gradle +++ /dev/null @@ -1,78 +0,0 @@ -apply plugin: "kotlin" -apply plugin: 'org.jetbrains.dokka' -apply plugin: 'com.github.dcendents.android-maven' -apply from: '../dependencies.gradle' -apply from: '../jitpack.gradle' -apply plugin: 'kotlin-kapt' -apply plugin: 'com.squareup.sqldelight' - -group = "$jitpackSdk.group" -version "$jitpackSdk.version" - -dependencies { - // kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - implementation "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlidn" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.7" - - // crypto - implementation "com.madgag.spongycastle:core:1.58.0.0" - implementation "com.madgag.spongycastle:prov:1.58.0.0" - implementation 'net.i2p.crypto:eddsa:0.3.0' - - // misc - implementation 'com.google.code.gson:gson:2.8.6' - implementation "com.squareup.sqldelight:sqlite-driver:1.4.0" - - //network - implementation 'com.squareup.retrofit2:retrofit:2.8.1' - implementation 'com.squareup.retrofit2:converter-moshi:2.6.0' - implementation 'com.squareup.moshi:moshi:1.9.2' - implementation "com.squareup.moshi:moshi-kotlin:1.9.2" - kapt("com.squareup.moshi:moshi-kotlin-codegen:1.9.2") - implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' - - // tests - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2' - testImplementation "com.google.truth:truth:1.0.1" -} - -sqldelight { - Database { - packageName = "com.tangem" - } -} - -sourceCompatibility = "8" -targetCompatibility = "8" - -buildscript { - ext.dokka_version = '0.10.0' - repositories { - mavenCentral() - jcenter() - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" - } -} -repositories { - mavenCentral() - jcenter() -} -compileKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} -compileTestKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} - -task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { - outputFormat = 'markdown' -} diff --git a/tangem-core/src/main/java/com/tangem/CardFilter.kt b/tangem-core/src/main/java/com/tangem/CardFilter.kt deleted file mode 100644 index d1275d1d1d..0000000000 --- a/tangem-core/src/main/java/com/tangem/CardFilter.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem - -import com.tangem.common.extensions.CardType -import java.util.* - -/** - * Filter that can be used to limit cards that can be interacted with in TangemSdk. - * - * @property allowedCardTypes Type of cards that are allowed to be interacted with in TangemSdk. - */ -data class CardFilter( - - var allowedCardTypes: EnumSet = EnumSet.allOf(CardType::class.java) -) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardReader.kt b/tangem-core/src/main/java/com/tangem/CardReader.kt deleted file mode 100644 index deb1f33f14..0000000000 --- a/tangem-core/src/main/java/com/tangem/CardReader.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem - -import com.tangem.common.CompletionResult -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.ResponseApdu -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.BroadcastChannel - -/** - * Allows interaction between the phone or any other terminal and Tangem card. - * - * Its default implementation, NfcCardReader, is in our tangem-sdk module. - */ -interface CardReader { - - val tag: BroadcastChannel - var scope: CoroutineScope? - - /** - * Sends data to the card and receives the reply in an asynchronous way using coroutines. - * - * @param apdu Data to be sent. [CommandApdu] serializes it to a [ByteArray] - * @param callback Returns response from the card, - * [ResponseApdu] Allows to convert raw data to [Tlv] - */ - suspend fun transceiveApdu(apdu: CommandApdu): CompletionResult - - /** - * Sends data to the card and receives the reply. - * - * @param apdu Data to be sent. [CommandApdu] serializes it to a [ByteArray] - * @param callback Returns response from the card, - * [ResponseApdu] Allows to convert raw data to [Tlv] - */ - fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult) -> Unit) - - /** - * Signals to [CardReader] to become ready to transceive data. - */ - fun startSession() - - /** - * Signals to [CardReader] that no further NFC transition is expected. - */ - fun stopSession(cancelled: Boolean = false) - - fun readSlixTag(callback: (result: CompletionResult) -> Unit) - -} - -interface ReadingActiveListener { - var readingIsActive: Boolean -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardSession.kt b/tangem-core/src/main/java/com/tangem/CardSession.kt deleted file mode 100644 index 7b0ec51200..0000000000 --- a/tangem-core/src/main/java/com/tangem/CardSession.kt +++ /dev/null @@ -1,318 +0,0 @@ -package com.tangem - -import com.tangem.commands.Command -import com.tangem.commands.CommandResponse -import com.tangem.commands.OpenSessionCommand -import com.tangem.commands.ReadCommand -import com.tangem.common.CompletionResult -import com.tangem.common.PinCode -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.getType -import com.tangem.crypto.EncryptionHelper -import com.tangem.crypto.pbkdf2Hash -import com.tangem.tasks.PinType -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* - -/** - * Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession] - */ -interface CardSessionRunnable { - - val requiresPin2: Boolean - - /** - * The starting point for custom business logic. - * Implement this interface and use [TangemSdk.startSessionWithRunnable] 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) -> Unit) -} - -enum class CardSessionState { - Inactive, - Active -} - -enum class TagType { - Nfc, - Slix -} - -/** - * 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 [TangemSdkError.WrongCardNumber] 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( - private val environmentService: SessionEnvironmentService, - private val reader: CardReader, - val viewDelegate: SessionViewDelegate, - private var cardId: String? = null, - private val initialMessage: Message? = null -) { - - var connectedTag: TagType? = null - - /** - * True if some operation is still in progress. - */ - private var state = CardSessionState.Inactive - - val scope = CoroutineScope(Dispatchers.IO) + CoroutineExceptionHandler { _, ex -> - throw ex - } - - private val tag = this.javaClass.simpleName - - private var performPreflightRead = true - private var pin2Required = false - - val environment = environmentService.createEnvironment(cardId) - - /** - * This method 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 , R : CommandResponse> startWithRunnable( - runnable: T, callback: (result: CompletionResult) -> Unit - ) { - - if ((runnable as? Command<*>)?.performPreflightRead == false) performPreflightRead = false - pin2Required = runnable.requiresPin2 - - start() { session, error -> - if (error != null) { - callback(CompletionResult.Failure(error)) - return@start - } - - runnable.run(this) { result -> - when (result) { - is CompletionResult.Success -> stop() - is CompletionResult.Failure -> { - if (result.error is TangemSdkError.ExtendedLengthNotSupported) { - if (session.environment.terminalKeys != null) { - session.environment.terminalKeys = null - startWithRunnable(runnable, callback) - return@run - } - } - stopWithError(result.error) - } - } - callback(result) - } - } - } - - /** - * Starts a card session and performs preflight [ReadCommand]. - * @param callback: callback with the card session. Can contain [TangemSdkError] if something goes wrong. - */ - fun start( - callback: (session: CardSession, error: TangemSdkError?) -> Unit - ) { - - if (state != CardSessionState.Inactive) { - callback(this, TangemSdkError.Busy()) - return - } - - - if (environment.pin1 == null) { - viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested(PinType.Pin1) { - environment.pin1 = PinCode(it) - start(callback) - } - return - } - - if (pin2Required && environment.pin2 == null) { - viewDelegate.onSessionStarted(cardId) - viewDelegate.onPinRequested(PinType.Pin2) { - environment.pin2 = PinCode(it) - start(callback) - } - return - } - - state = CardSessionState.Active - viewDelegate.onSessionStarted(cardId) - - scope.launch { - reader.tag - .asFlow() - .collect { tagType -> - if (tagType == null && connectedTag != null) { - handleTagLost() - } else if (tagType != null) { - connectedTag = tagType - viewDelegate.onTagConnected() - - if (tagType == TagType.Nfc && performPreflightRead) { - preflightCheck(callback) - } else { - callback(this@CardSession, null) - } - } - } - } - reader.scope = scope - reader.startSession() - } - - private fun handleTagLost() { - connectedTag = null - environment.encryptionKey = null - viewDelegate.onTagLost() - } - - private fun preflightCheck(callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - val readCommand = ReadCommand() - readCommand.run(this) { result -> - when (result) { - is CompletionResult.Failure -> { - stopWithError(result.error) - callback(this, result.error) - } - is CompletionResult.Success -> { - val receivedCardId = result.data.cardId - if (cardId != null && receivedCardId != cardId) { - viewDelegate.onWrongCard() - preflightCheck(callback) - return@run - } - val allowedCardTypes = environment.cardFilter.allowedCardTypes - if (!allowedCardTypes.contains(result.data.getType())) { - stopWithError(TangemSdkError.WrongCardType()) - callback(this, TangemSdkError.WrongCardType()) - return@run - } - environment.card = result.data - environmentService.updateEnvironment(environment, result.data.cardId) - cardId = receivedCardId - callback(this, null) - } - } - } - } - - fun readSlixTag(callback: (result: CompletionResult) -> Unit) { - reader.readSlixTag(callback) - } - - /** - * Stops the current session with the text message. - * @param message If null, the default message will be shown. - */ - private fun stop(message: Message? = null) { - stopSession() - viewDelegate.onSessionStopped(message) - } - - /** - * Stops the current session on error. - * @param error An error that will be shown. - */ - private fun stopWithError(error: TangemSdkError) { - stopSession() - if (error !is TangemSdkError.UserCancelled) { - Log.e(tag, "Finishing with error: ${error::class.simpleName}: ${error.code}") - viewDelegate.onError(error) - } else { - Log.i(tag, "User cancelled NFC session") - } - } - - private fun stopSession() { - environmentService.saveEnvironmentValues(environment, cardId) - reader.stopSession() - state = CardSessionState.Inactive - scope.cancel() - } - - fun send(apdu: CommandApdu, callback: (result: CompletionResult) -> Unit) { - val subscription = reader.tag.openSubscription() - scope.launch { - subscription.consumeAsFlow() - .filterNotNull() - .map { establishEncryptionIfNeeded() } - .map { apdu.encrypt(environment.encryptionMode, environment.encryptionKey) } - .map { encryptedApdu -> reader.transceiveApdu(encryptedApdu) } - .map { responseApdu -> decrypt(responseApdu) } - .catch { if (it is TangemSdkError) callback(CompletionResult.Failure(it)) } - .collect { result -> - subscription.cancel() - callback(result) - } - } - } - - fun pause() { - reader.stopSession() - } - - fun resume() { - reader.startSession() - } - - private suspend fun establishEncryptionIfNeeded(): CompletionResult { - if (environment.encryptionMode == EncryptionMode.NONE || environment.encryptionKey != null) { - return CompletionResult.Success(true) - } - - val encryptionHelper = EncryptionHelper.create(environment.encryptionMode) - ?: return CompletionResult.Success(true) - - val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) - val apdu = openSesssionCommand.serialize(environment) - - val response = reader.transceiveApdu(apdu) - when (response) { - is CompletionResult.Success -> { - val result = try { - openSesssionCommand.deserialize(environment, response.data) - } catch (error: TangemSdkError) { - return CompletionResult.Failure(error) - } - val uid = result.uid - val protocolKey = environment.pin1!!.value.pbkdf2Hash(uid, 50) - val secret = encryptionHelper.generateSecret(result.sessionKeyB) - val sessionKey = (secret + protocolKey).calculateSha256() - environment.encryptionKey = sessionKey - return CompletionResult.Success(true) - } - is CompletionResult.Failure -> return CompletionResult.Failure(response.error) - } - } - - private fun decrypt(result: CompletionResult): CompletionResult { - return when (result) { - is CompletionResult.Success -> { - try { - CompletionResult.Success( - result.data.decrypt(environment.encryptionKey) - ) - } catch (error: TangemSdkError) { - return CompletionResult.Failure(error) - } - } - is CompletionResult.Failure -> result - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/Config.kt b/tangem-core/src/main/java/com/tangem/Config.kt deleted file mode 100644 index 7b643c5e1f..0000000000 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ /dev/null @@ -1,53 +0,0 @@ -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. - */ - var linkedTerminal: Boolean = true, - - /** - * If not null, it will be used to validate Issuer data and issuer extra data. - * If null, issuerPublicKey from current card will be used. - */ - var issuerPublicKey: ByteArray? = null, - - /** - * Level of encryption used in communication with a Tangem Card. - */ - var encryptionMode: EncryptionMode = EncryptionMode.NONE, - - /** - * Filter that can be used to limit cards that can be interacted with in TangemSdk. - */ - val cardFilter: CardFilter = CardFilter(), - - var handleErrors: Boolean = true, - - var defaultPin1: String = DEFAULT_PIN_1, - - var defaultPin2: String = DEFAULT_PIN_2, - - var savePin1InStaticField: Boolean = true, - - var savePin2InStaticField: Boolean = false -) { - companion object { - const val DEFAULT_PIN_1 = "000000" - const val DEFAULT_PIN_2 = "000" - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/Log.kt b/tangem-core/src/main/java/com/tangem/Log.kt deleted file mode 100644 index b03303b1a5..0000000000 --- a/tangem-core/src/main/java/com/tangem/Log.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.tangem - -object Log { - - private var loggerInstance: LoggerInterface? = null - - fun i(logTag: String, message: String) { - loggerInstance?.i(logTag, message) - } - - fun e(logTag: String, message: String) { - loggerInstance?.e(logTag, message) - } - - fun v(logTag: String, message: String) { - - loggerInstance?.v(logTag, message) - } - - fun setLogger(logger: LoggerInterface) { - loggerInstance = logger - } -} - -/** - * Interface for logging events within the SDK. - * - * It allows to use Android logger or to choose another. - */ -interface LoggerInterface { - fun i(logTag: String, message: String) - fun e(logTag: String, message: String) - fun v(logTag: String, message: String) -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt deleted file mode 100644 index e8afbb33e5..0000000000 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem - -import com.tangem.commands.Card -import com.tangem.commands.EllipticCurve -import com.tangem.common.PinCode -import com.tangem.crypto.CryptoUtils.generatePublicKey - - -/** - * Contains data relating to a Tangem card. It is used in constructing all the commands, - * and commands can return modified [SessionEnvironment]. - * - * @param cardId Card ID, if it is known before tapping the card. - * @property config sets a number of parameters for communication with Tangem cards. - * @param terminalKeysService is used to retrieve terminal keys used in Linked Terminal feature. - * @param cardValuesStorage is used to save and retrieve some values relating to a particular card. - * - * @property pin1 An access Code, required to get access to a card. A default value is set in [Config] - * @property pin2 A code, required to perform particular operations with a card. A default value is set in [Config] - * @property terminalKeys generated terminal keys used in Linked Terminal feature - * @property cardFilter a property that defines types of card that this SDK will be able to interact with - * @property handleErrors if true, the SDK parses internal card errors into concrete [TangemSdkError] - * @property encryptionMode preferred [EncryptionMode] for interaction with cards - * @property encryptionKey is used for encrypted communication with a card - * - */ -class SessionEnvironment( - var pin1: PinCode? = PinCode(Config.DEFAULT_PIN_1, true), - var pin2: PinCode? = PinCode(Config.DEFAULT_PIN_2, true), - var cvc: ByteArray? = null, - - var terminalKeys: KeyPair? = null, - var cardFilter: CardFilter = CardFilter(), - val handleErrors: Boolean = true, - - var encryptionMode: EncryptionMode = EncryptionMode.NONE, - var encryptionKey: ByteArray? = null, - - var cardVerification: VerificationState = VerificationState.NotVerified, - var cardValidation: VerificationState = VerificationState.NotVerified, - var codeVerification: VerificationState = VerificationState.NotVerified, - - var card: Card? = null -) - -/** - * All possible encryption modes. - */ -enum class EncryptionMode(val code: Int) { - NONE(0x0), - FAST(0x1), - STRONG(0x2) -} - -class KeyPair(val publicKey: ByteArray, val privateKey: ByteArray) { - - constructor(privateKey: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1) : - this(generatePublicKey(privateKey, curve), privateKey) -} - -enum class VerificationState { - Passed, Offline, Failed, NotVerified, Cancelled -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt deleted file mode 100644 index d36c95c8ad..0000000000 --- a/tangem-core/src/main/java/com/tangem/SessionEnvironmentService.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.tangem - -import com.tangem.common.CardValuesStorage -import com.tangem.common.PinCode -import com.tangem.common.TerminalKeysService - -class SessionEnvironmentService( - private val config: Config, - private val terminalKeysService: TerminalKeysService?, - private val cardValuesStorage: CardValuesStorage? -) { - - fun createEnvironment(cardId: String?): SessionEnvironment { - val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null - - val cardValues = cardId?.let { cardValuesStorage?.getValues(cardId) } - - val cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified - val cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified - val codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified - - val pin1 = TangemSdk.pin1 - ?: if (cardValues?.isPin1Default != false) { - PinCode(config.defaultPin1, true) - } else { - null - } - - val pin2 = cardId?.let { TangemSdk.pin2[it] } - ?: if (cardValues?.isPin2Default != false) { - PinCode(config.defaultPin2, true) - } else { - null - } - - return SessionEnvironment( - terminalKeys = terminalKeys, - cardFilter = config.cardFilter, - handleErrors = config.handleErrors, - - encryptionMode = config.encryptionMode, - - cardVerification = cardVerification, - cardValidation = cardValidation, - codeVerification = codeVerification, - - pin1 = pin1, - pin2 = pin2 - ) - } - - fun updateEnvironment(environment: SessionEnvironment, cardId: String) { - val cardValues = cardId.let { cardValuesStorage?.getValues(it) } - environment.cardVerification = cardValues?.cardVerification ?: VerificationState.NotVerified - environment.cardValidation = cardValues?.cardValidation ?: VerificationState.NotVerified - environment.codeVerification = cardValues?.codeVerification ?: VerificationState.NotVerified - - if (cardValues?.isPin1Default == false && environment.pin1?.isDefault == true) environment.pin1 = null - if (cardValues?.isPin2Default == false && environment.pin1?.isDefault == true) environment.pin2 = null - } - - fun saveEnvironmentValues(environment: SessionEnvironment, cardId: String?) { - if (config.savePin1InStaticField) { - TangemSdk.pin1 = environment.pin1 - } - if (config.savePin2InStaticField) { - cardId?.let { cardId -> TangemSdk.pin2[cardId] = environment.pin2 } - } - cardId?.let { cardId -> - cardValuesStorage?.saveValues(cardId, - environment.pin1?.isDefault ?: false, - environment.pin2?.isDefault ?: false, - environment.cardVerification, environment.cardVerification, - environment.codeVerification) - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt deleted file mode 100644 index a62716454b..0000000000 --- a/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem - -import com.tangem.tasks.PinType - -/** - * Allows interaction with users and shows visual elements. - * - * Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module. - */ -interface SessionViewDelegate { - - /** - * It is called when user is expected to scan a Tangem Card with an Android device. - */ - fun onSessionStarted(cardId: String?, message: Message? = null) - - /** - * It is called when security delay is triggered by the card. - * A user is expected to hold the card until the security delay is over. - */ - fun onSecurityDelay(ms: Int, totalDurationSeconds: Int) - - /** - * It is called when long tasks are performed. - * A user is expected to hold the card until the task is complete. - */ - fun onDelay(total: Int, current: Int, step: Int) - - /** - * It is called when user takes the card away from the Android device during the scanning - * (for example when security delay is in progress) and the TagLostException is received. - */ - fun onTagLost() - - fun onTagConnected() - - fun onWrongCard() - - /** - * It is called when NFC session was completed and a user can take the card away from the Android device. - */ - fun onSessionStopped(message: Message? = null) - - /** - * It is called when some error occur during NFC session. - */ - fun onError(error: TangemSdkError) - - /** - * It is called when a user is expected to enter pin code. - */ - fun onPinRequested(pinType: PinType, callback: (pin: String) -> Unit) - - /** - * It is called when a user wants to change pin code. - */ - fun onPinChangeRequested(pinType: PinType, callback: (pin: 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) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/TangemSdk.kt b/tangem-core/src/main/java/com/tangem/TangemSdk.kt deleted file mode 100644 index 40b0a79848..0000000000 --- a/tangem-core/src/main/java/com/tangem/TangemSdk.kt +++ /dev/null @@ -1,440 +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.commands.verifycard.VerifyCardCommand -import com.tangem.commands.verifycard.VerifyCardResponse -import com.tangem.common.CardValuesStorage -import com.tangem.common.CompletionResult -import com.tangem.common.PinCode -import com.tangem.common.TerminalKeysService -import com.tangem.crypto.CryptoUtils -import com.tangem.tasks.ChangePinTask -import com.tangem.tasks.CreateWalletTask -import com.tangem.tasks.PinType -import com.tangem.tasks.ScanTask - -/** - * 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. - * @property terminalKeysService allows to retrieve saved terminal keys. - */ -class TangemSdk( - private val reader: CardReader, - private val viewDelegate: SessionViewDelegate, - var config: Config = Config(), - cardValuesStorage: CardValuesStorage, - terminalKeysService: TerminalKeysService? = null -) { - - private val environmentService = SessionEnvironmentService( - config, terminalKeysService, cardValuesStorage - ) - - 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 it’s 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 [TangemSdkError] in case of an error. - */ - fun scanCard(initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(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 [TangemSdkError] in case of an error. - */ - fun sign(hashes: Array, cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(SignCommand(hashes), cardId, initialMessage, callback) - } - - /** - * This method launches a [ReadIssuerDataCommand] on a new thread. - - * This command returns 512-byte Issuer Data field and its issuer’s 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 issuer’s 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 [TangemSdkError] in case of an error. - */ - fun readIssuerData(cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(ReadIssuerDataCommand(config.issuerPublicKey), cardId, initialMessage, callback) - } - - /** - * This method launches a [ReadIssuerExtraDataCommand] on a new thread. - * - * This command retrieves Issuer Extra Data field and its issuer’s 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 [TangemSdkError] in case of an error. - */ - fun readIssuerExtraData(cardId: String? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(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 issuer’s 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 issuer’s attestation data. - * - * @param cardId CID, Unique Tangem card ID number. - * @param issuerData Data provided by issuer. - * @param issuerDataSignature Issuer’s 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 [TangemSdkError] in case of an error. - */ - fun writeIssuerData(cardId: String? = null, - issuerData: ByteArray, - issuerDataSignature: ByteArray, - issuerDataCounter: Int? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val command = WriteIssuerDataCommand( - issuerData, - issuerDataSignature, - issuerDataCounter, - config.issuerPublicKey - ) - startSessionWithRunnable(command, cardId, initialMessage, callback) - } - - /** - * This method launches a [WriteIssuerExtraDataCommand] on a new thread. - * - * This command writes Issuer Extra Data field and its issuer’s 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 Issuer’s 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 Issuer’s 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 [TangemSdkError] in case of an error. - */ - fun writeIssuerExtraData(cardId: String? = null, - issuerData: ByteArray, - startingSignature: ByteArray, - finalizingSignature: ByteArray, - issuerDataCounter: Int? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val command = WriteIssuerExtraDataCommand( - issuerData, - startingSignature, finalizingSignature, - issuerDataCounter, - config.issuerPublicKey - ) - startSessionWithRunnable(command, cardId, initialMessage, callback) - } - - /** - * This method launches a [WriteUserDataCommand] on a new thread, writing UserData and UserCounter fields. - * - * User_Data is never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and its payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * The initial value of User_Counter can be set by an 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. - */ - fun writeUserData( - cardId: String? = null, - userData: ByteArray? = null, - userCounter: Int? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit - ) { - val command = WriteUserDataCommand(userData = userData, userCounter = userCounter) - startSessionWithRunnable(command, cardId, initialMessage, callback) - } - - /** - * This method launches a [WriteUserDataCommand] on a new thread, - * writing UserProtectedData and UserProtectedCounter fields. - * - * User_ProtectedData is never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and its payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * The initial value of User_ProtectedCounter can be set by an App and increased on every signing - * of a new transaction (on SIGN command that calculate new signatures). The App defines the purpose of use. - * For example, this fields may contain blockchain nonce value. - * - * UserProtectedCounter and UserProtectedData require PIN2 for confirmation. - */ - fun writeProtectedUserData( - cardId: String? = null, - userProtectedData: ByteArray? = null, - userProtectedCounter: Int? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit - ) { - val command = WriteUserDataCommand( - userProtectedData = userProtectedData, userProtectedCounter = userProtectedCounter - ) - startSessionWithRunnable(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 [TangemSdkError] in case of an error. - */ - fun readUserData(cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(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 [TangemSdkError] in case of an error. - */ - fun createWallet(cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(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 [TangemSdkError] in case of an error. - */ - fun purgeWallet(cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(PurgeWalletCommand(), cardId, initialMessage, callback) - } - - /** - * This method launches a [VerifyCardCommand] on a new thread. - * - * The command to ensures the card has not been counterfeited. - * By using standard challenge-response scheme, the card proves possession of CardPrivateKey - * that corresponds to CardPublicKey returned by [ReadCommand]. Then the data is sent - * to Tangem server to prove that this card was indeed issued by Tangem. - * The online part of the verification is unavailable for DevKit cards. - * - * - * @param cardId CID, Unique Tangem card ID number. - * @param online flag that allows disable online verification - * @param callback is triggered on the completion of the [VerifyCardCommand] and provides - * card response in the form of [VerifyCardResponse] if the task was performed successfully - * or [TangemSdkError] in case of an error. - */ - fun verify(cardId: String? = null, online: Boolean = true, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(VerifyCardCommand(online), 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 [TangemSdkError] in case of an error. - * */ - fun depersonalize(cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - startSessionWithRunnable(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 [TangemSdkError] in case of an error. - */ - fun personalize(config: CardConfig, - issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val command = PersonalizeCommand(config, issuer, manufacturer, acquirer) - startSessionWithRunnable(command, null, initialMessage, callback) - } - - fun changePin1(cardId: String? = null, - pin: ByteArray? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val command = ChangePinTask(PinType.Pin1, pin) - startSessionWithRunnable(command, cardId, initialMessage, callback) - } - - fun changePin2(cardId: String? = null, - pin: ByteArray? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val command = ChangePinTask(PinType.Pin2, pin) - startSessionWithRunnable(command, cardId, initialMessage, callback) - } - - fun changePin3(cardId: String? = null, - pin: ByteArray? = null, - initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val command = ChangePinTask(PinType.Pin3, pin) - startSessionWithRunnable(command, cardId, 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 [TangemSdkError.WrongCardNumber] 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 startSessionWithRunnable( - runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, - callback: (result: CompletionResult) -> Unit) { - val cardSession = CardSession(environmentService, 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 session 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 [TangemSdkError.WrongCardNumber] 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 [TangemSdkError] is not null, - * then you can use the [CardSession] to interact with a card. - */ - fun startSession(cardId: String? = null, initialMessage: Message? = null, - callback: (session: CardSession, error: TangemSdkError?) -> Unit) { - val cardSession = CardSession(environmentService, reader, viewDelegate, cardId, initialMessage) - Thread().run { cardSession.start(callback = callback) } - } - - companion object { - var pin1: PinCode? = null - val pin2: MutableMap = mutableMapOf() - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt b/tangem-core/src/main/java/com/tangem/TangemSdkError.kt deleted file mode 100644 index d58771fc77..0000000000 --- a/tangem-core/src/main/java/com/tangem/TangemSdkError.kt +++ /dev/null @@ -1,163 +0,0 @@ -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 TangemSdkError(val code: Int) : Exception(code.toString()) { - - /** - * 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 : TangemSdkError(10001) - /** - * This error is returned when NFC driver on an Android device does not support sending more than 261 bytes. - */ - class ExtendedLengthNotSupported : TangemSdkError(10002) - - - class SerializeCommandError : TangemSdkError(20001) - class DeserializeApduFailed : TangemSdkError(20002) - class EncodingFailedTypeMismatch : TangemSdkError(20003) - class EncodingFailed : TangemSdkError(20004) - class DecodingFailedMissingTag : TangemSdkError(20005) - class DecodingFailedTypeMismatch : TangemSdkError(20006) - class DecodingFailed : TangemSdkError(20007) - class InvalidResponse : TangemSdkError(20008) - - /** - * This error is returned when unknown [StatusWord] is received from a card. - */ - class UnknownStatus : TangemSdkError(30001) - /** - * 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 : TangemSdkError(30002) - /** - * 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 : TangemSdkError(30003) - /** - * 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 : TangemSdkError(30004) - /** - * 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 : TangemSdkError(30005) - /** - * This error is returned when a card's reply is [StatusWord.NeedEncryption] - * and the encryption was not established by TangemSdk. - */ - class NeedEncryption : TangemSdkError(30006) - - //Personalization Errors - class AlreadyPersonalized : TangemSdkError(40101) - - //Depersonalization Errors - class CannotBeDepersonalized : TangemSdkError(40201) - - //Read Errors - class Pin1Required : TangemSdkError(40401) - - //CreateWallet Errors - class AlreadyCreated : TangemSdkError(40501) - - //PurgeWallet Errors - class PurgeWalletProhibited : TangemSdkError(40601) - - //SetPin Errors - class Pin1CannotBeChanged : TangemSdkError(40801) - class Pin2CannotBeChanged : TangemSdkError(40802) - class Pin1CannotBeDefault : TangemSdkError(40803) - - //Sign Errors - class NoRemainingSignatures : TangemSdkError(40901) - /** - * This error is returned when a [com.tangem.commands.SignCommand] - * receives only empty hashes for signature. - */ - class EmptyHashes : TangemSdkError(40902) - /** - * This error is returned when a [com.tangem.commands.SignCommand] - * receives hashes of different lengths for signature. - */ - class HashSizeMustBeEqual : TangemSdkError(40903) - class CardIsEmpty : TangemSdkError(40904) - class SignHashesNotAvailable : TangemSdkError(40905) - /** - * 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 TooManyHashesInOneTransaction : TangemSdkError(40906) - - //Write Extra Issuer Data Errors - class ExtendedDataSizeTooLarge : TangemSdkError(41101) - - //General Errors - class NotPersonalized() : TangemSdkError(40001) - class NotActivated : TangemSdkError(40002) - class CardIsPurged : TangemSdkError(40003) - class Pin2OrCvcRequired : TangemSdkError(40004) - /** - * 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 : TangemSdkError(40005) - class DataSizeTooLarge : TangemSdkError(40006) - - /** - * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter - * (when the card's requires it), but the counter is missing. - */ - class MissingCounter : TangemSdkError(40007) - class OverwritingDataIsProhibited : TangemSdkError(40008) - class DataCannotBeWritten : TangemSdkError(40009) - class MissingIssuerPubicKey : TangemSdkError(40010) - - //SDK Errors - class UnknownError: TangemSdkError(50001) - /** - * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. - */ - class UserCancelled: TangemSdkError(50002) - /** - * This error is returned when [com.tangem.TangemSdk] was called with a new [Task], - * while a previous [Task] is still in progress. - */ - class Busy : TangemSdkError(50003) - /** - * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] - * is executed before performing other commands. - */ - class MissingPreflightRead : TangemSdkError(50004) - /** - * This error is returned when a [Task] expects a user to use a particular card, - * but the user tries to use a different card. - */ - class WrongCardNumber : TangemSdkError(50005) - /** - * This error is returned when a user scans a card of a [com.tangem.common.extensions.CardType] - * that is not specified in [Config.cardFilter]. - */ - class WrongCardType : TangemSdkError(50006) - /** - * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. - */ - class CardError : TangemSdkError(50007) - -} - diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt deleted file mode 100644 index 7b7116ba44..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.tangem.commands - -import com.tangem.CardSession -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag -import com.tangem.crypto.CryptoUtils - -/** - * Deserialized response from the Tangem card after [CheckWalletCommand]. - * - * @property cardId Unique Tangem card ID number - * @property salt Random salt generated by the card. - * @property walletSignature Challenge and salt signed with the wallet private key. - */ -class CheckWalletResponse( - val cardId: String, - val salt: ByteArray, - val walletSignature: ByteArray -) : CommandResponse { - - fun verify(curve: EllipticCurve, publicKey: ByteArray, challenge: ByteArray): Boolean { - return CryptoUtils.verify( - publicKey, - challenge + salt, - walletSignature, - curve) - } -} - -/** - * This command proves that the wallet private key from the card corresponds to the wallet public key. - * Standard challenge/response scheme is used. - * - * @property pin1 Hashed user’s pin 1 code to access the card. Default unhashed value: ‘000000’. - * @property cardId Unique Tangem card ID number - * @property challenge Random challenge generated by application - */ -class CheckWalletCommand( - private val curve: EllipticCurve, private val publicKey: ByteArray -) : Command() { - - private val challenge = CryptoUtils.generateRandomBytes(16) - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - super.run(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(TangemSdkError.VerificationFailed())) - } - } - } - } - } - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - return null - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CheckWalletResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return CheckWalletResponse( - cardId = decoder.decode(TlvTag.CardId), - salt = decoder.decode(TlvTag.Salt), - walletSignature = decoder.decode(TlvTag.Signature) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/Command.kt b/tangem-core/src/main/java/com/tangem/commands/Command.kt deleted file mode 100644 index 870cf3b446..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/Command.kt +++ /dev/null @@ -1,237 +0,0 @@ -package com.tangem.commands - -import com.tangem.* -import com.tangem.common.CompletionResult -import com.tangem.common.PinCode -import com.tangem.common.apdu.* -import com.tangem.common.extensions.toInt -import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.PinType - - -interface ApduSerializable { - /** - * 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 - */ - 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] - */ - fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): T -} - -/** - * Basic interface for a parsed response from [Command]. - */ -interface CommandResponse - -/** - * Basic class for Tangem card commands - */ -abstract class Command : ApduSerializable, CardSessionRunnable { - - open val performPreflightRead: Boolean = true - override val requiresPin2: Boolean = false - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - Log.i("Command", "Initializing ${this::class.java.simpleName}") - transceive(session, callback) - } - - open fun performPreCheck(card: Card): TangemSdkError? = null - - open fun mapError(card: Card?, error: TangemSdkError): TangemSdkError = error - - fun transceive(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val card = session.environment.card - if (session.environment.handleErrors && card != null) { - performPreCheck(card)?.let { error -> - callback(CompletionResult.Failure(error)) - return - } - } - if (requiresPin2 && session.environment.pin2?.isDefault == true) { - handlePin2(session, callback) - return - } - transceiveInternal(session, callback) - } - - private fun transceiveInternal(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val apdu = serialize(session.environment) - transceiveApdu(apdu, session) { result -> - when (result) { - is CompletionResult.Failure -> { - if (session.environment.handleErrors) { - val error = mapError(session.environment.card, result.error) - if (error is TangemSdkError.Pin1Required) { - session.environment.pin1 = null - handlePin1(session, callback) - return@transceiveApdu - } - if (error is TangemSdkError.Pin2OrCvcRequired) { - session.environment.pin2 = null - handlePin2(session, callback) - return@transceiveApdu - } - callback(CompletionResult.Failure(error)) - return@transceiveApdu - - } - callback(CompletionResult.Failure(result.error)) - } - is CompletionResult.Success -> { - try { - val response = deserialize(session.environment, result.data) - callback(CompletionResult.Success(response)) - } catch (error: TangemSdkError) { - callback(CompletionResult.Failure(error)) - } - } - } - } - } - - private fun transceiveApdu( - apdu: CommandApdu, - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - Log.i(this::class.simpleName!!, "transieve: ${Instruction.byCode(apdu.ins)}") - - session.send(apdu) { result -> - when (result) { - is CompletionResult.Success -> { - val responseApdu = result.data - - when (responseApdu.statusWord) { - StatusWord.ProcessCompleted, - StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.Pins12Changed, - StatusWord.Pin3Changed, StatusWord.Pins13Changed, StatusWord.Pins23Changed, - StatusWord.Pins123Changed -> { - callback(CompletionResult.Success(responseApdu)) - } - StatusWord.NeedPause -> { - // NeedPause is returned from the card whenever security delay is triggered. - val remainingTime = - deserializeSecurityDelay(responseApdu) - 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) - } - StatusWord.NeedEncryption -> { - Log.i(this::class.simpleName!!, "Establishing encryption") - when (session.environment.encryptionMode) { - EncryptionMode.NONE -> { - session.environment.encryptionKey = null - session.environment.encryptionMode = EncryptionMode.FAST - } - EncryptionMode.FAST -> { - session.environment.encryptionKey = null - session.environment.encryptionMode = EncryptionMode.STRONG - } - EncryptionMode.STRONG -> { - Log.e(this::class.simpleName!!, "Encryption doesn't work") - callback(CompletionResult.Failure(TangemSdkError.NeedEncryption())) - return@send - } - } - transceiveApdu(apdu, session, callback) - } - else -> { - val error = responseApdu.statusWord.toTangemSdkError() - if (error != null) { - callback(CompletionResult.Failure(error)) - } else { - callback(CompletionResult.Failure(TangemSdkError.UnknownError())) - } - } - } - } - is CompletionResult.Failure -> - if (result.error is TangemSdkError.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 - ): Int? { - val tlv = responseApdu.getTlvData() - return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() - } - - private fun handlePin1( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - session.pause() - session.viewDelegate.onPinRequested(PinType.Pin1) { pin1 -> - session.environment.pin1 = PinCode(pin1) - session.resume() - transceive(session, callback) - } - } - - private fun handlePin2( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - val currentPin1 = session.environment.pin1?.value ?: run { - callback(CompletionResult.Failure(TangemSdkError.Pin1Required())) - return - } - val currentPin2 = session.environment.pin2?.value ?: run { - callback(CompletionResult.Failure(TangemSdkError.Pin2OrCvcRequired())) - return - } - - val checkPinCommand = SetPinCommand(currentPin1, currentPin2) - checkPinCommand.run(session) { result -> - when (result) { - is CompletionResult.Failure -> { - session.environment.pin2 = null - getPin2FromDelegate(session, callback) - } - is CompletionResult.Success -> { - transceiveInternal(session, callback) - } - } - } - } - - private fun getPin2FromDelegate(session: CardSession, - callback: (result: CompletionResult) -> Unit) { - session.viewDelegate.onPinRequested(PinType.Pin2) { pin2 -> - session.environment.pin2 = PinCode(pin2) - transceive(session, callback) - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt deleted file mode 100644 index af6cce48b8..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class CreateWalletResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - /** - * Current status of the card [1 - Empty, 2 - Loaded, 3- Purged] - */ - val status: CardStatus, - /** - - */ - val walletPublicKey: ByteArray -) : CommandResponse - -/** - * 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. - * - * @property cardId CID, Unique Tangem card ID number. - */ -class CreateWalletCommand : Command() { - - override val requiresPin2 = true - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - - return when (card.status) { - CardStatus.Empty -> null - CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() - CardStatus.Loaded -> TangemSdkError.AlreadyCreated() - CardStatus.Purged -> TangemSdkError.CardIsPurged() - null -> TangemSdkError.CardError() - } - } - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams) { - return TangemSdkError.Pin2OrCvcRequired() - } - return error - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) - tlvBuilder.append(TlvTag.Cvc, environment.cvc) - return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): CreateWalletResponse { - val tlvData = apdu.getTlvData() - ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return CreateWalletResponse( - cardId = decoder.decode(TlvTag.CardId), - status = decoder.decode(TlvTag.Status), - walletPublicKey = decoder.decode(TlvTag.WalletPublicKey) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt deleted file mode 100644 index cb682706bb..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class OpenSessionResponse( - val sessionKeyB: ByteArray, - val uid: ByteArray -) : CommandResponse - -/** - * In case of encrypted communication, App should setup a session before calling any further command. - * [OpenSessionCommand] generates secret session_key that is used by both host and card - * to encrypt and decrypt commands’ payload. - */ -class OpenSessionCommand(private val sessionKeyA: ByteArray) : ApduSerializable { - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) - return CommandApdu( - Instruction.OpenSession.code, tlvBuilder.serialize(), 0, environment.encryptionMode.code - ) - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): OpenSessionResponse { - val tlvData = apdu.getTlvData() - ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return OpenSessionResponse( - sessionKeyB = decoder.decode(TlvTag.SessionKeyB), - uid = decoder.decode(TlvTag.Uid) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt deleted file mode 100644 index 3d0631f3df..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class PurgeWalletResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - /** - * Current status of the card [1 - Empty, 2 - Loaded, 3- Purged] - */ - val status: CardStatus -) : CommandResponse - -/** - * 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. - * @property cardId CID, Unique Tangem card ID number. - */ -class PurgeWalletCommand : Command() { - - override val requiresPin2 = true - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - if (card.settingsMask?.contains(Settings.ProhibitPurgeWallet) == true) { - return TangemSdkError.PurgeWalletProhibited() - } - - return when (card.status) { - CardStatus.Loaded -> null - CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() - CardStatus.Empty -> TangemSdkError.CardIsEmpty() - CardStatus.Purged -> TangemSdkError.CardIsPurged() - null -> TangemSdkError.CardError() - } - } - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams) { - return TangemSdkError.Pin2OrCvcRequired() - } - return error - } - - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) - return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PurgeWalletResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return PurgeWalletResponse( - cardId = decoder.decode(TlvTag.CardId), - status = decoder.decode(TlvTag.Status)) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt deleted file mode 100644 index 6ce78d98cc..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ /dev/null @@ -1,396 +0,0 @@ -package com.tangem.commands - -import com.google.gson.annotations.SerializedName -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -import com.tangem.commands.common.CardDeserializer -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.TlvTag -import java.util.* - -/** - * Determines which type of data is required for signing. - */ -data class SigningMethodMask(val rawValue: Int) { - - fun contains(signingMethod: SigningMethod): Boolean { - return if (rawValue and 0x80 == 0) { - signingMethod.code == rawValue - } else { - rawValue and (0x01 shl signingMethod.code) != 0 - } - } -} - -enum class SigningMethod(val code: Int) { - SignHash(0), - SignRaw(1), - SignHashValidateByIssuer(2), - SignRawValidateByIssuer(3), - SignHashValidateByIssuerWriteIssuerData(4), - SignRawValidateByIssuerWriteIssuerData(5), - SignPos(6) -} - -class SigningMethodMaskBuilder() { - - private val signingMethods = mutableSetOf() - - fun add(signingMethod: SigningMethod) { - signingMethods.add(signingMethod) - } - - fun build(): SigningMethodMask { - val rawValue: Int = when { - signingMethods.count() == 0 -> { - 0 - } - signingMethods.count() == 1 -> { - signingMethods.iterator().next().code - } - else -> { - signingMethods.fold( - 0x80, { acc, singingMethod -> acc + (0x01 shl singingMethod.code) } - ) - } - } - return SigningMethodMask(rawValue) - } -} - -/** - * Elliptic curve used for wallet key operations. - */ -enum class EllipticCurve(val curve: String) { - @SerializedName(value = "secp256k1") - Secp256k1("secp256k1"), - @SerializedName(value = "ed25519") - Ed25519("ed25519"); - - companion object { - private val values = values() - fun byName(curve: String): EllipticCurve? = values.find { it.curve == curve } - } -} - -/** - * Status of the card and its wallet. - */ -enum class CardStatus(val code: Int) { - NotPersonalized(0), - Empty(1), - Loaded(2), - Purged(3); - - companion object { - private val values = values() - fun byCode(code: Int): CardStatus? = values.find { it.code == code } - } -} - -/** - * Mask of products enabled on card - * @property rawValue Products mask values, - * while flags definitions and values are in [ProductMask.Companion] as constants. - */ -data class ProductMask(val rawValue: Int) { - - fun contains(product: Product): Boolean = (rawValue and product.code) != 0 - -} - -enum class Product(val code: Int) { - Note(0x01), - Tag(0x02), - IdCard(0x04), - IdIssuer(0x08) -} - -class ProductMaskBuilder() { - - private var productMaskValue = 0 - - fun add(product: Product) { - productMaskValue = productMaskValue or product.code - } - - fun build() = ProductMask(productMaskValue) - -} - -/** - * Stores and maps Tangem card settings. - * - * @property rawValue Card settings in a form of flags, - * while flags definitions and possible values are in [Settings]. - */ -data class SettingsMask(val rawValue: Int) { - fun contains(settings: Settings): Boolean = (rawValue and settings.code) != 0 -} - -enum class Settings(val code: Int) { - IsReusable(0x0001), - UseActivation(0x0002), - ProhibitPurgeWallet(0x0004), - UseBlock(0x0008), - - AllowSwapPIN(0x0010), - AllowSwapPIN2(0x0020), - UseCVC(0x0040), - ForbidDefaultPIN(0x0080), - - UseOneCommandAtTime(0x0100), - UseNdef(0x0200), - UseDynamicNdef(0x0400), - SmartSecurityDelay(0x0800), - - ProtocolAllowUnencrypted(0x1000), - ProtocolAllowStaticEncryption(0x2000), - - ProtectIssuerDataAgainstReplay(0x4000), - RestrictOverwriteIssuerDataEx(0x00100000), - - AllowSelectBlockchain(0x8000), - - DisablePrecomputedNdef(0x00010000), - - SkipSecurityDelayIfValidatedByLinkedTerminal(0x00080000), - SkipCheckPin2andCvcIfValidatedByIssuer(0x00040000), - SkipSecurityDelayIfValidatedByIssuer(0x00020000), - - RequireTermTxSignature(0x01000000), - RequireTermCertSignature(0x02000000), - CheckPIN3onCard(0x04000000) -} - - -class SettingsMaskBuilder() { - - private var settingsMaskValue = 0 - - fun add(settings: Settings) { - settingsMaskValue = settingsMaskValue or settings.code - } - - fun build() = SettingsMask(settingsMaskValue) - -} - -/** - * Detailed information about card contents. - */ -class CardData( - - /** - * Tangem internal manufacturing batch ID. - */ - val batchId: String?, - - /** - * Timestamp of manufacturing. - */ - val manufactureDateTime: Date?, - - /** - * Name of the issuer. - */ - val issuerName: String?, - - /** - * Name of the blockchain. - */ - val blockchainName: String?, - - /** - * Signature of CardId with manufacturer’s private key. - */ - val manufacturerSignature: ByteArray?, - - /** - * Mask of products enabled on card. - */ - val productMask: ProductMask?, - - /** - * Name of the token. - */ - val tokenSymbol: String?, - - /** - * Smart contract address. - */ - val tokenContractAddress: String?, - - /** - * Number of decimals in token value. - */ - val tokenDecimal: Int? -) - -/** - * Response for [ReadCommand]. Contains detailed card information. - */ -class Card( - - /** - * Unique Tangem card ID number. - */ - val cardId: String, - - /** - * Name of Tangem card manufacturer. - */ - val manufacturerName: String, - - /** - * Current status of the card. - */ - val status: CardStatus?, - - /** - * Version of Tangem COS. - */ - val firmwareVersion: String?, - - /** - * Public key that is used to authenticate the card against manufacturer’s database. - * It is generated one time during card manufacturing. - */ - val cardPublicKey: ByteArray?, - - /** - * Card settings defined by personalization (bit mask: 0 – Enabled, 1 – Disabled). - */ - val settingsMask: SettingsMask?, - - /** - * Public key that is used by the card issuer to sign IssuerData field. - */ - val issuerPublicKey: ByteArray?, - - /** - * Explicit text name of the elliptic curve used for all wallet key operations. - * Supported curves: ‘secp256k1’ and ‘ed25519’. - */ - val curve: EllipticCurve?, - - /** - * Total number of signatures allowed for the wallet when the card was personalized. - */ - val maxSignatures: Int?, - - /** - * Defines what data should be submitted to SIGN command. - */ - val signingMethods: SigningMethodMask?, - - /** - * Delay in seconds before COS executes commands protected by PIN2. - */ - val pauseBeforePin2: Int?, - - /** - * Public key of the blockchain wallet. - */ - val walletPublicKey: ByteArray?, - - /** - * Remaining number of [SignCommand] operations before the wallet will stop signing transactions. - */ - val walletRemainingSignatures: Int?, - - /** - * Total number of signed single hashes returned by the card in - * [SignCommand] responses since card personalization. - * Sums up array elements within all [SignCommand]. - */ - val walletSignedHashes: Int?, - - /** - * Any non-zero value indicates that the card experiences some hardware problems. - * User should withdraw the value to other blockchain wallet as soon as possible. - * Non-zero Health tag will also appear in responses of all other commands. - */ - val health: Int?, - - /** - * Whether the card requires issuer’s confirmation of activation. - * is "true" if the card requires activation, - * is 'false" if the card is activated or does not require activation - */ - val isActivated: Boolean, - - /** - * A random challenge generated by personalisation that should be signed and returned - * to COS by the issuer to confirm the card has been activated. - * This field will not be returned if the card is activated. - */ - val activationSeed: ByteArray?, - - /** - * Returned only if [SigningMethod.SignPos] enabling POS transactions is supported by card. - */ - val paymentFlowVersion: ByteArray?, - - /** - * This value can be initialized by terminal and will be increased by COS on execution of every [SignCommand]. - * For example, this field can store blockchain “nonce” for quick one-touch transaction on POS terminals. - * Returned only if [SigningMethod.SignPos] enabling POS transactions is supported by 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 - * with [TlvTag.TerminalTransactionSignature] parameter containing a correct signature of raw data - * to be signed made with [TlvTag.TerminalPublicKey]. - */ - val terminalIsLinked: Boolean, - - /** - * Detailed information about card contents. Format is defined by the card issuer. - * Cards complaint with Tangem Wallet application should have TLV format. - */ - val cardData: CardData? -) : CommandResponse - -/** - * 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 : Command() { - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams) { - return TangemSdkError.Pin1Required() - } - return error - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - /** - * [SessionEnvironment] stores the pin1 value. If no pin1 value was set, it will contain - * default value of ‘000000’. - * In order to obtain card’s 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, environment.pin1?.value) - tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) - return CommandApdu(Instruction.Read, tlvBuilder.serialize()) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt deleted file mode 100644 index 4d2301675a..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.tangem.commands - -import com.tangem.CardSession -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class ReadIssuerDataResponse( - - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - - /** - * Data defined by issuer. - */ - val issuerData: ByteArray, - - /** - * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). - * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: - * SHA256([cardId] | [issuerData]). - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of - * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: - * SHA256([cardId] | [issuerData] | [issuerDataCounter]). - */ - val issuerDataSignature: ByteArray, - - /** - * An optional counter that protect issuer data against replay attack. - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] - * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. - */ - val issuerDataCounter: Int? -) : CommandResponse - - -/** - * This command returns 512-byte Issuer Data field and its issuer’s 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 issuer’s attestation data. - * @property cardId CID, Unique Tangem card ID number. - */ -class ReadIssuerDataCommand( - val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : Command(), IssuerDataVerifier by verifier { - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - - val publicKey = issuerPublicKey ?: session.environment.card?.issuerPublicKey - - 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( - result.data.cardId, result.data.issuerData, result.data.issuerDataCounter - ) - if (verify(publicKey!!, result.data.issuerDataSignature, issuerDataToVerify)) { - callback(result) - } else { - callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) - } - } - } - } - } - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - issuerPublicKey ?: card.issuerPublicKey ?: return TangemSdkError.MissingIssuerPubicKey() - return null - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): ReadIssuerDataResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - 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) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt deleted file mode 100644 index 88e5af5569..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.tangem.commands - -import com.tangem.CardSession -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.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.TlvDecoder -import com.tangem.common.tlv.TlvTag -import java.io.ByteArrayOutputStream - -class ReadIssuerExtraDataResponse( - - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - - /** - * Size of all Issuer_Extra_Data field. - */ - val size: Int?, - - /** - * Data defined by issuer. - */ - val issuerData: ByteArray, - - /** - * Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). - * Issuer’s signature of SHA256-hashed [cardId] concatenated with [issuerData]: - * SHA256([cardId] | [issuerData]). - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] then signature of - * SHA256-hashed CID Issuer_Data concatenated with and [issuerDataCounter]: - * SHA256([cardId] | [issuerData] | [issuerDataCounter]). - */ - val issuerDataSignature: ByteArray?, - - /** - * An optional counter that protects issuer data against replay attack. - * When flag [Settings.ProtectIssuerDataAgainstReplay] set in [SettingsMask] - * then this value is mandatory and must increase on each execution of [WriteIssuerDataCommand]. - */ - val issuerDataCounter: Int? -) : CommandResponse - - -/** - * This command retrieves Issuer Extra Data field and its issuer’s 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. - */ -class ReadIssuerExtraDataCommand( - private val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : Command(), IssuerDataVerifier by verifier { - - private val issuerData = ByteArrayOutputStream() - private var offset: Int = 0 - private var issuerDataSize: Int = 0 - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - issuerPublicKey ?: card.issuerPublicKey ?: return TangemSdkError.MissingIssuerPubicKey() - return null - } - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - val publicKey = issuerPublicKey ?: session.environment.card?.issuerPublicKey - readIssuerData(session, publicKey!!, callback) - } - - private fun readIssuerData( - session: CardSession, publicKey: ByteArray, - callback: (result: CompletionResult) -> 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, publicKey, callback) - } else { - completeTask(result.data, publicKey, callback) - } - } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(result.error)) - } - } - } - } - - private fun completeTask( - data: ReadIssuerExtraDataResponse, publicKey: ByteArray, - callback: (result: CompletionResult) -> Unit - ) { - val dataToVerify = IssuerDataToVerify( - data.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(TangemSdkError.VerificationFailed())) - } - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) - tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): ReadIssuerExtraDataResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - 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 { - /** - * This mode value specifies that this command retrieves Issuer EXTRA data from the card - * (with value 0 the command will get instead simple Issuer Data from the card). - */ - const val EXTRA_DATA_MODE = 1 - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt deleted file mode 100644 index 58baea5b08..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class ReadUserDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - - /** - * Data defined by user's App. - */ - val userData: ByteArray, - - /** - * Data defined by user's App (confirmed by PIN2). - */ - val userProtectedData: ByteArray, - - /** - * Counter initialized by user's App and increased on every signing of new transaction - */ - val userCounter: Int, - - /** - * Counter initialized by user's App (confirmed by PIN2) and increased on every signing of new transaction - */ - val userProtectedCounter: Int - -) : CommandResponse - -/** - * 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. - */ -class ReadUserDataCommand : Command() { - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - return null - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val builder = TlvBuilder() - builder.append(TlvTag.CardId, environment.card?.cardId) - builder.append(TlvTag.Pin, environment.pin1?.value) - - return CommandApdu(Instruction.ReadUserData, builder.serialize()) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadUserDataResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - 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) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt deleted file mode 100644 index 6b7f12fa80..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/SetPinCommand.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.Instruction -import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.apdu.StatusWord -import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvDecoder -import com.tangem.common.tlv.TlvTag - -enum class SetPinStatus { - PinsNotChanged, - Pin1Changed, - Pin2Changed, - Pin3Changed, - Pins12Changed, - Pins13Changed, - Pins23Changed, - Pins123Changed, - ; - - companion object { - fun fromStatusWord(statusWord: StatusWord): SetPinStatus? { - return when (statusWord) { - StatusWord.ProcessCompleted -> PinsNotChanged - StatusWord.Pin1Changed -> Pin1Changed - StatusWord.Pin2Changed -> Pin2Changed - StatusWord.Pins12Changed -> Pins12Changed - StatusWord.Pin3Changed -> Pin3Changed - StatusWord.Pins13Changed -> Pins13Changed - StatusWord.Pins23Changed -> Pins23Changed - StatusWord.Pins123Changed -> Pins123Changed - else -> null - } - } - } -} - -class SetPinResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, - /** - * - */ - val status: SetPinStatus -) : CommandResponse - -class SetPinCommand( - private var newPin1: ByteArray, - private var newPin2: ByteArray, - private var newPin3: ByteArray? = null -) : Command() { - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) - tlvBuilder.append(TlvTag.Cvc, environment.cvc) - tlvBuilder.append(TlvTag.NewPin, newPin1) - tlvBuilder.append(TlvTag.NewPin2, newPin2) - tlvBuilder.append(TlvTag.NewPin3, newPin3) - return CommandApdu(Instruction.SetPin, tlvBuilder.serialize()) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SetPinResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val status = SetPinStatus.fromStatusWord(apdu.statusWord) - ?: throw TangemSdkError.DecodingFailed() - - val decoder = TlvDecoder(tlvData) - return SetPinResponse( - cardId = decoder.decode(TlvTag.CardId), - status = status - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt deleted file mode 100644 index 21c003b64b..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag -import com.tangem.crypto.sign - -/** - * @param cardId CID, Unique Tangem card ID number - * @param signature Signed hashes (array of resulting signatures) - * @param walletRemainingSignatures Remaining number of sign operations before the wallet will stop signing transactions. - * @param walletSignedHashes Total number of signed single hashes returned by the card in sign command responses. - * Sums up array elements within all SIGN commands - */ -class SignResponse( - val cardId: String, - val signature: ByteArray, - val walletRemainingSignatures: Int, - val walletSignedHashes: Int -) : CommandResponse - -/** - * Signs transaction hashes using a wallet private key, stored on the card. - * - * @property hashes Array of transaction hashes. - * @property cardId CID, Unique Tangem card ID number - */ -class SignCommand(private val hashes: Array) : Command() { - - override val requiresPin2 = true - - //TODO: Allow signing more than 10 hashes - - private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - if (card.walletRemainingSignatures == 0) { - return TangemSdkError.NoRemainingSignatures() - } - if (card.signingMethods?.contains(SigningMethod.SignHash) != true) { - return TangemSdkError.SignHashesNotAvailable() - } - if (hashSizes == 0) { - return TangemSdkError.EmptyHashes() - } - if (hashes.any { it.size != hashSizes }) { - return TangemSdkError.HashSizeMustBeEqual() - } - - return when (card.status) { - CardStatus.Loaded -> null - CardStatus.Empty -> TangemSdkError. - CardIsEmpty() - CardStatus.NotPersonalized -> TangemSdkError.NotPersonalized() - CardStatus.Purged -> TangemSdkError.CardIsPurged() - null -> TangemSdkError.CardError() - } - } - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams) { - return TangemSdkError.Pin2OrCvcRequired() - } - return error - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val dataToSign = flattenHashes() - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.Pin2, environment.pin2?.value) - 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()) - } - - private fun flattenHashes(): ByteArray { - return hashes.reduce { arr1, arr2 -> arr1 + arr2 } - } - - /** - * Application can optionally submit a public key Terminal_PublicKey in [SignCommand]. - * Submitted key is stored by the Tangem card if it differs from a previous submitted Terminal_PublicKey. - * The Tangem card will not enforce security delay if [SignCommand] will be called with - * 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( - 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(environment: SessionEnvironment, apdu: ResponseApdu): SignResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return SignResponse( - cardId = decoder.decode(TlvTag.CardId), - signature = decoder.decode(TlvTag.Signature), - walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures), - walletSignedHashes = decoder.decode(TlvTag.SignedHashes) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt deleted file mode 100644 index 62c44269e2..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class WriteIssuerDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String -) : CommandResponse - -/** - * This command writes 512-byte Issuer Data field and its issuer’s 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 issuer’s attestation data. - * @property cardId CID, Unique Tangem card ID number. - * @property issuerData Data provided by issuer. - * @property issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key (which is kept on card). - * @property issuerDataCounter An optional counter that protect issuer data against replay attack. - */ -class WriteIssuerDataCommand( - private val issuerData: ByteArray, - private val issuerDataSignature: ByteArray, - private val issuerDataCounter: Int? = null, - private val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : Command(), IssuerDataVerifier by verifier { - - override fun performPreCheck(card: Card): TangemSdkError? { - val publicKey = issuerPublicKey ?: card.issuerPublicKey - ?: return TangemSdkError.MissingIssuerPubicKey() - - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - if (issuerData.size > MAX_SIZE) { - return TangemSdkError.DataSizeTooLarge() - } - if (!isCounterValid(issuerDataCounter, card)) { - return TangemSdkError.MissingCounter() - } - if (!verifySignature(publicKey, card.cardId)) { - return TangemSdkError.VerificationFailed() - } - return null - } - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) { - return TangemSdkError.DataCannotBeWritten() - } - return error - } - - 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) == true - - 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, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData) - tlvBuilder.append(TlvTag.IssuerData, issuerData) - tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) - tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): WriteIssuerDataResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return WriteIssuerDataResponse( - cardId = decoder.decode(TlvTag.CardId) - ) - } - - companion object { - const val MAX_SIZE = 512 - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt deleted file mode 100644 index 6859aff915..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ /dev/null @@ -1,196 +0,0 @@ -package com.tangem.commands - -import com.tangem.CardSession -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -/** - * This command writes Issuer Extra Data field and its issuer’s 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 issuerData Data provided by issuer. - * @param startingSignature Issuer’s 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 Issuer’s 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. - */ -class WriteIssuerExtraDataCommand( - private val issuerData: ByteArray, - private val startingSignature: ByteArray, - private val finalizingSignature: ByteArray, - private val issuerDataCounter: Int? = null, - private val issuerPublicKey: ByteArray? = null, - verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : Command(), IssuerDataVerifier by verifier { - - var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData - var offset: Int = 0 - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - writeIssuerData(session, callback) - } - - override fun performPreCheck(card: Card): TangemSdkError? { - val publicKey = issuerPublicKey ?: card.issuerPublicKey - ?: return TangemSdkError.MissingIssuerPubicKey() - - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - if (issuerData.size > MAX_SIZE) { - return TangemSdkError.DataSizeTooLarge() - } - if (!isCounterValid(issuerDataCounter, card)) { - return TangemSdkError.MissingCounter() - } - if (!verifySignatures(publicKey, card.cardId)) { - return TangemSdkError.VerificationFailed() - } - return null - } - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) { - return TangemSdkError.DataCannotBeWritten() - } - if (error is TangemSdkError.InvalidState && - card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true) { - return TangemSdkError.OverwritingDataIsProhibited() - } - return error - } - - 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) == true - - private fun verifySignatures(publicKey: ByteArray, cardId: String): 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, - callback: (result: CompletionResult) -> Unit - ) { - - if (mode == IssuerDataMode.WriteExtraData) { - session.viewDelegate.onDelay( - issuerData.size, - offset, - SINGLE_WRITE_SIZE - ) - } - transceive(session) { result -> - when (result) { - is CompletionResult.Success -> { - when (mode) { - IssuerDataMode.InitializeWritingExtraData -> { - mode = IssuerDataMode.WriteExtraData - writeIssuerData(session, callback) - return@transceive - } - IssuerDataMode.WriteExtraData -> { - offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE - if (offset >= issuerData.size) { - mode = IssuerDataMode.FinalizeExtraData - } - writeIssuerData(session, callback) - return@transceive - } - IssuerDataMode.FinalizeExtraData -> { - callback(CompletionResult.Success(result.data)) - } - } - } - is CompletionResult.Failure -> { - if (session.environment.handleErrors) { - mapError(session.environment.card, result.error)?.let { - callback(CompletionResult.Failure(it)) - } - } - callback(CompletionResult.Failure(result.error)) - } - } - } - - - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Mode, mode) - - when (mode) { - IssuerDataMode.InitializeWritingExtraData -> { - tlvBuilder.append(TlvTag.Size, issuerData.size) - tlvBuilder.append(TlvTag.IssuerDataSignature, startingSignature) - tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - } - IssuerDataMode.WriteExtraData -> { - tlvBuilder.append(TlvTag.IssuerData, getDataToWrite()) - tlvBuilder.append(TlvTag.Offset, offset) - } - IssuerDataMode.FinalizeExtraData -> { - tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) - } - } - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) - } - - private fun getDataToWrite(): ByteArray = - issuerData.copyOfRange(offset, offset + calculatePartSize()) - - private fun calculatePartSize(): Int { - val bytesLeft = issuerData.size - offset - return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): WriteIssuerDataResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - return WriteIssuerDataResponse( - cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) - ) - } - - companion object { - const val SINGLE_WRITE_SIZE = 1524 - const val MAX_SIZE = 32 * 1024 - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt deleted file mode 100644 index fadb16731d..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.tangem.commands - -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -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.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class WriteUserDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String -) : CommandResponse - -/** - * This command writes to the card any 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. - */ -class WriteUserDataCommand(private val userData: ByteArray? = null, private val userProtectedData: ByteArray? = null, - private val userCounter: Int? = null, - private val userProtectedCounter: Int? = null) : Command() { - - override val requiresPin2 = true - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - if (userData?.size ?: 0 > MAX_SIZE || userProtectedData?.size ?: 0 > MAX_SIZE) { - return TangemSdkError.DataSizeTooLarge() - } - return null - } - - override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError { - if (error is TangemSdkError.InvalidParams) { - return TangemSdkError.Pin2OrCvcRequired() - } - return error - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val builder = TlvBuilder() - builder.append(TlvTag.CardId, environment.card?.cardId) - builder.append(TlvTag.Pin, environment.pin1?.value) - 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()) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteUserDataResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - return WriteUserDataResponse(TlvDecoder(tlvData).decode(TlvTag.CardId)) - } - - companion object{ - const val MAX_SIZE = 512 - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt b/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt deleted file mode 100644 index ccf809cf65..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/CardDeserializer.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.commands.common - -import com.tangem.TangemSdkError -import com.tangem.commands.Card -import com.tangem.commands.CardData -import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.tlv.Tlv -import com.tangem.common.tlv.TlvDecoder -import com.tangem.common.tlv.TlvTag - -class CardDeserializer() { - companion object { - fun deserialize(apdu: ResponseApdu): Card { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - - return Card( - cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", - manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", - status = decoder.decodeOptional(TlvTag.Status), - - 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), - signingMethods = 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) - ) - } - - private fun deserializeCardData(tlvData: List): CardData? { - val cardDataTlvs = tlvData.find { it.tag == TlvTag.CardData }?.let { - Tlv.deserialize(it.value) - } - if (cardDataTlvs.isNullOrEmpty()) return null - - val decoder = TlvDecoder(cardDataTlvs) - return CardData( - 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 = decoder.decodeOptional(TlvTag.TokenSymbol), - tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), - tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) - ) - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt b/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt deleted file mode 100644 index 2ebded1b6d..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataMode.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.commands.common - -import com.tangem.commands.WriteIssuerExtraDataCommand - -/** - * This enum specifies modes for [WriteIssuerExtraDataCommand]. - */ -enum class IssuerDataMode(val code: Byte) { - - - /** - * This mode is required to read issuer data from the card. - */ - ReadData(0), - - /** - * This mode is required to write issuer data to the card. - */ - WriteData(0), - /** - * This mode is required to read issuer extra data from the card. - */ - ReadExtraData(1), - /** - * This mode is required to initiate writing issuer extra data to the card. - */ - InitializeWritingExtraData(1), - /** - * With this mode, the command writes part of issuer extra data - * (block of a size [WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE]) to the card. - */ - WriteExtraData(2), - /** - * This mode is used after the issuer extra data was fully written to the card. - * Under this mode the command provides the issuer signature - * to confirm the validity of data that was written to card. - */ - FinalizeExtraData(3); - - companion object { - private val values = values() - fun byCode(code: Byte): IssuerDataMode? = values.find { it.code == code } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt b/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt deleted file mode 100644 index 03bf58d56e..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/IssuerDataVerifier.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.commands.common - -import com.tangem.common.tlv.TlvEncoder -import com.tangem.common.tlv.TlvTag -import com.tangem.crypto.CryptoUtils -import java.io.ByteArrayOutputStream - -interface IssuerDataVerifier { - fun verify( - issuerPublicKey: ByteArray, signature: ByteArray, issuerDataToVerify: IssuerDataToVerify - ): Boolean -} - -class IssuerDataToVerify( - val cardId: String, - val issuerData: ByteArray?, - val issuerDataCounter: Int? = null, - val issuerExtraDataSize: Int? = null -) - -class DefaultIssuerDataVerifier : IssuerDataVerifier { - override fun verify( - issuerPublicKey: ByteArray, - signature: ByteArray, - issuerDataToVerify: IssuerDataToVerify - ): Boolean { - - val tlvEncoder = TlvEncoder() - val dataToVerify = ByteArrayOutputStream() - dataToVerify.write(tlvEncoder.encodeValue(TlvTag.CardId, issuerDataToVerify.cardId)) - issuerDataToVerify.issuerData?.let { dataToVerify.write(it) } - issuerDataToVerify.issuerDataCounter?.let { counter -> - dataToVerify.write(tlvEncoder.encodeValue(TlvTag.IssuerDataCounter, counter)) - } - issuerDataToVerify.issuerExtraDataSize?.let { - dataToVerify.write(tlvEncoder.encodeValue(TlvTag.Size, it)) - } - return CryptoUtils.verify(issuerPublicKey, dataToVerify.toByteArray(), signature) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/ResponseConverter.kt b/tangem-core/src/main/java/com/tangem/commands/common/ResponseConverter.kt deleted file mode 100644 index 54d38798a1..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/ResponseConverter.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.tangem.commands.common - -import com.google.gson.* -import com.tangem.commands.* -import com.tangem.common.extensions.print -import com.tangem.common.extensions.toHexString -import java.lang.reflect.Type -import java.text.DateFormat -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -class ResponseConverter { - - val gson: Gson by lazy { init() } - - private val fieldConverter = ResponseFieldConverter() - - private fun init(): Gson { - val builder = GsonBuilder().apply { - registerTypeAdapter(ByteArray::class.java, ByteTypeAdapter(fieldConverter)) - registerTypeAdapter(SigningMethodMask::class.java, SigningMethodTypeAdapter(fieldConverter)) - registerTypeAdapter(SettingsMask::class.java, SettingsMaskTypeAdapter(fieldConverter)) - registerTypeAdapter(ProductMask::class.java, ProductMaskTypeAdapter(fieldConverter)) - registerTypeAdapter(Date::class.java, DateTypeAdapter()) - } - builder.setPrettyPrinting() - return builder.create() - } - - fun convertResponse(response: CommandResponse?): String = gson.toJson(response) -} - -class ByteTypeAdapter( - private val fieldConverter: ResponseFieldConverter -) : JsonSerializer { - override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return JsonPrimitive(fieldConverter.byteArrayToHex(src)) - } -} - -class SettingsMaskTypeAdapter( - private val fieldConverter: ResponseFieldConverter -) : JsonSerializer { - override fun serialize(src: SettingsMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return JsonArray().apply { - fieldConverter.settingsMaskList(src).forEach { add(it) } - } - } -} - -class ProductMaskTypeAdapter( - private val fieldConverter: ResponseFieldConverter -) : JsonSerializer { - override fun serialize(src: ProductMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return JsonArray().apply { - fieldConverter.productMaskList(src).forEach { add(it) } - } - } -} - -class SigningMethodTypeAdapter( - private val fieldConverter: ResponseFieldConverter -) : JsonSerializer { - override fun serialize(src: SigningMethodMask, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - return JsonArray().apply { - fieldConverter.signingMethodList(src).forEach { add(it) } - } - } -} - -class DateTypeAdapter : JsonSerializer { - override fun serialize(src: Date, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val formatter = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale("en_US")) - return JsonPrimitive(formatter.format(src).toString()) - } -} - -class ResponseFieldConverter { - - fun productMask(productMask: ProductMask?): String { - return productMaskList(productMask).print(wrap = false) - } - - fun productMaskList(productMask: ProductMask?): List { - val mask = productMask ?: return emptyList() - - return Product.values().filter { mask.contains(it) }.map { it.name } - } - - fun signingMethod(signingMask: SigningMethodMask?): String { - return signingMethodList(signingMask).print(wrap = false) - } - - fun signingMethodList(signingMask: SigningMethodMask?): List { - val mask = signingMask ?: return emptyList() - - return SigningMethod.values().filter { mask.contains(it) }.map { it.name } - } - - fun settingsMask(settingsMask: SettingsMask?): String { - return settingsMaskList(settingsMask).print(wrap = false) - } - - fun settingsMaskList(settingsMask: SettingsMask?): List { - val masks = settingsMask ?: return emptyList() - - return Settings.values().filter { masks.contains(it) }.map { it.name } - } - - fun byteArrayToHex(byteArray: ByteArray?): String? { - return byteArray?.toHexString() - } - - fun byteArrayToString(byteArray: ByteArray?): String? { - return if (byteArray == null) null else String(byteArray) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt deleted file mode 100644 index 6ff62b1021..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/network/NetworkUtils.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.commands.common.network - -import com.tangem.Log -import kotlinx.coroutines.delay -import java.io.IOException - -suspend fun retryIO( - times: Int = 3, - initialDelay: Long = 100, - maxDelay: Long = 1000, - factor: Double = 2.0, - block: suspend () -> T): T -{ - var currentDelay = initialDelay - repeat(times - 1) { - try { - return block() - } catch (e: IOException) { - Log.i("Network", e.localizedMessage) - } - delay(currentDelay) - currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) - } - return block() -} - -sealed class Result { - data class Success(val data: T) : Result() - data class Failure(val error: Throwable?) : Result() -} - -suspend fun performRequest(block: suspend () -> T): Result { - return try { - val result = retryIO { block() } - Result.Success(result) - } catch (exception: Exception) { - Result.Failure(exception) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt deleted file mode 100644 index ffbf990666..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/network/RetrofitBuilder.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.commands.common.network - -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory - - -class ApiTangem { - companion object { - const val TANGEM_ENDPOINT: String = "https://verify.tangem.com/" - - const val VERIFY = "verify" - const val VERIFY_AND_GET_INFO = "card/verify-and-get-info" - const val ARTWORK = "card/artwork" - } - -} - -private val moshi: Moshi by lazy { - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() -} - -fun createRetrofitInstance(baseUrl: String): Retrofit = - Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .build() \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt deleted file mode 100644 index 75507687c6..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/network/TangemApi.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.commands.common.network - - -import com.tangem.commands.common.network.ApiTangem.Companion.VERIFY_AND_GET_INFO -import com.tangem.commands.verifycard.CardVerifyAndGetInfo -import retrofit2.http.Body -import retrofit2.http.Headers -import retrofit2.http.POST - -interface TangemApi { - - @Headers("Content-Type: application/json") - @POST(VERIFY_AND_GET_INFO) - suspend fun getCardVerifyAndGetInfo(@Body requestBody: CardVerifyAndGetInfo.Request): CardVerifyAndGetInfo.Response - - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt b/tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt deleted file mode 100644 index 295536233d..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/common/network/TangemService.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.commands.common.network - -import com.tangem.commands.verifycard.CardVerifyAndGetInfo - -class TangemService { - - private val tangemApi: TangemApi by lazy { - createRetrofitInstance(ApiTangem.TANGEM_ENDPOINT).create(TangemApi::class.java) - } - - suspend fun verifyAndGetInfo( - cardId: String, - cardPublicKey: String - ): Result { - val requestsBody = CardVerifyAndGetInfo.Request() - requestsBody.requests = - listOf(CardVerifyAndGetInfo.Request.Item(cardId, cardPublicKey)) - - return performRequest { tangemApi.getCardVerifyAndGetInfo(requestsBody) } - } - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt deleted file mode 100644 index 912bb15e5f..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/DepersonalizeCommand.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.commands.personalization - -import com.tangem.SessionEnvironment -import com.tangem.commands.Command -import com.tangem.commands.CommandResponse -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.Instruction -import com.tangem.common.apdu.ResponseApdu - -data class DepersonalizeResponse(val success: Boolean) : CommandResponse - -/** - * Command available on SDK cards only - * - * This command resets card to initial state, - * erasing all data written during personalization and usage. - */ -class DepersonalizeCommand : Command() { - - override val performPreflightRead = false - - override fun serialize(environment: SessionEnvironment): CommandApdu { - return CommandApdu( - Instruction.Depersonalize, byteArrayOf() - ) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): DepersonalizeResponse { - return DepersonalizeResponse(true) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt deleted file mode 100644 index 6c6103556e..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/NdefEncoder.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.tangem.commands.personalization - -import com.tangem.commands.personalization.entities.NdefRecord -import java.io.ByteArrayOutputStream -import java.nio.charset.StandardCharsets - -/** - * Encodes information that is to be written on the card as an Ndef Tag. - */ -class NdefEncoder(private val ndefRecords: List, private val useDinamicNdef: Boolean) { - - fun encode(): ByteArray { - val bs = ByteArrayOutputStream() - // space for size - bs.write(0) - bs.write(0) - - for (i in ndefRecords.indices) { - val headerValue = (if (i == 0) 0x80 else 0x00) or (if (!useDinamicNdef && i == ndefRecords.size - 1) 0x40 else 0x00) - var value: ByteArray = ndefRecords[i].value.toByteArray(StandardCharsets.UTF_8) - encodeValue(ndefRecords[i], headerValue, bs) - } - - val result = bs.toByteArray() - result[0] = (result.size - 2 shr 8).toByte() - result[1] = (result.size - 2 and 0xFF).toByte() - return result - - } - - - private fun encodeValue(ndefRecord: NdefRecord, headerValue: Int, bs: ByteArrayOutputStream) { - when (ndefRecord.type) { - NdefRecord.Type.AAR -> { - bs.write((headerValue or 0x14)) // NDEF Header - bs.write(0x0F) // Length of the record type - bs.write(ndefRecord.valueInBytes.size) // Length of the payload data - bs.write(byteArrayOf(0x61.toByte(), 0x6E.toByte(), 0x64.toByte(), 0x72.toByte(), 0x6F.toByte(), 0x69.toByte(), 0x64.toByte(), 0x2E.toByte(), 0x63.toByte(), 0x6F.toByte(), 0x6D.toByte(), 0x3A.toByte(), - 0x70.toByte(), 0x6B.toByte(), 0x67.toByte())) // type name - bs.write(ndefRecord.valueInBytes) - } - NdefRecord.Type.URI -> { - bs.write((headerValue or 0x11)) // NDEF Header - bs.write(0x01) // Length of the record type - val uriIdentifierCode: Byte - val prefix: String - when { - ndefRecord.value.startsWith("http://www.") -> { - uriIdentifierCode = 0x01.toByte() - prefix = "http://www." - } - ndefRecord.value.startsWith("https://www.") -> { - uriIdentifierCode = 0x02.toByte() - prefix = "https://www." - } - ndefRecord.value.startsWith("http://") -> { - uriIdentifierCode = 0x03.toByte() - prefix = "http://" - } - ndefRecord.value.startsWith("https://") -> { - uriIdentifierCode = 0x04.toByte() - prefix = "https://" - } - else -> { - throw Exception() - } - } - val value = ndefRecord.value.substring(prefix.length).toByteArray() - bs.write(value.size + 1) // Length of the payload data - bs.write(0x55) // URI - bs.write(uriIdentifierCode.toInt()) // ? - bs.write(value) - } - NdefRecord.Type.TEXT -> { - bs.write((headerValue or 0x11)) // NDEF Header - bs.write(0x01) // Length of the record type - bs.write(ndefRecord.valueInBytes.size.toByte() + 1 + "en".length) // Length of the payload data - bs.write(0x54) // Text - bs.write(0x02) // UTF8(MSB=0)|"en".length - bs.write("en".toByteArray(StandardCharsets.US_ASCII)) - bs.write(ndefRecord.valueInBytes) - } - else -> throw Exception("Invalid NDEF record in config!") - } - } - - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt deleted file mode 100644 index 643b374a13..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/PersonalizeCommand.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.tangem.commands.personalization - -import com.tangem.CardSession -import com.tangem.EncryptionMode -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -import com.tangem.commands.Card -import com.tangem.commands.CardData -import com.tangem.commands.CardStatus -import com.tangem.commands.Command -import com.tangem.commands.common.CardDeserializer -import com.tangem.commands.personalization.entities.* -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.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvTag -import com.tangem.crypto.sign - -/** - * 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. - * @property config is a configuration file with all the card settings that are written on the card - * during personalization. - * @property issuer Issuer is a third-party team or company wishing to use Tangem cards. - * @property manufacturer Tangem Card Manufacturer. - * @property acquirer Acquirer is a trusted third-party company that operates proprietary - * (non-EMV) POS terminal infrastructure and transaction processing back-end. - */ -class PersonalizeCommand( - private val config: CardConfig, - private val issuer: Issuer, private val manufacturer: Manufacturer, - private val acquirer: Acquirer? = null -) : Command() { - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val encryptionMode = session.environment.encryptionMode - val encryptionKey = session.environment.encryptionKey - session.environment.encryptionMode = EncryptionMode.NONE - session.environment.encryptionKey = devPersonalizationKey - super.run(session) { result -> - session.environment.encryptionMode = encryptionMode - session.environment.encryptionKey = encryptionKey - callback(result) - } - } - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status != CardStatus.NotPersonalized) { - return TangemSdkError.AlreadyPersonalized() - } - return null - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - return CommandApdu(Instruction.Personalize, serializePersonalizationData(config)) - } - - override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { - return CardDeserializer.deserialize(apdu) - } - - private fun serializePersonalizationData(config: CardConfig): ByteArray { - val cardId = config.createCardId() ?: throw TangemSdkError.SerializeCommandError() - - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.CardId, cardId) - tlvBuilder.append(TlvTag.CurveId, config.curveID) - tlvBuilder.append(TlvTag.MaxSignatures, config.maxSignatures) - tlvBuilder.append(TlvTag.SigningMethod, config.signingMethods) - tlvBuilder.append(TlvTag.SettingsMask, config.createSettingsMask()) - tlvBuilder.append(TlvTag.PauseBeforePin2, config.pauseBeforePin2 / 10) - tlvBuilder.append(TlvTag.Cvc, config.cvc.toByteArray()) - if (!config.ndefRecords.isNullOrEmpty()) - tlvBuilder.append(TlvTag.NdefData, serializeNdef(config)) - - tlvBuilder.append(TlvTag.CreateWalletAtPersonalize, config.createWallet) - - tlvBuilder.append(TlvTag.NewPin, config.pin) - tlvBuilder.append(TlvTag.NewPin2, config.pin2) - tlvBuilder.append(TlvTag.NewPin3, config.pin3) - tlvBuilder.append(TlvTag.CrExKey, config.hexCrExKey) - tlvBuilder.append(TlvTag.IssuerDataPublicKey, issuer.dataKeyPair.publicKey) - tlvBuilder.append(TlvTag.IssuerTransactionPublicKey, issuer.transactionKeyPair.publicKey) - - tlvBuilder.append(TlvTag.AcquirerPublicKey, acquirer?.keyPair?.publicKey) - - tlvBuilder.append(TlvTag.CardData, serializeCardData(cardId, config.cardData)) - return tlvBuilder.serialize() - } - - private fun serializeNdef(config: CardConfig): ByteArray { - return NdefEncoder(config.ndefRecords, config.useDynamicNdef).encode() - } - - private fun serializeCardData(cardId: String, cardData: CardData): ByteArray { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Batch, cardData.batchId) - tlvBuilder.append(TlvTag.ProductMask, cardData.productMask) - tlvBuilder.append(TlvTag.ManufactureDateTime, cardData.manufactureDateTime) - tlvBuilder.append(TlvTag.IssuerId, issuer.id) - tlvBuilder.append(TlvTag.BlockchainId, cardData.blockchainName) - - if (cardData.tokenSymbol != null) { - tlvBuilder.append(TlvTag.TokenSymbol, cardData.tokenSymbol) - tlvBuilder.append(TlvTag.TokenContractAddress, cardData.tokenContractAddress) - tlvBuilder.append(TlvTag.TokenDecimal, cardData.tokenDecimal) - } - tlvBuilder.append( - TlvTag.CardIdManufacturerSignature, - cardId.hexToBytes().sign(manufacturer.keyPair.privateKey) - ) - return tlvBuilder.serialize() - } - - companion object { - val devPersonalizationKey = "1234".calculateSha256().copyOf(32) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt deleted file mode 100644 index ec4cff04b0..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Acquirer.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.commands.personalization.entities - -import com.tangem.KeyPair - -data class Acquirer( - val keyPair: KeyPair, - val name: String? = null, - val id: String? = null -) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt deleted file mode 100644 index 7241e6c4f8..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfig.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.tangem.commands.personalization.entities - -import com.tangem.commands.CardData -import com.tangem.commands.EllipticCurve -import com.tangem.commands.SigningMethodMask -import com.tangem.common.extensions.calculateSha256 - -data class NdefRecord( - val type: Type, - val value: String -) { - enum class Type { - URI, AAR, TEXT - } - - @delegate:Transient - val valueInBytes: ByteArray by lazy { value.toByteArray() } -} - -/** - * It is a configuration file with all the card settings that are written on the card - * during [PersonalizeCommand]. - */ -class CardConfig( - val issuerName: String? = null, - val acquirerName: String? = null, - val series: String? = null, - val startNumber: Long = 0, - val count: Int = 0, - pin: String, - pin2: String, - pin3: String, - val hexCrExKey: String?, - val cvc: String, - val pauseBeforePin2: Int, - val smartSecurityDelay: Boolean, - val curveID: EllipticCurve, - val signingMethods: SigningMethodMask, - val maxSignatures: Int, - val isReusable: Boolean, - val allowSwapPin: Boolean, - val allowSwapPin2: Boolean, - val useActivation: Boolean, - val useCvc: Boolean, - val useNdef: Boolean, - val useDynamicNdef: Boolean, - val useOneCommandAtTime: Boolean, - val useBlock: Boolean, - val allowSelectBlockchain: Boolean, - val forbidPurgeWallet: Boolean, - val protocolAllowUnencrypted: Boolean, - val protocolAllowStaticEncryption: Boolean, - val protectIssuerDataAgainstReplay: Boolean, - val forbidDefaultPin: Boolean, - val disablePrecomputedNdef: Boolean, - val skipSecurityDelayIfValidatedByIssuer: Boolean, - val skipCheckPIN2andCVCIfValidatedByIssuer: Boolean, - val skipSecurityDelayIfValidatedByLinkedTerminal: Boolean, - - val restrictOverwriteIssuerDataEx: Boolean, - - val requireTerminalTxSignature: Boolean, - val requireTerminalCertSignature: Boolean, - val checkPin3onCard: Boolean, - - val createWallet: Boolean, - - val cardData: CardData, - val ndefRecords: List -) { - - val pin: ByteArray = pin.calculateSha256() - val pin2: ByteArray = pin2.calculateSha256() - val pin3: ByteArray = pin3.calculateSha256() - - companion object -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt deleted file mode 100644 index 78e5709596..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/CardConfigExtensions.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.tangem.commands.personalization.entities - -import com.tangem.commands.Settings -import com.tangem.commands.SettingsMask -import com.tangem.commands.SettingsMaskBuilder - -internal fun CardConfig.createSettingsMask(): SettingsMask { - val builder = SettingsMaskBuilder() - - if (allowSwapPin) builder.add(Settings.AllowSwapPIN) - if (allowSwapPin2) builder.add(Settings.AllowSwapPIN2) - if (useCvc) builder.add(Settings.UseCVC) - if (isReusable) builder.add(Settings.IsReusable) - - if (useOneCommandAtTime) builder.add(Settings.UseOneCommandAtTime) - if (useNdef) builder.add(Settings.UseNdef) - if (useDynamicNdef) builder.add(Settings.UseDynamicNdef) - if (disablePrecomputedNdef) builder.add(Settings.DisablePrecomputedNdef) - - if (protocolAllowUnencrypted) builder.add(Settings.ProtocolAllowUnencrypted) - if (protocolAllowStaticEncryption) builder.add(Settings.ProtocolAllowStaticEncryption) - - if (forbidDefaultPin) builder.add(Settings.ForbidDefaultPIN) - - if (useActivation) builder.add(Settings.UseActivation) - - if (useBlock) builder.add(Settings.UseBlock) - if (smartSecurityDelay) builder.add(Settings.SmartSecurityDelay) - - if (protectIssuerDataAgainstReplay) builder.add(Settings.ProtectIssuerDataAgainstReplay) - - if (forbidPurgeWallet) builder.add(Settings.ProhibitPurgeWallet) - if (allowSelectBlockchain) builder.add(Settings.AllowSelectBlockchain) - - if (skipCheckPIN2andCVCIfValidatedByIssuer) builder.add(Settings.SkipCheckPin2andCvcIfValidatedByIssuer) - if (skipSecurityDelayIfValidatedByIssuer) builder.add(Settings.SkipSecurityDelayIfValidatedByIssuer) - - if (skipSecurityDelayIfValidatedByLinkedTerminal) builder.add(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal) - if (restrictOverwriteIssuerDataEx) builder.add(Settings.RestrictOverwriteIssuerDataEx) - - if (requireTerminalTxSignature) builder.add(Settings.RequireTermTxSignature) - - if (requireTerminalCertSignature) builder.add(Settings.RequireTermCertSignature) - - if (checkPin3onCard) builder.add(Settings.CheckPIN3onCard) - - return builder.build() -} - -internal fun CardConfig.createCardId(): String? { - if (series == null) return null - if (startNumber <= 0 || (series.length != 2 && series.length != 4)) return null - - val Alf = "ABCDEF0123456789" - fun checkSeries(series: String): Boolean { - val containsList = series.filter { Alf.contains(it) } - return containsList.length == series.length - } - if (!checkSeries(series)) return null - - val tail = if (series.length == 2) String.format("%013d", startNumber) else String.format("%011d", startNumber) - var cardId = (series + tail).replace(" ", "") - if (cardId.length != 15 || Alf.indexOf(cardId[0]) == -1 || Alf.indexOf(cardId[1]) == -1) - return null - - cardId += "0" - val length = cardId.length - var sum = 0 - for (i in 0 until length) { - // get digits in reverse order - var digit: Int - val cDigit = cardId[length - i - 1] - digit = if (cDigit in '0'..'9') cDigit - '0' else cDigit - 'A' - - // every 2nd number multiply with 2 - if (i % 2 == 1) digit *= 2 - sum += if (digit > 9) digit - 9 else digit - } - val lunh = (10 - sum % 10) % 10 - return cardId.substring(0, 15) + String.format("%d", lunh) -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt deleted file mode 100644 index ee7ab58fa7..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Issuer.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.commands.personalization.entities - -import com.tangem.KeyPair - -data class Issuer( - val name: String, - val id: String, - val dataKeyPair: KeyPair, - val transactionKeyPair: KeyPair -) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt b/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt deleted file mode 100644 index 9e30287bb0..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/personalization/entities/Manufacturer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.commands.personalization.entities - -import com.tangem.KeyPair - -data class Manufacturer( - val keyPair: KeyPair, - val name: String? = null -) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt deleted file mode 100644 index bbb3454e66..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardCommand.kt +++ /dev/null @@ -1,159 +0,0 @@ -package com.tangem.commands.verifycard - -import com.tangem.CardSession -import com.tangem.SessionEnvironment -import com.tangem.TangemSdkError -import com.tangem.commands.Card -import com.tangem.commands.CardStatus -import com.tangem.commands.Command -import com.tangem.commands.CommandResponse -import com.tangem.commands.common.network.Result -import com.tangem.commands.common.network.TangemService -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.extensions.CardType -import com.tangem.common.extensions.getType -import com.tangem.common.extensions.toHexString -import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvDecoder -import com.tangem.common.tlv.TlvTag -import com.tangem.crypto.CryptoUtils -import kotlinx.coroutines.launch - -class VerifyCardResponse( - val cardId: String, - val verificationState: VerifyCardState? = null, - val artworkInfo: ArtworkInfo? = null, - internal val salt: ByteArray, - internal val cardSignature: ByteArray -) : CommandResponse { - - fun verify(publicKey: ByteArray, challenge: ByteArray): Boolean { - return CryptoUtils.verify( - publicKey, - challenge + salt, - cardSignature - ) - } -} - -enum class VerifyCardState { - VerifiedOnline, - VerifiedOffline, -} - -class VerifyCardCommand(private val onlineVerification: Boolean) : Command() { - - private val challenge = CryptoUtils.generateRandomBytes(16) - private val tangemService = TangemService() - - override fun run( - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - val card = session.environment.card - val cardPublicKey = card?.cardPublicKey - if (cardPublicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead())) - return - } - super.run(session) { result -> - when (result) { - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(result.error)) - } - is CompletionResult.Success -> { - val response = result.data - val verified = response.verify(cardPublicKey, challenge) - if (!verified) { - callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) - return@run - } - if (!onlineVerification || card.getType() != CardType.Release) { - callback( - CompletionResult.Success( - VerifyCardResponse( - response.cardId, VerifyCardState.VerifiedOffline, null, - response.salt, response.cardSignature - ) - ) - ) - } else { - verify(result.data, card.cardId, cardPublicKey, session, callback) - } - } - } - } - } - - private fun verify( - response: VerifyCardResponse, cardId: String, cardPublicKey: ByteArray, - session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - session.scope.launch { - val result = tangemService.verifyAndGetInfo(cardId, cardPublicKey.toHexString()) - - when (result) { - is Result.Success -> { - if (result.data.results?.firstOrNull()?.passed == true) { - callback( - CompletionResult.Success( - VerifyCardResponse( - response.cardId, VerifyCardState.VerifiedOnline, response.artworkInfo, - response.salt, response.cardSignature - ) - ) - ) - } else { - callback(CompletionResult.Failure(TangemSdkError.VerificationFailed())) - } - } - is Result.Failure -> { - callback( - CompletionResult.Success( - VerifyCardResponse( - response.cardId, VerifyCardState.VerifiedOffline, null, - response.salt, response.cardSignature - ) - ) - ) - } - } - } - } - - override fun performPreCheck(card: Card): TangemSdkError? { - if (card.status == CardStatus.NotPersonalized) { - return TangemSdkError.NotPersonalized() - } - if (card.isActivated) { - return TangemSdkError.NotActivated() - } - return null - } - - override fun serialize(environment: SessionEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, environment.pin1?.value) - tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) - tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu(Instruction.VerifyCard, tlvBuilder.serialize()) - } - - override fun deserialize( - environment: SessionEnvironment, - apdu: ResponseApdu - ): VerifyCardResponse { - val tlvData = apdu.getTlvData() ?: throw TangemSdkError.DeserializeApduFailed() - - val decoder = TlvDecoder(tlvData) - return VerifyCardResponse( - cardId = decoder.decode(TlvTag.CardId), - salt = decoder.decode(TlvTag.Salt), - cardSignature = decoder.decode(TlvTag.CardSignature) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt b/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt deleted file mode 100644 index 8bc7efd052..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/verifycard/VerifyCardRequest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.commands.verifycard - -import com.squareup.moshi.JsonClass -import java.text.SimpleDateFormat -import java.util.* - - -class CardVerifyAndGetInfo { - - @JsonClass(generateAdapter = true) - data class Request( - var requests: List? = null - ) { - @JsonClass(generateAdapter = true) - data class Item( - var CID: String = "", - var publicKey: String = "" - ) - } - - @JsonClass(generateAdapter = true) - data class Response( - var results: List? = null - ) { - @JsonClass(generateAdapter = true) - data class Item( - var error: String? = null, - var CID: String = "", - var passed: Boolean = false, - var batch: String = "", - var artwork: ArtworkInfo? = null, - var substitution: SubstitutionInfo? = null - ) { - - @JsonClass(generateAdapter = true) - data class SubstitutionInfo( - var data: String? = null, - var signature: String? = null - ) - - } - } -} - -@JsonClass(generateAdapter = true) -data class ArtworkInfo( - var id: String = "", - var hash: String = "", - var date: String = "" -) { - fun getUpdateDate(): Date? { - return try { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US).parse(date) - } catch (e: Exception) { - e.printStackTrace() - null - } - } - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt b/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt deleted file mode 100644 index 26262ccddb..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/CardValuesStorage.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.common - -import com.squareup.sqldelight.EnumColumnAdapter -import com.squareup.sqldelight.db.SqlDriver -import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver -import com.tangem.CardValues -import com.tangem.CardValuesEntityQueries -import com.tangem.Database -import com.tangem.VerificationState - -interface CardValuesStorage { - - fun saveValues(cardId: String, - isPin1Default: Boolean, isPin2Default: Boolean, - cardVerification: VerificationState?, - cardValidation: VerificationState?, - codeVerification: VerificationState?) - - fun getValues(cardId: String): CardValues? - -} - -class CardValuesDbStorage(driver: SqlDriver) : CardValuesStorage { - - private val cardValuesQueries: CardValuesEntityQueries - - init { - val database = Database(driver, cardValuesAdapter = CardValues.Adapter( - cardVerificationAdapter = EnumColumnAdapter(), - cardValidationAdapter = EnumColumnAdapter(), - codeVerificationAdapter = EnumColumnAdapter() - )) - cardValuesQueries = database.cardValuesEntityQueries - } - - override fun saveValues(cardId: String, - isPin1Default: Boolean, isPin2Default: Boolean, - cardVerification: VerificationState?, - cardValidation: VerificationState?, - codeVerification: VerificationState?) { - cardValuesQueries.insertOrReplace( - cardId, - isPin1Default, isPin2Default, - cardVerification, cardValidation, codeVerification - ) - } - - override fun getValues(cardId: String): CardValues? = - cardValuesQueries.selectByCardId(cardId).executeAsOneOrNull() - - companion object { - fun initJvm() = CardValuesDbStorage( - JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).also { Database.Schema.create(it) } - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt b/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt deleted file mode 100644 index 52bf7a9351..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.common - -import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult.Success - -/** - * Response class encapsulating successful and failed results. - * @param T Type of data that is returned in [Success]. - */ -sealed class CompletionResult { - class Success(val data: T) : CompletionResult() - class Failure(val error: TangemSdkError) : CompletionResult() -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/PinCode.kt b/tangem-core/src/main/java/com/tangem/common/PinCode.kt deleted file mode 100644 index 0262089def..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/PinCode.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.common - -import com.tangem.common.extensions.calculateSha256 - -class PinCode( - val value: ByteArray, - val isDefault: Boolean -) { - constructor(value: String, isDefault: Boolean = false): this(value.calculateSha256(), isDefault) -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt b/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt deleted file mode 100644 index 3ba365247d..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt +++ /dev/null @@ -1,14 +0,0 @@ -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.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]. - */ -interface TerminalKeysService { - fun getKeys(): KeyPair -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt deleted file mode 100644 index a152a5f2d2..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.tangem.common.apdu - -import com.tangem.EncryptionMode -import com.tangem.common.extensions.calculateCrc16 -import com.tangem.common.extensions.toByteArray -import com.tangem.crypto.encrypt -import java.io.ByteArrayOutputStream - -/** - * Class that provides conversion of serialized request and Instruction code - * to a raw data that can be sent to the card. - * - * @property ins Instruction code that determines the type of request for the card. - * @property tlvs Tlvs encoded to a [ByteArray] that are to be sent to the card. - */ -class CommandApdu( - - val ins: Int, - private val tlvs: ByteArray, - - private val p1: Int, - private val p2: Int, - - private val le: Int = 0x00, - - private val cla: Int = ISO_CLA -) { - - constructor( - instruction: Instruction, - tlvs: ByteArray - ) : this( - instruction.code, - tlvs, - 0, - 0 - ) - - /** - * Request converted to a raw data - */ - val apduData: ByteArray - - init { - apduData = toBytes() - } - - - private fun toBytes(): ByteArray { - val data = tlvs - - val byteStream = ByteArrayOutputStream() - byteStream.write(cla) - byteStream.write(ins) - byteStream.write(p1) - byteStream.write(p2) - if (data.isNotEmpty()) { - byteStream.writeLength(data.size) - byteStream.write(data) - } - return byteStream.toByteArray() - } - - private fun ByteArrayOutputStream.writeLength(lc: Int) { - this.write(0) - this.write(lc shr 8) - this.write(lc and 0xFF) - } - - - fun encrypt( - encryptionMode: EncryptionMode, - encryptionKey: ByteArray? - ): CommandApdu { - - if (encryptionKey == null || p1 != EncryptionMode.NONE.code) { - return this - } - val crc: ByteArray = tlvs.calculateCrc16() - val dataToEncrypt = tlvs.size.toByteArray(2) + crc + tlvs - val encryptedData = dataToEncrypt.encrypt(encryptionKey) - - return CommandApdu(ins, encryptedData, encryptionMode.code, p2, le, cla) - } - - companion object { - const val ISO_CLA = 0x00 - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt deleted file mode 100644 index 02a5eabf7b..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.common.apdu - -/** - * Instruction code that determines the type of the command that is sent to the Tangem card. - * It is used in the construction of [com.tangem.common.apdu.CommandApdu]. - */ -enum class Instruction(var code: Int) { - Unknown(0x00), - Personalize(0xF1), - Read(0xF2), - VerifyCard(0xF3), - ValidateCard(0xF4), - VerifyCode(0xF5), - WriteIssuerData(0xF6), - ReadIssuerData(0xF7), - CreateWallet(0xF8), - CheckWallet(0xF9), - SetPin(0xFA), - Sign(0xFB), - PurgeWallet(0xFC), - Activate(0xFE), - OpenSession(0xFF), - WriteUserData(0xE0), - ReadUserData(0xE1), - Depersonalize(0xE3); - - - companion object { - private val values = values() - fun byCode(code: Int): Instruction = values.find { it.code == code } ?: Unknown - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt deleted file mode 100644 index afc869f64a..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.tangem.common.apdu - -import com.tangem.TangemSdkError -import com.tangem.common.extensions.calculateCrc16 -import com.tangem.common.tlv.Tlv -import com.tangem.crypto.decrypt -import java.io.ByteArrayInputStream - -/** - * Stores response data from the card and parses it to [Tlv] and [StatusWord]. - * - * @property data Raw response from the card. - * @property sw Status word code, reflecting the status of the response. - * @property statusWord Parsed status word. - */ -class ResponseApdu(private val data: ByteArray) { - - private val sw1: Int = 0x00FF and data[data.size - 2].toInt() - private val sw2: Int = 0x00FF and data[data.size - 1].toInt() - - val sw: Int = sw1 shl 8 or sw2 - - val statusWord: StatusWord = StatusWord.byCode(sw) - - /** - * Converts raw response data to the list of TLVs. - * - * @param encryptionKey key to decrypt response. - * (Encryption / decryption functionality is not implemented yet.) - */ - fun getTlvData(): List? { - return if (data.size <= 2) { - null - } else { - Tlv.deserialize(data.copyOf(data.size - 2)) - } - } - - fun decrypt(encryptionKey: ByteArray?): ResponseApdu { - if (encryptionKey == null) return this - - //nothing to decrypt - if (data.size < 18) return this - - val responseData = data.copyOf(data.size - 2) - - val decryptedData: ByteArray = responseData.decrypt(encryptionKey) - - val inputStream = ByteArrayInputStream(decryptedData) - val baLength = ByteArray(2) - inputStream.read(baLength) - val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) - if (length > decryptedData.size - 4) throw TangemSdkError.InvalidResponse() - val baCRC = ByteArray(2) - inputStream.read(baCRC) - val answerData = ByteArray(length) - inputStream.read(answerData) - val crc: ByteArray = answerData.calculateCrc16() - if (!baCRC.contentEquals(crc)) throw TangemSdkError.InvalidResponse() - - return ResponseApdu(answerData + data[data.size - 2] + data[data.size - 1]) - } - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt b/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt deleted file mode 100644 index 0b60f3ff91..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/apdu/StatusWord.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.common.apdu - -import com.tangem.TangemSdkError - -/** - * Part of a response from the card, shows the status of the operation - */ -enum class StatusWord(val code: Int) { - - ProcessCompleted(0x9000), - InvalidParams(0x6A86), - ErrorProcessingCommand(0x6286), - InvalidState(0x6985), - - //PinsNotChanged(0x9000) is equal to ProcessCompleted(0x9000) - Pin1Changed(0x9001), - Pin2Changed(0x9002), - Pins12Changed(0x9003), - Pin3Changed(0x9004), - Pins13Changed(0x9005), - Pins23Changed(0x9006), - Pins123Changed(0x9007), - - - InsNotSupported(0x6D00), - NeedEncryption(0x6982), - NeedPause(0x9789), - Unknown(0x0000); - - companion object { - private val values = values() - fun byCode(code: Int): StatusWord = values.find { it.code == code } ?: Unknown - } -} - -fun StatusWord.toTangemSdkError(): TangemSdkError? { - return when (this) { - StatusWord.ProcessCompleted, StatusWord.Pin1Changed, - StatusWord.Pin2Changed, StatusWord.Pins12Changed, StatusWord.Pin3Changed, - StatusWord.Pins13Changed, StatusWord.Pins23Changed, StatusWord.Pins123Changed -> null - StatusWord.NeedPause -> null - StatusWord.InvalidParams -> TangemSdkError.InvalidParams() - StatusWord.ErrorProcessingCommand -> TangemSdkError.ErrorProcessingCommand() - StatusWord.InvalidState -> TangemSdkError.InvalidState() - StatusWord.InsNotSupported -> TangemSdkError.InsNotSupported() - StatusWord.NeedEncryption -> TangemSdkError.NeedEncryption() - StatusWord.Unknown -> TangemSdkError.UnknownStatus() - } -} diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/BigDecimal.kt b/tangem-core/src/main/java/com/tangem/common/extensions/BigDecimal.kt deleted file mode 100644 index 6d23ffee09..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/extensions/BigDecimal.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.common.extensions - -import java.math.BigDecimal - -fun BigDecimal.isZero() : Boolean { - return this.compareTo(BigDecimal.ZERO) == 0 -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt deleted file mode 100644 index 226018b9d1..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.tangem.common.extensions - -import org.spongycastle.crypto.digests.RIPEMD160Digest -import org.spongycastle.jce.ECNamedCurveTable -import java.nio.ByteBuffer -import java.security.MessageDigest -import java.util.* -import kotlin.experimental.and -import kotlin.experimental.xor - -/** - * Extension functions for [ByteArray]. - */ - -fun ByteArray.toHexString(): String = joinToString("") { "%02X".format(it) } - -fun ByteArray.toUtf8(): String = String(this).removeSuffix("\u0000") - -fun ByteArray.toInt(): Int { - return when (this.size) { - 1 -> (this[0] and 0xFF.toByte()).toInt() - 2 -> ByteBuffer.wrap(this).short.toInt() - 4 -> ByteBuffer.wrap(this).int - else -> throw IllegalArgumentException("Length must be 1,2 or 4. Length = " + this.size) - } -} - -fun ByteArray.toDate(): Date { - val year = copyOfRange(0, 2).toInt() - val month = if (this.size > 2) this[2] - 1 else 0 - val day = if (this.size > 3) this[3].toInt() else 0 - val cd = Calendar.getInstance() - cd.set(year, month, day, 0, 0, 0) - return cd.time -} - -fun ByteArray.calculateSha512(): ByteArray = MessageDigest.getInstance("SHA-512").digest(this) - -fun ByteArray.calculateSha256(): ByteArray = MessageDigest.getInstance("SHA-256").digest(this) - -fun ByteArray.calculateRipemd160(): ByteArray { - val digest = RIPEMD160Digest() - digest.update(this, 0, this.size) - val out = ByteArray(20) - digest.doFinal(out, 0) - return out -} - -fun ByteArray.toCompressedPublicKey(): ByteArray { - return if (this.size == 65) { - val spec = ECNamedCurveTable.getParameterSpec("secp256k1") - val publicKeyPoint = spec.curve.decodePoint(this) - publicKeyPoint.getEncoded(true) - } else { - this - } -} - -fun ByteArray.calculateCrc16(): ByteArray { - var chBlock: Byte - // STEP 1 Initialize the CRC-16 value - var wCRC = 0x6363 // ITU-V.41 - var i = 0 - // STEP 2 Update data and Calucuate their CRC - do { - chBlock = this.get(i++) - chBlock = chBlock xor (wCRC and 0x00FF).toByte() - val chBlockInt = (chBlock.toInt() xor (chBlock.toInt() shl 4)) - wCRC = wCRC shr 8 xor (chBlockInt and 0xFF shl 8) and 0xFFFF xor (chBlockInt and 0xFF shl 3 and 0xFFFF) xor (chBlockInt and 0xFF shr 4 and 0xFFFF) - // (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4); - } while (i < this.size) - - return byteArrayOf((wCRC and 0xFF).toByte(), (wCRC and 0xFFFF shr 8).toByte()) -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/Card.kt b/tangem-core/src/main/java/com/tangem/common/extensions/Card.kt deleted file mode 100644 index 97243f3655..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/extensions/Card.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.common.extensions - -import com.tangem.commands.Card - -fun Card.getType(): CardType { - val firmware = this.firmwareVersion ?: return CardType.Unknown - return when { - firmware.endsWith("d SDK") -> { - CardType.Sdk - } - firmware.endsWith("r") -> { - CardType.Release - } - else -> { - CardType.Unknown - } - } -} - -enum class CardType { - Sdk, Release, Unknown -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt b/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt deleted file mode 100644 index 0e8aa998da..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/extensions/IntExtensions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.common.extensions - -import java.nio.ByteBuffer - -fun Int.toByteArray(size: Int = Int.SIZE_BYTES): ByteArray { - if (size == Int.SIZE_BYTES) { - val buffer = ByteBuffer.allocate(size) - buffer.putInt(this) - return buffer.array() - } else if (size == Short.SIZE_BYTES){ - return byteArrayOf( - (this ushr 8).toByte(), - this.toByte()) - } - return byteArrayOf() -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/List.kt b/tangem-core/src/main/java/com/tangem/common/extensions/List.kt deleted file mode 100644 index 2579809de6..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/extensions/List.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.common.extensions - -fun List.print(delimiter: String = ", ", wrap: Boolean = true): String { - val builder = StringBuilder() - forEach { builder.append(it).append(delimiter) } - val length = builder.length - if (length > delimiter.length) { - builder.delete(length - delimiter.length, length) - } - val result = builder.toString() - - return if (wrap) "[$result]" else result -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/String.kt b/tangem-core/src/main/java/com/tangem/common/extensions/String.kt deleted file mode 100644 index fbddb5829c..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/extensions/String.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.common.extensions - -import java.nio.charset.Charset -import java.security.MessageDigest - -/** - * Extension functions for [String]. - */ -fun String.calculateSha256(): ByteArray { - val sha256 = MessageDigest.getInstance("SHA-256") - val data = this.toByteArray(Charset.forName("UTF-8")) - return sha256.digest(data) -} - -fun String.calculateSha512(): ByteArray { - val sha = MessageDigest.getInstance("SHA-512") - val data = this.toByteArray(Charset.forName("UTF-8")) - return sha.digest(data) -} - -fun String.hexToBytes(): ByteArray { - return ByteArray(this.length / 2) - { i -> - Integer.parseInt(this.substring(2 * i, 2 * i + 2), 16).toByte() - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt deleted file mode 100644 index 5b0f5a47c5..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.tangem.common.tlv - -import com.tangem.Log -import com.tangem.common.extensions.toHexString -import java.io.ByteArrayInputStream -import java.io.IOException - -/** - * The data converted to the Tag Length Value protocol. - */ -class Tlv { - - val tag: TlvTag - val value: ByteArray - val tagRaw: Int - - constructor(tagCode: Int, value: ByteArray = byteArrayOf()) { - this.tag = TlvTag.byCode(tagCode) - this.tagRaw = tagCode - this.value = value - } - - constructor(tag: TlvTag, value: ByteArray = byteArrayOf()) { - this.tag = tag - this.tagRaw = tag.code - this.value = value - } - - - companion object { - private fun tlvFromBytes(stream: ByteArrayInputStream): Tlv? { - val code = stream.read() - if (code == -1) return null - var len = stream.read() - if (len == -1) - throw IOException("Can't read TLV") - if (len == 0xFF) { - val lenH = stream.read() - if (lenH == -1) - throw IOException("Can't read TLV") - len = stream.read() - if (len == -1) - throw IOException("Can't read TLV") - len = len or (lenH shl 8) - } - val value = ByteArray(len) - if (len > 0) { - if (len != stream.read(value)) { - throw IOException("Can't read TLV") - } - } - val tag = TlvTag.byCode(code) - return if (tag == TlvTag.Unknown) Tlv(code, value) else Tlv(tag, value) - } - - - fun deserialize(data: ByteArray, nfcV: Boolean = false): List? { - val tlvList = mutableListOf() - val stream = ByteArrayInputStream(data) - var tlv: Tlv? - do { - try { - tlv = tlvFromBytes(stream) - if (tlv != null) tlvList.add(tlv) - } catch (e: IOException) { - Log.e(this::class.java.simpleName,"TLVError: " + e.message) - if (nfcV) break else return null - } - - } while (tlv != null) - return tlvList - } - } - - override fun toString(): String { - return "${this.tag} ($tagRaw): ${value.toHexString()}" - } - -} - -fun List.serialize(): ByteArray = - this.map { it.serialize() }.reduce { arr1, arr2 -> arr1 + arr2 } - -fun Tlv.serialize(): ByteArray { - val tag = byteArrayOf(this.tag.code.toByte()) - val length = getLengthInBytes(this.value.size) - val value = if (this.value.isNotEmpty()) this.value else byteArrayOf(0x00) - return tag + length + value -} - -private fun getLengthInBytes(tlvLength: Int): ByteArray { - return if (tlvLength > 0) { - if (tlvLength > 0xFE) { - byteArrayOf( - 0xFF.toByte(), - (tlvLength shr 8 and 0xFF).toByte(), - (tlvLength and 0xFF).toByte() - ) - } else { - byteArrayOf((tlvLength and 0xFF).toByte()) - } - } else { - byteArrayOf() - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt deleted file mode 100644 index 8c7ec12fad..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.common.tlv - -import com.tangem.Log - -class TlvBuilder { - private val tlvs = mutableListOf() - private val encoder = TlvEncoder() - - internal inline fun append(tag: TlvTag, value: T?) { - if (value == null) return - - tlvs.add(encoder.encode(tag, value)) - } - - fun serialize(): ByteArray { - Log.v("TLV", - "Data encoded to TLVs:\n${tlvs.joinToString("\n")}") - return tlvs.serialize() - } - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt deleted file mode 100644 index 866fbbd41c..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.tangem.common.tlv - -import com.tangem.Log -import com.tangem.TangemSdkError -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 java.util.* - -/** - * Maps value fields in [Tlv] from raw [ByteArray] to concrete classes - * according to their [TlvTag] and corresponding [TlvValueType]. - * - * @property tlvList List of TLVs, which values are to be converted to particular classes. - */ -class TlvDecoder(val tlvList: List) { - - init { - Log.v("TLV", - "Decoding data from TLV:\n${tlvList.joinToString("\n")}") - } - - /** - * Finds [Tlv] by its [TlvTag]. - * Returns null if [Tlv] is not found, otherwise converts its value to [T]. - * - * @param tag [TlvTag] of a [Tlv] which value is to be returned. - * - * @return Value converted to a nullable type [T]. - */ - inline fun decodeOptional(tag: TlvTag): T? = - try { - decode(tag, false) - } catch (exception: TangemSdkError.DecodingFailedMissingTag) { - null - } - - /** - * Finds [Tlv] by its [TlvTag]. - * Throws [TaskError.MissingTag] if [Tlv] is not found, - * otherwise converts [Tlv] value to [T]. - * - * @param tag [TlvTag] of a [Tlv] which value is to be returned. - * - * @return [Tlv] value converted to a nullable type [T]. - * - * @throws [TangemSdkError.DecodingFailedMissingTag] exception if no [Tlv] is found by the Tag. - */ - inline fun decode(tag: TlvTag, logError: Boolean = true): T { - val tlvValue: ByteArray = tlvList.find { it.tag == tag }?.value - ?: if (tag.valueType() == TlvValueType.BoolValue && T::class == Boolean::class) { - return false as T - } else { - if (logError) { - Log.e(this::class.simpleName!!, "TLV $tag not found") - } else { - Log.v(this::class.simpleName!!, "TLV $tag not found, but it is not required") - } - throw TangemSdkError.DecodingFailedMissingTag() - } - - return when (tag.valueType()) { - TlvValueType.HexString, TlvValueType.HexStringToHash -> { - typeCheck(tag) - tlvValue.toHexString() as T - } - TlvValueType.Utf8String -> { - typeCheck(tag) - tlvValue.toUtf8() as T - } - TlvValueType.Uint16, TlvValueType.Uint32 -> { - typeCheck(tag) - try { - tlvValue.toInt() as T - } catch (exception: IllegalArgumentException) { - Log.e(this::class.simpleName!!, exception.message ?: "") - throw TangemSdkError.DecodingFailed() - } - } - TlvValueType.BoolValue -> { - typeCheck(tag) - true as T - } - TlvValueType.ByteArray -> { - typeCheck(tag) - tlvValue as T - } - TlvValueType.EllipticCurve -> { - typeCheck(tag) - try { - EllipticCurve.byName(tlvValue.toUtf8()) as T - } catch (exception: Exception) { - logException(tag, tlvValue.toUtf8(), exception) - throw TangemSdkError.DecodingFailed() - } - - - } - TlvValueType.DateTime -> { - typeCheck(tag) - try { - tlvValue.toDate() as T - } catch (exception: Exception) { - logException(tag, tlvValue.toHexString(), exception) - throw TangemSdkError.DecodingFailed() - } - } - TlvValueType.ProductMask -> { - typeCheck(tag) - ProductMask(tlvValue.toInt()) as T - } - TlvValueType.SettingsMask -> { - typeCheck(tag) - SettingsMask(tlvValue.toInt()) as T - } - TlvValueType.CardStatus -> { - typeCheck(tag) - try { - CardStatus.byCode(tlvValue.toInt()) as T - } catch (exception: Exception) { - logException(tag, tlvValue.toInt().toString(), exception) - throw TangemSdkError.DecodingFailed() - } - } - TlvValueType.SigningMethod -> { - typeCheck(tag) - try { - SigningMethodMask(tlvValue.toInt()) as T - } catch (exception: Exception) { - logException(tag, tlvValue.toInt().toString(), exception) - throw TangemSdkError.DecodingFailed() - } - } - TlvValueType.IssuerDataMode -> { - typeCheck(tag) - try { - IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T - } catch (exception: Exception) { - logException(tag, tlvValue.toInt().toString(), exception) - throw TangemSdkError.DecodingFailed() - } - } - } - } - - fun logException(tag: TlvTag, value: String, exception: Exception) { - Log.e(this::class.simpleName!!, - "Unknown ${tag.name} with value of: value, \n${exception.message}") - } - - inline fun typeCheck(tag: TlvTag) { - 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 TangemSdkError.DecodingFailedTypeMismatch() - } - } - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt deleted file mode 100644 index 99589b7d40..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.tangem.common.tlv - -import com.tangem.Log -import com.tangem.TangemSdkError -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 java.util.* - -/** - * Encodes information that is to be written on the card from parsed classes into [ByteArray] - * (according to the provided [TlvTag] and corresponding [TlvValueType]) - * and then forms [Tlv] with the encoded values. - */ -class TlvEncoder { - /** - - * @param value information that is to be encoded into [Tlv]. - */ - internal inline fun encode(tag: TlvTag, value: T?): Tlv { - if (value != null) { - return Tlv(tag, encodeValue(tag, value)) - } else { - Log.e(this::class.simpleName!!, "Encoding error. Value for tag $tag is null") - throw TangemSdkError.EncodingFailed() - } - } - - internal inline fun encodeValue(tag: TlvTag, value: T): ByteArray { - return when (tag.valueType()) { - TlvValueType.HexString -> { - typeCheck(tag) - (value as String).hexToBytes() - } - TlvValueType.HexStringToHash -> { - typeCheck(tag) - (value as String).calculateSha256() - } - TlvValueType.Utf8String -> { - typeCheck(tag) - (value as String).toByteArray() - } - TlvValueType.Uint16 -> { - typeCheck(tag) - (value as Int).toByteArray(2) - } - TlvValueType.Uint32 -> { - typeCheck(tag) - (value as Int).toByteArray() - } - TlvValueType.BoolValue -> { - typeCheck(tag) - val booleanValue = value as Boolean - if (booleanValue) byteArrayOf(1) else byteArrayOf(0) - } - TlvValueType.ByteArray -> { - typeCheck(tag) - value as ByteArray - } - TlvValueType.EllipticCurve -> { - typeCheck(tag) - (value as EllipticCurve).curve.toByteArray() - } - TlvValueType.DateTime -> { - typeCheck(tag) - val calendar = Calendar.getInstance().apply { time = (value as Date) } - val year = calendar.get(Calendar.YEAR) - val month = calendar.get(Calendar.MONTH) + 1 - val day = calendar.get(Calendar.DAY_OF_MONTH) - return year.toByteArray(2) + month.toByte() + day.toByte() - } - TlvValueType.ProductMask -> { - typeCheck(tag) - byteArrayOf( - (value as ProductMask).rawValue.toByte() - ) - } - TlvValueType.SettingsMask -> { - typeCheck(tag) - val rawValue = (value as SettingsMask).rawValue - rawValue.toByteArray(determineByteArraySize(rawValue)) - } - TlvValueType.CardStatus -> { - typeCheck(tag) - (value as CardStatus).code.toByteArray() - } - TlvValueType.SigningMethod -> { - typeCheck(tag) - byteArrayOf((value as SigningMethodMask).rawValue.toByte()) - } - TlvValueType.IssuerDataMode -> { - typeCheck(tag) - byteArrayOf((value as IssuerDataMode).code) - } - } - } - - private fun determineByteArraySize(value: Int): Int { - val mask = 0xFFFF0000.toInt() - return if ((value and mask) != 0) 4 else 2 - } - - private inline fun typeCheck(tag: TlvTag) { - 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 TangemSdkError.EncodingFailedTypeMismatch() - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt deleted file mode 100644 index 944ee4cc24..0000000000 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ /dev/null @@ -1,150 +0,0 @@ -package com.tangem.common.tlv - -/** - * Contains all possible value types that value for [TlvTag] can contain. - */ -enum class TlvValueType { - HexString, - HexStringToHash, - Utf8String, - Uint16, - Uint32, - BoolValue, - ByteArray, - EllipticCurve, - DateTime, - ProductMask, - SettingsMask, - CardStatus, - SigningMethod, - IssuerDataMode -} - -/** - * Contains all TLV tags, with their code and descriptive name. - */ -enum class TlvTag(val code: Int) { - Unknown(0x00), - CardId(0x01), - Status(0x02), - CardPublicKey(0x03), - CardSignature(0x04), - CurveId(0x05), - HashAlgID(0x06), - SigningMethod(0x07), - MaxSignatures(0x08), - PauseBeforePin2(0x09), - SettingsMask(0x0A), - CardData(0x0C), - NdefData(0x0D), - CreateWalletAtPersonalize(0x0E), - Health(0x0F), - - Pin(0x10), - Pin2(0x11), - NewPin(0x12), - NewPin2(0x13), - NewPinHash(0x14), - NewPin2Hash(0x15), - Challenge(0x16), - Salt(0x17), - ValidationCounter(0x18), - Cvc(0x19), - - SessionKeyA(0x1A), - SessionKeyB(0x1B), - Pause(0x1C), - NewPin3(0x1E), - CrExKey(0x1F), - - Uid(0x0B), - - ManufactureId(0x20), - ManufacturerSignature(0x86), - - IssuerDataPublicKey(0x30), - IssuerTransactionPublicKey(0x31), - IssuerData(0x32), - IssuerDataSignature(0x33), - IssuerTransactionSignature(0x34), - IssuerDataCounter(0x35), - AcquirerPublicKey(0x37), - - Size(0x25), - Mode(0x23), - Offset(0x24), - - IsActivated(0x3A), - ActivationSeed(0x3B), - ResetPin(0x36), - - CodePageAddress(0x40), - CodePageCount(0x41), - CodeHash(0x42), - - TransactionOutHash(0x50), - TransactionOutHashSize(0x51), - TransactionOutRaw(0x52), - - WalletPublicKey(0x60), - Signature(0x61), - RemainingSignatures(0x62), - SignedHashes(0x63), - - Firmware(0x80), - Batch(0x81), - ManufactureDateTime(0x82), - IssuerId(0x83), - BlockchainId(0x84), - ManufacturerPublicKey(0x85), - CardIdManufacturerSignature(0x86), - - ProductMask(0x8A), - PaymentFlowVersion(0x54), - - TokenSymbol(0xA0), - TokenContractAddress(0xA1), - TokenDecimal(0xA2), - Denomination(0xC0), - ValidatedBalance(0xC1), - LastSignDate(0xC2), - DenominationText(0xC3), - - TerminalIsLinked(0x58), - TerminalPublicKey(0x5C), - TerminalTransactionSignature(0x57), - - UserData(0x2A), - UserProtectedData(0x2B), - UserCounter(0x2C), - UserProtectedCounter(0x2D); - - /** - * @return [TlvValueType] associated with a [TlvTag] - */ - fun valueType(): TlvValueType { - return when (this) { - CardId, Batch, CrExKey -> TlvValueType.HexString - ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress -> - TlvValueType.Utf8String - CurveId -> TlvValueType.EllipticCurve - PauseBeforePin2, RemainingSignatures, SignedHashes, Health, TokenDecimal, - Offset, Size -> TlvValueType.Uint16 - MaxSignatures, UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32 - IsActivated, TerminalIsLinked, CreateWalletAtPersonalize -> TlvValueType.BoolValue - ManufactureDateTime -> TlvValueType.DateTime - ProductMask -> TlvValueType.ProductMask - SettingsMask -> TlvValueType.SettingsMask - Status -> TlvValueType.CardStatus - SigningMethod -> TlvValueType.SigningMethod - Mode -> TlvValueType.IssuerDataMode - else -> TlvValueType.ByteArray - } - } - - companion object { - private val values = values() - fun byCode(code: Int): TlvTag = values.find { it.code == code } ?: Unknown - } -} - diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt deleted file mode 100644 index af6f5e2b76..0000000000 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.tangem.crypto - -import com.tangem.commands.EllipticCurve -import net.i2p.crypto.eddsa.EdDSASecurityProvider -import org.spongycastle.jce.provider.BouncyCastleProvider -import java.security.PublicKey -import java.security.SecureRandom -import java.security.Security -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - - -object CryptoUtils { - - fun initCrypto() { - Security.insertProviderAt(BouncyCastleProvider(), 1) - Security.addProvider(EdDSASecurityProvider()) - } - - /** - * Generates ByteArray of random bytes. - * It is used, among other things, to generate helper private keys - * (not the one for the blockchains, that one is generated on the card and does not leave the card). - * - * @param length length of the ByteArray that is to be generated. - */ - fun generateRandomBytes(length: Int): ByteArray { - val bytes = ByteArray(length) - SecureRandom().nextBytes(bytes) - return bytes - } - - /** - * Helper function to verify that the data was signed with a private key that corresponds - * to the provided public key. - * - * @param publicKey Corresponding to the private key that was used to sing a message - * @param message The data that was signed - * @param signature Signed data - * @param curve Elliptic curve used - * - * @return Result of a verification - */ - fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray, - curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean { - return when (curve) { - EllipticCurve.Secp256k1 -> Secp256k1.verify(publicKey, message, signature) - EllipticCurve.Ed25519 -> Ed25519.verify(publicKey, message, signature) - } - } - - /** - * Helper function that generates public key from a private key. - * - * @param privateKeyArray A private key from which a public key is generated - * @param curve Elliptic curve used - * - * @return Public key [ByteArray] - */ - fun generatePublicKey( - privateKeyArray: ByteArray, - curve: EllipticCurve = EllipticCurve.Secp256k1 - ): ByteArray { - return when (curve) { - EllipticCurve.Secp256k1 -> Secp256k1.generatePublicKey(privateKeyArray) - EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray) - } - } - - fun loadPublicKey( - publicKey: ByteArray, - curve: EllipticCurve = EllipticCurve.Secp256k1 - ): PublicKey { - return when (curve) { - EllipticCurve.Secp256k1 -> Secp256k1.loadPublicKey(publicKey) - EllipticCurve.Ed25519 -> Ed25519.loadPublicKey(publicKey) - } - } -} - -/** - * Extension function to sign a ByteArray with an elliptic curve cryptography. - * - * @param privateKeyArray Key to sign data - * @param curve Elliptic curve that is used to sign data - * - * @return Signed data - */ -fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1): ByteArray { - return when (curve) { - EllipticCurve.Secp256k1 -> Secp256k1.sign(this, privateKeyArray) - EllipticCurve.Ed25519 -> Ed25519.sign(this, privateKeyArray) - } -} - -fun ByteArray.encrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { - val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING - val secretKeySpec = SecretKeySpec(key, spec) - val cipher = Cipher.getInstance(spec, "SC") - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) - return cipher.doFinal(this) -} - -fun ByteArray.decrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { - val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING - val secretKeySpec = SecretKeySpec(key, spec) - val cipher = Cipher.getInstance(spec) - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) - return cipher.doFinal(this.copyOfRange(0, this.size)) -} - -fun ByteArray.pbkdf2Hash(salt: ByteArray, iterations: Int): ByteArray { - return Pbkdf2().deriveKey(this, salt, iterations) -} - -private const val ENCRYPTION_SPEC_PKCS7 = "AES/CBC/PKCS7PADDING" -private const val ENCRYPTION_SPEC_NO_PADDING = "AES/CBC/NOPADDING" - - - diff --git a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt deleted file mode 100644 index 5c07b5a4e9..0000000000 --- a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.crypto - -import com.tangem.common.extensions.calculateSha512 -import net.i2p.crypto.eddsa.EdDSAEngine -import net.i2p.crypto.eddsa.EdDSAPrivateKey -import net.i2p.crypto.eddsa.EdDSAPublicKey -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec -import java.security.MessageDigest -import java.security.PublicKey - -object Ed25519 { - - internal fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean { - val messageSha512 = message.calculateSha512() - val loadedPublicKey = loadPublicKey(publicKey) - val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) - val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm)) - signatureInstance.initVerify(loadedPublicKey) - - signatureInstance.update(messageSha512) - - return signatureInstance.verify(signature) - } - - internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { - val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) - val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec) - return EdDSAPublicKey(pubKey) - } - - internal fun sign(data: ByteArray, privateKeyArray: ByteArray): ByteArray { - - val dataSha512 = data.calculateSha512() - val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) - val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm)) - - val privateKeySpec = EdDSAPrivateKeySpec(privateKeyArray, spec) - val privateKey = EdDSAPrivateKey(privateKeySpec) - - signatureInstance.initSign(privateKey) - signatureInstance.update(dataSha512) - - return signatureInstance.sign() - } - - internal fun generatePublicKey(privateKeyArray: ByteArray): ByteArray { - val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) - val privateKeySpec = EdDSAPrivateKeySpec(privateKeyArray, spec) - val publicKeySpec = EdDSAPublicKeySpec(privateKeySpec.a, spec) - val publicKey = EdDSAPublicKey(publicKeySpec) - return publicKey.abyte - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt deleted file mode 100644 index 1360914241..0000000000 --- a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.tangem.crypto - -import com.tangem.EncryptionMode -import org.spongycastle.jce.interfaces.ECPublicKey -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.SecureRandom -import java.security.spec.ECGenParameterSpec -import javax.crypto.KeyAgreement - -interface EncryptionHelper { - val keyA: ByteArray - - fun generateSecret(keyB: ByteArray): ByteArray - - companion object { - fun create(encryptionMode: EncryptionMode): EncryptionHelper? { - return when (encryptionMode) { - EncryptionMode.NONE -> null - EncryptionMode.FAST -> FastEncryptionHelper() - EncryptionMode.STRONG -> StrongEncryptionHelper() - } - } - } -} - -class StrongEncryptionHelper : EncryptionHelper { - private val keyPair = generateKeyPair() - private val keyAgreement = generateKeyAgreement(keyPair) - override val keyA = provideKeyA(keyPair) - - override fun generateSecret(keyB: ByteArray): ByteArray { - keyAgreement.doPhase(CryptoUtils.loadPublicKey(keyB), true) - return keyAgreement.generateSecret() - } - - private fun generateKeyPair(): KeyPair { - val kpgen = KeyPairGenerator.getInstance("ECDH", "SC") - kpgen.initialize(ECGenParameterSpec("secp256k1"), SecureRandom()) - return kpgen.generateKeyPair() - } - - private fun generateKeyAgreement(keyPair: KeyPair): KeyAgreement { - val keyAgreement = KeyAgreement.getInstance("ECDH", "SC") - keyAgreement.init(keyPair.private) - return keyAgreement - } - - private fun provideKeyA(keyPair: KeyPair): ByteArray { - val eckey = keyPair.public as ECPublicKey - return eckey.q.getEncoded(false) - } -} - -class FastEncryptionHelper : EncryptionHelper { - override val keyA = CryptoUtils.generateRandomBytes(16) - - override fun generateSecret(keyB: ByteArray): ByteArray { - return keyA + keyB - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt deleted file mode 100644 index 4c532b3692..0000000000 --- a/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.tangem.crypto - -import org.spongycastle.crypto.CipherParameters -import org.spongycastle.crypto.digests.SHA256Digest -import org.spongycastle.crypto.macs.HMac -import org.spongycastle.crypto.params.KeyParameter -import java.security.InvalidKeyException -import java.util.* -import kotlin.experimental.xor -import kotlin.math.min -import kotlin.math.pow - -class Pbkdf2 { - private val F: HMac = HMac(SHA256Digest()) - - fun deriveKey(password: ByteArray, salt: ByteArray, iterations: Int): ByteArray { - - val macSize = F.macSize - // Check key length - if (macSize > (2.0.pow(32.0) - 1) * macSize) throw InvalidKeyException("Derived key to long") - - val derivedKey = ByteArray(macSize) - - val J = 0 - val K: Int = macSize - val U: Int = macSize shl 1 - val B = K + U - val workingArray = ByteArray(K + U + 4) - - // Initialize F - val macParams: CipherParameters = KeyParameter(password) - F.init(macParams) - - // Perform iterations - var kpos = 0 - var blk = 1 - while (kpos < macSize) { - storeInt32BE(blk, workingArray, B) - F.update(salt, 0, salt.size) - F.reset() - F.update(salt, 0, salt.size) - F.update(workingArray, B, 4) - F.doFinal(workingArray, U) - System.arraycopy(workingArray, U, workingArray, J, K) - var i = 1 - var j = J - var k = K - while (i < iterations) { - F.init(macParams) - F.update(workingArray, j, K) - F.doFinal(workingArray, k) - var u = U - var v = k - while (u < B) { - workingArray[u] = workingArray[u] xor workingArray[v] - u++ - v++ - } - val swp = k - k = j - j = swp - i++ - } - val tocpy = min(macSize - kpos, K) - System.arraycopy(workingArray, U, derivedKey, kpos, tocpy) - kpos += K - blk++ - } - Arrays.fill(workingArray, 0.toByte()) - return derivedKey - } - - /** - * Convert a 32-bit integer value into a big-endian byte array - * - * @param value The integer value to convert - * @param bytes The byte array to store the converted value - * @param offSet The offset in the output byte array - */ - private fun storeInt32BE(value: Int, bytes: ByteArray, offSet: Int) { - bytes[offSet + 3] = value.toByte() - bytes[offSet + 2] = (value ushr 8).toByte() - bytes[offSet + 1] = (value ushr 16).toByte() - bytes[offSet] = (value ushr 24).toByte() - } - - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt deleted file mode 100644 index 97eba2a81c..0000000000 --- a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.tangem.crypto - -import com.tangem.common.extensions.toHexString -import org.spongycastle.asn1.ASN1EncodableVector -import org.spongycastle.asn1.ASN1Integer -import org.spongycastle.asn1.DERSequence -import org.spongycastle.jce.ECNamedCurveTable -import org.spongycastle.jce.spec.ECPrivateKeySpec -import org.spongycastle.jce.spec.ECPublicKeySpec -import java.math.BigInteger -import java.security.KeyFactory -import java.security.PublicKey -import java.security.Signature - -object Secp256k1 { - - internal fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray): Boolean { - val signatureInstance = Signature.getInstance("SHA256withECDSA") - val loadedPublicKey = loadPublicKey(publicKey) - signatureInstance.initVerify(loadedPublicKey) - signatureInstance.update(message) - - val v = ASN1EncodableVector() - val size = signature.size / 2 - v.add(calculateR(signature, size)) - v.add(calculateS(signature, size)) - val sigDer = DERSequence(v).encoded - - return signatureInstance.verify(sigDer) - } - - internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { - - val spec = ECNamedCurveTable.getParameterSpec("secp256k1") - val factory = KeyFactory.getInstance("EC", "SC") - - val p1 = spec.curve.decodePoint(publicKeyArray) - val keySpec = ECPublicKeySpec(p1, spec) - - return factory.generatePublic(keySpec) - } - - private fun calculateR(signature: ByteArray, size: Int): ASN1Integer = - ASN1Integer(BigInteger(1, signature.copyOfRange(0, size))) - - private fun calculateS(signature: ByteArray, size: Int): ASN1Integer = - ASN1Integer(BigInteger(1, signature.copyOfRange(size, size * 2))) - - - internal fun sign(data: ByteArray, privateKeyArray: ByteArray): ByteArray { - - val spec = ECNamedCurveTable.getParameterSpec("secp256k1") - val factory = KeyFactory.getInstance("EC", "SC") - - val keySpecP = ECPrivateKeySpec(BigInteger(1, privateKeyArray), spec) - - val signature = Signature.getInstance("SHA256withECDSA") - - val privateKey = factory.generatePrivate(keySpecP) - signature.initSign(privateKey) - signature.update(data) - - val enc = signature.sign() - checkSignatureForErrors(enc) - - val res = toByte64(enc) - - if (!verify(generatePublicKey(privateKeyArray), data, res)) { - throw Exception("Signature self verify failed - ,enc:" + enc.toHexString() + ",res:" + res.toHexString()) - } - - return res - } - - private fun checkSignatureForErrors(enc: ByteArray) { - if (enc[0].toInt() != 0x30) throw Exception("bad encoding 1") - if (enc[1].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 1") - if (enc[2].toInt() != 0x02) throw Exception("bad encoding 2") - if (enc[3].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 2") - var rLength = enc[3].toInt() - if (enc[4 + rLength].toInt() != 0x02) throw Exception("bad encoding 3") - if (enc[5 + rLength].toInt() and 0x80 != 0) - throw Exception("unsupported length encoding 3") - } - - private fun toByte64(enc: ByteArray): ByteArray { - - var rLength = enc[3].toInt() - var sLength = enc[5 + rLength].toInt() - - val sPos = 6 + rLength - val res = ByteArray(64) - if (rLength <= 32) { - System.arraycopy(enc, 4, res, 32 - rLength, rLength) - rLength = 32 - } else if (rLength == 33 && enc[4].toInt() == 0) { - rLength-- - System.arraycopy(enc, 5, res, 0, rLength) - } else { - throw Exception("unsupported r-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString()) - } - if (sLength <= 32) { - System.arraycopy(enc, sPos, res, rLength + 32 - sLength, sLength) - sLength = 32 - } else if (sLength == 33 && enc[sPos].toInt() == 0) { - System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1) - } else { - throw Exception("unsupported s-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString()) - } - - return res - } - - internal fun generatePublicKey(privateKeyArray: ByteArray): ByteArray { - val spec = ECNamedCurveTable.getParameterSpec("secp256k1") - return spec.g.multiply(BigInteger(1, privateKeyArray)).getEncoded(false) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt deleted file mode 100644 index fed66d31b3..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/ChangePinTask.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.tangem.tasks - -import com.tangem.CardSession -import com.tangem.CardSessionRunnable -import com.tangem.SessionEnvironment -import com.tangem.SessionViewDelegate -import com.tangem.commands.SetPinCommand -import com.tangem.commands.SetPinResponse -import com.tangem.common.CompletionResult -import com.tangem.common.PinCode -import com.tangem.common.extensions.calculateSha256 -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume - -enum class PinType { - Pin1, - Pin2, - Pin3, - ; -} - -class ChangePinTask( - private val pinType: PinType, - private val pin: ByteArray? = null -) : CardSessionRunnable { - override val requiresPin2 = false - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.scope.launch { - val pin = this@ChangePinTask.pin - ?: requestNewPin(pinType, session.viewDelegate).calculateSha256() - runSetPin(pin, session, callback) - } - } - - private suspend fun runSetPin(pin: ByteArray, session: CardSession, callback: (result: CompletionResult) -> Unit) { - val pin1: ByteArray - val pin2: ByteArray - val pin3: ByteArray? - - if (session.environment.pin1 == null) { - session.environment.pin1 = PinCode(requestPin(PinType.Pin1, session.viewDelegate)) - } - if (session.environment.pin2 == null) { - session.environment.pin2 = PinCode(requestPin(PinType.Pin2, session.viewDelegate)) - } - - when (pinType) { - PinType.Pin1 -> { - pin1 = pin - pin2 = session.environment.pin2!!.value - pin3 = null - } - PinType.Pin2 -> { - pin1 = session.environment.pin1!!.value - pin2 = pin - pin3 = null - } - PinType.Pin3 -> { - pin1 = session.environment.pin1!!.value - pin2 = session.environment.pin2!!.value - pin3 = pin - } - } - val command = SetPinCommand(pin1, pin2, pin3) - command.run(session) { result -> - when (result) { - is CompletionResult.Success -> { - savePin(pin, session.environment) - callback(result) - } - is CompletionResult.Failure -> callback(result) - } - } - } - - private suspend fun requestPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = - suspendCancellableCoroutine { continuation -> - viewDelegate.onPinRequested(pinType) { result -> - if (continuation.isActive) continuation.resume(result) - } - } - - private suspend fun requestNewPin(pinType: PinType, viewDelegate: SessionViewDelegate): String = - suspendCancellableCoroutine { continuation -> - viewDelegate.onPinChangeRequested(pinType) { result -> - if (continuation.isActive) continuation.resume(result) - } - } - - private fun savePin(pin: ByteArray, environment: SessionEnvironment) { - when (pinType) { - PinType.Pin1 -> environment.pin1 = PinCode(pin, false) - PinType.Pin2 -> environment.pin2 = PinCode(pin, false) - PinType.Pin3 -> {} - } - } - - -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt deleted file mode 100644 index a4bb23d8b5..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.tasks - -import com.tangem.CardSession -import com.tangem.CardSessionRunnable -import com.tangem.TangemSdkError -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 { - - override val requiresPin2 = false - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val curve = session.environment.card?.curve - if (curve == null) { - callback(CompletionResult.Failure(TangemSdkError.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(TangemSdkError.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) - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt deleted file mode 100644 index d9702f5a6d..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.tangem.tasks - -import com.tangem.CardSession -import com.tangem.CardSessionRunnable -import com.tangem.TagType -import com.tangem.TangemSdkError -import com.tangem.commands.* -import com.tangem.commands.common.CardDeserializer -import com.tangem.common.CompletionResult - -/** - * Task that allows to read Tangem card and verify its private key. - * - * It performs two commands, [ReadCommand] and [CheckWalletCommand], subsequently. - */ -internal class ScanTask : CardSessionRunnable { - - override val requiresPin2 = false - - override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (session.connectedTag == TagType.Slix) { - readSlixTag(session, callback) - return - } - val card = session.environment.card - if (card == null) { - callback(CompletionResult.Failure(TangemSdkError.CardError())) - return - } - if (session.environment.pin1 != null && session.environment.pin2 != null) { - val checkPinCommand = SetPinCommand(session.environment.pin1!!.value, session.environment.pin2!!.value) - checkPinCommand.run(session) { result -> - when (result) { - is CompletionResult.Failure -> { - session.environment.pin2 = null - } - } - runCheckWalletIfNeeded(card, session, callback) - } - } else { - runCheckWalletIfNeeded(card, session, callback) - } - - } - - private fun runCheckWalletIfNeeded( - card: Card, session: CardSession, - callback: (result: CompletionResult) -> Unit - ) { - if (card.cardData?.productMask?.contains(Product.Tag) != false) { - callback(CompletionResult.Success(card)) - - } else if (card.status != CardStatus.Loaded) { - callback(CompletionResult.Success(card)) - - } else if (card.curve == null || card.walletPublicKey == null) { - callback(CompletionResult.Failure(TangemSdkError.CardError())) - - } else { - val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) - checkWalletCommand.run(session) { result -> - when (result) { - is CompletionResult.Success -> callback(CompletionResult.Success(card)) - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - } - - private fun readSlixTag(session: CardSession, callback: (result: CompletionResult) -> Unit) { - session.readSlixTag { result -> - when (result) { - is CompletionResult.Success -> { - try { - val card = CardDeserializer.deserialize(result.data) - callback(CompletionResult.Success(card)) - } catch (error: TangemSdkError) { - callback(CompletionResult.Failure(error)) - } - } - is CompletionResult.Failure -> { - callback(CompletionResult.Failure(result.error)) - } - } - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq b/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq deleted file mode 100644 index 7ac4521e98..0000000000 --- a/tangem-core/src/main/sqldelight/com/tangem/CardValuesEntity.sq +++ /dev/null @@ -1,34 +0,0 @@ -import com.tangem.VerificationState; - -CREATE TABLE cardValues ( - cardId TEXT NOT NULL UNIQUE PRIMARY KEY, - isPin1Default INTEGER AS Boolean DEFAULT 1, - isPin2Default INTEGER AS Boolean DEFAULT 1, - cardVerification TEXT AS VerificationState, - cardValidation TEXT AS VerificationState, - codeVerification TEXT AS VerificationState -); - -insertOrReplace: -INSERT OR REPLACE INTO cardValues( - cardId, - isPin1Default, - isPin2Default, - cardVerification, - cardValidation, - codeVerification -) -VALUES (?, ?, ?, ?, ?, ?); - -selectByCardId: -SELECT * -FROM cardValues -WHERE cardId = ?; - -deleteAll: -DELETE FROM cardValues; - -deleteByCardId: -DELETE -FROM cardValues -WHERE cardId = ?; \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt deleted file mode 100644 index 67ac0cf109..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.common.apdu - -import com.google.common.truth.Truth.assertThat -import com.tangem.SessionEnvironment -import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvTag -import org.junit.Test - - -class CommandApduTest { - - private val sessionEnvironment = SessionEnvironment() - - @Test - fun `simple READ command to bytes`() { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1?.value) - val commandApdu = CommandApdu( - Instruction.Read, - tlvBuilder.serialize() - ) - val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 34, 16, 32, -111, -76, -47, 66, -126, 63, 125, - 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, - -45, 19, -124, -123, -55, -94, 3) - - assertThat(commandApdu.apduData) - .isEqualTo(expected) - assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1, 2)) - } - - @Test - fun `READ with terminal key to bytes`() { - val terminalPublicKey = byteArrayOf(4, 80, -122, 58, -42, 74, -121, -82, -118, 47, -24, 60, - 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, 126, - 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, - -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1?.value) - tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey) - val commandApdu = CommandApdu( - Instruction.Read, - tlvBuilder.serialize() - ) - - val expected = byteArrayOf(0, -14, 0, 0, 0, 0, 101, 16, 32, -111, -76, -47, 66, -126, 63, - 125, 32, -59, -16, -115, -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, - -10, -45, 19, -124, -123, -55, -94, 3, 92, 65, 4, 80, -122, 58, -42, 74, -121, -82, -118, - 47, -24, 60, 26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, - 126, 91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58, - -68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90) - - assertThat(commandApdu.apduData) - .isEqualTo(expected) - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/apdu/ResponseApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/ResponseApduTest.kt deleted file mode 100644 index 0b6f7fc8b9..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/apdu/ResponseApduTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.common.apdu - -import com.google.common.truth.Truth.assertThat -import com.tangem.common.tlv.TlvTag -import org.junit.Test - -class ResponseApduTest { - - @Test - fun `get StatusWord returns Unknown`() { - val corruptData = byteArrayOf(0, 0, 0, 0) - val responseApdu = ResponseApdu(corruptData) - assertThat(responseApdu.statusWord) - .isEqualTo(StatusWord.Unknown) - } - - @Test - fun `get StatusWord returns ProcessCompleted`() { - val data = byteArrayOf(0, 0, 0, 0, -112, 0) - val responseApdu = ResponseApdu(data) - assertThat(responseApdu.statusWord) - .isEqualTo(StatusWord.ProcessCompleted) - } - - @Test - fun `corrupt response, getTlvData returns null`() { - val corruptData = byteArrayOf(0, 0, 0) - val responseApdu = ResponseApdu(corruptData) - assertThat(responseApdu.getTlvData()) - .isNull() - } - - @Test - fun `response, getTlvData returns cardId`() { - val data = 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, -112, 0) - val responseApdu = ResponseApdu(data) - assertThat(responseApdu.getTlvData()) - .isNotNull() - assertThat(responseApdu.getTlvData()) - .isNotEmpty() - assertThat(responseApdu.getTlvData()?.filter { it.tag == TlvTag.Unknown }) - .isEmpty() - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt b/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt deleted file mode 100644 index 42cdecc708..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/extensions/ByteArrayExtensionsTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.tangem.common.extensions - -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import java.util.* - - -class ByteArrayExtensionsTest { - - @Test - fun `card Id to Hex String`() { - val hex = "CB22000000027374" - val bytes = byteArrayOf(-53, 34, 0, 0, 0, 2, 115, 116) - assertThat(bytes.toHexString()) - .matches(hex) - } - - @Test - fun `batch Id to Hex String`() { - val hex = "0029" - val bytes = byteArrayOf(0, 41) - assertThat(bytes.toHexString()) - .matches(hex) - } - - @Test - fun `curve name to Utf8`() { - val bytes = byteArrayOf(115, 101, 99, 112, 50, 53, 54, 107, 49, 0) - val expected = "secp256k1" - val converted = bytes.toUtf8() - assertThat(converted) - .matches(expected) - } - - @Test - fun `empty byteArray to Utf8 returns empty String`() { - val bytes = byteArrayOf() - val expected = "" - assertThat(bytes.toUtf8()) - .matches(expected) - } - - @Test - fun `blockchain name to Utf8`() { - val bytes = byteArrayOf(69, 84, 72) - val expected = "ETH" - val converted = bytes.toUtf8() - assertThat(converted) - .matches(expected) - } - - @Test - fun `bytes to int`() { - val bytes = byteArrayOf(0, 2, 106, 3) - val expected = 158211 - assertThat(bytes.toInt()) - .isEqualTo(expected) - - val bytes1 = byteArrayOf(0, 0, 0, 13) - val expected1 = 13 - assertThat(bytes1.toInt()) - .isEqualTo(expected1) - } - - @Test - fun `zero to int`() { - val bytes = byteArrayOf(0) - val expected = 0 - assertThat(bytes.toInt()) - .isEqualTo(expected) - } - - @Test - fun toDate() { - val bytes1 = byteArrayOf(7, -30, 7, 27) - val expected1 = Calendar.getInstance().apply { this.set(2018, 6, 27, 0, 0, 0) }.time - val converted1 = bytes1.toDate() - assertThat(converted1.toString()) - .isEqualTo(expected1.toString()) - - val bytes2 = byteArrayOf(7, -30, 7, 27, 30) - val expected2 = Calendar.getInstance().apply { this.set(2018, 6, 27, 0, 0, 0) }.time - val converted2 = bytes2.toDate() - assertThat(converted2.toString()) - .isEqualTo(expected2.toString()) - - val bytes3 = byteArrayOf(7, -30, 7) - val expected3 = Calendar.getInstance().apply { this.set(2018, 6, 0, 0, 0, 0) }.time - val converted3 = bytes3.toDate() - assertThat(converted3.toString()) - .isEqualTo(expected3.toString()) - } - - @Test - fun `calculate sha512`() { - val bytes = ByteArray(64) { 5 } - val expected = byteArrayOf( - -123, 96, 121, 57, -117, -23, -108, 57, 25, -119, -22, 97, 11, -91, - 74, -19, -88, 21, -108, -116, -100, 111, 6, -78, 114, -115, 70, -121, 29, 102, 104, 65, - -21, -68, -111, 121, -51, 109, -94, -24, -40, 108, -25, 70, -26, 61, 38, 12, -127, -34, - -77, -81, 81, -32, -89, -112, -31, -33, 91, 114, 89, 127, -123, -58) - assertThat(bytes.calculateSha512()) - .isEqualTo(expected) - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt b/tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt deleted file mode 100644 index 710f817e28..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/extensions/IntExtensionsTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.common.extensions - -import com.google.common.truth.Truth.assertThat -import org.junit.jupiter.api.Test - - -class IntExtensionsTest { - - @Test - fun `small int toByteArray`() { - val int = 13 - val expected = byteArrayOf(0, 0, 0, 13) - assertThat(int.toByteArray()) - .isEqualTo(expected) - } - - @Test - fun `int toByteArray`() { - val int = 999 - val expected = byteArrayOf(0, 0, 3, -25) - assertThat(int.toByteArray()) - .isEqualTo(expected) - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/extensions/StringExtensionsTest.kt b/tangem-core/src/test/java/com/tangem/common/extensions/StringExtensionsTest.kt deleted file mode 100644 index ba5b3add73..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/extensions/StringExtensionsTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.common.extensions - -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class StringExtensionsTest { - - @Test - fun `calculate SHA 256 for default PIN 1`() { - val pin = "000000" - val expected = byteArrayOf(-111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, -111, - 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, -55, -94, 3) - assertThat(pin.calculateSha256()) - .isEqualTo(expected) - } - - @Test - fun `calculate SHA 256 for default PIN 2`() { - val pin = "000" - val expected = byteArrayOf(42, -55, -90, 116, 106, -54, 84, 58, -8, -33, -13, -104, -108, -49, - -24, 23, 58, -5, -94, 30, -80, 28, 111, -82, 51, -43, 41, 71, 34, 40, 85, -17) - assertThat(pin.calculateSha256()) - .isEqualTo(expected) - } - - @Test - fun `calculate SHA 256 for a sample PIN 1`() { - val pin = "999999" - val expected = byteArrayOf(-109, 115, 119, -16, 86, 22, 15, -60, -79, 94, 11, 119, 12, 103, - 19, 106, 95, 3, -63, 82, 5, -76, -45, -65, -111, -126, 104, -2, -6, 44, 109, 10) - assertThat(pin.calculateSha256()) - .isEqualTo(expected) - } - - @Test - fun `calculate SHA 256 for a sample PIN 2`() { - val pin = "999" - val expected = byteArrayOf(-125, -49, -117, 96, -99, -26, 0, 54, -88, 39, 123, -48, -23, 97, - 53, 117, 27, -68, 7, -21, 35, 66, 86, -44, -74, 91, -119, 51, 96, 101, 27, -14) - assertThat(pin.calculateSha256()) - .isEqualTo(expected) - } - - @Test - fun `card ID hex to bytes`() { - val cardId = "cb22000000027374" - val expected = byteArrayOf(-53, 34, 0, 0, 0, 2, 115, 116) - assertThat(cardId.hexToBytes()) - .isEqualTo(expected) - } - - -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt deleted file mode 100644 index d0cc8a62e7..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt +++ /dev/null @@ -1,192 +0,0 @@ -package com.tangem.common.tlv - -import com.google.common.truth.Truth.assertThat -import com.tangem.TangemSdkError -import com.tangem.commands.* -import com.tangem.common.extensions.hexToBytes -import org.junit.Test -import org.junit.jupiter.api.assertThrows -import java.util.* - -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 = TlvDecoder(tlvData!!) - - 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.decodeOptional(TlvTag.SettingsMask) - assertThat(settingsMask) - .isNotNull() - } - - @Test - fun `map optional when no tag returns null`() { - val tokenSymbol: String? = tlvMapper.decodeOptional(TlvTag.TokenSymbol) - assertThat(tokenSymbol) - .isNull() - } - - @Test - fun `map when value is null throws MissingTagException`() { - assertThrows { - tlvMapper.decode(TlvTag.TokenSymbol) - } - } - - @Test - fun `map optional to wrong type throws WrongTypeException`() { - assertThrows { - tlvMapper.decodeOptional(TlvTag.CardData) - } - } - - @Test - fun `map to wrong type throws WrongTypeException`() { - assertThrows { - tlvMapper.decode(TlvTag.CardData) - } - } - - @Test - fun `map boolean missing flag returns false`() { - val terminalIsLinked: Boolean = tlvMapper.decode(TlvTag.TerminalIsLinked) - assertThat(terminalIsLinked) - .isFalse() - } - - @Test - fun `map SettingsMask returns correct value`() { - val settingsMask: SettingsMask = tlvMapper.decode(TlvTag.SettingsMask) - assertThat(settingsMask) - .isNotNull() - assertThat(settingsMask.rawValue) - .isEqualTo(32289) - assertThat(settingsMask.contains(Settings.SkipSecurityDelayIfValidatedByLinkedTerminal)) - .isFalse() - assertThat(settingsMask.contains(Settings.IsReusable)) - .isTrue() - assertThat(settingsMask.contains(Settings.AllowSwapPIN2)) - .isTrue() - assertThat(settingsMask.contains(Settings.UseDynamicNdef)) - .isTrue() - assertThat(settingsMask.contains(Settings.ProhibitPurgeWallet)) - .isFalse() - } - - @Test - fun `map SigningMethods single value returns correct value`() { - val signingMethods: SigningMethodMask = tlvMapper.decode(TlvTag.SigningMethod) - assertThat(signingMethods.contains(SigningMethod.SignHash)) - .isTrue() - } - - @Test - fun `map SigningMethods set of methods returns correct value`() { - val localMapper = TlvDecoder(Tlv.deserialize("070195".hexToBytes())!!) - - val signingMethods: SigningMethodMask = localMapper.decode(TlvTag.SigningMethod) - assertThat(signingMethods.contains(SigningMethod.SignHash)) - .isTrue() - assertThat(signingMethods.contains(SigningMethod.SignHashValidateByIssuer)) - .isTrue() - assertThat(signingMethods.contains(SigningMethod.SignHashValidateByIssuerWriteIssuerData)) - .isTrue() - assertThat(signingMethods.contains(SigningMethod.SignRaw)) - .isFalse() - assertThat(signingMethods.contains(SigningMethod.SignRawValidateByIssuer)) - .isFalse() - assertThat(signingMethods.contains(SigningMethod.SignRawValidateByIssuerWriteIssuerData)) - .isFalse() - assertThat(signingMethods.contains(SigningMethod.SignPos)) - .isFalse() - } - - @Test - fun `map CardStatus returns correct value`() { - 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 = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5)))) - val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask) - assertThat(productMask.contains(Product.Note) && productMask.contains(Product.IdCard)) - .isTrue() - } - - @Test - fun `map ProductMask with raw value 1 returns correct value`() { - val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1)))) - val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask) - assertThat(productMask.contains(Product.Note)) - .isTrue() - } - - @Test - fun `map Enum with unknown code throws ConversionException error`() { - val localMapper = TlvDecoder(listOf(Tlv(TlvTag.CurveId, "test".toByteArray()))) - assertThrows { - localMapper.decode(TlvTag.CurveId) - } - } - - @Test - fun `map DateTime returns correct value`() { - 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()) - } - - @Test - fun `map EllipticCurve returns correct value`() { - val ellipticCurve: EllipticCurve = tlvMapper.decode(TlvTag.CurveId) - assertThat(ellipticCurve) - .isEqualTo(EllipticCurve.Secp256k1) - } - - @Test - fun `map ByteArray returns correctly`() { - val cardPublicKey: ByteArray = tlvMapper.decode(TlvTag.CardPublicKey) - assertThat(cardPublicKey) - .isInstanceOf(ByteArray::class.java) - } - - @Test - fun `map Int returns correct value`() { - val signedHashes: Int = tlvMapper.decode(TlvTag.SignedHashes) - assertThat(signedHashes) - .isEqualTo(13) - } - - @Test - fun `map Int with wrong value throws ConversionException`() { - val localMapper = TlvDecoder(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5)))) - assertThrows { - localMapper.decode(TlvTag.SignedHashes) - } - } - - @Test - fun `map UTF8 returns correct value`() { - val blockchainId: String = cardDataMapper.decode(TlvTag.BlockchainId) - assertThat(blockchainId) - .isEqualTo("ETH") - } - - @Test - fun `map Hex returns correct value`() { - val cardId: String = tlvMapper.decode(TlvTag.CardId) - assertThat(cardId) - .isEqualTo("CB22000000027374") - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt deleted file mode 100644 index c2d1e0e09d..0000000000 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.tangem.common.tlv - -import com.google.common.truth.Truth.assertThat -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import org.junit.Test - - -class TlvTest { - - @Test - fun `TLVs to bytes, only PIN`() { - val tlvs = listOf( - Tlv(TlvTag.Pin, "000000".calculateSha256()) - ) - val expected = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, - -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, - -123, -55, -94, 3) - - assertThat(tlvs.serialize()) - .isEqualTo(expected) - } - - @Test - fun `TLVs to bytes, check wallet`() { - val tlvs = listOf( - Tlv(TlvTag.Pin, "000000".calculateSha256()), - Tlv(TlvTag.CardId, "cb22000000027374".hexToBytes()), - Tlv(TlvTag.Challenge, byteArrayOf(-82, -78, -31, 34, 66, -19, -86, -1, 26, 8, 100, -126, -74, 20, -28, 83)) - ) - - val expected = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, - -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, - -55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19, - -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) - - assertThat(tlvs.serialize()) - .isEqualTo(expected) - } - - @Test - fun `Bytes to Tlvs, only PIN`() { - val bytes = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, - -10, -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, - -123, -55, -94, 3) - - val tlvs = Tlv.deserialize(bytes) - - assertThat(tlvs) - .isNotNull() - assertThat(tlvs) - .isNotEmpty() - - val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value - val pinExpected = "000000".calculateSha256() - - assertThat(pin) - .isEqualTo(pinExpected) - } - - @Test - fun `Bytes to TLVs, check wallet TLVs`() { - val bytes = byteArrayOf(16, 32, -111, -76, -47, 66, -126, 63, 125, 32, -59, -16, -115, -10, - -111, 34, -34, 67, -13, 95, 5, 122, -104, -115, -106, 25, -10, -45, 19, -124, -123, - -55, -94, 3, 1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 22, 16, -82, -78, -31, 34, 66, -19, - -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) - - val tlvs = Tlv.deserialize(bytes) - - assertThat(tlvs) - .isNotNull() - assertThat(tlvs) - .isNotEmpty() - - val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value - val pinExpected = "000000".calculateSha256() - assertThat(pin) - .isEqualTo(pinExpected) - - val cardId = tlvs.find { it.tag == TlvTag.CardId }?.value - val cardIdExpected = "cb22000000027374".hexToBytes() - assertThat(cardId) - .isEqualTo(cardIdExpected) - - val challenge = tlvs.find { it.tag == TlvTag.Challenge }?.value - val challengeExpected = byteArrayOf(-82, -78, -31, 34, 66, -19, -86, -1, 26, 8, 100, -126, -74, 20, -28, 83) - assertThat(challenge) - .isEqualTo(challengeExpected) - } - - @Test - fun `Bytes to TLVs, wrong values`() { - val bytes = byteArrayOf(0) - val tlvs = Tlv.deserialize(bytes) - assertThat(tlvs) - .isNull() - - val bytes1 = byteArrayOf(0, 0, 0, 0, 0, 0, 0) - val tlvs1 = Tlv.deserialize(bytes1) - assertThat(tlvs1) - .isNull() - } - - @Test - fun `parse Slix tag response`() { - val response = "03ff010f91010b550474616e67656d2e636f6d140f11616e64726f69642e636f6d3a706b67636f6d2e74616e67656d2e77616c6c65745411c974616e67656d2e636f6d3a77616c6c657490000c618102ffff8a0102820407e40109830b54414e47454d2053444b008403584c4d86400e71c1f060387029688254320b90abeae471bcafbbe8ea3880903bdb8d1cc389d032b982e1ffd7ef49e66f1780123b763dd2f3a9a9494eb0fad4ae8cf306672c60207c967a51077c14fc49d867f23b8d0eaf60cad479a56587e894571b7fb33690176140345fbe53f5be0ec871e91c317cde2bd0396d47e4b945c138c153b0271f636a73cf531df1bc54ac4fcdbce42f81b40d58e0265d34e28121a4c50fdfe329a97f6000fe000000000000000000000000000000000000000000000000000000000000000000000000000000" - val tlvs = Tlv.deserialize(response.hexToBytes(), true) - assertThat(tlvs) - .isNotEmpty() - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/crypto/CryptoUtilsTest.kt b/tangem-core/src/test/java/com/tangem/crypto/CryptoUtilsTest.kt deleted file mode 100644 index afdb6da581..0000000000 --- a/tangem-core/src/test/java/com/tangem/crypto/CryptoUtilsTest.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.tangem.crypto - -import com.google.common.truth.Truth.assertThat -import com.tangem.commands.EllipticCurve -import com.tangem.crypto.CryptoUtils.generatePublicKey -import com.tangem.crypto.CryptoUtils.generateRandomBytes -import com.tangem.crypto.CryptoUtils.verify -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - - -class CryptoUtilsTest { - - @BeforeEach - internal fun setUp() { - CryptoUtils.initCrypto() - } - - @Test - fun generateRandomBytesTest() { - val privateKey: ByteArray = generateRandomBytes(32) - assertThat(privateKey) - .hasLength(32) - assertThat(privateKey.sum()) - .isNotEqualTo(0) - } - - @Test - internal fun verifyEd25519Test() { - val verified = verifySignature_withSampleData(EllipticCurve.Ed25519) - assertThat(verified) - .isTrue() - } - - @Test - internal fun verifySecp256k1Test() { - val verified = verifySignature_withSampleData(EllipticCurve.Secp256k1) - assertThat(verified) - .isTrue() - } - - private fun verifySignature_withSampleData(curve: EllipticCurve): Boolean { - val privateKey = ByteArray(32) { 1 } - val publicKey = generatePublicKey(privateKey, curve) - val message = ByteArray(64) { 5 } - val signature = message.sign(privateKey, curve) - return verify(publicKey, message, signature, curve) - } - -} \ No newline at end of file diff --git a/tangem-devkit/.gitignore b/tangem-devkit/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/tangem-devkit/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/tangem-devkit/build.gradle b/tangem-devkit/build.gradle deleted file mode 100644 index fd35b8f7ed..0000000000 --- a/tangem-devkit/build.gradle +++ /dev/null @@ -1,56 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -android { - compileSdkVersion 29 - buildToolsVersion "29.0.3" - - defaultConfig { - applicationId "com.tangem.devkit" - minSdkVersion 21 - targetSdkVersion 29 - versionCode 4 - versionName "1.2" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - implementation project(':tangem-core') - implementation project(':tangem-sdk') - implementation fileTree(dir: 'libs', include: ['*.jar']) - - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" - - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.2.0' - implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4" - implementation "androidx.navigation:navigation-fragment-ktx:2.2.1" - implementation "androidx.navigation:navigation-ui-ktx:2.2.1" - implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02" - implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" - implementation "com.google.android.material:material:1.2.0-alpha05" - implementation "androidx.viewpager2:viewpager2:1.0.0" - - implementation 'com.google.code.gson:gson:2.8.6' - implementation 'com.github.gbIxaHue:eu4d:0.3.8' -} diff --git a/tangem-devkit/proguard-rules.pro b/tangem-devkit/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/tangem-devkit/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/tangem-devkit/src/main/AndroidManifest.xml b/tangem-devkit/src/main/AndroidManifest.xml deleted file mode 100644 index b74aaf74df..0000000000 --- a/tangem-devkit/src/main/AndroidManifest.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tangem-devkit/src/main/ic_launcher-playstore.png b/tangem-devkit/src/main/ic_launcher-playstore.png deleted file mode 100644 index 6e5af9f8f6193aa9ddfbfa06c4a00ab3c06f605e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14393 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 IOE(_>4+!d&v;Y7A diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/AppTangemDemo.kt b/tangem-devkit/src/main/java/com/tangem/devkit/AppTangemDemo.kt deleted file mode 100644 index 60ee3d4564..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/AppTangemDemo.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.devkit - -import android.app.Application -import android.content.Context -import android.content.SharedPreferences -import com.tangem.devkit._arch.structure.ILog -import com.tangem.devkit._arch.structure.ItemLogger -import com.tangem.devkit.commons.TangemLogger -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -class AppTangemDemo : Application() { - - override fun onCreate() { - super.onCreate() - - AppTangemDemo.appInstance = this - setupLoggers() - } - - private fun setupLoggers() { - Log.setLogger(TangemLogger()) - ILog.setLogger(ItemLogger()) - } - - fun sharedPreferences(name: String = "DevKitApp", mode: Int = Context.MODE_PRIVATE): SharedPreferences { - return getSharedPreferences(name, mode) - } - - companion object { - lateinit var appInstance: AppTangemDemo - } -} diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/Old_MainActivity.kt b/tangem-devkit/src/main/java/com/tangem/devkit/Old_MainActivity.kt deleted file mode 100644 index ffccc70603..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/Old_MainActivity.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.tangem.devkit - -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.tangem.TangemSdk -import com.tangem.common.CompletionResult -import com.tangem.tangem_sdk_new.extensions.init -import kotlinx.android.synthetic.main.old_activity_main.* - -class Old_MainActivity : AppCompatActivity() { - - private lateinit var tangemSdk: TangemSdk - private lateinit var cardId: String - private lateinit var issuerData: ByteArray - private lateinit var issuerDataSignature: ByteArray - private var issuerDataCounter: Int = 1 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.old_activity_main) - - tangemSdk = TangemSdk.init(this) - - btn_scan?.setOnClickListener { _ -> - tangemSdk.scanCard { taskEvent -> - when (taskEvent) { - 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 - - } - } - } - } - } - btn_sign?.setOnClickListener { _ -> - tangemSdk.sign( - createSampleHashes(), - cardId) { - when (it) { - is CompletionResult.Failure -> { - runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } - } - is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." } - } - } - } - btn_read_issuer_data?.setOnClickListener { _ -> - tangemSdk.readIssuerData(cardId) { - when (it) { - is CompletionResult.Failure -> { - runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } - } - is CompletionResult.Success -> runOnUiThread { - btn_write_issuer_data.isEnabled = true - tv_card_cid?.text = it.data.issuerData.contentToString() - issuerData = it.data.issuerData - issuerDataSignature = it.data.issuerDataSignature - } - } - } - } - btn_write_issuer_data?.setOnClickListener { _ -> - tangemSdk.writeIssuerData( - cardId, - issuerData, - issuerDataSignature) { - when (it) { - is CompletionResult.Failure -> { - runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } - } - is CompletionResult.Success -> runOnUiThread { - tv_card_cid?.text = it.data.cardId - } - } - } - } - btn_read_issuer_extra_data?.setOnClickListener { _ -> - tangemSdk.readIssuerExtraData(cardId) { - when (it) { - is CompletionResult.Failure -> { - runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } - } - 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." - } - } - } - } - btn_purge_wallet?.setOnClickListener { _ -> - tangemSdk.purgeWallet( - cardId) { - when (it) { - is CompletionResult.Failure -> { - runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } - } - is CompletionResult.Success -> runOnUiThread { - tv_card_cid?.text = it.data.status.name - } - } - } - } - btn_create_wallet?.setOnClickListener { _ -> - tangemSdk.createWallet( - cardId) { - when (it) { - is CompletionResult.Failure -> { - runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } - } - is CompletionResult.Success -> runOnUiThread { - tv_card_cid?.text = it.data.status.name - btn_sign.isEnabled = true - btn_read_issuer_data.isEnabled = true - btn_purge_wallet.isEnabled = true - btn_create_wallet.isEnabled = false - - } - } - } - } - btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) } - } - - private fun createSampleHashes(): Array { - val hash1 = ByteArray(32) { 1 } - val hash2 = ByteArray(32) { 2 } - return arrayOf(hash1, hash2) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt b/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt deleted file mode 100644 index 2be072959f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/TestUserDataActivity.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.tangem.devkit - -import android.os.Bundle -import android.view.View -import android.widget.CompoundButton -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import com.tangem.TangemSdk -import com.tangem.TangemSdkError -import com.tangem.common.CompletionResult -import com.tangem.tangem_sdk_new.extensions.init -import kotlinx.android.synthetic.main.activity_test_user_data.* -import java.nio.charset.StandardCharsets - -/** -[REDACTED_AUTHOR] - */ -class TestUserDataActivity : AppCompatActivity() { - - private lateinit var tangemSdk: TangemSdk - private lateinit var writeOptions: WriteOptions - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_test_user_data) - - init() - initWriteOptions() - } - - private fun init() { - tangemSdk = TangemSdk.init(this) - - btn_scan?.setOnClickListener { _ -> - tangemSdk.scanCard { taskEvent -> - when (taskEvent) { - is CompletionResult.Success -> { - // Handle returned card data - writeOptions.cardId = taskEvent.data.cardId - runOnUiThread { showReadWriteSection(true) } - } - } - } - } - - btn_write.setOnClickListener { - if (writeOptions.cardId == null) return@setOnClickListener - - tangemSdk.writeUserData( - writeOptions.cardId!!, - writeOptions.userData, - writeOptions.userCounter - ) { - when (it) { - is CompletionResult.Failure -> handleError(tv_write_result, it.error) - is CompletionResult.Success -> { - runOnUiThread { tv_write_result?.text = "Success" } - } - } - } - } - - btn_read.setOnClickListener { - if (writeOptions.cardId == null) return@setOnClickListener - - tangemSdk.readUserData(writeOptions.cardId!!) { - when (it) { - is CompletionResult.Failure -> handleError(tv_write_result, it.error) - is CompletionResult.Success -> { - runOnUiThread { - - tv_read_result?.text = "Success" - - writeOptions.userData = it.data.userData - writeOptions.userProtectedData = it.data.userProtectedData - writeOptions.userCounter = it.data.userCounter - writeOptions.userProtectedCounter = it.data.userProtectedCounter - - 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() - } - - } - } - } - } - } - - private fun handleError(tv: TextView, error: TangemSdkError) { - if (error is TangemSdkError.UserCancelled) return - - runOnUiThread { tv.text = error::class.simpleName } - } - - private fun initWriteOptions() { - writeOptions = WriteOptions() - - chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) } - chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } - chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } - chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } -// chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } - } - - private fun showReadWriteSection(show: Boolean) { - val state = if (show) View.VISIBLE else View.GONE - cl_read_write.visibility = state - } -} - -class WriteOptions { - var cardId: String? = null - var userData: ByteArray? = null - var userProtectedData: ByteArray? = null - var userCounter: Int? = null - var userProtectedCounter: Int? = null - var pin2: String? = null - - fun updateData(chbx: CompoundButton) { - val value = "simple user data".toByteArray() - userData = if (chbx.isChecked) value else null - } - - fun updateProtectedData(chbx: CompoundButton) { - val value = "protected user data".toByteArray() - userProtectedData = if (chbx.isChecked) value else null - } - - fun updateCounter(chbx: CompoundButton) { - val value = if (userCounter == null) 0 else userCounter!! + 1 - userCounter = if (chbx.isChecked) value else null - } - - fun updateProtectedCounter(chbx: CompoundButton) { - val value = if (userProtectedCounter == null) 0 else userProtectedCounter!! + 1 - userProtectedCounter = if (chbx.isChecked) value else null - } - -// fun updatePin2(chbx: CompoundButton) { -// val value = SessionEnvironment.pin1.DEFAULT_PIN2 -// pin2 = if (chbx.isChecked) value else null -// } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/SingleLiveEvent.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/SingleLiveEvent.kt deleted file mode 100644 index 1e6ed8667e..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/SingleLiveEvent.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.devkit._arch - -import android.util.Log -import androidx.annotation.MainThread -import androidx.annotation.Nullable -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import java.util.concurrent.atomic.AtomicBoolean - -/** -[REDACTED_AUTHOR] - */ -class SingleLiveEvent : MutableLiveData() { - private val mPending: AtomicBoolean = AtomicBoolean(false) - - override fun observe(owner: LifecycleOwner, observer: Observer) { - if (hasActiveObservers()) { - Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") - } - - // Observe the internal MutableLiveData - super.observe(owner, Observer { - if (mPending.compareAndSet(true, false)) { - observer.onChanged(it) - } - }) - } - - @MainThread - override fun setValue(@Nullable t: T?) { - mPending.set(true) - super.setValue(t) - } - - @MainThread - fun call() { - setValue(null) - } - - companion object { - private const val TAG = "SingleLiveEvent" - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/HelpAbstracts.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/HelpAbstracts.kt deleted file mode 100644 index 018e074061..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/HelpAbstracts.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.devkit._arch.structure - -/** -[REDACTED_AUTHOR] - */ -typealias Payload = MutableMap - -interface PayloadHolder { - val payload: Payload - - fun get(key: String): Any? = payload[key] - fun remove(key: String): Any? = payload.remove(key) - fun set(key: String, value: Any?) { - payload[key] = value - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Ids.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Ids.kt deleted file mode 100644 index bc05f80fca..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Ids.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.devkit._arch.structure - -/** -[REDACTED_AUTHOR] - */ -interface Id { - - companion object { - fun getTag(id: Id): String { - val className = id.javaClass.simpleName - return if (id is Enum<*>) "$className.${id.name}" else className - } - } -} - -class StringId(val value: String) : Id - -class StringResId(val value: Int) : Id - -enum class Additional : Id { - UNDEFINED, - JSON_INCOMING, - JSON_OUTGOING, - JSON_TAILS, -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Logger.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Logger.kt deleted file mode 100644 index 5ba36bbbaa..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/Logger.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.devkit._arch.structure - -import ru.dev.gbixahue.eu4d.lib.android.global.log.Logger -import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger -import ru.dev.gbixahue.eu4d.lib.android.global.log.profiling.SimpleLogProfiler -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -/** -[REDACTED_AUTHOR] - */ -object ILog { - - private var logger: Logger? = null - - fun setLogger(logger: Logger) { - ILog.logger = logger - } - - fun d(from: Any, msg: Any?, value: Any? = null) { - logger?.d(from, stringOf(msg), value) - } - - fun w(from: Any, msg: Any?, value: Any? = null) { - logger?.w(from, stringOf(msg), value) - } - - fun e(from: Any, msg: Any?, value: Any? = null) { - logger?.e(from, stringOf(msg), value) - } -} - -class ItemLogger : TagLogger("ITEM") { - init { - msProfiler = SimpleLogProfiler() - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Extension.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Extension.kt deleted file mode 100644 index 41a4aab5ae..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Extension.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.devkit._arch.structure.abstraction - -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ -fun List.findItem(id: Id): Item? { - var foundItem: Item? = null - iterate { - if (it.id == id) { - foundItem = it - return@iterate - } - } - return foundItem -} - -fun List.iterate(func: (Item) -> Unit) { - forEach { - when (it) { - is BaseItem -> func(it) - is ItemGroup -> { - func(it) - it.itemList.iterate(func) - } - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Item.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Item.kt deleted file mode 100644 index d1fae9c535..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Item.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.tangem.devkit._arch.structure.abstraction - -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ - -interface UpdateBy{ - fun update(value: B) -} - -interface Item: UpdateBy { - val id: Id - var parent: Item? - var viewModel: ItemViewModel - - fun added(parent: Item) { - this.parent = parent - } - - fun removed(parent: Item) { - this.parent = null - } - - fun getData(): D? = viewModel.data as? D - - fun setData(value: Any?) { - viewModel.data = value - } - - fun restoreDefaultData() { - setData(viewModel.defaultData) - } -} - -open class BaseItem( - override val id: Id, - override var viewModel: ItemViewModel -) : Item { - - override var parent: Item? = null - - override fun update(value: Item) { - viewModel.update(value.viewModel) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemGroup.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemGroup.kt deleted file mode 100644 index 1d38ba6f6d..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemGroup.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.devkit._arch.structure.abstraction - -import com.tangem.devkit._arch.structure.ILog -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ -interface ItemGroup : Item { - val itemList: MutableList - - fun setItems(list: MutableList) - fun getItems(): MutableList - fun addItem(item: Item) - fun removeItem(item: Item) - fun clear() -} - -open class SimpleItemGroup( - override val id: Id, - override var viewModel: ItemViewModel = BaseItemViewModel() -) : ItemGroup { - - override var parent: Item? = null - override val itemList: MutableList = mutableListOf() - - override fun setItems(list: MutableList) { - ILog.d(this, "setItems into: $id, count: ${list.size}") - itemList.forEach { it.removed(this) } - itemList.clear() - list.forEach { addItem(it) } - } - - override fun getItems(): MutableList = itemList - - override fun addItem(item: Item) { - ILog.d(this, "addItem into: $id, who: ${item.id}") - itemList.add(item) - item.added(this) - } - - override fun removeItem(item: Item) { - ILog.d(this, "removeItem from: $id, which: ${item.id}") - itemList.remove(item) - item.removed(this) - } - - override fun clear() { - ILog.d(this, "clear $id") - itemList.clear() - } - - override fun update(value: Item) { - // nothing to do - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemViewModel.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemViewModel.kt deleted file mode 100644 index eead6c8e3d..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ItemViewModel.kt +++ /dev/null @@ -1,115 +0,0 @@ -package com.tangem.devkit._arch.structure.abstraction - -import com.tangem.devkit._arch.structure.ILog -import com.tangem.devkit._arch.structure.Payload -import com.tangem.devkit._arch.structure.PayloadHolder - - -/** -[REDACTED_AUTHOR] - */ -typealias ValueChanged = (V?) -> Unit -typealias SafeValueChanged = (V) -> Unit - -class KeyValue(val key: String, val value: Any) - -class ViewState( - isVisible: Boolean? = null, - bgColor: Int? = -1 -) : UpdateBy { - - class State( - stateValue: T, - var onValueChanged: SafeValueChanged? = null - ) { - var value = stateValue - set(value) { - if (preventSameChanges && field == value) return - - field = value - onValueChanged?.invoke(value) - } - - internal var preventSameChanges = true - } - - var isVisibleState = State(isVisible) - var backgroundColor = State(bgColor) - var descriptionVisibility = State(0x00000008) - - internal fun preventSameChanges(isPrevented: Boolean) { - val states = listOf(isVisibleState, backgroundColor, descriptionVisibility) - states.forEach { it.preventSameChanges = isPrevented } - } - - override fun update(value: ViewState) { -// isVisibleState.update(value.isVisibleState) -// backgroundColor.update(value.backgroundColor) -// descriptionVisibility.update(value.descriptionVisibility) - } -} - -interface ItemViewModel : PayloadHolder, UpdateBy { - val viewState: ViewState - var data: Any? - var defaultData: Any? - var onDataUpdated: ValueChanged? - - fun updateDataByView(data: Any?) -} - -open class BaseItemViewModel( - value: Any? = null, - override val viewState: ViewState = ViewState() -) : ItemViewModel { - - override val payload: Payload = mutableMapOf() - - // Don't update it directly from a View. Use for it updateDataByView() - override var data: Any? = value - set(value) { - if (handleDataUpdates(value)) field = value - } - - // Data for restoring initial value - override var defaultData: Any? = value - set(value) { - field = value - data = value - } - - // Use it for handling data updates in View - override var onDataUpdated: ValueChanged? = null - - // When data updates directly it invokes onDataUpdated - // return true = data will update - // return false = data won't update - protected open fun handleDataUpdates(value: Any?): Boolean { - ILog.d(this, "handleDateUpdates: $value") - onDataUpdated?.invoke(value) - return true - } - - // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop - override fun updateDataByView(data: Any?) { - ILog.d(this, "data changed: $data") - val callback = onDataUpdated - onDataUpdated = null - this.data = data - onDataUpdated = callback - } - - override fun update(value: ItemViewModel) { - viewState.update(value.viewState) - defaultData = value.defaultData - data = value.data - payload.clear() - payload.putAll(value.payload) - } -} - -class ListViewModel( - val itemList: List, - var selectedItem: Any?, - override val viewState: ViewState = ViewState() -) : BaseItemViewModel(selectedItem) \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ModelConverter.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ModelConverter.kt deleted file mode 100644 index 30c69cd6b2..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/ModelConverter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.devkit._arch.structure.abstraction - -import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter - -/** -[REDACTED_AUTHOR] - */ -interface DefaultConverter { - fun convert(from: A, default: B): B -} - -interface TwoWayConverter { - fun aToB(from: A): B - fun bToA(from: B): A -} - -interface ItemsToModel : DefaultConverter, M> -interface ModelToItems : Converter> - -interface ModelConverter : DefaultConverter, M>, Converter> \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Plugin.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Plugin.kt deleted file mode 100644 index aaa6254d7c..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/abstraction/Plugin.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.devkit._arch.structure.abstraction - -/** -[REDACTED_AUTHOR] - */ -typealias PluginCallBackResult = (Any?) -> Item - -interface Plugin : Item { - fun invoke(data: Any?, asyncCallback: PluginCallBackResult? = null): Any? -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/impl/Items.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/impl/Items.kt deleted file mode 100644 index 80145d31e4..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/structure/impl/Items.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.devkit._arch.structure.impl - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.abstraction.* - -/** -[REDACTED_AUTHOR] - */ - -open class TypedItem(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewModel) { - open fun getTypedData(): D? = viewModel.data as? D -} - -open class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { - constructor(id: Id, value: String? = null, viewState: ViewState = ViewState()) - : this(id, BaseItemViewModel(value, viewState)) -} - -open class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { - constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState()) - : this(id, BaseItemViewModel(value, viewState)) -} - -open class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { - constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState()) - : this(id, BaseItemViewModel(value, viewState)) -} - - -open class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) { - constructor(id: Id, value: String? = null, viewState: ViewState = ViewState()) - : this(id, BaseItemViewModel(value, viewState)) -} - - -open class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem(id, viewModel) { - constructor(id: Id, list: List, selectedValue: Any?, viewState: ViewState = ViewState()) - : this(id, ListViewModel(list, selectedValue, viewState)) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/ItemWidgetBuilder.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/ItemWidgetBuilder.kt deleted file mode 100644 index 4659c0ac49..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/ItemWidgetBuilder.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.devkit._arch.widget - -import android.view.ViewGroup -import com.tangem.devkit._arch.structure.abstraction.BaseItem -import com.tangem.devkit._arch.widget.abstraction.ViewWidget - -/** -[REDACTED_AUTHOR] - */ -interface ItemWidgetBuilder { - fun build(item: BaseItem, parent: ViewGroup): ViewWidget? -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/WidgetBuilder.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/WidgetBuilder.kt deleted file mode 100644 index cb39aa9072..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/WidgetBuilder.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.tangem.devkit._arch.widget - -import android.view.ViewGroup -import com.tangem.devkit._arch.structure.abstraction.BaseItem -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.ItemGroup -import com.tangem.devkit._arch.widget.abstraction.ViewWidget -import com.tangem.devkit._arch.widget.impl.LinearGroupWidget -import com.tangem.devkit._arch.widget.impl.StubWidget - -/** -[REDACTED_AUTHOR] - */ -class WidgetBuilder( - private val itemBuilder: ItemWidgetBuilder -) { - - fun build(item: Item, parent: ViewGroup): ViewWidget? { - return when (item) { - is ItemGroup -> buildBlock(item, parent) - is BaseItem -> itemBuilder.build(item, parent) - else -> StubWidget(item.id, parent) - } - } - - private fun buildBlock(itemGroup: ItemGroup, parent: ViewGroup): ViewWidget { - return when (itemGroup) { - is ItemGroup -> { - val linearBlock = LinearGroupWidget(parent, itemGroup) - itemGroup.getItems().forEach { build(it, linearBlock.view as ViewGroup) } - linearBlock - } - else -> StubWidget(itemGroup.id, parent) - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/abstraction/ViewWidget.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/abstraction/ViewWidget.kt deleted file mode 100644 index e414e89cf7..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/abstraction/ViewWidget.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.tangem.devkit._arch.widget.abstraction - -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.annotation.ColorRes -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.StringId -import com.tangem.devkit._arch.structure.StringResId -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.ViewState -import ru.dev.gbixahue.eu4d.lib.android._android.views.colorFrom -import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder - -/** -[REDACTED_AUTHOR] - */ -interface ViewWidget : LayoutHolder { - val view: View - var item: Item - - fun getName(): String - fun setBackgroundColor(@ColorRes colorId: Int?) -} - -abstract class BaseViewWidget( - parent: ViewGroup, - override var item: Item -) : ViewWidget { - - override val view: View = inflate(getLayoutId(), parent) - - private var defaultBackground: Drawable? = view.background - - init { - subscribeToViewStateChanges(item.viewModel.viewState) - initViewState(item.viewModel.viewState) - view.tag = Id.getTag(item.id) - } - - protected open fun subscribeToViewStateChanges(viewState: ViewState) { - viewState.isVisibleState.onValueChanged = { state -> - state?.let { view.visibility = if (it) View.VISIBLE else View.GONE } - } - viewState.backgroundColor.onValueChanged = { setBackgroundColor(it) } - } - - protected open fun initViewState(viewState: ViewState) { - viewState.preventSameChanges(false) - viewState.isVisibleState.value = viewState.isVisibleState.value - if (viewState.backgroundColor.value != -1) setBackgroundColor(viewState.backgroundColor.value) - viewState.preventSameChanges(true) - } - - override fun getName(): String { - return when (val id = item.id) { - is StringId -> id.value - is StringResId -> view.resources.getString(id.value) - else -> view.resources.getString(R.string.unknown) - } - } - - override fun setBackgroundColor(colorId: Int?) { - val background = when { - colorId == null -> null - colorId == -1 -> defaultBackground - else -> ColorDrawable(view.colorFrom(colorId)) - } - view.background = background - } -} - -internal fun inflate(id: Int, parent: ViewGroup): View { - val layoutId = if (id <= 0) R.layout.w_empty else id - val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false) - parent.addView(inflatedView) - return inflatedView -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/LinearGroupWidget.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/LinearGroupWidget.kt deleted file mode 100644 index 54948387de..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/LinearGroupWidget.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.devkit._arch.widget.impl - -import android.view.ViewGroup -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.abstraction.ItemGroup -import com.tangem.devkit._arch.widget.abstraction.BaseViewWidget - -/** -[REDACTED_AUTHOR] - */ -class LinearGroupWidget( - parent: ViewGroup, - itemGroup: ItemGroup -) : BaseViewWidget(parent, itemGroup) { - - override fun getLayoutId(): Int = R.layout.w_personilize_block - -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/StubWidget.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/StubWidget.kt deleted file mode 100644 index 0edc41b3be..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_arch/widget/impl/StubWidget.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.devkit._arch.widget.impl - -import android.view.ViewGroup -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.abstraction.BaseItem -import com.tangem.devkit._arch.structure.abstraction.BaseItemViewModel -import com.tangem.devkit._arch.widget.abstraction.BaseViewWidget - -/** -[REDACTED_AUTHOR] - */ -class StubWidget(id: Id, parent: ViewGroup) : BaseViewWidget(parent, BaseItem(id, BaseItemViewModel())) { - override fun getLayoutId(): Int = R.layout.w_empty -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_main/MainActivity.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_main/MainActivity.kt deleted file mode 100644 index dbfd82105f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_main/MainActivity.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.tangem.devkit._main - -import android.content.res.Resources -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.navigateUp -import androidx.navigation.ui.setupWithNavController -import com.tangem.devkit.R -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -class MainActivity : AppCompatActivity() { - - private val mainVM: MainViewModel by viewModels() - private lateinit var appBarConfiguration: AppBarConfiguration - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.a_main) - - val toolbar = findViewById(R.id.toolbar) - setSupportActionBar(toolbar) - - val host: NavHostFragment = supportFragmentManager - .findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return - - val navController = host.navController - appBarConfiguration = AppBarConfiguration(navController.graph) - toolbar.setupWithNavController(navController, appBarConfiguration) - - navController.addOnDestinationChangedListener { _, destination, _ -> - val dest: String = try { - resources.getResourceName(destination.id) - } catch (e: Resources.NotFoundException) { - destination.id.toString() - } - Log.d(this, "Navigated to $dest") - } - } - - override fun onSupportNavigateUp(): Boolean { - return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_activity_main, menu) - return true - } - - override fun onPrepareOptionsMenu(menu: Menu): Boolean { - val switchItem = menu.findItem(R.id.action_toggle_description_visibility) - switchItem.isChecked = mainVM.descriptionSwitchState - return super.onPrepareOptionsMenu(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val result = when (item.itemId) { - R.id.action_toggle_description_visibility -> { - item.isChecked = !item.isChecked - mainVM.switchToggled(item.isChecked) - } - else -> null - } - return if (result == null) super.onOptionsItemSelected(item) else true - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_main/MainViewModel.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_main/MainViewModel.kt deleted file mode 100644 index 6766c6d77f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_main/MainViewModel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.devkit._main - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.tangem.commands.CommandResponse -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -class MainViewModel : ViewModel() { - val ldDescriptionSwitch = MutableLiveData(false) - var descriptionSwitchState = false - - var commandResponse: CommandResponse? = null - - fun switchToggled(state: Boolean) { - descriptionSwitchState = state - ldDescriptionSwitch.postValue(state) - } - - fun changeResponseEvent(commandResponse: CommandResponse?) { - Log.d(this, "changeResponseEvent") - val response = commandResponse ?: return - - this.commandResponse = response - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/ActionListFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/ActionListFragment.kt deleted file mode 100644 index 2656204d6b..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/ActionListFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.tangem.devkit._main.entryPoint - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.tangem.devkit.R -import com.tangem.devkit._main.MainViewModel -import com.tangem.devkit.extensions.view.beginDelayedTransition -import com.tangem.devkit.ucase.getDefaultNavigationOptions -import com.tangem.devkit.ucase.resources.ActionType -import com.tangem.devkit.ucase.resources.MainResourceHolder -import com.tangem.devkit.ucase.ui.BaseFragment -import kotlinx.android.synthetic.main.fg_entry_point.* - -/** -[REDACTED_AUTHOR] - */ -class ActionListFragment : BaseFragment() { - - private lateinit var rvActions: RecyclerView - - private val mainActivityVM: MainViewModel by activityViewModels() - - override fun getLayoutId(): Int = R.layout.fg_entry_point - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initRecyclerView() - } - - private fun initRecyclerView() { - val layoutManager = LinearLayoutManager(activity) - rvActions = rv_actions - rvActions.layoutManager = layoutManager - rvActions.addItemDecoration(DividerItemDecoration(activity, layoutManager.orientation)) - - val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false) - - rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> - navigateTo(data, options = getDefaultNavigationOptions()) - } - mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { - vhDataWrapper.descriptionIsVisible = it - rvActions.beginDelayedTransition() - rvActions.adapter?.notifyDataSetChanged() - }) - } - - override fun onResume() { - super.onResume() - val adapter: RvActionsAdapter = rvActions.adapter as? RvActionsAdapter ?: return - - adapter.setItemList(getNavigateOptions()) - adapter.notifyDataSetChanged() - } - - private fun getNavigateOptions(): MutableList { - return mutableListOf( - ActionType.Scan, - ActionType.Sign, - ActionType.Personalize, - ActionType.Depersonalize, - ActionType.CreateWallet, - ActionType.PurgeWallet, - ActionType.ReadIssuerData, - ActionType.WriteIssuerData, - ActionType.ReadIssuerExData, - ActionType.WriteIssuerExData, - ActionType.ReadUserData, - ActionType.WriteUserData, - ActionType.WriteProtectedUserData - ) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/RvActions.kt b/tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/RvActions.kt deleted file mode 100644 index 01162d2eee..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/_main/entryPoint/RvActions.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.devkit._main.entryPoint - -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.annotation.StringRes -import androidx.recyclerview.widget.RecyclerView -import com.tangem.devkit.R -import com.tangem.devkit.ucase.resources.ActionRes -import com.tangem.devkit.ucase.resources.ActionType -import com.tangem.devkit.ucase.resources.MainResourceHolder -import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate -import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseAdapter -import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvBaseVH -import ru.dev.gbixahue.eu4d.lib.android._android.views.recycler_view.RvCallback - -/** -[REDACTED_AUTHOR] - */ -class RvActionsAdapter( - private val wrapper: VhExDataWrapper, - private val callback: RvCallback -) : RvBaseAdapter() { - - override fun onBindViewHolder(holder: RvActionsVH, position: Int) { - holder.bindData(itemList[position]) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH { - val view = parent.inflate(R.layout.vh_actions, false) - return RvActionsVH(view, wrapper, callback) - } - -} - -class VhExDataWrapper( - val resHolder: MainResourceHolder, - var descriptionIsVisible: Boolean -) - -class RvActionsVH( - itemView: View, - private val wrapper: VhExDataWrapper, - private val callback: RvCallback? -) : RvBaseVH(itemView) { - - private val tvAction = itemView.findViewById(R.id.tv_title) - private val containerDescription = itemView.findViewById(R.id.container_description) - private val tvDescription = itemView.findViewById(R.id.tv_description) - - override fun bindData(data: ActionType) { - val res: ActionRes = wrapper.resHolder.safeGet(data) - tvAction.text = getString(res.resName) - tvDescription.text = getString(res.resDescription) - - itemView.setOnClickListener { - res.resNavigation?.let { callback?.invoke(itemViewType, adapterPosition, it) } - } - - containerDescription.visibility = if (wrapper.descriptionIsVisible) View.VISIBLE else View.GONE - } -} - -fun RecyclerView.ViewHolder.getString(@StringRes id: Int?, ifNull: String = ""): String { - val reqId = id ?: return ifNull - return itemView.context.getString(reqId) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/commons/Abstractions.kt b/tangem-devkit/src/main/java/com/tangem/devkit/commons/Abstractions.kt deleted file mode 100644 index 3a1776e7c6..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/commons/Abstractions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.devkit.commons - -/** -[REDACTED_AUTHOR] - */ -interface Store { - fun save(value: M) - fun restore(): M -} - -interface KeyedStore { - fun save(key: String, value: M) - fun restore(key: String): M - fun restoreAll(): MutableMap - fun delete(key: String) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/commons/DialogController.kt b/tangem-devkit/src/main/java/com/tangem/devkit/commons/DialogController.kt deleted file mode 100644 index 5d4c924409..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/commons/DialogController.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.tangem.devkit.commons - -import android.app.Activity -import android.app.Dialog -import android.view.LayoutInflater -import android.view.View -import androidx.appcompat.app.AlertDialog - -class DialogController { - var onDismissCallback: (() -> Unit)? = null - var onShowCallback: (() -> Unit)? = null - var view: View? = null - - private var rawDialog: Dialog? = null - - private var inShowingProcess = false - private var inDismissingProcess = false - private var autoReleaseOnDismiss = false - - fun createAlert(context: Activity, resLayout: Int): AlertDialog { - view = LayoutInflater.from(context).inflate(resLayout, null) - rawDialog = AlertDialog.Builder(context).setView(view).create().apply { - setOnShowListener { onShow() } - setOnDismissListener { onDismiss() } - } - return rawDialog as AlertDialog - } - - fun set(dialog: Dialog) { - rawDialog = dialog - dialog.setOnShowListener { onShow() } - dialog.setOnDismissListener { onDismiss() } - } - - private fun onShow() { - inShowingProcess = false - onShowCallback?.invoke() - } - - private fun onDismiss() { - inDismissingProcess = false - if (autoReleaseOnDismiss) release() - onDismissCallback?.invoke() - } - - fun show() { - val dialog = rawDialog ?: return - if (inShowingProcess) return - if (dialog.isShowing) return - - inShowingProcess = true - dialog.show() - } - - fun dismiss(autoRelease: Boolean = true) { - val dialog = rawDialog ?: return - if (inDismissingProcess) return - if (!dialog.isShowing) return - - autoReleaseOnDismiss = autoRelease - inDismissingProcess = true - dialog.dismiss() - } - - fun release() { - onDismissCallback = null - onShowCallback = null - - inShowingProcess = false - inDismissingProcess = false - autoReleaseOnDismiss = false - - view = null - rawDialog = null - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/commons/GlobalFunctions.kt b/tangem-devkit/src/main/java/com/tangem/devkit/commons/GlobalFunctions.kt deleted file mode 100644 index 0f80ba2e83..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/commons/GlobalFunctions.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.devkit.commons - -/** -[REDACTED_AUTHOR] - */ -fun performAction(a: A?, b: B?, action: (A, B) -> Unit, onFail: (() -> Unit)? = null) { - if (a != null && b != null) action(a, b) - else onFail?.invoke() -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/commons/Logger.kt b/tangem-devkit/src/main/java/com/tangem/devkit/commons/Logger.kt deleted file mode 100644 index 94e709455a..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/commons/Logger.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.devkit.commons - -import ru.dev.gbixahue.eu4d.lib.android.global.log.TagLogger - -/** -[REDACTED_AUTHOR] - */ -class TangemLogger : TagLogger("TangemDemo") \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/commons/view/MultiActionView.kt b/tangem-devkit/src/main/java/com/tangem/devkit/commons/view/MultiActionView.kt deleted file mode 100644 index 4e134e0336..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/commons/view/MultiActionView.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.tangem.devkit.commons.view - -import android.widget.TextView -import androidx.annotation.StringRes -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.StringId -import com.tangem.devkit._arch.structure.abstraction.SafeValueChanged -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -/** -[REDACTED_AUTHOR] - */ -open class MultiActionView( - stateActionList: MutableList, - val child: V -) { - - interface State { - val id: Id - - fun getAction(): SimpleFunction - fun getResNameId(): Int - } - - var afterAction: SafeValueChanged? = null - - var state: Id = DefaultId.default - set(value) { - if (field == value) return - - field = value - Log.d(this, "state changed to: ${getKey(value)}") - btnState = stateHolder[getKey(value)] - } - - init { - child.setOnClickListener { - val state = btnState ?: return@setOnClickListener - - Log.d(this, "child handled OnClick for: ${getKey(state.id)}") - state.getAction().invoke() - afterAction?.invoke(state.id) - } - } - - protected var btnState: State? = null - set(value) { - if (value == null) return - - field = value - Log.d(this, "btnState changed to: ${getKey(value.id)}") - child.setText(value.getResNameId()) - } - - protected val stateHolder: MutableMap = stateActionList.associateBy { getKey(it.id) }.toMutableMap() - - fun performAction(id: Id) { - Log.d(this, "performAction ${getKey(id)}") - state = id - child.performClick() - } - - protected open fun getKey(id: Id): String { - return when (id) { - is StringId -> id.value - else -> stringOf(id) - } - } -} - -enum class DefaultId : Id { default } - -typealias SimpleFunction = () -> Unit - -class ViewAction( - override val id: Id, - @StringRes val name: Int, - private val action: SimpleFunction -) : MultiActionView.State { - - override fun getAction(): SimpleFunction = action - - override fun getResNameId(): Int = name -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/extensions/AndroidComponents.kt b/tangem-devkit/src/main/java/com/tangem/devkit/extensions/AndroidComponents.kt deleted file mode 100644 index d7ae706d37..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/extensions/AndroidComponents.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.devkit.extensions - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent -import androidx.fragment.app.Fragment - -/** -[REDACTED_AUTHOR] - */ -fun Context.copyToClipboard(value: Any, label: String = "") { - val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return - - val clip: ClipData = ClipData.newPlainText(label, value.toString()) - clipboard.setPrimaryClip(clip) -} - -fun Context.shareText(text: String) { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, text) - type = "text/plain" - } - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(shareIntent) -} - -fun Fragment.shareText(text: String) { - requireContext().shareText(text) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/extensions/List.kt b/tangem-devkit/src/main/java/com/tangem/devkit/extensions/List.kt deleted file mode 100644 index f40be815b4..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/extensions/List.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.devkit.extensions - -/** -[REDACTED_AUTHOR] - */ -fun List.print(delimiter: String = ", ", wrap: Boolean = true): String { - val builder = StringBuilder() - forEach { builder.append(it).append(delimiter) } - val length = builder.length - if (length > delimiter.length) { - builder.delete(length - delimiter.length, length) - } - val result = builder.toString() - - return if (wrap) "[$result]" else result -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/extensions/view/Transitions.kt b/tangem-devkit/src/main/java/com/tangem/devkit/extensions/view/Transitions.kt deleted file mode 100644 index 1a6762eca0..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/extensions/view/Transitions.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.devkit.extensions.view - -import android.view.ViewGroup -import androidx.transition.AutoTransition -import androidx.transition.Transition -import androidx.transition.TransitionManager - -/** -[REDACTED_AUTHOR] - */ -fun ViewGroup.beginDelayedTransition(transition: Transition = AutoTransition()) { - TransitionManager.beginDelayedTransition(this, transition) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/NavigationOp.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/NavigationOp.kt deleted file mode 100644 index c006bf6aa4..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/NavigationOp.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.devkit.ucase - -import androidx.navigation.NavOptions -import androidx.navigation.navOptions -import com.tangem.devkit.R - -/** -[REDACTED_AUTHOR] - */ -fun getDefaultNavigationOptions(): NavOptions { - return navOptions { - anim { - enter = R.anim.slide_in_right - exit = R.anim.slide_out_left - popEnter = R.anim.slide_in_left - popExit = R.anim.slide_out_right - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/Action.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/Action.kt deleted file mode 100644 index f0c92ad43e..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/Action.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.TangemSdk -import com.tangem.common.CompletionResult -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.Payload -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification -import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence - -/** -[REDACTED_AUTHOR] - * - * The Card Action class family is designed for calling Card Manager functions - * and then processing the response. It also allows you to extract the main action as a lambda expression - */ -data class AttrForAction( - val tangemSdk: TangemSdk, - val itemList: List, - val payload: Payload, - val consequence: ItemsChangeConsequence? -) - -interface Action { - fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) - fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null -} - -abstract class BaseAction : Action { - protected open fun handleResult( - payload: PayloadHolder, - commandResult: CompletionResult<*>, - modifier: AfterActionModification?, - attrs: AttrForAction, - callback: ActionCallback - ) { - val modifiedItems = mutableListOf() - modifier?.modify(payload, commandResult, attrs.itemList)?.forEach { item -> - modifiedItems.add(item) - attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) } - } - callback(commandResult, modifiedItems) - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/CreateWalletAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/CreateWalletAction.kt deleted file mode 100644 index 0e02ca670f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/CreateWalletAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId - -class CreateWalletAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val item = attrs.itemList.findItem(TlvId.CardId) ?: return - val cardId = item.viewModel.data as? String ?: return - - attrs.tangemSdk.createWallet(cardId) { handleResult(payload, it, null, attrs, callback) } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/DepersonalizeAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/DepersonalizeAction.kt deleted file mode 100644 index 9cfec29492..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/DepersonalizeAction.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier - -/** -[REDACTED_AUTHOR] - */ -class DepersonalizeAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - attrs.tangemSdk.depersonalize { handleResult(payload, it, AfterScanModifier(), attrs, callback) } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PersonalizeAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PersonalizeAction.kt deleted file mode 100644 index 5e0519b5ce..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PersonalizeAction.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit._arch.structure.impl.EditTextItem -import com.tangem.devkit._arch.structure.impl.NumberItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey -import com.tangem.devkit.ucase.tunnel.ActionView -import com.tangem.devkit.ucase.tunnel.ItemError -import com.tangem.devkit.ucase.variants.personalize.CardNumberId -import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationConfigConverter -import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationJsonConverter -import com.tangem.devkit.ucase.variants.personalize.dto.DefaultPersonalizationParams -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig -import com.tangem.devkit.ucase.variants.personalize.dto.toCardConfig -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -/** -[REDACTED_AUTHOR] - */ -class PersonalizeAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val itemList = attrs.payload[PayloadKey.itemList] as? List ?: return - val actionView = attrs.payload[PayloadKey.actionView] as? ActionView ?: return - - if (!checkSeries(itemList)) { - actionView.showSnackbar(ItemError.BadSeries) - return - } - if (!checkNumber(itemList)) { - actionView.showSnackbar(ItemError.BadCardNumber) - return - } - - val issuer = DefaultPersonalizationParams.issuer() - val acquirer = DefaultPersonalizationParams.acquirer() - val manufacturer = DefaultPersonalizationParams.manufacturer() - - val config = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig.default()) - val jsonDto = PersonalizationJsonConverter().bToA(config) - - attrs.tangemSdk.personalize(jsonDto.toCardConfig(), issuer, manufacturer, acquirer) { - handleResult(payload, it, null, attrs, callback) - } - } - - private fun checkSeries(itemList: List): Boolean { - val seriesItem = itemList.findItem(CardNumberId.Series) as? EditTextItem ?: return false - val data = seriesItem.getData() as? String ?: return false - - seriesItem.setData(data.toUpperCase()) - return data.length == 2 || data.length == 4 - } - - private fun checkNumber(itemList: List): Boolean { - val seriesItem = itemList.findItem(CardNumberId.Series) as? EditTextItem ?: return false - val numberItem = itemList.findItem(CardNumberId.Number) as? NumberItem ?: return false - val seriesData = seriesItem.getData() as? String ?: return false - val numberData = stringOf(numberItem.getData() as? Number) - - return if (seriesData.length == 2 && numberData.length > 13) false - else !(seriesData.length == 4 && numberData.length > 11) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PurgeWalletAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PurgeWalletAction.kt deleted file mode 100644 index 4c2efe395e..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/PurgeWalletAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId - -class PurgeWalletAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val item = attrs.itemList.findItem(TlvId.CardId) ?: return - val cardId = item.viewModel.data as? String ?: return - - attrs.tangemSdk.purgeWallet(cardId) { handleResult(payload, it, null, attrs, callback) } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerDataAction.kt deleted file mode 100644 index 580e43d4f2..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerDataAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId - -class ReadIssuerDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val item = attrs.itemList.findItem(TlvId.CardId) ?: return - val cardId = item.viewModel.data as? String ?: return - - attrs.tangemSdk.readIssuerData(cardId) { handleResult(payload, it, null, attrs, callback) } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerExtraDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerExtraDataAction.kt deleted file mode 100644 index 4483a0bd78..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadIssuerExtraDataAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId - -class ReadIssuerExtraDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val item = attrs.itemList.findItem(TlvId.CardId) ?: return - val cardId = item.viewModel.data as? String ?: return - - attrs.tangemSdk.readIssuerExtraData(cardId) { handleResult(payload, it, null, attrs, callback) } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadUserDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadUserDataAction.kt deleted file mode 100644 index 6014c60757..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ReadUserDataAction.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId - -class ReadUserDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val item = attrs.itemList.findItem(TlvId.CardId) ?: return - val cardId = item.viewModel.data as? String ?: return - - attrs.tangemSdk.readUserData(cardId) { handleResult(payload, it, null, attrs, callback) } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ScanAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ScanAction.kt deleted file mode 100644 index 5e4e42aa08..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/ScanAction.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction.AfterScanModifier - -/** -[REDACTED_AUTHOR] - */ -class ScanAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - attrs.tangemSdk.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/SignAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/SignAction.kt deleted file mode 100644 index a1cf64ae35..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/SignAction.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -/** -[REDACTED_AUTHOR] - */ -class SignAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val dataForHashing = attrs.itemList.findItem(TlvId.TransactionOutHash) ?: return - val hash = dataForHashing.getData() as? String ?: return - val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - - attrs.tangemSdk.sign(arrayOf(hash.toByteArray()), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerDataAction.kt deleted file mode 100644 index c93f5edfc1..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerDataAction.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toByteArray -import com.tangem.crypto.sign -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId -import com.tangem.devkit.ucase.variants.personalize.dto.DefaultPersonalizationParams -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -class WriteIssuerDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - val counter = (attrs.itemList.findItem(TlvId.Counter)?.viewModel?.data as? Int) ?: 1 - - val issuerDataItem = attrs.itemList.findItem(TlvId.IssuerData) ?: return - val issuerData = (issuerDataItem.getData() as? String)?.toByteArray() ?: return - - val signedIssuerData = ( - stringOf(cardId).hexToBytes() + issuerData + counter.toByteArray(4)) - .sign(DefaultPersonalizationParams.issuer().dataKeyPair.privateKey) - - attrs.tangemSdk.writeIssuerData(stringOf(cardId), issuerData, signedIssuerData, counter) { - handleResult(payload, it, null, attrs, callback) - } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerExtraDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerExtraDataAction.kt deleted file mode 100644 index 17c5052f9f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteIssuerExtraDataAction.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.commands.WriteIssuerExtraDataCommand -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toByteArray -import com.tangem.crypto.CryptoUtils -import com.tangem.crypto.sign -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId -import com.tangem.devkit.ucase.variants.personalize.dto.DefaultPersonalizationParams -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -class WriteIssuerExtraDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - val counter = (attrs.itemList.findItem(TlvId.Counter)?.viewModel?.data as? String)?.toInt() ?: 1 - - val issuerData = CryptoUtils.generateRandomBytes(WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE * 5) - - - val startingSignature = ( - stringOf(cardId).hexToBytes() + counter.toByteArray(4) + issuerData.size.toByteArray(2)) - .sign(DefaultPersonalizationParams.issuer().dataKeyPair.privateKey) - - val finalizingSignature = ( - stringOf(cardId).hexToBytes() + issuerData + counter.toByteArray(4)) - .sign(DefaultPersonalizationParams.issuer().dataKeyPair.privateKey) - - attrs.tangemSdk.writeIssuerExtraData( - stringOf(cardId), issuerData, startingSignature, finalizingSignature, counter - ) { - handleResult(payload, it, null, attrs, callback) - } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserDataAction.kt deleted file mode 100644 index 7727546c05..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserDataAction.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -class WriteUserDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val userData = (attrs.itemList.findItem(TlvId.UserData)?.getData() as? String)?.toByteArray() - ?: return - val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - val counter = (attrs.itemList.findItem(TlvId.Counter)?.viewModel?.data as? Int) ?: 1 - - attrs.tangemSdk.writeUserData(stringOf(cardId), userData, counter) { - handleResult(payload, it, null, attrs, callback) - } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserProtectedDataAction.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserProtectedDataAction.kt deleted file mode 100644 index e315107b8b..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/actions/WriteUserProtectedDataAction.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.devkit.ucase.domain.actions - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.TlvId -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - - -class WriteUserProtectedDataAction : BaseAction() { - override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - val protectedUserData = (attrs.itemList.findItem(TlvId.ProtectedUserData)?.getData() as? String)?.toByteArray() - ?: return - val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - val counter = (attrs.itemList.findItem(TlvId.Counter)?.viewModel?.data as? Int) ?: 1 - - attrs.tangemSdk.writeProtectedUserData(stringOf(cardId), protectedUserData, counter) { - handleResult(payload, it, null, attrs, callback) - } - } - - override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { - return when (id) { - TlvId.CardId -> { callback -> ScanAction().executeMainAction(payload, attrs, callback) } - else -> null - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/ItemsManager.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/ItemsManager.kt deleted file mode 100644 index 35022b0af4..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/ItemsManager.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager - -import com.tangem.TangemSdk -import com.tangem.common.CompletionResult -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.Payload -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence - -typealias ActionResponse = CompletionResult<*> -typealias AffectedList = List -typealias AffectedItemsCallback = (AffectedList) -> Unit -typealias ActionCallback = (ActionResponse, AffectedList) -> Unit - -/** -[REDACTED_AUTHOR] - */ -interface ItemsManager : PayloadHolder { - - fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback? = null) - fun setItems(items: List) - fun getItems(): List - fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) - fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) - fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? - fun attachPayload(payload: Payload) - fun updateByItemList(list: List) -} - -interface PayloadKey { - companion object { - val cardConfig: String = "cardConfig" - val incomingJson: String = "incomingJson" - val card = "card" - val actionView = "actionView" - val itemList = "itemList" - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/BaseItemsManager.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/BaseItemsManager.kt deleted file mode 100644 index 7fcba9d02c..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/BaseItemsManager.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager.managers - -import androidx.lifecycle.LifecycleObserver -import com.tangem.TangemSdk -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.Payload -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit._arch.structure.abstraction.iterate -import com.tangem.devkit.ucase.domain.actions.Action -import com.tangem.devkit.ucase.domain.actions.AttrForAction -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.domain.paramsManager.AffectedItemsCallback -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -open class BaseItemsManager(protected val action: Action) : ItemsManager, LifecycleObserver { - - init { - Log.d(this, "new instance created") - } - - override val payload: MutableMap = mutableMapOf() - - protected var changeConsequence: ItemsChangeConsequence? = null - protected val itemList: MutableList = mutableListOf() - - override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) { - if (itemList.isEmpty()) return - val foundItem = itemList.findItem(id) ?: return - - foundItem.setData(value) - applyChangesByAffectedItems(foundItem, callback) - } - - override fun setItems(items: List) { - itemList.clear() - itemList.addAll(items) - } - - override fun getItems(): List = itemList - - override fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) { - this.changeConsequence = consequence - } - - override fun updateByItemList(list: List) { - list.iterate { itemList.findItem(it.id)?.update(it) } - } - - override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(tangemSdk), callback) - } - - override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? { - return action.getActionByTag(this, id, getAttrsForAction(tangemSdk)) - } - - override fun attachPayload(payload: Payload) { - payload.forEach { this.payload[it.key] = it.value } - } - - // Use it if current item needs to be affect any other items - protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) { - changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) } - } - - protected fun getAttrsForAction(tangemSdk: TangemSdk) - : AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt deleted file mode 100644 index a8125f2a51..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager.managers - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.OnLifecycleEvent -import com.tangem.TangemSdk -import com.tangem.devkit.commons.Store -import com.tangem.devkit.ucase.domain.actions.PersonalizeAction -import com.tangem.devkit.ucase.domain.paramsManager.ActionCallback -import com.tangem.devkit.ucase.variants.personalize.converter.PersonalizationConfigConverter -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig - -/** -[REDACTED_AUTHOR] - */ -class PersonalizationItemsManager( - private val store: Store -) : BaseItemsManager(PersonalizeAction()) { - - private val converter = PersonalizationConfigConverter() - - init { - val config = store.restore() - setItems(converter.convert(config)) - } - - override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(tangemSdk), callback) - } - - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - fun onDestroy() { - val config = converter.convert(itemList, PersonalizationConfig.default()) - store.save(config) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/SimpleManagers.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/SimpleManagers.kt deleted file mode 100644 index 350a72b9f1..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/managers/SimpleManagers.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager.managers - -import com.tangem.devkit._arch.structure.impl.EditTextItem -import com.tangem.devkit.ucase.domain.actions.* -import com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence.CardIdConsequence -import com.tangem.devkit.ucase.variants.TlvId - -/** -[REDACTED_AUTHOR] - */ -class ScanItemsManager : BaseItemsManager(ScanAction()) - -class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) - -class SignItemsManager : BaseItemsManager(SignAction()) { - - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf( - EditTextItem(TlvId.CardId, null), - EditTextItem(TlvId.TransactionOutHash, "Data used for hashing") - )) - } -} - -class CreateWalletItemsManager : BaseItemsManager(CreateWalletAction()) { - - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf(EditTextItem(TlvId.CardId, null))) - } -} - -class PurgeWalletItemsManager : BaseItemsManager(PurgeWalletAction()) { - - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf(EditTextItem(TlvId.CardId, null))) - } -} - -class ReadIssuerDataItemsManager : BaseItemsManager(ReadIssuerDataAction()) { - - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf(EditTextItem(TlvId.CardId, null))) - } -} - -class WriteIssuerDataItemsManager : BaseItemsManager(WriteIssuerDataAction()) { - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf( - EditTextItem(TlvId.CardId, null), - EditTextItem(TlvId.IssuerData, "Data to be written on a card as issuer data"), - EditTextItem(TlvId.Counter, "1") - )) - } -} - -class ReadIssuerExtraDataItemsManager : BaseItemsManager(ReadIssuerExtraDataAction()) { - - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf(EditTextItem(TlvId.CardId, null))) - } -} - -class WriteIssuerExtraDataItemsManager : BaseItemsManager(WriteIssuerExtraDataAction()) { - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf( - EditTextItem(TlvId.CardId, null), - EditTextItem(TlvId.Counter, "1") - )) - } -} - -class ReadUserDataItemsManager : BaseItemsManager(ReadUserDataAction()) { - - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf(EditTextItem(TlvId.CardId, null))) - } -} - -class WriteUserDataItemsManager : BaseItemsManager(WriteUserDataAction()) { - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf( - EditTextItem(TlvId.CardId, null), - EditTextItem(TlvId.Counter, "1"), - EditTextItem(TlvId.UserData, "User data to be written on a card") - )) - } -} - - class WriteProtectedUserDataItemsManager : BaseItemsManager(WriteUserProtectedDataAction()) { - init { - setItemChangeConsequences(CardIdConsequence()) - setItems(listOf( - EditTextItem(TlvId.CardId, null), - EditTextItem(TlvId.Counter, "1"), - EditTextItem(TlvId.ProtectedUserData, "Protected user data to be written on a card") - )) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt deleted file mode 100644 index 74940b79d1..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction - -import com.tangem.common.CompletionResult -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.Item - -/** -[REDACTED_AUTHOR] - * - * The After Action Modification class family is intended for modifying items (if necessary) - * after calling CardManager.anyAction. - * Returns a list of items that have been modified - */ -interface AfterActionModification { - fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List): List -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt deleted file mode 100644 index 219f6a72c9..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager.triggers.afterAction - -import com.tangem.commands.Card -import com.tangem.commands.CardStatus -import com.tangem.common.CompletionResult -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.findItem -import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey -import com.tangem.devkit.ucase.tunnel.ActionView -import com.tangem.devkit.ucase.tunnel.CardError -import com.tangem.devkit.ucase.variants.TlvId - -import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI - -/** -[REDACTED_AUTHOR] - */ -class AfterScanModifier : AfterActionModification { - override fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List): List { - val foundItem = itemList.findItem(TlvId.CardId) ?: return listOf() - val card = smartCast(commandResult) ?: return listOf() - val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf() - - return if (isNotPersonalized(card)) { - actionView.showSnackbar(CardError.NotPersonalized) - postUI { actionView.enableActionFab(false) } - listOf() - } else { - payload.set(PayloadKey.card, card) - foundItem.setData(card.cardId) - postUI { actionView.enableActionFab(true) } - listOf(foundItem) - } - } - - private fun smartCast(commandResult: CompletionResult<*>): Card? { - return (commandResult as? CompletionResult.Success)?.data - } - - private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt deleted file mode 100644 index 6da18ea8b7..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/domain/paramsManager/triggers/changeConsequence/ParamChangeConsequence.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.devkit.ucase.domain.paramsManager.triggers.changeConsequence - -import com.tangem.devkit._arch.structure.PayloadHolder -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey -import com.tangem.devkit.ucase.tunnel.ActionView -import com.tangem.devkit.ucase.variants.TlvId -import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI - -/** -[REDACTED_AUTHOR] - * - * The ParamsChangeConsequence class family modifies parameters depending on the state - * of the incoming parameter - */ -interface ItemsChangeConsequence { - fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List -} - -class CardIdConsequence : ItemsChangeConsequence { - - override fun affectChanges(payload: PayloadHolder, changedItem: Item, itemList: List): List { - val affectedList = mutableListOf() - if (changedItem.id != TlvId.CardId) return affectedList - - val cidDataIsNull = changedItem.getData() == null - postUI { (payload.get(PayloadKey.actionView) as? ActionView)?.enableActionFab(!cidDataIsNull) } - return affectedList - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/Ids.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/Ids.kt deleted file mode 100644 index 90e2d54588..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/Ids.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.devkit.ucase.resources - -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ -enum class ActionType : Id { - Scan, - Sign, - CreateWallet, - PurgeWallet, - ReadIssuerData, - WriteIssuerData, - ReadIssuerExData, - WriteIssuerExData, - ReadUserData, - WriteUserData, - WriteProtectedUserData, - Personalize, - Depersonalize, - Unknown, -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/MainResourceHolder.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/MainResourceHolder.kt deleted file mode 100644 index e62d993f69..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/MainResourceHolder.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.tangem.devkit.ucase.resources - -import com.tangem.devkit.AppTangemDemo -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.StringId -import com.tangem.devkit._arch.structure.StringResId -import com.tangem.devkit.ucase.resources.initializers.ActionResources -import com.tangem.devkit.ucase.resources.initializers.PersonalizationResources -import com.tangem.devkit.ucase.resources.initializers.ResponseResources -import com.tangem.devkit.ucase.resources.initializers.TlvResources -import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder - -/** -[REDACTED_AUTHOR] - */ -open class ResourceHolder : BaseTypedHolder() - -object MainResourceHolder : ResourceHolder() { - init { - PersonalizationResources().init(this) - ActionResources().init(this) - TlvResources().init(this) - ResponseResources().init(this) - } - - override fun get(type: Id): Resources? { - return when(type) { - is StringId -> getResourcesFromName(type.value) - is StringResId -> Resources(type.value) - else -> super.get(type) - } - } - - private fun getResourcesFromName(resName: String): Resources? { - val context = AppTangemDemo.appInstance.applicationContext - val resNameId = context.resources.getIdentifier(resName, "string", context.packageName) - val resInfoId = context.resources.getIdentifier("info_$resName", "string", context.packageName) - if (resNameId == 0) return null - - return Resources(resNameId, if (resInfoId == 0) null else resInfoId) - } - - inline fun safeGet(id: Id): Res { - val result = super.get(id) ?: Resources(R.string.unknown, R.string.unknown) - return result as Res - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/ResourseTypes.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/ResourseTypes.kt deleted file mode 100644 index 8ba64f087d..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/ResourseTypes.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.devkit.ucase.resources - -/** -[REDACTED_AUTHOR] - */ -open class Resources( - val resName: Int, - val resDescription: Int? = null -) - -class ActionRes( - resName: Int, - resDescription: Int? = null, - val resNavigation: Int? = null -) : Resources(resName, resDescription) \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ActionResources.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ActionResources.kt deleted file mode 100644 index 7ca4301b5f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ActionResources.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.devkit.ucase.resources.initializers - -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit.ucase.resources.ActionRes -import com.tangem.devkit.ucase.resources.ActionType -import com.tangem.devkit.ucase.resources.ResourceHolder - -/** -[REDACTED_AUTHOR] - */ -class ActionResources { - - fun init(holder: ResourceHolder) { - holder.register(ActionType.Scan, ActionRes(R.string.action_card_scan, R.string.info_action_card_scan, R.id.action_nav_entry_point_to_nav_scan)) - holder.register(ActionType.Sign, ActionRes(R.string.action_card_sign, R.string.info_action_card_sign, R.id.action_nav_entry_point_to_nav_sign)) - holder.register(ActionType.Personalize, ActionRes(R.string.action_personalize, R.string.info_action_personalize, R.id.action_nav_entry_point_to_nav_personalize)) - holder.register(ActionType.Depersonalize, ActionRes(R.string.action_depersonalize, R.string.info_action_depersonalize, R.id.action_nav_entry_point_to_nav_depersonalize)) - holder.register(ActionType.CreateWallet, ActionRes(R.string.action_wallet_create, R.string.info_action_wallet_create, R.id.action_nav_entry_point_to_nav_wallet_create)) - holder.register(ActionType.PurgeWallet, ActionRes(R.string.action_wallet_purge, R.string.info_action_wallet_purge, R.id.action_nav_entry_point_to_nav_wallet_purge)) - holder.register(ActionType.ReadIssuerData, ActionRes(R.string.action_issuer_read_data, R.string.info_action_issuer_read_data, R.id.action_nav_entry_point_to_nav_issuer_read_data)) - holder.register(ActionType.WriteIssuerData, ActionRes(R.string.action_issuer_write_data, R.string.info_action_issuer_write_data, R.id.action_nav_entry_point_to_nav_issuer_write_data)) - holder.register(ActionType.ReadIssuerExData, ActionRes(R.string.action_issuer_read_ex_data, R.string.info_action_issuer_read_ex_data, R.id.action_nav_entry_point_to_nav_issuer_read_ex_data)) - holder.register(ActionType.WriteIssuerExData, ActionRes(R.string.action_issuer_write_ex_data, R.string.info_action_issuer_write_ex_data, R.id.action_nav_entry_point_to_nav_issuer_write_ex_data)) - holder.register(ActionType.ReadUserData, ActionRes(R.string.action_user_read_data, R.string.info_action_user_read_data, R.id.action_nav_entry_point_to_nav_user_read_data)) - holder.register(ActionType.WriteUserData, ActionRes(R.string.action_user_write_data, R.string.info_action_user_write_data, R.id.action_nav_entry_point_to_nav_user_write_data)) - holder.register(ActionType.WriteProtectedUserData, ActionRes(R.string.action_user_write_protected_data, R.string.info_action_user_write_protected_data, R.id.action_nav_entry_point_to_nav_user_write_protected_data)) -// holder.register(ActionType.Unknown, getIfNotContains()) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/PersonalizationResources.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/PersonalizationResources.kt deleted file mode 100644 index 2319076b7b..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/PersonalizationResources.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.tangem.devkit.ucase.resources.initializers - -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit.ucase.resources.ResourceHolder -import com.tangem.devkit.ucase.resources.Resources -import com.tangem.devkit.ucase.variants.personalize.* -import ru.dev.gbixahue.eu4d.lib.kotlin.common.TypedHolder - -/** -[REDACTED_AUTHOR] - */ -class PersonalizationResources() { - fun init(holder: ResourceHolder) { - initBlock(holder) - initCardNumber(holder) - initCommon(holder) - initSigningMethod(holder) - initSignHashExProp(holder) - initToken(holder) - initProductMask(holder) - initSettingsMask(holder) - initSettingsMaskProtocolEnc(holder) - initSettingsMaskNdef(holder) - initPins(holder) - } - - private fun initBlock(holder: TypedHolder) { - holder.register(BlockId.CardNumber, Resources(R.string.pers_block_card_number, R.string.info_pers_block_card_number)) - holder.register(BlockId.Common, Resources(R.string.pers_block_common, R.string.info_pers_block_common)) - holder.register(BlockId.SigningMethod, Resources(R.string.pers_block_signing_method, R.string.info_pers_block_signing_method)) - holder.register(BlockId.SignHashExProp, Resources(R.string.pers_block_sign_hash_ex_prop, R.string.info_pers_block_sign_hash_ex_prop)) - holder.register(BlockId.Denomination, Resources(R.string.pers_block_denomination, R.string.info_pers_block_denomination)) - holder.register(BlockId.Token, Resources(R.string.pers_block_token, R.string.info_pers_block_token)) - holder.register(BlockId.ProdMask, Resources(R.string.pers_block_product_mask, R.string.info_pers_block_product_mask)) - holder.register(BlockId.SettingsMask, Resources(R.string.pers_block_settings_mask, R.string.info_pers_block_settings_mask)) - holder.register(BlockId.SettingsMaskProtocolEnc, Resources(R.string.pers_block_settings_mask_protocol_enc, R.string.info_pers_block_settings_mask_protocol_enc)) - holder.register(BlockId.SettingsMaskNdef, Resources(R.string.pers_block_settings_mask_ndef, R.string.info_pers_block_settings_mask_ndef)) - holder.register(BlockId.Pins, Resources(R.string.pers_block_pins, R.string.info_pers_block_pins)) - } - - private fun initCardNumber(holder: TypedHolder) { - holder.register(CardNumberId.Series, Resources(R.string.pers_item_series, R.string.info_pers_item_series)) - holder.register(CardNumberId.Number, Resources(R.string.pers_item_number, R.string.info_pers_item_number)) - holder.register(CardNumberId.BatchId, Resources(R.string.pers_item_batch_id, R.string.info_pers_item_batch_id)) - } - - private fun initCommon(holder: TypedHolder) { - holder.register(CommonId.Curve, Resources(R.string.pers_item_curve, R.string.info_pers_item_curve)) - holder.register(CommonId.Blockchain, Resources(R.string.pers_item_blockchain, R.string.info_pers_item_blockchain)) - holder.register(CommonId.BlockchainCustom, Resources(R.string.pers_item_custom_blockchain, R.string.info_pers_item_custom_blockchain)) - holder.register(CommonId.MaxSignatures, Resources(R.string.pers_item_max_signatures, R.string.info_pers_item_max_signatures)) - holder.register(CommonId.CreateWallet, Resources(R.string.pers_item_create_wallet, R.string.info_pers_item_create_wallet)) - } - - private fun initSigningMethod(holder: TypedHolder) { - holder.register(SigningMethodId.SignTx, Resources(R.string.pers_item_sign_tx_hashes, R.string.info_pers_item_sign_tx_hashes)) - holder.register(SigningMethodId.SignTxRaw, Resources(R.string.pers_item_sign_raw_tx, R.string.info_pers_item_sign_raw_tx)) - holder.register(SigningMethodId.SignValidatedTx, Resources(R.string.pers_item_sign_validated_tx_hashes, R.string.info_pers_item_sign_validated_tx_hashes)) - holder.register(SigningMethodId.SignValidatedTxRaw, Resources(R.string.pers_item_sign_validated_raw_tx, R.string.info_pers_item_sign_validated_raw_tx)) - holder.register(SigningMethodId.SignValidatedTxIssuer, Resources(R.string.pers_item_sign_validated_tx_hashes_with_iss_data, R.string.info_pers_item_sign_validated_tx_hashes_with_iss_data)) - holder.register(SigningMethodId.SignValidatedTxRawIssuer, Resources(R.string.pers_item_sign_validated_raw_tx_with_iss_data, R.string.info_pers_item_sign_validated_raw_tx_with_iss_data)) - holder.register(SigningMethodId.SignExternal, Resources(R.string.pers_item_sign_hash_ex, R.string.info_pers_item_sign_hash_ex)) - } - - private fun initSignHashExProp(holder: TypedHolder) { - holder.register(SignHashExPropId.PinLessFloorLimit, Resources(R.string.pers_item_pin_less_floor_limit, R.string.info_pers_item_pin_less_floor_limit)) - holder.register(SignHashExPropId.CryptoExKey, Resources(R.string.pers_item_cr_ex_key, R.string.info_pers_item_cr_ex_key)) - holder.register(SignHashExPropId.RequireTerminalCertSig, Resources(R.string.pers_item_require_terminal_cert_sig, R.string.info_pers_item_require_terminal_cert_sig)) - holder.register(SignHashExPropId.RequireTerminalTxSig, Resources(R.string.pers_item_require_terminal_tx_sig, R.string.info_pers_item_require_terminal_tx_sig)) - holder.register(SignHashExPropId.CheckPin3, Resources(R.string.pers_item_check_pin3_on_card, R.string.info_pers_item_check_pin3_on_card)) - } - - private fun initToken(holder: TypedHolder) { - holder.register(TokenId.ItsToken, Resources(R.string.pers_item_its_token, R.string.info_pers_item_its_token)) - holder.register(TokenId.Symbol, Resources(R.string.pers_item_symbol, R.string.info_pers_item_symbol)) - holder.register(TokenId.ContractAddress, Resources(R.string.pers_item_contract_address, R.string.info_pers_item_contract_address)) - holder.register(TokenId.Decimal, Resources(R.string.pers_item_decimal, R.string.info_pers_item_decimal)) - } - - private fun initProductMask(holder: TypedHolder) { - holder.register(ProductMaskId.Note, Resources(R.string.pers_item_note, R.string.info_pers_item_note)) - holder.register(ProductMaskId.Tag, Resources(R.string.pers_item_tag, R.string.info_pers_item_tag)) - holder.register(ProductMaskId.IdCard, Resources(R.string.pers_item_id_card, R.string.info_pers_item_id_card)) - holder.register(ProductMaskId.IdIssuerCard, Resources(R.string.pers_item_id_issuer_card, R.string.info_pers_item_id_issuer_card)) - } - - private fun initSettingsMask(holder: TypedHolder) { - holder.register(SettingsMaskId.IsReusable, Resources(R.string.pers_item_is_reusable, R.string.info_pers_item_is_reusable)) - holder.register(SettingsMaskId.NeedActivation, Resources(R.string.pers_item_need_activation, R.string.info_pers_item_need_activation)) - holder.register(SettingsMaskId.ForbidPurge, Resources(R.string.pers_item_forbid_purge, R.string.info_pers_item_forbid_purge)) - holder.register(SettingsMaskId.AllowSelectBlockchain, Resources(R.string.pers_item_allow_select_blockchain, R.string.info_pers_item_allow_select_blockchain)) - holder.register(SettingsMaskId.UseBlock, Resources(R.string.pers_item_use_block, R.string.info_pers_item_use_block)) - holder.register(SettingsMaskId.OneApdu, Resources(R.string.pers_item_one_apdu_at_once, R.string.info_pers_item_one_apdu_at_once)) - holder.register(SettingsMaskId.UseCvc, Resources(R.string.pers_item_use_cvc, R.string.info_pers_item_use_cvc)) - holder.register(SettingsMaskId.AllowSwapPin, Resources(R.string.pers_item_allow_swap_pin, R.string.info_pers_item_allow_swap_pin)) - holder.register(SettingsMaskId.AllowSwapPin2, Resources(R.string.pers_item_allow_swap_pin2, R.string.info_pers_item_allow_swap_pin2)) - holder.register(SettingsMaskId.ForbidDefaultPin, Resources(R.string.pers_item_forbid_default_pin, R.string.info_pers_item_forbid_default_pin)) - holder.register(SettingsMaskId.SmartSecurityDelay, Resources(R.string.pers_item_smart_security_delay, R.string.info_pers_item_smart_security_delay)) - holder.register(SettingsMaskId.ProtectIssuerDataAgainstReplay, Resources(R.string.pers_item_protect_issuer_data_against_replay, R.string.info_pers_item_protect_issuer_data_against_replay)) - holder.register(SettingsMaskId.SkipSecurityDelayIfValidated, Resources(R.string.pers_item_skip_security_delay_if_validated, R.string.info_pers_item_skip_security_delay_if_validated)) - holder.register(SettingsMaskId.SkipPin2CvcIfValidated, Resources(R.string.pers_item_skip_pin2_and_cvc_if_validated, R.string.info_pers_item_skip_pin2_and_cvc_if_validated)) - holder.register(SettingsMaskId.SkipSecurityDelayOnLinkedTerminal, Resources(R.string.pers_item_skip_security_delay_on_linked_terminal, R.string.info_pers_item_skip_security_delay_on_linked_terminal)) - holder.register(SettingsMaskId.RestrictOverwriteExtraIssuerData, Resources(R.string.pers_item_restrict_overwrite_ex_issuer_data, R.string.info_pers_item_restrict_overwrite_ex_issuer_data)) - } - - private fun initSettingsMaskProtocolEnc(holder: TypedHolder) { - holder.register(SettingsMaskProtocolEncId.AllowUnencrypted, Resources(R.string.pers_item_allow_unencrypted, R.string.info_pers_item_allow_unencrypted)) - holder.register(SettingsMaskProtocolEncId.AllowStaticEncryption, Resources(R.string.pers_item_allow_fast_encryption, R.string.info_pers_item_allow_fast_encryption)) - } - - private fun initSettingsMaskNdef(holder: TypedHolder) { - holder.register(SettingsMaskNdefId.UseNdef, Resources(R.string.pers_item_use_ndef, R.string.info_pers_item_use_ndef)) - holder.register(SettingsMaskNdefId.DynamicNdef, Resources(R.string.pers_item_dynamic_ndef, R.string.info_pers_item_dynamic_ndef)) - holder.register(SettingsMaskNdefId.DisablePrecomputedNdef, Resources(R.string.pers_item_disable_precomputed_ndef, R.string.info_pers_item_disable_precomputed_ndef)) - holder.register(SettingsMaskNdefId.Aar, Resources(R.string.pers_item_aar, R.string.info_pers_item_aar)) - holder.register(SettingsMaskNdefId.AarCustom, Resources(R.string.pers_item_custom_aar_package_name, R.string.info_pers_item_custom_aar_package_name)) - holder.register(SettingsMaskNdefId.Uri, Resources(R.string.pers_item_uri, R.string.info_pers_item_uri)) - } - - private fun initPins(holder: TypedHolder) { - holder.register(PinsId.Pin, Resources(R.string.pers_item_pin, R.string.info_pers_item_pin)) - holder.register(PinsId.Pin2, Resources(R.string.pers_item_pin2, R.string.info_pers_item_pin2)) - holder.register(PinsId.Pin3, Resources(R.string.pers_item_pin3, R.string.info_pers_item_pin3)) - holder.register(PinsId.Cvc, Resources(R.string.pers_item_cvc, R.string.info_pers_item_cvc)) - holder.register(PinsId.PauseBeforePin2, Resources(R.string.pers_item_pause_before_pin2, R.string.info_pers_item_pause_before_pin2)) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ResponseResources.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ResponseResources.kt deleted file mode 100644 index 792a4ee0cb..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/ResponseResources.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.tangem.devkit.ucase.resources.initializers - -import com.tangem.devkit.R -import com.tangem.devkit.ucase.resources.MainResourceHolder -import com.tangem.devkit.ucase.resources.Resources -import com.tangem.devkit.ucase.variants.responses.* - -/** -[REDACTED_AUTHOR] - */ -class ResponseResources { - fun init(holder: MainResourceHolder) { - initCard(holder) - initCardData(holder) - initSignResponse(holder) - initDepersonalizeResponse(holder) - initCreateWalletResponse(holder) - initPurgeWalletResponse(holder) - initReadIssuerDataResponse(holder) - initWriteIssuerDataResponse(holder) - initReadIssuerExtraDataResponse(holder) - initReadUserDataResponse(holder) - initWriteUserDataResponse(holder) - } - - private fun initCard(holder: MainResourceHolder) { - holder.register(CardId.cardId, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - holder.register(CardId.manufacturerName, Resources(R.string.response_card_manufacturer_name, R.string.info_response_card_manufacturer_name)) - holder.register(CardId.status, Resources(R.string.response_card_status, R.string.info_response_card_status)) - holder.register(CardId.firmwareVersion, Resources(R.string.response_card_firmware_version, R.string.info_response_card_firmware_version)) - holder.register(CardId.cardPublicKey, Resources(R.string.response_card_public_key, R.string.info_response_card_public_key)) - holder.register(CardId.settingsMask, Resources(R.string.response_card_settings_mask, R.string.info_response_card_settings_mask)) - holder.register(CardId.cardData, Resources(R.string.response_card_card_data, R.string.info_response_card_card_data)) - holder.register(CardId.issuerPublicKey, Resources(R.string.response_card_issuer_data_public_key, R.string.info_response_card_issuer_data_public_key)) - holder.register(CardId.curve, Resources(R.string.response_card_curve, R.string.info_response_card_curve)) - holder.register(CardId.maxSignatures, Resources(R.string.response_card_max_signatures, R.string.info_response_card_max_signatures)) - holder.register(CardId.signingMethod, Resources(R.string.response_card_signing_method, R.string.info_response_card_signing_method)) - holder.register(CardId.pauseBeforePin2, Resources(R.string.response_card_pause_before_pin2, R.string.info_response_card_allow_pin2)) - holder.register(CardId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key)) - holder.register(CardId.walletRemainingSignatures, Resources(R.string.response_card_wallet_remaining_signatures, R.string.info_response_card_wallet_remaining_signatures)) - holder.register(CardId.walletSignedHashes, Resources(R.string.response_card_wallet_signed_hashes, R.string.info_response_card_wallet_signed_hashes)) - holder.register(CardId.health, Resources(R.string.response_card_health, R.string.info_response_card_health)) - holder.register(CardId.isActivated, Resources(R.string.response_card_is_activated, R.string.info_response_card_is_activated)) - holder.register(CardId.activationSeed, Resources(R.string.response_card_activation_seed, R.string.info_response_card_activation_seed)) - holder.register(CardId.paymentFlowVersion, Resources(R.string.response_card_payment_flow_version, R.string.info_response_card_payment_flow_version)) - holder.register(CardId.userCounter, Resources(R.string.response_card_user_counter, R.string.info_response_card_user_counter)) - holder.register(CardId.userProtectedCounter, Resources(R.string.response_card_user_protected_counter, R.string.info_response_card_user_protected_counter)) - } - - private fun initCardData(holder: MainResourceHolder) { - holder.register(CardDataId.batchId, Resources(R.string.response_card_card_data_batch_id, R.string.info_response_card_card_data_batch_id)) - holder.register(CardDataId.manufactureDateTime, Resources(R.string.response_card_card_data_manufacture_date_time, R.string.info_response_card_card_data_manufacture_date_time)) - holder.register(CardDataId.issuerName, Resources(R.string.response_card_card_data_issuer_name, R.string.info_response_card_card_data_issuer_name)) - holder.register(CardDataId.blockchainName, Resources(R.string.response_card_card_data_blockchain_name, R.string.info_response_card_card_data_blockchain_name)) - holder.register(CardDataId.manufacturerSignature, Resources(R.string.response_card_card_data_manufacturer_signature, R.string.info_response_card_card_data_manufacturer_signature)) - holder.register(CardDataId.productMask, Resources(R.string.response_card_card_data_product_mask, R.string.info_response_card_card_data_product_mask)) - holder.register(CardDataId.tokenSymbol, Resources(R.string.response_card_card_data_token_symbol, R.string.info_response_card_card_data_token_symbol)) - holder.register(CardDataId.tokenContractAddress, Resources(R.string.response_card_card_data_token_contract_address, R.string.info_response_card_card_data_token_contract_address)) - holder.register(CardDataId.tokenDecimal, Resources(R.string.response_card_card_data_token_decimal, R.string.info_response_card_card_data_token_decimal)) - } - - private fun initSignResponse(holder: MainResourceHolder) { - holder.register(SignId.cid, Resources(R.string.response_sign_cid, R.string.info_response_sign_cid)) - holder.register(SignId.walletSignedHashes, Resources(R.string.response_sign_wallet_signed_hashes, R.string.info_response_sign_wallet_signed_hashes)) - holder.register(SignId.walletRemainingSignatures, Resources(R.string.response_sign_wallet_remaining_signatures, R.string.info_response_sign_wallet_remaining_signatures)) - holder.register(SignId.signature, Resources(R.string.response_sign_signature, R.string.info_response_sign_signature)) - } - - private fun initDepersonalizeResponse(holder: MainResourceHolder) { - holder.register(DepersonalizeId.isSuccess, Resources(R.string.response_depersonalize_is_success, R.string.info_response_depersonalize_is_success)) - } - - private fun initCreateWalletResponse(holder: MainResourceHolder) { - holder.register(CreateWalletId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - holder.register(CreateWalletId.cardStatus, Resources(R.string.response_card_status, R.string.info_response_card_status)) - holder.register(CreateWalletId.walletPublicKey, Resources(R.string.response_card_wallet_public_key, R.string.info_response_card_wallet_public_key)) - } - - private fun initPurgeWalletResponse(holder: MainResourceHolder) { - holder.register(PurgeWalletId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - holder.register(PurgeWalletId.cardStatus, Resources(R.string.response_card_status, R.string.info_response_card_status)) - } - - private fun initReadIssuerDataResponse(holder: MainResourceHolder) { - holder.register(ReadIssuerDataId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - holder.register(ReadIssuerDataId.issuerData, Resources(R.string.response_issuer_data, R.string.info_response_issuer_data)) - holder.register(ReadIssuerDataId.issuerDataSignature, Resources(R.string.response_issuer_data_signature, R.string.info_response_issuer_data_signature)) - holder.register(ReadIssuerDataId.issuerDataCounter, Resources(R.string.response_issuer_data_counter, R.string.info_response_issuer_data_counter)) - } - - private fun initWriteIssuerDataResponse(holder: MainResourceHolder) { - holder.register(ReadIssuerDataId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - } - - private fun initReadIssuerExtraDataResponse(holder: MainResourceHolder) { - holder.register(ReadIssuerExtraDataId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - holder.register(ReadIssuerExtraDataId.size, Resources(R.string.response_issuer_data_size, R.string.info_response_issuer_data_size)) - holder.register(ReadIssuerExtraDataId.issuerData, Resources(R.string.response_issuer_data, R.string.info_response_issuer_data)) - holder.register(ReadIssuerExtraDataId.issuerDataSignature, Resources(R.string.response_issuer_data_signature, R.string.info_response_issuer_data_signature)) - holder.register(ReadIssuerExtraDataId.issuerDataCounter, Resources(R.string.response_issuer_data_counter, R.string.info_response_issuer_data_counter)) - } - - private fun initReadUserDataResponse(holder: MainResourceHolder) { - holder.register(ReadUserDataId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - holder.register(ReadUserDataId.userData, Resources(R.string.response_user_data, R.string.info_response_user_data)) - holder.register(ReadUserDataId.userProtectedData, Resources(R.string.response_user_protected_data, R.string.info_response_user_protected_data)) - holder.register(ReadUserDataId.userCounter, Resources(R.string.response_user_data_counter, R.string.info_response_user_data_counter)) - holder.register(ReadUserDataId.userProtectedCounter, Resources(R.string.response_user_data_protected_counter, R.string.info_response_user_data_protected_counter)) - } - - private fun initWriteUserDataResponse(holder: MainResourceHolder) { - holder.register(ReadIssuerDataId.cid, Resources(R.string.response_card_cid, R.string.info_response_card_cid)) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/TlvResources.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/TlvResources.kt deleted file mode 100644 index c7527b36ed..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/resources/initializers/TlvResources.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.devkit.ucase.resources.initializers - -import com.tangem.devkit.R -import com.tangem.devkit.ucase.resources.MainResourceHolder -import com.tangem.devkit.ucase.resources.Resources -import com.tangem.devkit.ucase.variants.TlvId - -/** -[REDACTED_AUTHOR] - */ -class TlvResources { - fun init(holder: MainResourceHolder) { - initScan(holder) - } - - private fun initScan(holder: MainResourceHolder) { - holder.register(TlvId.CardId, Resources(R.string.tlv_card_id, R.string.info_tlv_card_id)) - holder.register(TlvId.TransactionOutHash, Resources(R.string.tlv_transaction_out_hash, R.string.info_tlv_transaction_out_hash)) - holder.register(TlvId.Counter, Resources(R.string.tlv_counter, R.string.info_tlv_counter)) - holder.register(TlvId.IssuerData, Resources(R.string.tlv_issuer_data, R.string.info_tlv_issuer_data)) - holder.register(TlvId.UserData, Resources(R.string.tlv_user_data, R.string.info_tlv_user_data)) - holder.register(TlvId.ProtectedUserData, Resources(R.string.tlv_user_protected_data, R.string.info_tlv_protected_user_data)) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/Errors.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/Errors.kt deleted file mode 100644 index 72cbde4699..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/Errors.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.devkit.ucase.tunnel - -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ -interface Errors : Id - -enum class CardError : Errors { - NotPersonalized, -} - -enum class ItemError : Errors { - BadSeries, - BadCardNumber, -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/ViewScreen.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/ViewScreen.kt deleted file mode 100644 index 64aa754c6b..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/tunnel/ViewScreen.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.devkit.ucase.tunnel - -import com.google.android.material.snackbar.Snackbar -import com.tangem.devkit._arch.structure.Id - -interface SnackbarHolder { - fun showSnackbar(message: String, length: Int = Snackbar.LENGTH_SHORT) - fun showSnackbar(id: Int, length: Int = Snackbar.LENGTH_SHORT) -} - -interface ViewScreen - -interface ActionView : ViewScreen, SnackbarHolder { - fun enableActionFab(enable: Boolean) - fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)? = null) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/ActionViewModel.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/ActionViewModel.kt deleted file mode 100644 index 272bb11671..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/ActionViewModel.kt +++ /dev/null @@ -1,178 +0,0 @@ -package com.tangem.devkit.ucase.ui - -import android.view.View -import androidx.annotation.UiThread -import androidx.lifecycle.* -import com.google.gson.Gson -import com.tangem.TangemSdk -import com.tangem.TangemSdkError -import com.tangem.commands.Card -import com.tangem.commands.CommandResponse -import com.tangem.common.CompletionResult -import com.tangem.common.extensions.CardType -import com.tangem.devkit._arch.SingleLiveEvent -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.Payload -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.iterate -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.resources.ActionType -import com.tangem.devkit.ucase.tunnel.ViewScreen -import com.tangem.devkit.ucase.variants.personalize.converter.ItemTypes -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = ActionViewModel(manager) as T -} - -class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver { - - val seResponse = SingleLiveEvent() - val seResponseData = SingleLiveEvent() - val seResponseCardData = SingleLiveEvent() - - val ldItemList = MutableLiveData(itemsManager.getItems()) - val seError: MutableLiveData = SingleLiveEvent() - val seChangedItems: MutableLiveData> = SingleLiveEvent() - - private val notifier: Notifier = Notifier(this) - private lateinit var tangemSdk: TangemSdk - - fun setCardManager(tangemSdk: TangemSdk) { - this.tangemSdk = tangemSdk - this.tangemSdk.config.cardFilter.allowedCardTypes = EnumSet.of(CardType.Sdk) - } - - @Deprecated("Events must be send directly from the Widget") - fun userChangedItem(id: Id, value: Any?) { - itemChanged(id, value) - } - - private fun itemChanged(id: Id, value: Any?) { - itemsManager.itemChanged(id, value) { notifier.notifyItemsChanged(it) } - } - - //invokes Scan, Sign etc... - fun invokeMainAction() { - 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, tangemSdk) ?: return null - - return { - itemFunction { response, listOfChangedParams -> - notifier.handleActionResult(response, listOfChangedParams) - } - } - } - - fun toggleDescriptionVisibility(state: Boolean) { - ldItemList.value?.iterate { - it.viewModel.viewState.descriptionVisibility.value = if (state) View.VISIBLE else View.GONE - } - } - - fun attachToPayload(payload: Payload) { - itemsManager.attachPayload(payload) - } - - @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) - fun viewOnDestroy() { - val keyList = mutableListOf() - itemsManager.payload.filterValues { it is ViewScreen }.forEach { keyList.add(it.key) } - keyList.forEach { itemsManager.payload.remove(it) } - } - - fun showFields(type: ActionType) { - toggleFieldsVisibility(type, true) - } - - fun hideFields(type: ActionType) { - toggleFieldsVisibility(type, false) - } - - private fun toggleFieldsVisibility(type: ActionType, show: Boolean) { - val oftenUsed = getItemsForTogglingVisibilityState(type) - val hidden = getItemIdsWhichWontShows(type) - itemsManager.getItems().iterate { - if (!hidden.contains(it.id)) { - if (!oftenUsed.contains(it.id)) { - it.viewModel.viewState.isVisibleState.value = show - } - } - - } - } - - private fun getItemsForTogglingVisibilityState(type: ActionType): List { - return when (type) { - ActionType.Personalize -> ItemTypes().oftenUsedList - else -> emptyList() - } - } - - private fun getItemIdsWhichWontShows(type: ActionType): List { - return when (type) { - ActionType.Personalize -> ItemTypes().hiddenList - else -> emptyList() - } - } -} - -internal class Notifier(private val vm: ActionViewModel) { - - private var notShowedError: TangemSdkError? = null - - fun handleActionResult(result: CompletionResult<*>, list: List) { - if (list.isNotEmpty()) notifyItemsChanged(list) - handleCompletionResult(result) - } - - @UiThread - fun notifyItemsChanged(list: List) { - vm.seChangedItems.postValue(list) - } - - fun handleCompletionResult(result: CompletionResult<*>) { - val commandResponse = result as? CompletionResult ?: return - - when (commandResponse) { - is CompletionResult.Success -> handleData(commandResponse.data) - is CompletionResult.Failure -> handleError(commandResponse.error) - } - } - - private fun handleData(event: CommandResponse?) { - vm.seResponse.postValue(event) - when (event) { - is Card -> vm.seResponseCardData.postValue(event) - else -> vm.seResponseData.postValue(event) - } - } - - private fun handleError(error: TangemSdkError) { - Log.d(this, "error = $error") - when (error) { - is TangemSdkError.UserCancelled -> { - if (notShowedError == null) { - vm.seError.postValue("User canceled the action") - } else { - vm.seError.postValue("${notShowedError!!::class.simpleName}") - notShowedError = null - } - } - else -> notShowedError = error - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseCardActionFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseCardActionFragment.kt deleted file mode 100644 index 36cf46321e..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseCardActionFragment.kt +++ /dev/null @@ -1,162 +0,0 @@ -package com.tangem.devkit.ucase.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.plusAssign -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.google.android.material.floatingactionbutton.FloatingActionButton -import com.tangem.TangemSdk -import com.tangem.commands.Card -import com.tangem.commands.CommandResponse -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._main.MainViewModel -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey -import com.tangem.devkit.ucase.tunnel.ActionView -import com.tangem.devkit.ucase.tunnel.CardError -import com.tangem.devkit.ucase.ui.widgets.ParameterWidget -import com.tangem.tangem_sdk_new.extensions.init -import ru.dev.gbixahue.eu4d.lib.android._android.views.enable -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -abstract class BaseCardActionFragment : BaseFragment(), ActionView { - - protected lateinit var swrLayout: SwipeRefreshLayout - protected lateinit var contentContainer: ViewGroup - protected lateinit var actionFab: FloatingActionButton - - protected abstract val itemsManager: ItemsManager - protected val mainActivityVM by activityViewModels() - protected val actionVM: ActionViewModel by viewModels { ActionViewModelFactory(itemsManager) } - protected val UNDEFINED = -1 - - private val paramsWidgetList = mutableListOf() - - override fun getLayoutId(): Int = R.layout.fg_base_action_layout - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - Log.d(this, "onViewCreated") - - bindViews() - viewLifecycleOwner.lifecycle.addObserver(actionVM) - actionVM.setCardManager(TangemSdk.init(requireActivity())) - actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView)) - - initViews() - createWidgets { - widgetsWasCreated() - subscribeToViewModelChanges() - } - } - - protected open fun widgetsWasCreated() {} - - protected open fun bindViews() { - swrLayout = mainView.findViewById(R.id.swr_layout) - contentContainer = mainView.findViewById(R.id.ll_content_container) - actionFab = mainView.findViewById(R.id.fab_action) - } - - protected open fun initViews() { - swrLayout.isEnabled = false - enableActionFab(false) - actionFab.setOnClickListener { actionVM.invokeMainAction() } - } - - protected open fun createWidgets(widgetCreatedCallback: () -> Unit) { - Log.d(this, "createWidgets") - actionVM.ldItemList.observe(viewLifecycleOwner, Observer { itemList -> - itemList.forEach { param -> - val widget = ParameterWidget(inflateParamView(contentContainer), param) - widget.onValueChanged = { id, value -> actionVM.userChangedItem(id, value) } - widget.onActionBtnClickListener = actionVM.getItemAction(param.id) - paramsWidgetList.add(widget) - } - widgetCreatedCallback() - }) - } - - protected open fun subscribeToViewModelChanges() { - Log.d(this, "subscribeToViewModelChanges") - actionVM.seResponse.observe(viewLifecycleOwner, Observer { - Log.d(this, "handle response: $it") - handleResponse(it) - }) - actionVM.seResponseData.observe(viewLifecycleOwner, Observer { - Log.d(this, "handle responseData: $it") - handleResponseData(it) - }) - actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer { - Log.d(this, "handle responseCardData: $it") - handleResponseCardData(it) - }) - actionVM.seError.observe(viewLifecycleOwner, Observer { - Log.d(this, "handle error: $it") - handleError(it) - }) - mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer { - Log.d(this, "handle descriptionVisibilityState: $it") - handleDescriptionSwitchChanges(it) - }) - listenChangedItems() - } - - protected open fun handleResponse(response: CommandResponse) { - mainActivityVM.changeResponseEvent(response) - } - - protected open fun handleResponseData(response: CommandResponse) { - navigateTo(R.id.action_nav_card_action_to_response_screen, options = null) - } - - protected open fun handleResponseCardData(card: Card) {} - - protected open fun handleError(error: String) { - showSnackbar(error) - } - - protected open fun handleDescriptionSwitchChanges(descriptionVisibilityState: Boolean) { - actionVM.toggleDescriptionVisibility(descriptionVisibilityState) - paramsWidgetList.forEach { it.toggleDescriptionVisibility(descriptionVisibilityState) } - } - - @Deprecated("Start to use itemViewModel") - protected open fun listenChangedItems() { - actionVM.seChangedItems.observe(viewLifecycleOwner, Observer { itemList -> - itemList.forEach { item -> - Log.d(this, "item changed from VM - name: ${item.id}, value:${item.viewModel.data}") - paramsWidgetList.firstOrNull { it.id == item.id }?.changeParamValue(item.viewModel.data) - } - }) - } - - protected fun inflateParamView(where: ViewGroup): ViewGroup { - val inflater = LayoutInflater.from(where.context) - val view = inflater.inflate(R.layout.w_card_incoming_param, where, false) - where.plusAssign(view) - return view as ViewGroup - } - - override fun enableActionFab(enable: Boolean) { - actionFab.enable(enable) - } - - override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) { - val resourceId = when (id) { - CardError.NotPersonalized -> R.string.card_error_not_personalized - else -> additionalHandler?.invoke(id) ?: UNDEFINED - } - - if (resourceId != UNDEFINED) showSnackbar(resourceId) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseFragment.kt deleted file mode 100644 index e3b6f16cad..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/BaseFragment.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.tangem.devkit.ucase.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.navigation.NavOptions -import androidx.navigation.fragment.NavHostFragment.findNavController -import com.google.android.material.snackbar.Snackbar -import com.tangem.devkit.ucase.getDefaultNavigationOptions -import com.tangem.devkit.ucase.tunnel.SnackbarHolder -import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder - -/** -[REDACTED_AUTHOR] - */ -abstract class BaseFragment : Fragment(), LayoutHolder, SnackbarHolder { - - protected lateinit var mainView: View - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - mainView = inflater.inflate(getLayoutId(), container, false) - return mainView - } - - protected fun navigateTo(navigationId: Int, bundle: Bundle? = null, options: NavOptions? = getDefaultNavigationOptions()) { - findNavController(this).navigate(navigationId, bundle, options) - } - - override fun showSnackbar(id: Int, length: Int) { - showSnackbar(requireContext().getString(id), length) - } - - override fun showSnackbar(message: String, length: Int) { - Snackbar.make(mainView, message, length).show() - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/widgets/ParameterWidget.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/widgets/ParameterWidget.kt deleted file mode 100644 index 366a99e449..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/ui/widgets/ParameterWidget.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.tangem.devkit.ucase.ui.widgets - -import android.text.Editable -import android.text.TextWatcher -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.TextView -import com.google.android.material.textfield.TextInputEditText -import com.google.android.material.textfield.TextInputLayout -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.impl.EditTextItem -import com.tangem.devkit.extensions.view.beginDelayedTransition -import com.tangem.devkit.ucase.resources.ActionType -import com.tangem.devkit.ucase.resources.MainResourceHolder -import com.tangem.devkit.ucase.resources.Resources -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log -import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf - -/** -[REDACTED_AUTHOR] - */ -class ParameterWidget( - private val parent: ViewGroup, - item: Item -) { - - val id: Id = item.id - val typedItem = item as EditTextItem - - var onValueChanged: ((Id, Any?) -> Unit)? = null - var onActionBtnClickListener: (() -> Unit)? = null - set(value) { - field = value - toggleActionBtnVisibility() - } - - private val tilValue: TextInputLayout = parent.findViewById(R.id.til_param) - private val etValue: TextInputEditText = parent.findViewById(R.id.et_param) - private val btnAction: Button = parent.findViewById(R.id.btn_action) - private val valueWatcher: TextWatcher by lazy { getWatcher() } - - private val descriptionContainer: ViewGroup by lazy { parent.findViewById(R.id.container_description) } - private val tvDescription: TextView? by lazy { descriptionContainer.findViewById(R.id.tv_description) } - - private var actionBtnVisibilityState: Int = btnAction.visibility - private var value: Any? = typedItem.viewModel.data - - init { - tilValue.hint = tilValue.context.getString(getResNameId()) - etValue.setText(stringOf(typedItem.viewModel.data)) - etValue.addTextChangedListener(valueWatcher) - btnAction.setOnClickListener { onActionBtnClickListener?.invoke() } - btnAction.setText(getResNameId(ActionType.Scan)) - toggleActionBtnVisibility() - initDescriptionWidget() - parent.tag = Id.getTag(typedItem.id) - } - - fun changeParamValue(data: Any?, silent: Boolean = true) { - Log.d(this, "changeParamValue: tag: $id, value: $data") - value = data - toggleActionBtnVisibility() - if (silent) { - etValue.removeTextChangedListener(valueWatcher) - etValue.setText(stringOf(value)) - etValue.addTextChangedListener(valueWatcher) - } else { - etValue.setText(stringOf(value)) - } - } - - fun toggleDescriptionVisibility(state: Boolean) { - (parent.parent as ViewGroup).beginDelayedTransition() - descriptionContainer.visibility = if (state) View.VISIBLE else View.GONE - } - - private fun getWatcher(): TextWatcher { - return object : TextWatcher { - override fun afterTextChanged(editable: Editable?) { - Log.d(this, "afterTextChanged $editable") - value = if (editable.isNullOrEmpty()) null else editable.toString() - toggleActionBtnVisibility() - onValueChanged?.invoke(id, value) - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - } - } - - private fun toggleActionBtnVisibility() { - fun switchVisibilityState(newState: Int) { - actionBtnVisibilityState = newState - parent.beginDelayedTransition() - btnAction.visibility = actionBtnVisibilityState - } - when { - onActionBtnClickListener == null -> { - if (actionBtnVisibilityState == View.GONE) return - switchVisibilityState(View.GONE) - } - value == null && actionBtnVisibilityState != View.VISIBLE -> switchVisibilityState(View.VISIBLE) - value != null && actionBtnVisibilityState != View.GONE -> switchVisibilityState(View.GONE) - } - } - - private fun initDescriptionWidget() { - getResDescription()?.let { tvDescription?.setText(it) } - } -} - -fun ParameterWidget.getResNameId(id: Id? = null): Int = MainResourceHolder.safeGet(id - ?: this.id).resName - -fun ParameterWidget.getResDescription(): Int? = MainResourceHolder.safeGet(id).resDescription \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/Ids.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/Ids.kt deleted file mode 100644 index 2200b40724..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/Ids.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.devkit.ucase.variants - -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ -enum class TlvId : Id { - CardId, - TransactionOutHash, - IssuerData, - Counter, - UserData, - ProtectedUserData -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/createwallet/ui/CreateWalletActionFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/createwallet/ui/CreateWalletActionFragment.kt deleted file mode 100644 index 605f9dd649..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/createwallet/ui/CreateWalletActionFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.devkit.ucase.variants.createwallet.ui - -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.managers.CreateWalletItemsManager -import com.tangem.devkit.ucase.ui.BaseCardActionFragment - -class CreateWalletActionFragment : BaseCardActionFragment() { - - override val itemsManager: ItemsManager by lazy { CreateWalletItemsManager() } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt deleted file mode 100644 index 9d1409d58d..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/depersonalize/ui/DepersonalizeActionFragment.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.tangem.devkit.ucase.variants.depersonalize.ui - -import android.os.Bundle -import android.util.TypedValue -import android.view.Gravity -import android.view.View -import android.widget.FrameLayout -import android.widget.TextView -import com.tangem.commands.Card -import com.tangem.devkit.R -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.managers.DepersonalizeItemsManager -import com.tangem.devkit.ucase.ui.BaseCardActionFragment -import com.tangem.devkit.ucase.variants.responses.ui.ResponseFragment -import ru.dev.gbixahue.eu4d.lib.android._android.views.dpToPx - -/** -[REDACTED_AUTHOR] - */ -class DepersonalizeActionFragment : BaseCardActionFragment() { - - override val itemsManager: ItemsManager by lazy { DepersonalizeItemsManager() } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val howToUseView = createHowToUse() - contentContainer.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER) - contentContainer.addView(howToUseView) - } - - private fun createHowToUse(): View { - val fl = FrameLayout(requireContext()) - fl.layoutParams = FrameLayout.LayoutParams(-1, -1, Gravity.CENTER) - val tv = TextView(requireContext()).apply { - val padding = dpToPx(16f).toInt() - setPadding(padding, 0, padding, 0) - gravity = Gravity.CENTER - setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f) - setText(R.string.htu_depersonalize_action) - } - fl.addView(tv) - return fl - } - - override fun initViews() { - swrLayout.isEnabled = false - actionFab.setOnClickListener { actionVM.invokeMainAction() } - } - - override fun handleResponseCardData(card: Card) { - super.handleResponseCardData(card) - val bundle = ResponseFragment.setTittle(R.string.fg_name_response_depersonalization) - navigateTo(R.id.action_nav_card_action_to_response_screen, bundle, null) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerDataFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerDataFragment.kt deleted file mode 100644 index 6d3ac9be86..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerDataFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.devkit.ucase.variants.issuerdata.ui - -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.managers.ReadIssuerDataItemsManager -import com.tangem.devkit.ucase.ui.BaseCardActionFragment - -class ReadIssuerDataFragment : BaseCardActionFragment() { - - override val itemsManager: ItemsManager by lazy { ReadIssuerDataItemsManager() } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerExtraDataFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerExtraDataFragment.kt deleted file mode 100644 index 74024ca176..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/ReadIssuerExtraDataFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.devkit.ucase.variants.issuerdata.ui - -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.managers.ReadIssuerExtraDataItemsManager -import com.tangem.devkit.ucase.ui.BaseCardActionFragment - -class ReadIssuerExtraDataFragment : BaseCardActionFragment() { - - override val itemsManager: ItemsManager by lazy { ReadIssuerExtraDataItemsManager() } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerDataFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerDataFragment.kt deleted file mode 100644 index 88e956818c..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerDataFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.devkit.ucase.variants.issuerdata.ui - -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.managers.WriteIssuerDataItemsManager -import com.tangem.devkit.ucase.ui.BaseCardActionFragment - -class WriteIssuerDataFragment : BaseCardActionFragment() { - - override val itemsManager: ItemsManager by lazy { WriteIssuerDataItemsManager() } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerExtraDataFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerExtraDataFragment.kt deleted file mode 100644 index a90fce5e7a..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/issuerdata/ui/WriteIssuerExtraDataFragment.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.devkit.ucase.variants.issuerdata.ui - -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.managers.WriteIssuerExtraDataItemsManager -import com.tangem.devkit.ucase.ui.BaseCardActionFragment - -class WriteIssuerExtraDataFragment : BaseCardActionFragment() { - - override val itemsManager: ItemsManager by lazy { WriteIssuerExtraDataItemsManager() } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/Ids.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/Ids.kt deleted file mode 100644 index fef98a3cbc..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/Ids.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize - -import com.tangem.devkit._arch.structure.Id - -/** -[REDACTED_AUTHOR] - */ - -interface PersonalizationId : Id - -enum class BlockId : PersonalizationId { - CardNumber, - Common, - SigningMethod, - SignHashExProp, - Denomination, - Token, - ProdMask, - SettingsMask, - SettingsMaskProtocolEnc, - SettingsMaskNdef, - Pins, -} - -enum class CardNumberId : PersonalizationId { - Series, - Number, - BatchId, -} - -enum class CommonId : PersonalizationId { - Curve, - Blockchain, - BlockchainCustom, - MaxSignatures, - CreateWallet, -} - -enum class SigningMethodId : PersonalizationId { - SignTx, - SignTxRaw, - SignValidatedTx, - SignValidatedTxRaw, - SignValidatedTxIssuer, - SignValidatedTxRawIssuer, - SignExternal, -} - -enum class SignHashExPropId : PersonalizationId { - PinLessFloorLimit, - CryptoExKey, - RequireTerminalCertSig, - RequireTerminalTxSig, - CheckPin3, -} - -enum class TokenId : PersonalizationId { - ItsToken, - Symbol, - ContractAddress, - Decimal -} - -enum class ProductMaskId : PersonalizationId { - Note, - Tag, - IdCard, - IdIssuerCard -} - -enum class SettingsMaskId : PersonalizationId { - IsReusable, - NeedActivation, - ForbidPurge, - AllowSelectBlockchain, - UseBlock, - OneApdu, - UseCvc, - AllowSwapPin, - AllowSwapPin2, - ForbidDefaultPin, - SmartSecurityDelay, - ProtectIssuerDataAgainstReplay, - SkipSecurityDelayIfValidated, - SkipPin2CvcIfValidated, - SkipSecurityDelayOnLinkedTerminal, - RestrictOverwriteExtraIssuerData, -} - -enum class SettingsMaskProtocolEncId : PersonalizationId { - AllowUnencrypted, - AllowStaticEncryption -} - -enum class SettingsMaskNdefId : PersonalizationId { - UseNdef, - DynamicNdef, - DisablePrecomputedNdef, - Aar, - AarCustom, - Uri, -} - -enum class PinsId : PersonalizationId { - Pin, - Pin2, - Pin3, - Cvc, - PauseBeforePin2 -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/PersonalizationConfigStore.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/PersonalizationConfigStore.kt deleted file mode 100644 index a0c9435a4f..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/PersonalizationConfigStore.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize - -import android.content.Context -import android.content.SharedPreferences -import androidx.core.content.edit -import com.google.gson.Gson -import com.tangem.devkit.AppTangemDemo -import com.tangem.devkit.commons.KeyedStore -import com.tangem.devkit.commons.Store -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig - -class PersonalizationConfigStore(context: Context) : Store, KeyedStore { - - companion object { - val defaultKey = "default" - } - - private val sharedPreferencesKey = "personalization_presets" - - private val sp: SharedPreferences = (context.applicationContext as AppTangemDemo).sharedPreferences(sharedPreferencesKey) - private val gson: Gson = Gson() - - override fun save(value: PersonalizationConfig) { - save(defaultKey, value) - } - - override fun restore(): PersonalizationConfig = restore(defaultKey) - - override fun save(key: String, value: PersonalizationConfig) { - sp.edit(true) { putString(key, toJson(value)) } - } - - override fun restore(key: String): PersonalizationConfig { - val json = sp.getString(key, toJson(getDefaultConfig())) - return fromJson(json!!) - } - - override fun restoreAll(): MutableMap { - val map = mutableMapOf() - sp.all.forEach { map[it.key] = fromJson(it.value as String) } - return map.toSortedMap() - } - - override fun delete(key: String) { - sp.edit(true) { remove(key) } - } - - private fun getDefaultConfig(): PersonalizationConfig = PersonalizationConfig.default() - - private fun toJson(value: PersonalizationConfig): String = gson.toJson(value) - - private fun fromJson(json: String): PersonalizationConfig = gson.fromJson(json, PersonalizationConfig::class.java) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/DefaultValues.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/DefaultValues.kt deleted file mode 100644 index 4af4f57691..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/DefaultValues.kt +++ /dev/null @@ -1,136 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.converter - -import com.tangem.commands.EllipticCurve -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.abstraction.KeyValue -import com.tangem.devkit.ucase.variants.personalize.* -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationJson -import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder - -/** -[REDACTED_AUTHOR] - */ -class ConfigValuesHolder : BaseTypedHolder() { - - // All registered association values can't be objects - fun init(default: PersonalizationConfig) { - register(CardNumberId.Series, Value(default.series)) - register(CardNumberId.Number, Value(default.startNumber)) - register(CardNumberId.BatchId, Value(default.cardData.batch)) - register(CommonId.Curve, Value(default.curveID, Helper.listOfCurves())) - register(CommonId.Blockchain, Value(default.cardData.blockchain, Helper.listOfBlockchain())) - register(CommonId.BlockchainCustom, Value(default.blockchainCustom)) - register(CommonId.MaxSignatures, Value(default.MaxSignatures)) - register(CommonId.CreateWallet, Value(default.createWallet)) - register(SigningMethodId.SignTx, Value(default.SigningMethod0)) - register(SigningMethodId.SignTxRaw, Value(default.SigningMethod1)) - register(SigningMethodId.SignValidatedTx, Value(default.SigningMethod2)) - register(SigningMethodId.SignValidatedTxRaw, Value(default.SigningMethod3)) - register(SigningMethodId.SignValidatedTxIssuer, Value(default.SigningMethod4)) - register(SigningMethodId.SignValidatedTxRawIssuer, Value(default.SigningMethod5)) - register(SigningMethodId.SignExternal, Value(default.SigningMethod6)) - register(SignHashExPropId.PinLessFloorLimit, Value(default.pinLessFloorLimit)) - register(SignHashExPropId.CryptoExKey, Value(default.hexCrExKey)) - register(SignHashExPropId.RequireTerminalCertSig, Value(default.requireTerminalCertSignature)) - register(SignHashExPropId.RequireTerminalTxSig, Value(default.requireTerminalTxSignature)) - register(SignHashExPropId.CheckPin3, Value(default.checkPIN3onCard)) - register(TokenId.ItsToken, Value(default.itsToken)) - register(TokenId.Symbol, Value(default.cardData.token_symbol)) - register(TokenId.ContractAddress, Value(default.cardData.token_contract_address)) - register(TokenId.Decimal, Value(default.cardData.token_decimal)) - register(ProductMaskId.Note, Value(default.cardData.product_note)) - register(ProductMaskId.Tag, Value(default.cardData.product_tag)) - register(ProductMaskId.IdCard, Value(default.cardData.product_id_card)) - register(ProductMaskId.IdIssuerCard, Value(default.cardData.product_id_issuer)) - register(SettingsMaskId.IsReusable, Value(default.isReusable)) - register(SettingsMaskId.NeedActivation, Value(default.useActivation)) - register(SettingsMaskId.ForbidPurge, Value(default.forbidPurgeWallet)) - register(SettingsMaskId.AllowSelectBlockchain, Value(default.allowSelectBlockchain)) - register(SettingsMaskId.UseBlock, Value(default.useBlock)) - register(SettingsMaskId.OneApdu, Value(default.useOneCommandAtTime)) - register(SettingsMaskId.UseCvc, Value(default.useCVC)) - register(SettingsMaskId.AllowSwapPin, Value(default.allowSwapPIN)) - register(SettingsMaskId.AllowSwapPin2, Value(default.allowSwapPIN2)) - register(SettingsMaskId.ForbidDefaultPin, Value(default.forbidDefaultPIN)) - register(SettingsMaskId.SmartSecurityDelay, Value(default.smartSecurityDelay)) - register(SettingsMaskId.ProtectIssuerDataAgainstReplay, Value(default.protectIssuerDataAgainstReplay)) - register(SettingsMaskId.SkipSecurityDelayIfValidated, Value(default.skipSecurityDelayIfValidatedByIssuer)) - register(SettingsMaskId.SkipPin2CvcIfValidated, Value(default.skipCheckPIN2andCVCIfValidatedByIssuer)) - register(SettingsMaskId.SkipSecurityDelayOnLinkedTerminal, Value(default.skipSecurityDelayIfValidatedByLinkedTerminal)) - register(SettingsMaskId.RestrictOverwriteExtraIssuerData, Value(default.restrictOverwriteIssuerDataEx)) - register(SettingsMaskProtocolEncId.AllowUnencrypted, Value(default.protocolAllowUnencrypted)) - register(SettingsMaskProtocolEncId.AllowStaticEncryption, Value(default.protocolAllowStaticEncryption)) - register(SettingsMaskNdefId.UseNdef, Value(default.useNDEF)) - register(SettingsMaskNdefId.DynamicNdef, Value(default.useDynamicNDEF)) - register(SettingsMaskNdefId.DisablePrecomputedNdef, Value(default.disablePrecomputedNDEF)) - register(SettingsMaskNdefId.Aar, Value(default.aar, Helper.aarList())) - register(SettingsMaskNdefId.AarCustom, Value(default.aarCustom)) - register(SettingsMaskNdefId.Uri, Value(default.uri)) - register(PinsId.Pin, Value(default.PIN)) - register(PinsId.Pin2, Value(default.PIN2)) - register(PinsId.Pin3, Value(default.PIN3)) - register(PinsId.Cvc, Value(default.CVC)) - register(PinsId.PauseBeforePin2, Value(default.pauseBeforePIN2, Helper.pauseBeforePin())) - } -} - -class Value( - private var value: Any? = null, - val list: List? = mutableListOf() -) { - fun get(): Any? = value - fun set(newValue: Any?) { - value = newValue - } -} - -internal class Helper { - companion object { - fun listOfCurves(): List { - return EllipticCurve.values().map { KeyValue(it.name, it.curve) } - } - - fun listOfBlockchain(): List { - return mutableListOf( - KeyValue(PersonalizationJson.CUSTOM, PersonalizationJson.CUSTOM), - KeyValue("BTC", "BTC"), - KeyValue("BTC/test", "BTC/test"), - KeyValue("ETH", "ETH"), - KeyValue("ETH/test", "ETH/test"), - KeyValue("BCH", "BCH"), - KeyValue("BCH/test", "BCH/test"), - KeyValue("LTC", "LTC"), - KeyValue("XLM", "XLM"), - KeyValue("XLM/test", "XLM/test"), - KeyValue("RSK", "RSK"), - KeyValue("XPR", "XPR"), - KeyValue("CARDANO", "CARDANO"), - KeyValue("BNB", "BINANCE"), - KeyValue("XTZ", "TEZOS"), - KeyValue("DUC", "DUC") - ) - } - - fun aarList(): List { - return mutableListOf( - KeyValue(PersonalizationJson.CUSTOM, PersonalizationJson.CUSTOM), - KeyValue("Release APP", "com.tangem.wallet"), - KeyValue("Debug APP", "com.tangem.wallet.debug"), - KeyValue("None", "") - ) - } - - fun pauseBeforePin(): List { - return mutableListOf( - KeyValue("immediately", 0L), - KeyValue("2 seconds", 2000L), - KeyValue("5 seconds", 5000L), - KeyValue("15 seconds", 15000L), - KeyValue("30 seconds", 30000L), - KeyValue("1 minute", 60000L), - KeyValue("2 minute", 120000L) - ) - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/ItemTypes.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/ItemTypes.kt deleted file mode 100644 index 052430a792..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/ItemTypes.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.converter - -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit.ucase.variants.personalize.* - -class ItemTypes { - - val blockIdList = mutableListOf( - BlockId.CardNumber, BlockId.Common, BlockId.SigningMethod, BlockId.SignHashExProp, BlockId.Denomination, - BlockId.Token, BlockId.ProdMask, BlockId.SettingsMask, BlockId.SettingsMaskProtocolEnc, - BlockId.SettingsMaskNdef, BlockId.Pins - ) - - val listItemList = mutableListOf(CommonId.Curve, CommonId.Blockchain, SettingsMaskNdefId.Aar, PinsId.PauseBeforePin2) - - val boolList = mutableListOf( - CommonId.CreateWallet, SigningMethodId.SignTx, SigningMethodId.SignTxRaw, SigningMethodId.SignValidatedTx, - SigningMethodId.SignValidatedTxRaw, SigningMethodId.SignValidatedTxIssuer, - SigningMethodId.SignValidatedTxRawIssuer, SigningMethodId.SignExternal, SignHashExPropId.RequireTerminalCertSig, - SignHashExPropId.RequireTerminalTxSig, SignHashExPropId.CheckPin3, TokenId.ItsToken, - ProductMaskId.Note, ProductMaskId.Tag, ProductMaskId.IdCard, ProductMaskId.IdIssuerCard, SettingsMaskId.IsReusable, SettingsMaskId.NeedActivation, - SettingsMaskId.ForbidPurge, SettingsMaskId.AllowSelectBlockchain, SettingsMaskId.UseBlock, SettingsMaskId.OneApdu, - SettingsMaskId.UseCvc, SettingsMaskId.AllowSwapPin, SettingsMaskId.AllowSwapPin2, SettingsMaskId.ForbidDefaultPin, - SettingsMaskId.SmartSecurityDelay, SettingsMaskId.ProtectIssuerDataAgainstReplay, - SettingsMaskId.SkipSecurityDelayIfValidated, SettingsMaskId.SkipPin2CvcIfValidated, - SettingsMaskId.SkipSecurityDelayOnLinkedTerminal, SettingsMaskId.RestrictOverwriteExtraIssuerData, - SettingsMaskProtocolEncId.AllowUnencrypted, SettingsMaskProtocolEncId.AllowStaticEncryption, - SettingsMaskNdefId.UseNdef, SettingsMaskNdefId.DynamicNdef, SettingsMaskNdefId.DisablePrecomputedNdef - ) - - val editTextList = mutableListOf( - CardNumberId.Series, CardNumberId.BatchId, CommonId.BlockchainCustom, SignHashExPropId.CryptoExKey, TokenId.Symbol, - TokenId.ContractAddress, PinsId.Pin, PinsId.Pin2, PinsId.Pin3, PinsId.Cvc, SettingsMaskNdefId.AarCustom, SettingsMaskNdefId.Uri - ) - - val numberList = mutableListOf( - CardNumberId.Number, CommonId.MaxSignatures, SignHashExPropId.PinLessFloorLimit, TokenId.Decimal - ) - - val hiddenList = mutableListOf( - CardNumberId.Series, CardNumberId.BatchId, SigningMethodId.SignExternal, - SignHashExPropId.CryptoExKey, SignHashExPropId.CheckPin3, SettingsMaskId.OneApdu, - SettingsMaskId.UseBlock, SettingsMaskId.ProtectIssuerDataAgainstReplay, - SignHashExPropId.RequireTerminalCertSig, SignHashExPropId.RequireTerminalTxSig, - BlockId.Pins, PinsId.Pin, PinsId.Pin2, PinsId.Pin3, PinsId.Cvc - ) - - val oftenUsedList = listOf( -// BlockId.CardNumber, - BlockId.Common, -// BlockId.SigningMethod, -// BlockId.SignHashExProp, -// BlockId.Denomination, -// BlockId.Token, - BlockId.ProdMask, -// BlockId.SettingsMask, -// BlockId.SettingsMaskProtocolEnc, - BlockId.SettingsMaskNdef, -// BlockId.Pins, - CommonId.Curve, - CommonId.Blockchain, - CommonId.BlockchainCustom, -// Common.MaxSignatures, - CommonId.CreateWallet, -// SigningMethod.SignTx, -// SigningMethod.SignTxRaw, -// SigningMethod.SignValidatedTx, -// SigningMethod.SignValidatedTxRaw, -// SigningMethod.SignValidatedTxIssuer, -// SigningMethod.SignValidatedTxRawIssuer, -// SigningMethod.SignExternal, -// SignHashExProp.PinLessFloorLimit, -// SignHashExProp.CryptoExKey, -// SignHashExProp.RequireTerminalCertSig, -// SignHashExProp.RequireTerminalTxSig, -// SignHashExProp.CheckPin3, -// Denomination.WriteOnPersonalize, -// Denomination.Denomination, -// Token.ItsToken, -// Token.Symbol, -// Token.ContractAddress, -// Token.Decimal, - ProductMaskId.Note, - ProductMaskId.Tag, - ProductMaskId.IdCard, - ProductMaskId.IdIssuerCard, -// SettingsMask.IsReusable, -// SettingsMask.NeedActivation, -// SettingsMask.ForbidPurge, -// SettingsMask.AllowSelectBlockchain, -// SettingsMask.UseBlock, -// SettingsMask.OneApdu, -// SettingsMask.UseCvc, -// SettingsMask.AllowSwapPin, -// SettingsMask.AllowSwapPin2, -// SettingsMask.ForbidDefaultPin, -// SettingsMask.SmartSecurityDelay, -// SettingsMask.ProtectIssuerDataAgainstReplay, -// SettingsMask.SkipSecurityDelayIfValidated, -// SettingsMask.SkipPin2CvcIfValidated, -// SettingsMask.SkipSecurityDelayOnLinkedTerminal, -// SettingsMask.RestrictOverwriteExtraIssuerData, -// SettingsMaskProtocolEnc.AllowUnencrypted, -// SettingsMaskProtocolEnc.AllowStaticEncryption, -// SettingsMaskNdef.UseNdef, -// SettingsMaskNdef.DynamicNdef, -// SettingsMaskNdef.DisablePrecomputedNdef, - SettingsMaskNdefId.Aar, - SettingsMaskNdefId.AarCustom, - SettingsMaskNdefId.Uri, -// Pins.Pin, -// Pins.Pin2, -// Pins.Pin3, -// Pins.Cvc, - PinsId.PauseBeforePin2 - ) - -} diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt deleted file mode 100644 index ce10098ef1..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationConfigConverter.kt +++ /dev/null @@ -1,301 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.converter - -import com.tangem.devkit._arch.structure.Additional -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.abstraction.* -import com.tangem.devkit._arch.structure.impl.* -import com.tangem.devkit.ucase.variants.personalize.* -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log - -/** -[REDACTED_AUTHOR] - */ -class PersonalizationConfigConverter : ModelConverter { - - private val toModel: ItemsToModel = ItemsToPersonalizationConfig() - private val toItems: ModelToItems = PersonalizationConfigToItems() - - override fun convert(from: List, default: PersonalizationConfig): PersonalizationConfig { - return toModel.convert(from, default) - } - - override fun convert(from: PersonalizationConfig): List { - return toItems.convert(from) - } -} - -class ItemsToPersonalizationConfig : ItemsToModel { - protected val valuesHolder = ConfigValuesHolder() - - override fun convert(from: List, default: PersonalizationConfig): PersonalizationConfig { - valuesHolder.init(default) - mapListItems(from) - return createModel() - } - - private fun mapListItems(itemList: List) { - itemList.iterate { item -> mapItemToHolder(item) } - } - - private fun mapItemToHolder(item: Item) { - when (item) { - is ItemGroup -> mapListItems(item.itemList) - is BaseItem -> { - val defValue = valuesHolder.get(item.id) ?: return - defValue.set(item.viewModel.data) - } - } - } - - private fun createModel(): PersonalizationConfig { - val export = PersonalizationConfig() - export.series = getTyped(CardNumberId.Series) - export.startNumber = getTyped(CardNumberId.Number) - export.cardData.batch = getTyped(CardNumberId.BatchId) - export.curveID = getTyped(CommonId.Curve) - export.cardData.blockchain = getTyped(CommonId.Blockchain) - export.blockchainCustom = getTyped(CommonId.BlockchainCustom) - export.MaxSignatures = getTyped(CommonId.MaxSignatures) - export.createWallet = getTyped(CommonId.CreateWallet) - export.SigningMethod0 = getTyped(SigningMethodId.SignTx) - export.SigningMethod1 = getTyped(SigningMethodId.SignTxRaw) - export.SigningMethod2 = getTyped(SigningMethodId.SignValidatedTx) - export.SigningMethod3 = getTyped(SigningMethodId.SignValidatedTxRaw) - export.SigningMethod4 = getTyped(SigningMethodId.SignValidatedTxIssuer) - export.SigningMethod5 = getTyped(SigningMethodId.SignValidatedTxRawIssuer) - export.SigningMethod6 = getTyped(SigningMethodId.SignExternal) - export.pinLessFloorLimit = getTyped(SignHashExPropId.PinLessFloorLimit) - export.hexCrExKey = getTyped(SignHashExPropId.CryptoExKey) - export.requireTerminalCertSignature = getTyped(SignHashExPropId.RequireTerminalCertSig) - export.requireTerminalTxSignature = getTyped(SignHashExPropId.RequireTerminalTxSig) - export.checkPIN3onCard = getTyped(SignHashExPropId.CheckPin3) - export.itsToken = getTyped(TokenId.ItsToken) - export.cardData.token_symbol = getTypedUnsafe(TokenId.Symbol) - export.cardData.token_contract_address = getTypedUnsafe(TokenId.ContractAddress) - export.cardData.token_decimal = getTypedUnsafe(TokenId.Decimal) - export.cardData = export.cardData.apply { this.product_note = getTyped(ProductMaskId.Note) } - export.cardData = export.cardData.apply { this.product_tag = getTyped(ProductMaskId.Tag) } - export.cardData = export.cardData.apply { this.product_id_card = getTyped(ProductMaskId.IdCard) } - export.cardData = export.cardData.apply { this.product_id_issuer = getTyped(ProductMaskId.IdIssuerCard) } - export.isReusable = getTyped(SettingsMaskId.IsReusable) - export.useActivation = getTyped(SettingsMaskId.NeedActivation) - export.forbidPurgeWallet = getTyped(SettingsMaskId.ForbidPurge) - export.allowSelectBlockchain = getTyped(SettingsMaskId.AllowSelectBlockchain) - export.useBlock = getTyped(SettingsMaskId.UseBlock) - export.useOneCommandAtTime = getTyped(SettingsMaskId.OneApdu) - export.useCVC = getTyped(SettingsMaskId.UseCvc) - export.allowSwapPIN = getTyped(SettingsMaskId.AllowSwapPin) - export.allowSwapPIN2 = getTyped(SettingsMaskId.AllowSwapPin2) - export.forbidDefaultPIN = getTyped(SettingsMaskId.ForbidDefaultPin) - export.smartSecurityDelay = getTyped(SettingsMaskId.SmartSecurityDelay) - export.protectIssuerDataAgainstReplay = getTyped(SettingsMaskId.ProtectIssuerDataAgainstReplay) - export.skipSecurityDelayIfValidatedByIssuer = getTyped(SettingsMaskId.SkipSecurityDelayIfValidated) - export.skipCheckPIN2andCVCIfValidatedByIssuer = getTyped(SettingsMaskId.SkipPin2CvcIfValidated) - export.skipSecurityDelayIfValidatedByLinkedTerminal = getTyped(SettingsMaskId.SkipSecurityDelayOnLinkedTerminal) - export.restrictOverwriteIssuerDataEx = getTyped(SettingsMaskId.RestrictOverwriteExtraIssuerData) - export.protocolAllowUnencrypted = getTyped(SettingsMaskProtocolEncId.AllowUnencrypted) - export.protocolAllowStaticEncryption = getTyped(SettingsMaskProtocolEncId.AllowStaticEncryption) - export.useNDEF = getTyped(SettingsMaskNdefId.UseNdef) - export.useDynamicNDEF = getTyped(SettingsMaskNdefId.DynamicNdef) - export.disablePrecomputedNDEF = getTyped(SettingsMaskNdefId.DisablePrecomputedNdef) - export.aar = getTyped(SettingsMaskNdefId.Aar) - export.aarCustom = getTyped(SettingsMaskNdefId.AarCustom) - export.uri = getTyped(SettingsMaskNdefId.Uri) - export.PIN = getTyped(PinsId.Pin) - export.PIN2 = getTyped(PinsId.Pin2) - export.PIN3 = getTyped(PinsId.Pin3) - export.CVC = getTyped(PinsId.Cvc) - export.pauseBeforePIN2 = getTyped(PinsId.PauseBeforePin2) - return export - } - - private inline fun getTyped(id: Id): Type { - return getTypedBy(valuesHolder, id)!! - } - - private inline fun getTypedUnsafe(id: Id): Type? { - return getTypedBy(valuesHolder, id) - } - - private inline fun getTypedBy(holder: ConfigValuesHolder, id: Id): Type? { - Log.d(this, "getTyped for id: $id") - var typedValue = holder.get(id)?.get() - - typedValue = when (typedValue) { - is ListViewModel -> typedValue.selectedItem as Type - else -> typedValue as? Type ?: null - } - return typedValue - } -} - -class PersonalizationConfigToItems : ModelToItems { - private val valuesHolder = ConfigValuesHolder() - private val itemTypes = ItemTypes() - - override fun convert(from: PersonalizationConfig): List { - valuesHolder.init(from) - val blocList = mutableListOf() - blocList.add(cardNumber()) - blocList.add(common()) - blocList.add(signingMethod()) - blocList.add(signHashExProperties()) - blocList.add(token()) - blocList.add(productMask()) - blocList.add(settingsMask()) - blocList.add(settingsMaskProtocolEnc()) - blocList.add(settingsMaskNdef()) - blocList.add(pins()) - blocList.iterate { - if (itemTypes.hiddenList.contains(it.id)) { - it.viewModel.viewState.isVisibleState.value = false - } - } - return blocList - } - - private fun cardNumber(): ItemGroup { - val block = createGroup(BlockId.CardNumber) - mutableListOf( - CardNumberId.Series, - CardNumberId.Number, - CardNumberId.BatchId - ).forEach { createItem(block, it as Id) } - return block - } - - private fun common(): ItemGroup { - val block = createGroup(BlockId.Common) - mutableListOf( - CommonId.Blockchain, - CommonId.BlockchainCustom, - CommonId.Curve, - CommonId.MaxSignatures, - CommonId.CreateWallet, - PinsId.PauseBeforePin2 - ).forEach { createItem(block, it as Id) } - return block - } - - private fun signingMethod(): ItemGroup { - val block = createGroup(BlockId.SigningMethod) - mutableListOf( - SigningMethodId.SignTx, - SigningMethodId.SignTxRaw, - SigningMethodId.SignValidatedTx, - SigningMethodId.SignValidatedTxRaw, - SigningMethodId.SignValidatedTxIssuer, - SigningMethodId.SignValidatedTxRawIssuer, - SigningMethodId.SignExternal - ).forEach { createItem(block, it) } - return block - } - - private fun signHashExProperties(): ItemGroup { - val block = createGroup(BlockId.SignHashExProp) - mutableListOf( - SignHashExPropId.PinLessFloorLimit, - SignHashExPropId.CryptoExKey, - SignHashExPropId.RequireTerminalCertSig, - SignHashExPropId.RequireTerminalTxSig, - SignHashExPropId.CheckPin3 - ).forEach { createItem(block, it) } - return block - } - - private fun token(): ItemGroup { - val block = createGroup(BlockId.Token) - mutableListOf( - TokenId.ItsToken, - TokenId.Symbol, - TokenId.ContractAddress, - TokenId.Decimal - ).forEach { createItem(block, it) } - return block - } - - private fun productMask(): ItemGroup { - val block = createGroup(BlockId.ProdMask) - mutableListOf( - ProductMaskId.Note, - ProductMaskId.Tag, - ProductMaskId.IdCard, - ProductMaskId.IdIssuerCard - ).forEach { createItem(block, it) } - return block - } - - private fun settingsMask(): ItemGroup { - val block = createGroup(BlockId.SettingsMask) - mutableListOf( - SettingsMaskId.IsReusable, - SettingsMaskId.NeedActivation, - SettingsMaskId.ForbidPurge, - SettingsMaskId.AllowSelectBlockchain, - SettingsMaskId.UseBlock, - SettingsMaskId.OneApdu, - SettingsMaskId.UseCvc, - SettingsMaskId.AllowSwapPin, - SettingsMaskId.AllowSwapPin2, - SettingsMaskId.ForbidDefaultPin, - SettingsMaskId.SmartSecurityDelay, - SettingsMaskId.ProtectIssuerDataAgainstReplay, - SettingsMaskId.SkipSecurityDelayIfValidated, - SettingsMaskId.SkipPin2CvcIfValidated, - SettingsMaskId.SkipSecurityDelayOnLinkedTerminal, - SettingsMaskId.RestrictOverwriteExtraIssuerData - ).forEach { createItem(block, it) } - return block - } - - private fun settingsMaskProtocolEnc(): ItemGroup { - val block = createGroup(BlockId.SettingsMaskProtocolEnc) - mutableListOf( - SettingsMaskProtocolEncId.AllowUnencrypted, - SettingsMaskProtocolEncId.AllowStaticEncryption - ).forEach { createItem(block, it) } - return block - } - - private fun settingsMaskNdef(): ItemGroup { - val block = createGroup(BlockId.SettingsMaskNdef) - mutableListOf( - SettingsMaskNdefId.UseNdef, - SettingsMaskNdefId.DynamicNdef, - SettingsMaskNdefId.DisablePrecomputedNdef, - SettingsMaskNdefId.Aar, - SettingsMaskNdefId.AarCustom, - SettingsMaskNdefId.Uri - ).forEach { createItem(block, it) } - return block - } - - private fun pins(): ItemGroup { - val block = createGroup(BlockId.Pins) - mutableListOf( - PinsId.Pin, - PinsId.Pin2, - PinsId.Pin3, - PinsId.Cvc - ).forEach { createItem(block, it) } - return block - } - - private fun createGroup(id: Id): ItemGroup { - return SimpleItemGroup(id).apply { addItem(TextItem(id)) } - } - - private fun createItem(itemGroup: ItemGroup, id: Id) { - val holder = valuesHolder.get(id) ?: return - val item = when { - itemTypes.blockIdList.contains(id) -> TextItem(id, holder.get() as? String) - itemTypes.listItemList.contains(id) -> SpinnerItem(id, holder.list as List, holder.get()) - itemTypes.boolList.contains(id) -> BoolItem(id, holder.get() as? Boolean) - itemTypes.editTextList.contains(id) -> EditTextItem(id, holder.get() as? String) - itemTypes.numberList.contains(id) -> NumberItem(id, holder.get() as? Number) - else -> SimpleItemGroup(Additional.UNDEFINED) - } - itemGroup.addItem(item) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationJsonConverter.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationJsonConverter.kt deleted file mode 100644 index a2590ebb3c..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/converter/PersonalizationJsonConverter.kt +++ /dev/null @@ -1,204 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.converter - -import com.tangem.commands.EllipticCurve -import com.tangem.commands.SigningMethod -import com.tangem.commands.SigningMethodMask -import com.tangem.commands.personalization.entities.NdefRecord -import com.tangem.devkit._arch.structure.abstraction.TwoWayConverter -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationConfig -import com.tangem.devkit.ucase.variants.personalize.dto.PersonalizationJson -import ru.dev.gbixahue.eu4d.lib.kotlin.common.Converter - -/** -[REDACTED_AUTHOR] - */ -class PersonalizationJsonConverter : TwoWayConverter { - - override fun aToB(from: PersonalizationJson): PersonalizationConfig = JsonToConfig().convert(from) - - override fun bToA(from: PersonalizationConfig): PersonalizationJson = ConfigToJson().convert(from) -} - -internal class JsonToConfig : Converter { - - override fun convert(jsonDto: PersonalizationJson): PersonalizationConfig { - val config = PersonalizationConfig.default() - config.apply { - series = jsonDto.series - startNumber = jsonDto.startNumber - curveID = EllipticCurve.byName(jsonDto.curveID)?.curve ?: "" - blockchainCustom = "" - MaxSignatures = jsonDto.MaxSignatures - createWallet = jsonDto.createWallet != 0L - hexCrExKey = jsonDto.hexCrExKey - requireTerminalTxSignature = jsonDto.requireTerminalTxSignature - requireTerminalCertSignature = jsonDto.requireTerminalCertSignature - checkPIN3onCard = jsonDto.checkPIN3onCard - isReusable = jsonDto.isReusable - useActivation = jsonDto.useActivation - forbidPurgeWallet = jsonDto.forbidPurgeWallet - allowSelectBlockchain = jsonDto.allowSelectBlockchain - useBlock = jsonDto.useBlock - useOneCommandAtTime = jsonDto.useOneCommandAtTime - useCVC = jsonDto.useCVC - allowSwapPIN = jsonDto.allowSwapPIN - allowSwapPIN2 = jsonDto.allowSwapPIN2 - forbidDefaultPIN = jsonDto.forbidDefaultPIN - smartSecurityDelay = jsonDto.smartSecurityDelay - protectIssuerDataAgainstReplay = jsonDto.protectIssuerDataAgainstReplay - skipSecurityDelayIfValidatedByIssuer = jsonDto.skipSecurityDelayIfValidatedByIssuer - skipCheckPIN2andCVCIfValidatedByIssuer = jsonDto.skipCheckPIN2andCVCIfValidatedByIssuer - skipSecurityDelayIfValidatedByLinkedTerminal = jsonDto.skipSecurityDelayIfValidatedByLinkedTerminal - restrictOverwriteIssuerDataEx = jsonDto.restrictOverwriteIssuerDataEx - protocolAllowUnencrypted = jsonDto.protocolAllowUnencrypted - protocolAllowStaticEncryption = jsonDto.protocolAllowStaticEncryption - useNDEF = jsonDto.useNDEF - useDynamicNDEF = jsonDto.useDynamicNDEF - disablePrecomputedNDEF = jsonDto.disablePrecomputedNDEF - PIN = jsonDto.PIN - PIN2 = jsonDto.PIN2 - PIN3 = jsonDto.PIN3 - CVC = jsonDto.CVC - pauseBeforePIN2 = jsonDto.pauseBeforePIN2 - count = jsonDto.count - issuerName = jsonDto.issuerName - issuerData = jsonDto.issuerData -// numberFormat = from.numberFormat -// pinLessFloorLimit = 100000L - } - - fillCardData(jsonDto, config) - fillSigningMethod(jsonDto, config) - fillNdef(jsonDto, config) - - return config - } - - private fun fillCardData(jsonDto: PersonalizationJson, config: PersonalizationConfig) { - val jsonCardData = jsonDto.cardData - - // copy whole object and then checking tricky places - config.cardData.copyFrom(jsonCardData) - - config.itsToken = jsonCardData.token_contract_address?.isNotEmpty() ?: false - || jsonCardData.token_symbol?.isNotEmpty() ?: false - - val selectedBlockchain = Helper.listOfBlockchain().firstOrNull { it.value == jsonCardData.blockchain } - if (selectedBlockchain == null) { - config.cardData.blockchain = PersonalizationJson.CUSTOM - config.blockchainCustom = jsonCardData.blockchain - } - } - - private fun fillSigningMethod(jsonDto: PersonalizationJson, config: PersonalizationConfig) { - val mask = SigningMethodMask(jsonDto.SigningMethod.toInt()) - if (mask.contains(SigningMethod.SignHash)) config.SigningMethod0 = true - if (mask.contains(SigningMethod.SignRaw)) config.SigningMethod1 = true - if (mask.contains(SigningMethod.SignHashValidateByIssuer)) config.SigningMethod2 = true - if (mask.contains(SigningMethod.SignRawValidateByIssuer)) config.SigningMethod3 = true - if (mask.contains(SigningMethod.SignHashValidateByIssuerWriteIssuerData)) config.SigningMethod4 = true - if (mask.contains(SigningMethod.SignRawValidateByIssuerWriteIssuerData)) config.SigningMethod5 = true - } - - private fun fillNdef(jsonDto: PersonalizationJson, config: PersonalizationConfig) { - jsonDto.ndef.forEach { record -> - when (record.type) { - NdefRecord.Type.URI -> config.uri = record.value - NdefRecord.Type.AAR -> config.aar = record.value - NdefRecord.Type.TEXT -> { - } - } - } - val selectedAar = Helper.aarList().firstOrNull { it.value == config.aar } - if (selectedAar == null) { - config.aarCustom = config.aar - config.aar = "" - } - } -} - -internal class ConfigToJson : Converter { - - override fun convert(from: PersonalizationConfig): PersonalizationJson { - val jsonDto = PersonalizationJson() - jsonDto.apply { - releaseVersion = false - issuerName = from.issuerName - series = from.series - startNumber = from.startNumber - count = from.count - PIN = from.PIN - PIN2 = from.PIN2 - PIN3 = from.PIN3 - hexCrExKey = from.hexCrExKey - CVC = from.CVC - pauseBeforePIN2 = from.pauseBeforePIN2 - smartSecurityDelay = from.smartSecurityDelay - curveID = from.curveID - MaxSignatures = from.MaxSignatures - isReusable = from.isReusable - allowSwapPIN = from.allowSwapPIN - allowSwapPIN2 = from.allowSwapPIN2 - useActivation = from.useActivation - useCVC = from.useCVC - useNDEF = from.useNDEF - useDynamicNDEF = from.useDynamicNDEF - useOneCommandAtTime = from.useOneCommandAtTime - useBlock = from.useBlock - allowSelectBlockchain = from.allowSelectBlockchain - forbidPurgeWallet = from.forbidPurgeWallet - protocolAllowUnencrypted = from.protocolAllowUnencrypted - protocolAllowStaticEncryption = from.protocolAllowStaticEncryption - protectIssuerDataAgainstReplay = from.protectIssuerDataAgainstReplay - forbidDefaultPIN = from.forbidDefaultPIN - disablePrecomputedNDEF = from.disablePrecomputedNDEF - skipSecurityDelayIfValidatedByIssuer = from.skipSecurityDelayIfValidatedByIssuer - skipCheckPIN2andCVCIfValidatedByIssuer = from.skipCheckPIN2andCVCIfValidatedByIssuer - skipSecurityDelayIfValidatedByLinkedTerminal = from.skipSecurityDelayIfValidatedByLinkedTerminal - restrictOverwriteIssuerDataEx = from.restrictOverwriteIssuerDataEx - requireTerminalTxSignature = from.requireTerminalTxSignature - requireTerminalCertSignature = from.requireTerminalCertSignature - checkPIN3onCard = from.checkPIN3onCard - createWallet = if (from.createWallet) 1 else 0 - issuerData = from.issuerData -// numberFormat = from.numberFormat -// pinLessFloorLimit = 100000L - } - - fillCardData(from, jsonDto) - fillSigningMethod(from, jsonDto) - fillNdef(from, jsonDto) - return jsonDto - } - - private fun fillCardData(config: PersonalizationConfig, jsonDto: PersonalizationJson) { - jsonDto.cardData = config.cardData - if (config.cardData.blockchain == PersonalizationJson.CUSTOM) { - jsonDto.cardData.blockchain = config.blockchainCustom - } - if (config.itsToken) { - jsonDto.cardData.token_contract_address = config.cardData.token_contract_address - jsonDto.cardData.token_symbol = config.cardData.token_symbol - jsonDto.cardData.token_decimal = config.cardData.token_decimal - } else { - jsonDto.cardData.token_contract_address = null - jsonDto.cardData.token_symbol = null - jsonDto.cardData.token_decimal = null - } - } - - private fun fillSigningMethod(config: PersonalizationConfig, jsonDto: PersonalizationJson) { - jsonDto.SigningMethod = PersonalizationConfig.makeSigningMethodMask(config).rawValue.toLong() - } - - private fun fillNdef(config: PersonalizationConfig, jsonDto: PersonalizationJson) { - fun add(value: String, type: NdefRecord.Type) { - if (value.isNotEmpty()) jsonDto.ndef.add(NdefRecord(type, value)) - } - - if (config.aarCustom.isNotEmpty()) add(config.aarCustom, NdefRecord.Type.AAR) - else add(config.aar, NdefRecord.Type.AAR) - - add(config.uri, NdefRecord.Type.URI) - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt deleted file mode 100644 index aa68fb4215..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.tangem.devkit.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.extensions.hexToBytes - -/** -[REDACTED_AUTHOR] - */ - -interface DefaultPersonalizationParams { - companion object { - - fun issuer(): Issuer { - val name = "TANGEM SDK" - return Issuer( - name = name, - id = name + "\u0000", - dataKeyPair = KeyPair( - ("045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f" + - "361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26").hexToBytes(), - "11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() - ), - transactionKeyPair = KeyPair( - ("0484c5192e9bfa6c528a344f442137a92b89ea835bfef1d04cb4362eb906b508c5889846cfea71ba6dc7b" + - "3120c2208df9c46127d3d85cb5cfbd1479e97133a39d8").hexToBytes(), - "11121314151617184771ED81F2BACF57479E4735EB1405081918171615141312".hexToBytes() - ) - ) - } - - fun acquirer(): Acquirer { - val name = "Smart Cash" - return Acquirer( - name = name, - id = name + "\u0000", - keyPair = KeyPair( - ("0456ad1a82b22bcb40c38fd08939f87e6b80e40dec5b3bdb351c55fcd709e47f9fb2ed00c2304d3a9" + - "86f79c5ae0ac3c84e88da46dc8f513b7542c716af8c9a2daf").hexToBytes(), - "21222324252627284771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes() - ) - ) - } - - fun manufacturer(): Manufacturer { - val keys = Manufacturer(com.tangem.devkit.ucase.variants.personalize.dto.Manufacturer.Mode.Developer) - return Manufacturer( - name = "Tangem", - keyPair = KeyPair(keys.publicKey, keys.privateKey) - ) - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/Manufacturer.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/Manufacturer.kt deleted file mode 100644 index eccc075888..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/Manufacturer.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.dto - -class Manufacturer(private val mode: Mode) { - - private val devPrivateKeyArray = byteArrayOf( - 0x1B.toByte(), 0x48.toByte(), 0xCF.toByte(), 0xD2.toByte(), 0x4B.toByte(), 0xBB.toByte(), 0x5B.toByte(), 0x39.toByte(), - 0x47.toByte(), 0x71.toByte(), 0xED.toByte(), 0x81.toByte(), 0xF2.toByte(), 0xBA.toByte(), 0xCF.toByte(), 0x57.toByte(), - 0x47.toByte(), 0x9E.toByte(), 0x47.toByte(), 0x35.toByte(), 0xEB.toByte(), 0x14.toByte(), 0x05.toByte(), 0x08.toByte(), - 0x39.toByte(), 0x27.toByte(), 0x37.toByte(), 0x2D.toByte(), 0x40.toByte(), 0xDA.toByte(), 0x9E.toByte(), 0x92.toByte() - ) - private val devPublicKeyArray = byteArrayOf(0x04, - 0xBA.toByte(), 0xB8.toByte(), 0x6D.toByte(), 0x56.toByte(), 0x29.toByte(), 0x8C.toByte(), 0x99.toByte(), 0x6F.toByte(), - 0x56.toByte(), 0x4A.toByte(), 0x84.toByte(), 0xFC.toByte(), 0x88.toByte(), 0xE2.toByte(), 0x8A.toByte(), 0xED.toByte(), - 0x38.toByte(), 0x18.toByte(), 0x4B.toByte(), 0x12.toByte(), 0xF0.toByte(), 0x7E.toByte(), 0x51.toByte(), 0x91.toByte(), - 0x13.toByte(), 0xBE.toByte(), 0xF4.toByte(), 0x8C.toByte(), 0x76.toByte(), 0xF3.toByte(), 0xDF.toByte(), 0x3A.toByte(), - 0xDC.toByte(), 0x30.toByte(), 0x35.toByte(), 0x99.toByte(), 0xB0.toByte(), 0x8A.toByte(), 0xC0.toByte(), 0x5B.toByte(), - 0x55.toByte(), 0xEC.toByte(), 0x3D.toByte(), 0xF9.toByte(), 0x8D.toByte(), 0x93.toByte(), 0x38.toByte(), 0x57.toByte(), - 0x3A.toByte(), 0x62.toByte(), 0x42.toByte(), 0xF7.toByte(), 0x6F.toByte(), 0x5D.toByte(), 0x28.toByte(), 0xF4.toByte(), - 0xF0.toByte(), 0xF3.toByte(), 0x64.toByte(), 0xE8.toByte(), 0x7E.toByte(), 0x8F.toByte(), 0xCA.toByte(), 0x2F.toByte() - ) - private val releasePublicKeyArray = byteArrayOf(0x04.toByte(), - 0x2E.toByte(), 0xDE.toByte(), 0x11.toByte(), 0x9B.toByte(), 0xF3.toByte(), 0x37.toByte(), 0xB2.toByte(), 0x64.toByte(), - 0xFD.toByte(), 0xA1.toByte(), 0x32.toByte(), 0xCF.toByte(), 0xC7.toByte(), 0xC1.toByte(), 0x77.toByte(), 0x82.toByte(), - 0x4D.toByte(), 0x36.toByte(), 0x17.toByte(), 0xDA.toByte(), 0xC8.toByte(), 0x0F.toByte(), 0x25.toByte(), 0xDB.toByte(), - 0xB2.toByte(), 0xA4.toByte(), 0xA8.toByte(), 0xA1.toByte(), 0x18.toByte(), 0x3C.toByte(), 0x03.toByte(), 0xB9.toByte(), - 0x15.toByte(), 0x23.toByte(), 0x05.toByte(), 0xF8.toByte(), 0xF1.toByte(), 0xDB.toByte(), 0x97.toByte(), 0x00.toByte(), - 0x45.toByte(), 0x18.toByte(), 0x48.toByte(), 0x0D.toByte(), 0x50.toByte(), 0x91.toByte(), 0xAD.toByte(), 0xC1.toByte(), - 0xCA.toByte(), 0xB9.toByte(), 0xEA.toByte(), 0xCC.toByte(), 0xC1.toByte(), 0x8E.toByte(), 0x1B.toByte(), 0x9E.toByte(), - 0x9C.toByte(), 0x3B.toByte(), 0xEF.toByte(), 0xB2.toByte(), 0x93.toByte(), 0xDD.toByte(), 0x37.toByte(), 0xB2.toByte() - ) - - val publicKey: ByteArray - get() = if (mode == Mode.Developer) { - devPublicKeyArray - } else { - releasePublicKeyArray - } - - @get:Throws(Exception::class) - val privateKey: ByteArray - get() = if (mode == Mode.Developer) { - devPrivateKeyArray - } else { - throw Exception("Real manufacturer key is very secret!") - } - - val name: String - get() = if (mode == Mode.Release) "SMART CASH" else "SMART CASH SDK" - - enum class Mode { - Developer, Release - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt deleted file mode 100644 index c3bdaf8c09..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationConfig.kt +++ /dev/null @@ -1,232 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.dto - -import com.tangem.commands.EllipticCurve -import com.tangem.commands.SigningMethod -import com.tangem.commands.SigningMethodMask -import com.tangem.commands.SigningMethodMaskBuilder - -/** -[REDACTED_AUTHOR] - */ -class PersonalizationConfig { - - // Card number - var series = "" - var startNumber: Long = 0 - - // Common - var curveID = "" - var blockchainCustom = "" - var MaxSignatures: Long = 0 - var createWallet = false - - // Signing method - var SigningMethod0 = false - var SigningMethod1 = false - var SigningMethod2 = false - var SigningMethod3 = false - var SigningMethod4 = false - var SigningMethod5 = false - var SigningMethod6 = false - - // Sign hash external properties - var pinLessFloorLimit: Long = 0 - var hexCrExKey = "" - var requireTerminalTxSignature = false - var requireTerminalCertSignature = false - var checkPIN3onCard = false - - // Token - var itsToken = false - - // Product mask - var cardData = CardData() - - // Settings mask - var isReusable = false - var useActivation = false - var forbidPurgeWallet = false - var allowSelectBlockchain = false - var useBlock = false - var useOneCommandAtTime = false - var useCVC = false - var allowSwapPIN = false - var allowSwapPIN2 = false - var forbidDefaultPIN = false - var smartSecurityDelay = false - var protectIssuerDataAgainstReplay = false - var skipSecurityDelayIfValidatedByIssuer = false - var skipCheckPIN2andCVCIfValidatedByIssuer = false - var skipSecurityDelayIfValidatedByLinkedTerminal = false - var restrictOverwriteIssuerDataEx = false - - // Settings mask - protocol encryption - var protocolAllowUnencrypted = false - var protocolAllowStaticEncryption = false - - // Settings mask - var useNDEF = false - var useDynamicNDEF = false - var disablePrecomputedNDEF = false - var aar = "" - var aarCustom = "" - var uri = "" - - // Pins - var PIN = "" - var PIN2 = "" - var PIN3 = "" - var CVC = "" - var pauseBeforePIN2: Long = 0 - - var count: Long = 0 - var issuerName = "" - var issuerData = null - - companion object { - - fun default(): PersonalizationConfig { - return PersonalizationConfig().apply { - // Card number - series = "BB" - startNumber = 300000000000L - - // Common - curveID = EllipticCurve.Secp256k1.curve - blockchainCustom = "" - MaxSignatures = 999999L - createWallet = true - - // Signing method - SigningMethod0 = true - SigningMethod1 = false - SigningMethod2 = false - SigningMethod3 = false - SigningMethod4 = false - SigningMethod5 = false - SigningMethod6 = false - - // Sign hash external properties - pinLessFloorLimit = 100000L - hexCrExKey = "00112233445566778899AABBCCDDEEFFFFEEDDCCBBAA998877665544332211000000111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFF" - requireTerminalTxSignature = false - requireTerminalCertSignature = false - checkPIN3onCard = true - - // Token - itsToken = false - - cardData = CardData.default() - - // Settings mask - isReusable = true - useActivation = false - forbidPurgeWallet = false - allowSelectBlockchain = false - useBlock = false - useOneCommandAtTime = false - useCVC = false - allowSwapPIN = true - allowSwapPIN2 = true - forbidDefaultPIN = false - smartSecurityDelay = true - protectIssuerDataAgainstReplay = true - skipSecurityDelayIfValidatedByIssuer = true - skipCheckPIN2andCVCIfValidatedByIssuer = true - skipSecurityDelayIfValidatedByLinkedTerminal = true - restrictOverwriteIssuerDataEx = false - - // Settings mask - protocol encryption - protocolAllowUnencrypted = true - protocolAllowStaticEncryption = true - - useNDEF = true - useDynamicNDEF = true - disablePrecomputedNDEF = false - aar = "com.tangem.wallet" - aarCustom = "" - uri = "https://tangem.com" - - // Pins - PIN = "000000" - PIN2 = "000" - PIN3 = "" - CVC = "000" - pauseBeforePIN2 = 5000L - - count = 1050 - issuerName = "TANGEM" - issuerData = null - } - } - - fun makeSigningMethodMask(from: PersonalizationConfig): SigningMethodMask { - val signingMethodMaskBuilder = SigningMethodMaskBuilder() - if (from.SigningMethod0) { - signingMethodMaskBuilder.add(SigningMethod.SignHash) - } - if (from.SigningMethod1) { - signingMethodMaskBuilder.add(SigningMethod.SignRaw) - } - if (from.SigningMethod2) { - signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuer) - } - if (from.SigningMethod3) { - signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuer) - } - if (from.SigningMethod4) { - signingMethodMaskBuilder.add(SigningMethod.SignHashValidateByIssuerWriteIssuerData) - } - if (from.SigningMethod5) { - signingMethodMaskBuilder.add(SigningMethod.SignRawValidateByIssuerWriteIssuerData) - } - if (from.SigningMethod6) { - signingMethodMaskBuilder.add(SigningMethod.SignPos) - } - return signingMethodMaskBuilder.build() - } - } -} - -class CardData { - var date = "" - var batch = "" - var blockchain = "" - var token_symbol: String? = null - var token_contract_address: String? = null - var token_decimal: Long? = null - var product_note = false - var product_tag = false - var product_id_card = false - var product_id_issuer = false - - fun copyFrom(applyData: CardData) { - date = applyData.date - batch = applyData.batch - blockchain = applyData.blockchain - token_symbol = applyData.token_symbol - token_contract_address = applyData.token_contract_address - token_decimal = applyData.token_decimal - product_note = applyData.product_note - product_tag = applyData.product_tag - product_id_card = applyData.product_id_card - product_id_issuer = applyData.product_id_issuer - } - - companion object { - fun default(): CardData { - return CardData().apply { - date = "2020-02-17" - batch = "FFFF" - blockchain = "ETH" - token_symbol = "" - token_contract_address = "" - token_decimal = 0 - product_note = true - product_tag = false - product_id_card = false - product_id_issuer = false - } - } - } -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationJson.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationJson.kt deleted file mode 100644 index a024383090..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/dto/PersonalizationJson.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.dto - -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.tangem.commands.EllipticCurve -import com.tangem.commands.Product -import com.tangem.commands.ProductMaskBuilder -import com.tangem.commands.SigningMethodMask -import com.tangem.commands.personalization.entities.CardConfig -import com.tangem.commands.personalization.entities.NdefRecord -import java.util.* - -class PersonalizationJson { - - var issuerName = "" - var series = "" - var startNumber = 0L - var count = 0L - var PIN = "" - var PIN2 = "" - var PIN3 = "" - var hexCrExKey = "" - var CVC = "" - var pauseBeforePIN2 = 0L - var smartSecurityDelay = false - var curveID = "" - var SigningMethod = 0L - var MaxSignatures = 0L - var isReusable = false - var allowSwapPIN = false - var allowSwapPIN2 = false - var useActivation = false - var useCVC = false - var useNDEF = false - var useDynamicNDEF = false - var useOneCommandAtTime = false - var useBlock = false - var allowSelectBlockchain = false - var forbidPurgeWallet = false - var protocolAllowUnencrypted = false - var protocolAllowStaticEncryption = false - var protectIssuerDataAgainstReplay = false - var forbidDefaultPIN = false - var disablePrecomputedNDEF = false - var skipSecurityDelayIfValidatedByIssuer = false - var skipCheckPIN2andCVCIfValidatedByIssuer = false - var skipSecurityDelayIfValidatedByLinkedTerminal = false - var restrictOverwriteIssuerDataEx = false - var requireTerminalTxSignature = false - var requireTerminalCertSignature = false - var checkPIN3onCard = false - var createWallet = 0L - var issuerData = null - - var ndef = mutableListOf() - var cardData = CardData() - - var releaseVersion = false - var numberFormat = "" - - companion object { - const val CUSTOM = "--- CUSTOM ---" - - fun getJsonConverter(): Gson { - val builder = GsonBuilder().setPrettyPrinting() - return builder.create() - } - - fun clarifyJson(json: String): String { - val unsupportedQuotes = mutableListOf("“", "”", "«", "»") - var clearedJson = json - unsupportedQuotes.forEach { - if (clearedJson.contains(it)) clearedJson = clearedJson.replace(it, "\"") - } - // 160 is 00A0 symbol (No-Break Space) - return clearedJson.replace(160.toChar().toString(), "").trim() - } - } -} - -fun PersonalizationJson.toCardConfig(): CardConfig { - val isNote = cardData.product_note - val isTag = cardData.product_tag - val isIdCard = cardData.product_id_card - val isIdIssuer = cardData.product_id_issuer - - val productMaskBuilder = ProductMaskBuilder() - if (isNote) productMaskBuilder.add(Product.Note) - if (isTag) productMaskBuilder.add(Product.Tag) - if (isIdCard) productMaskBuilder.add(Product.IdCard) - if (isIdIssuer) productMaskBuilder.add(Product.IdIssuer) - val productMask = productMaskBuilder.build() - - val sdkCardData = com.tangem.commands.CardData( - blockchainName = this.cardData.blockchain, - batchId = this.cardData.batch, - productMask = productMask, - tokenSymbol = this.cardData.token_symbol, - tokenContractAddress = this.cardData.token_contract_address, - tokenDecimal = this.cardData.token_decimal?.toInt(), - issuerName = this.issuerName, - manufactureDateTime = Calendar.getInstance().time, - manufacturerSignature = null) - - return CardConfig( - this.issuerName, - "Tangem Test", - this.series, - this.startNumber, - this.count.toInt(), - this.PIN, - this.PIN2, - this.PIN3, - this.hexCrExKey, - this.CVC, - this.pauseBeforePIN2.toInt(), - this.smartSecurityDelay, - EllipticCurve.byName(this.curveID) ?: EllipticCurve.Secp256k1, - SigningMethodMask(this.SigningMethod.toInt()), - this.MaxSignatures.toInt(), - this.isReusable, - this.allowSwapPIN, - this.allowSwapPIN2, - this.useActivation, - this.useCVC, - this.useNDEF, - this.useDynamicNDEF, - this.useOneCommandAtTime, - this.useBlock, - this.allowSelectBlockchain, - this.forbidPurgeWallet, - this.protocolAllowUnencrypted, - this.protocolAllowStaticEncryption, - this.protectIssuerDataAgainstReplay, - this.forbidDefaultPIN, - this.disablePrecomputedNDEF, - this.skipSecurityDelayIfValidatedByIssuer, - this.skipCheckPIN2andCVCIfValidatedByIssuer, - this.skipSecurityDelayIfValidatedByLinkedTerminal, - this.restrictOverwriteIssuerDataEx, - this.requireTerminalTxSignature, - this.requireTerminalCertSignature, - this.checkPIN3onCard, - this.createWallet != 0L, - sdkCardData, - this.ndef - ) -} \ No newline at end of file diff --git a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/PersonalizationFragment.kt b/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/PersonalizationFragment.kt deleted file mode 100644 index 9e2d3ad01a..0000000000 --- a/tangem-devkit/src/main/java/com/tangem/devkit/ucase/variants/personalize/ui/PersonalizationFragment.kt +++ /dev/null @@ -1,271 +0,0 @@ -package com.tangem.devkit.ucase.variants.personalize.ui - -import android.content.DialogInterface -import android.os.Bundle -import android.view.* -import android.view.inputmethod.InputMethodManager -import android.widget.Button -import android.widget.EditText -import android.widget.LinearLayout -import android.widget.TextView -import androidx.core.content.ContextCompat.getSystemService -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.transition.Fade -import com.google.android.material.textfield.TextInputLayout -import com.tangem.commands.Card -import com.tangem.devkit.R -import com.tangem.devkit._arch.structure.Id -import com.tangem.devkit._arch.structure.StringId -import com.tangem.devkit._arch.structure.abstraction.Item -import com.tangem.devkit._arch.structure.abstraction.SafeValueChanged -import com.tangem.devkit._arch.widget.WidgetBuilder -import com.tangem.devkit.commons.DialogController -import com.tangem.devkit.commons.view.MultiActionView -import com.tangem.devkit.commons.view.ViewAction -import com.tangem.devkit.extensions.copyToClipboard -import com.tangem.devkit.extensions.shareText -import com.tangem.devkit.extensions.view.beginDelayedTransition -import com.tangem.devkit.ucase.domain.paramsManager.ItemsManager -import com.tangem.devkit.ucase.domain.paramsManager.PayloadKey -import com.tangem.devkit.ucase.domain.paramsManager.managers.PersonalizationItemsManager -import com.tangem.devkit.ucase.resources.ActionType -import com.tangem.devkit.ucase.tunnel.ActionView -import com.tangem.devkit.ucase.tunnel.ItemError -import com.tangem.devkit.ucase.ui.BaseCardActionFragment -import com.tangem.devkit.ucase.variants.personalize.PersonalizationConfigStore -import com.tangem.devkit.ucase.variants.personalize.ui.presets.PersonalizationPresetManager -import com.tangem.devkit.ucase.variants.personalize.ui.presets.PersonalizationPresetView -import com.tangem.devkit.ucase.variants.personalize.ui.presets.RvPresetNamesAdapter -import com.tangem.devkit.ucase.variants.personalize.ui.widgets.PersonalizationItemBuilder -import com.tangem.devkit.ucase.variants.responses.ui.ResponseFragment -import ru.dev.gbixahue.eu4d.lib.android._android.views.inflate -import ru.dev.gbixahue.eu4d.lib.android.global.log.Log -import ru.dev.gbixahue.eu4d.lib.android.global.threading.post -import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI -import ru.dev.gbixahue.eu4d.lib.android.global.threading.postWork - - -/** -[REDACTED_AUTHOR] - */ -class PersonalizationFragment : BaseCardActionFragment(), PersonalizationPresetView { - - private lateinit var personalizationItemsManager: PersonalizationItemsManager - - override val itemsManager: ItemsManager by lazy { - personalizationItemsManager = PersonalizationItemsManager(PersonalizationConfigStore(requireContext())) - personalizationItemsManager - } - - override fun getLayoutId(): Int = R.layout.fg_base_action_layout - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - lifecycle.addObserver(itemsManager as PersonalizationItemsManager) - } - - override fun bindViews() { - super.bindViews() - swrLayout.isRefreshing = true - } - - override fun initViews() { - actionFab.setOnClickListener { actionVM.invokeMainAction() } - } - - override fun createWidgets(widgetCreatedCallback: () -> Unit) { - Log.d(this, "createWidgets") - val itemList = mutableListOf() - - val maxDelay = 500 - val timeStart = System.currentTimeMillis() - actionVM.ldItemList.observe(viewLifecycleOwner, Observer { list -> - Log.d(this, "ldBlockList size: ${list.size}") - itemList.addAll(list) - val llContainer = LinearLayout(requireContext()).apply { orientation = LinearLayout.VERTICAL } - postWork { - val builder = WidgetBuilder(PersonalizationItemBuilder()) - itemList.forEach { builder.build(it, llContainer) } - actionVM.attachToPayload(mutableMapOf( - PayloadKey.actionView to this as ActionView, - PayloadKey.itemList to itemList - )) - val timeEnd = System.currentTimeMillis() - val diff = timeEnd - timeStart - postUI(maxDelay - diff) { - contentContainer.beginDelayedTransition(Fade()) - contentContainer.addView(llContainer) - widgetCreatedCallback() - swrLayout.isRefreshing = false - swrLayout.isEnabled = false - } - } - }) - } - - override fun widgetsWasCreated() { - super.widgetsWasCreated() - - val footerContainer = contentContainer.inflate(R.layout.fg_personalization_footer) - - fun initShowHideBtn(parent: ViewGroup) { - val btn = parent.findViewById