diff --git a/.idea/dictionaries/tangem.xml b/.idea/dictionaries/tangem.xml new file mode 100644 index 0000000000..e77bbeb7ed --- /dev/null +++ b/.idea/dictionaries/tangem.xml @@ -0,0 +1,16 @@ + + + + binance + blockchain + cardano + ducatus + ethereum + litecoin + matic + tangem + testnet + tezos + + + \ No newline at end of file diff --git a/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt b/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt index 0028ee31c5..76d9ce0ffa 100644 --- a/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt +++ b/blockchain-demo/src/main/java/com/tangem/blockchain_demo/BlockchainDemoActivity.kt @@ -4,24 +4,23 @@ import android.os.Bundle import android.text.Editable import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import com.tangem.CardManager +import com.tangem.SessionError +import com.tangem.TangemSdk import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.Signer import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain_demo.databinding.ActivityBlockchainDemoBinding import com.tangem.commands.Card +import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent import kotlinx.coroutines.* import java.math.BigDecimal import kotlin.coroutines.CoroutineContext class BlockchainDemoActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager + private lateinit var tangemSdk: TangemSdk private lateinit var signer: TransactionSigner private lateinit var card: Card private lateinit var walletManager: WalletManager @@ -47,8 +46,8 @@ class BlockchainDemoActivity : AppCompatActivity() { val view = binding.root setContentView(view) - cardManager = CardManager.init(this) - signer = Signer(cardManager) + tangemSdk = TangemSdk.init(this) + signer = Signer(tangemSdk) binding.btnScan.setOnClickListener { scan() } @@ -59,27 +58,20 @@ class BlockchainDemoActivity : AppCompatActivity() { private fun scan() { - cardManager.scanCard { taskEvent -> - when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - card = (taskEvent.data as ScanEvent.OnReadEvent).card - walletManager = WalletManagerFactory.makeWalletManager(card)!! + tangemSdk.scanCard { result -> + when (result) { + is CompletionResult.Success -> { + walletManager = WalletManagerFactory.makeWalletManager(result.data)!! getInfo() } - } - } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error !is TaskError.UserCancelled) { - handleError(taskEvent.error.toString()) + is CompletionResult.Failure -> { + if (result.error !is SessionError.UserCancelled) { + handleError(result.error.toString()) } } } } } - } private fun getInfo() { scope.launch { diff --git a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt index 58ae155e27..72a18f08fc 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/bitcoin/BitcoinWalletManager.kt @@ -8,8 +8,8 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet +import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskEvent import java.math.BigDecimal class BitcoinWalletManager( @@ -66,11 +66,11 @@ class BitcoinWalletManager( is Result.Failure -> return SimpleResult.Failure(buildTransactionResult.error) is Result.Success -> { when (val signerResponse = signer.sign(buildTransactionResult.data.toTypedArray(), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey) return networkManager.sendTransaction(transactionToSend.toHexString()) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt index edf2acefbb..86b5f25a51 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/cardano/CardanoWalletManager.kt @@ -8,7 +8,7 @@ import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.common.extensions.encodeBase64NoWrap import com.tangem.blockchain.wallets.CurrencyWallet -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult class CardanoWalletManager( private val cardId: String, @@ -47,11 +47,11 @@ class CardanoWalletManager( val transactionHash = transactionBuilder.buildToSign(transactionData) when (val signerResponse = signer.sign(arrayOf(transactionHash), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature, walletPublicKey) return networkManager.sendTransaction(transactionToSend.encodeBase64NoWrap()) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } 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 c39d9b6c0e..6fe5a862ea 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/WalletManager.kt @@ -4,7 +4,7 @@ import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet import com.tangem.commands.SignResponse -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult interface WalletManager { var wallet: CurrencyWallet @@ -18,7 +18,7 @@ interface TransactionSender { } interface TransactionSigner { - suspend fun sign(hashes: Array, cardId: String): TaskEvent + suspend fun sign(hashes: Array, cardId: String): CompletionResult } interface FeeProvider { diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt b/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt index cdf9edd92e..8451a23b67 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/extensions/Coroutines.kt @@ -1,9 +1,9 @@ package com.tangem.blockchain.common.extensions -import com.tangem.CardManager +import com.tangem.TangemSdk import com.tangem.blockchain.common.TransactionSigner import com.tangem.commands.SignResponse -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine import java.io.IOException @@ -54,10 +54,10 @@ sealed class SimpleResult { data class Failure(val error: Throwable?) : SimpleResult() } -class Signer(private val cardManager: CardManager) : TransactionSigner { - override suspend fun sign(hashes: Array, cardId: String): TaskEvent = +class Signer(private val tangemSdk: TangemSdk) : TransactionSigner { + override suspend fun sign(hashes: Array, cardId: String): CompletionResult = suspendCancellableCoroutine { continuation -> - cardManager.sign(hashes, cardId) { result -> + tangemSdk.sign(hashes, cardId) { result -> if (continuation.isActive) continuation.resume(result) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt index d7c26e526e..3999bdf7a3 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/ethereum/EthereumWalletManager.kt @@ -8,8 +8,8 @@ import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.ethereum.network.EthereumNetworkManager import com.tangem.blockchain.ethereum.network.EthereumResponse import com.tangem.blockchain.wallets.CurrencyWallet +import com.tangem.common.CompletionResult import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskEvent import org.kethereum.DEFAULT_GAS_LIMIT import org.kethereum.crypto.api.ec.ECDSASignature import org.kethereum.crypto.determineRecId @@ -81,11 +81,11 @@ class EthereumWalletManager( val transactionToSign = builder.buildToSign(transactionData, txCount.toBigInteger()) ?: return SimpleResult.Failure(Exception("Not enough data")) when (val signerResponse = signer.sign(transactionToSign.hashes.toTypedArray(), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = builder.buildToSend(signerResponse.data.signature, transactionToSign, walletPublicKey) return networkManager.sendTransaction(String.format("0x%s", transactionToSend.toHexString())) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt index e65278fa35..754cf7fa60 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/stellar/StellarWalletManager.kt @@ -5,7 +5,7 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.extensions.Result import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult import java.math.BigDecimal import java.util.* @@ -58,11 +58,11 @@ class StellarWalletManager( override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val hashes = builder.buildToSign(transactionData, sequence, baseFee.toStroops()) when (val signerResponse = signer.sign(hashes.toTypedArray(), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = builder.buildToSend(signerResponse.data.signature) return networkManager.sendTransaction(transactionToSend) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } diff --git a/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt index bba2e26427..cf6c8206a8 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/xrp/XrpWalletManager.kt @@ -7,7 +7,7 @@ import com.tangem.blockchain.common.extensions.SimpleResult import com.tangem.blockchain.wallets.CurrencyWallet import com.tangem.blockchain.xrp.network.XrpInfoResponse import com.tangem.blockchain.xrp.network.XrpNetworkManager -import com.tangem.tasks.TaskEvent +import com.tangem.common.CompletionResult class XrpWalletManager( private val cardId: String, @@ -68,11 +68,11 @@ class XrpWalletManager( val transactionHash = transactionBuilder.buildToSign(transactionData) when (val signerResponse = signer.sign(arrayOf(transactionHash), cardId)) { - is TaskEvent.Event -> { + is CompletionResult.Success -> { val transactionToSend = transactionBuilder.buildToSend(signerResponse.data.signature) return networkManager.sendTransaction(transactionToSend) } - is TaskEvent.Completion -> return SimpleResult.Failure(signerResponse.error) + is CompletionResult.Failure -> return SimpleResult.Failure(signerResponse.error) } } 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 a476130557..9ce1fd90ac 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,7 @@ package com.tangem.blockchain.common import com.google.common.truth.Truth -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment import com.tangem.blockchain.bitcoin.BitcoinWalletManager import com.tangem.blockchain.cardano.CardanoWalletManager import com.tangem.blockchain.ethereum.EthereumWalletManager @@ -18,7 +18,7 @@ internal class WalletManagerFactoryTest { fun createBitcoinWalletManager() { val data = "0108bb00000000000304200754414e47454d00020102800a322e3432642053444b000341040876bdec26b89bd2159a668b9af3d9fe86370f318717c92b8d6c1186fb3648c32a5f9321998cc2d042901c91d40601e79a641e1cbcebe7a2358be6054e1b6e5d0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034254438640e17ceec48c5be36240c98019f95ad8b6e56acfebe60d11979c6279f715d607d76a860a137da8d109e805753f3f56b0130709f4bbf4cb9974b4c57b8469bf4b873041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b310008040000006407010009020bb8604104752a727e14bba5bd73b6714d72500f61ffd11026ad1196d2e1c54577cbeeac3d11fc68a64700f8d533f4e311964ea8fb3aa26c588295f2133868d69c3e62869362040000005c6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -29,7 +29,7 @@ internal class WalletManagerFactoryTest { fun createEthereumWalletManager() { val data = "0108bb00000000000536200754414e47454d00020102800a322e3432642053444b000341046c8aea0d5a850b0a608acf9a0c453c39ea86131e88bfa78800de3cfb5bf1007aeaa7b9ffc184212255758605c2461be343c0a661d73cabafa4c9c175b3f0e59a0a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b0084034554488640431b6244acfeac479becdff201a7f720a7d70a97edc4e019fb678596baf52dfe9d0e8faf08ceb4443b82d4e66815541f2dc8ec6dd3ff83eb42f06e5eab07f25f3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd26050a736563703235366b3100080400000064070100090205dc60410464dddc3f356744aaecfa07427f9eb996ff537d65f20fb5be3abccf0354352a6b5f8a1942e0f8ddeea3a170eda78d060be8162ad60e94e4e91fbbdf0a7054785562040000005b6304000000090f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -40,7 +40,7 @@ internal class WalletManagerFactoryTest { fun createStellarWalletManager() { val data = "0108bb00000000000379200754414e47454d00020102800a322e3432642053444b0003410487d7bb51b189213e3cedc3fcfa3fc047b3b71b7805b5b215e14639b3a8ebb1952c9dd5ea4354441b6ada4e8b8327674bb102ddae69df55be69643a2c916edf650a04041e76310c618102ffff8a0101820407e30b0d830b54414e47454d2053444b008403584c4d86409a4bc2baf0e5836887da21167cf33458d5249d1a610bced0e31dc053f23729ed24d715912bf89e6804669430dfe396ed83274e0031f6803e2bdb8c041fa993413041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc6020e078212d58b2b9d0edc9c936830d10081cd38b90c31778c56dfb1171027e294e62040000003863040000002c0f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -51,7 +51,7 @@ internal class WalletManagerFactoryTest { fun createCardanoWalletManager() { val data = "0108bb00000000000502200754414e47454d00020102800a322e3432642053444b0003410402c1e39257d60583489da2d67d35d1cc2a1c005cc05c1021f44838edcaf25d5615cad7c9d11c2e23f5efa93e50904d33c88808d0e169060508df840992e31f4d0a04041e76310c658102ffff8a0101820407e30b0d830b54414e47454d2053444b00840743415244414e4f8640f24ef5c8c6eba0ff97560d5b013edb4a452594270db9647bd0a3543df8104dec75731d4db3ebe0fc493f2afee00195e560b51e3c41189b7c61ba7895d6434b9d3041045f16bd1d2eafe463e62a335a09e6b2bbcbd04452526885cb679fc4d27af1bd22f553c7deefb54fd3d4f361d14e6dc3f11b7d4ea183250a60720ebdf9e110cd2605086564323535313900080400000064070100090205dc60208a71161cfdf1e0a85d8e7ff372aa4a01136046292aceb5f9ad7ebdb98d3f60a86204000000646304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) @@ -62,7 +62,7 @@ internal class WalletManagerFactoryTest { fun createXrpWalletManager() { val data = "0108cb21000000002154200b534d4152542043415348000201028006322e31317200034104bdad63848f97c535da53cf8fd300d24fa33f0516d194aa78ec164a06994d00204bae243a424e316c6ec845e02d9b15eafae8c19018a926b0b7435e6e941cdadb0a0400007e210c5a81020028820407e30502830754414e47454d00840358525086400ed8734b877869722c7d0b37ffb154b9fef21c54bf2c6496feb1fb5c1fc28a2ac28e201dde84f27495fa7f08b3ca2be2fb4954bf0fe78af027d6cdc16c3eee923041048196aa4b410ac44a3b9cce18e7be226aea070acc83a9cf67540fac49af25129f6a538a28ad6341358e3c4f9963064f7e365372a651d374e5c23cdd37fd099bf2050a736563703235366b31000804000f4240070100090205dc604104d2b9fb288540d54e5b32ecaf0381cd571f97f6f1ecd036b66bb11aa52ffe9981110d883080e2e255c6b1640586f7765e6faa325d1340f49b56b83d9de56bc7ed6204000f42406304000000000f01009000" val responseApdu = ResponseApdu(data.hexToBytes()) - val card = ReadCommand().deserialize(CardEnvironment(), responseApdu) + val card = ReadCommand().deserialize(SessionEnvironment(), responseApdu) val walletManager = WalletManagerFactory.makeWalletManager(card!!) Truth.assertThat(walletManager) diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt deleted file mode 100644 index c5a1f99e77..0000000000 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ /dev/null @@ -1,330 +0,0 @@ -package com.tangem - -import com.tangem.commands.* -import com.tangem.commands.personalization.DepersonalizeCommand -import com.tangem.commands.personalization.DepersonalizeResponse -import com.tangem.commands.personalization.PersonalizeCommand -import com.tangem.commands.personalization.entities.Acquirer -import com.tangem.commands.personalization.entities.CardConfig -import com.tangem.commands.personalization.entities.Issuer -import com.tangem.commands.personalization.entities.Manufacturer -import com.tangem.common.CardEnvironment -import com.tangem.common.TerminalKeysService -import com.tangem.crypto.CryptoUtils -import com.tangem.tasks.* - -/** - * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. - * - * @property reader is an interface that is responsible for NFC connection and - * transfer of data to and from the Tangem Card. - * Its default implementation, NfcCardReader, is in our tangem-sdk module. - * @property cardManagerDelegate An interface that allows interaction with users and shows relevant UI. - * Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module. - */ -class CardManager( - private val reader: CardReader, - private val cardManagerDelegate: CardManagerDelegate? = null, - private val config: Config = Config() -) { - - private var terminalKeysService: TerminalKeysService? = null - private var isBusy = false - - init { - CryptoUtils.initCrypto() - } - - /** - * To start using any card, you first need to read it using the scanCard() method. - * This method launches an NFC session, and once 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. - * - * It launches on the new thread a [ScanTask] that will send the following events in a callback: - * [ScanEvent.OnReadEvent] after completing [com.tangem.commands.ReadCommand] - * [ScanEvent.OnVerifyEvent] after completing [com.tangem.commands.CheckWalletCommand] - * [TaskEvent.Completion] with an error field null after successful completion of a task or - * [TaskEvent.Completion] with a [TaskError] if some error occurs. - * @param callback is triggered on events during a performance of the task, - * provides data in form of [ScanEvent] subclasses. - */ - fun scanCard(callback: (result: TaskEvent) -> Unit) { - val task = ScanTask() - runTask(task, callback = callback) - } - - /** - * This method allows you to sign one or multiple hashes. - * Simultaneous signing of array of hashes in a single [SignCommand] is required to support - * Bitcoin-type multi-input blockchains (UTXO). - * The [SignCommand] will return a corresponding array of signatures. - * - * This method launches on the new thread [SignCommand] that will send the following events in a callback: - * [SignResponse] after completing [SignCommand] - * [TaskEvent.Completion] with an error field null after successful completion of a task or - * [TaskEvent.Completion] with a [TaskError] if some error occurs. - * Please note that Tangem cards usually protect the signing with a security delay - * that may last up to 90 seconds, depending on a card. - * It is for [CardManagerDelegate] to notify users of security delay. - * @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length. - * @param cardId CID, Unique Tangem card ID number - * @param callback is triggered on the completion of the [SignCommand], - * provides card response in the form of [SignResponse]. - */ - fun sign(hashes: Array, cardId: String, - callback: (result: TaskEvent) -> Unit) { - val signCommand = SignCommand(hashes) - val task = SingleCommandTask(signCommand) - runTask(task, cardId, callback) - } - - /** - * 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], - * provides card response in the form of [ReadIssuerDataResponse]. - */ - fun readIssuerData(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val task = ReadIssuerDataTask(config.issuerPublicKey) - runTask(task, cardId, callback) - } - - /** - * This task 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 [ReadIssuerExtraDataTask], - * provides card response in the form of [ReadIssuerExtraDataResponse]. - */ - fun readIssuerExtraData(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val task = ReadIssuerExtraDataTask(config.issuerPublicKey) - runTask(task, cardId, callback) - } - - /** - * 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], - * provides card response in the form of [WriteIssuerDataResponse]. - */ - fun writeIssuerData(cardId: String, - issuerData: ByteArray, - issuerDataSignature: ByteArray, - issuerDataCounter: Int? = null, - callback: (result: TaskEvent) -> Unit) { - val task = WriteIssuerDataTask( - issuerData, - issuerDataSignature, - issuerDataCounter, - config.issuerPublicKey - ) - runTask(task, cardId, callback) - } - - /** - * This task 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 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 [WriteIssuerDataCommand], - * provides card response in the form of [WriteIssuerDataResponse]. - */ - fun writeIssuerExtraData(cardId: String, - issuerData: ByteArray, - startingSignature: ByteArray, - finalizingSignature: ByteArray, - issuerDataCounter: Int? = null, - callback: (result: TaskEvent) -> Unit) { - val task = WriteIssuerExtraDataTask( - issuerData, - startingSignature, finalizingSignature, - config.issuerPublicKey, - issuerDataCounter - ) - runTask(task, cardId, callback) - } - - /** - * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. - * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing - * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. - * For example, this fields may contain blockchain nonce value. - * - * Writing of User_Counter and User_Data protected only by PIN1. - * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. - */ - fun writeUserData( - cardId: String, - userData: ByteArray? = null, - userProtectedData: ByteArray? = null, - userCounter: Int? = null, - userProtectedCounter: Int? = null, - callback: (result: TaskEvent) -> Unit - ) { - val writeUserDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) - val task = SingleCommandTask(writeUserDataCommand) - runTask(task, cardId, callback) - } - - /** - * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and - * User_Protected_Counter fields. - * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing - * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. - * For example, this fields may contain blockchain nonce value. - */ - fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(ReadUserDataCommand()) - runTask(task, cardId, callback) - } - - /** - * This command will create a new wallet on the card having ‘Empty’ state. - * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card. - * App will need to obtain Wallet_PublicKey from the response of [CreateWalletCommand] or [ReadCommand] - * and then transform it into an address of corresponding blockchain wallet - * according to a specific blockchain algorithm. - * WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand]. - * RemainingSignature is set to MaxSignatures. - * @param cardId CID, Unique Tangem card ID number. - */ - fun createWallet(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val createWalletCommand = CreateWalletCommand() - val task = SingleCommandTask(createWalletCommand) - runTask(task, cardId, callback) - } - - /** - * This command deletes all wallet data. If Is_Reusable flag is enabled during personalization, - - * If Is_Reusable flag is disabled, the card switches to ‘Purged’ state. - * ‘Purged’ state is final, it makes the card useless. - * @param cardId CID, Unique Tangem card ID number. - */ - fun purgeWallet(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val purgeWalletCommand = PurgeWalletCommand() - val task = SingleCommandTask(purgeWalletCommand) - runTask(task, cardId, callback) - } - - /** - * Command available on SDK cards only - * - * This command resets card to initial state, - * erasing all data written during personalization and usage. - * @param cardId CID, Unique Tangem card ID number. - */ - fun depersonalize(cardId: String, - callback: (result: TaskEvent) -> Unit) { - val depersonalizeCommand = DepersonalizeCommand() - val task = SingleCommandTask(depersonalizeCommand) - runTask(task, cardId, callback) - } - - /** - * Command available on SDK cards only - * - * Personalization is an initialization procedure, required before starting using a card. - * During this procedure a card setting is set up. - * During this procedure all data exchange is encrypted. - * @param config is a configuration file with all the card settings that are written on the card - * during personalization. - * @param issuer Issuer is a third-party team or company wishing to use Tangem cards. - * @param manufacturer Tangem Card Manufacturer. - * @param acquirer Acquirer is a trusted third-party company that operates proprietary - * (non-EMV) POS terminal infrastructure and transaction processing back-end. - */ - fun personalize(config: CardConfig, - issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null, - callback: (result: TaskEvent) -> Unit) { - val personalizationCommand = PersonalizeCommand(config, issuer, manufacturer, acquirer) - val task = SingleCommandTask(personalizationCommand) - task.performPreflightRead = false - runTask(task, callback = callback) - } - - /** - - */ - fun runTask(task: Task, cardId: String? = null, callback: (result: TaskEvent) -> Unit) { - if (isBusy) { - callback(TaskEvent.Completion(TaskError.Busy())) - return - } - - val environment = prepareCardEnvironment(cardId) - isBusy = true - - task.reader = reader - task.delegate = cardManagerDelegate - - Thread().run { - task.run(environment) { taskEvent -> - if (taskEvent is TaskEvent.Completion) isBusy = false - callback(taskEvent) - } - } - } - - /** - - */ - fun runCommand(command: CommandSerializer, - cardId: String? = null, - callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(command) - runTask(task, cardId, callback) - } - - /** - * Allows to set a particular [TerminalKeysService] to retrieve terminal keys. - * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. - */ - fun setTerminalKeysService(terminalKeysService: TerminalKeysService) { - this.terminalKeysService = terminalKeysService - } - - private fun prepareCardEnvironment(cardId: String?): CardEnvironment { - val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null - return CardEnvironment( - cardId = cardId, - terminalKeys = terminalKeys - ) - } - - companion object -} \ 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 new file mode 100644 index 0000000000..9977e9b17f --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/CardSession.kt @@ -0,0 +1,221 @@ +package com.tangem + +import com.tangem.commands.Card +import com.tangem.commands.CommandResponse +import com.tangem.commands.OpenSessionCommand +import com.tangem.commands.ReadCommand +import com.tangem.common.CompletionResult +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.extensions.calculateSha256 +import com.tangem.crypto.EncryptionHelper +import com.tangem.crypto.FastEncryptionHelper +import com.tangem.crypto.StrongEncryptionHelper +import com.tangem.crypto.pbkdf2Hash + +/** + * Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession] + */ +interface CardSessionRunnable { + + /** + * The starting point for custom business logic. + * Implement this interface and use [TangemSdk.startSession] to run. + * @param session run commands in this [CardSession]. + * @param callback trigger the callback to complete the task. + */ + fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) +} + +/** + * Allows interaction with Tangem cards. Should be opened before sending commands. + * + * @property environment + * @property reader is an interface that is responsible for NFC connection and + * transfer of data to and from the Tangem Card. + * @property viewDelegate is an interface that allows interaction with users and shows relevant UI. + * @property cardId ID, Unique Tangem card ID number. If not null, the SDK will check that you the card + * with which you tapped a phone has this [cardId] and SDK will return + * the [SessionError.WrongCard] otherwise. + * @property initialMessage A custom description that will be shown at the beginning of the NFC session. + * If null, a default header and text body will be used. + */ +class CardSession( + val environment: SessionEnvironment, + private val reader: CardReader, + val viewDelegate: SessionViewDelegate, + private var cardId: String? = null, + private val initialMessage: Message? = null +) { + + /** + * True if some operation is still in progress. + */ + private var isBusy = false + + /** + * This metod starts a card session, performs preflight [ReadCommand], + * invokes [CardSessionRunnable.run] and closes the session. + * @param runnable [CardSessionRunnable] that will be performed in the session. + * @param callback will be triggered with a [CompletionResult] of a session. + */ + fun , R : CommandResponse> startWithRunnable( + runnable: T, callback: (result: CompletionResult) -> Unit) { + + start { session, error -> + if (error != null) { + callback(CompletionResult.Failure(error)) + return@start + } + if (runnable is ReadCommand) { + callback(CompletionResult.Success(environment.card as R)) + return@start + } + + runnable.run(this) { result -> + stop() + callback(result) + } + } + } + + /** + * Starts a card session and performs preflight [ReadCommand]. + * @param callback: callback with the card session. Can contain [SessionError] if something goes wrong. + */ + fun start(callback: (session: CardSession, error: SessionError?) -> Unit) { + try { + startSession() + } catch (error: SessionError) { + callback(this, error) + } + + preflightRead() { result -> + when (result) { + is CompletionResult.Failure -> { + callback(this, result.error) + stopWithError(result.error) + } + is CompletionResult.Success -> { + callback(this, null) + + } + } + } + } + + private fun startSession() { + if (isBusy) throw SessionError.Busy() + isBusy = true + viewDelegate.onNfcSessionStarted(cardId, initialMessage) + reader.openSession() + } + + private fun preflightRead(callback: (result: CompletionResult) -> Unit) { + val readCommand = ReadCommand() + readCommand.run(this) { result -> + when (result) { + is CompletionResult.Failure -> { + tryHandleError(result.error) { handleErrorResult -> + when (handleErrorResult) { + is CompletionResult.Success -> preflightRead(callback) + is CompletionResult.Failure -> { + stopWithError(result.error) + callback(CompletionResult.Failure(result.error)) + } + } + } + } + is CompletionResult.Success -> { + val receivedCardId = result.data.cardId + if (cardId != null && receivedCardId != cardId) { + stopWithError(SessionError.WrongCard()) + callback(CompletionResult.Failure(SessionError.WrongCard())) + return@run + } + environment.card = result.data + cardId = receivedCardId + callback(CompletionResult.Success(result.data)) + } + } + } + } + + /** + * Stops the current session with the text message. + * @param message If null, the default message will be shown. + */ + private fun stop(message: Message? = null) { + reader.closeSession() + viewDelegate.onNfcSessionCompleted(message) + isBusy = false + } + + /** + * Stops the current session on error. + * @param error An error that will be shown. + */ + private fun stopWithError(error: Exception) { + reader.closeSession() + isBusy = false + + val errorMessage = if (error is SessionError) { + "${error::class.simpleName}: ${error.code}" + } else { + error.localizedMessage + } + if (error !is SessionError.UserCancelled) viewDelegate.onError(errorMessage) + } + + fun send(apdu: CommandApdu, callback: (result: CompletionResult) -> Unit) { + reader.transceiveApdu(apdu, callback) + } + + private fun tryHandleError( + error: SessionError, callback: (result: CompletionResult) -> Unit) { + + when (error) { + is SessionError.NeedEncryption -> { + when (environment.encryptionMode) { + EncryptionMode.NONE -> { + environment.encryptionKey = null + environment.encryptionMode = EncryptionMode.FAST + } + EncryptionMode.FAST -> { + environment.encryptionKey = null + environment.encryptionMode = EncryptionMode.STRONG + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(SessionError.NeedEncryption())) + } + } + return establishEncryption(callback) + } + else -> callback(CompletionResult.Failure(SessionError.UnknownError())) + } + } + + private fun establishEncryption(callback: (result: CompletionResult) -> Unit) { + val encryptionHelper: EncryptionHelper = + if (environment.encryptionMode == EncryptionMode.STRONG) { + StrongEncryptionHelper() + } else { + FastEncryptionHelper() + } + val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA) + openSesssionCommand.run(this) { result -> + when (result) { + is CompletionResult.Success -> { + val uid = result.data.uid + val protocolKey = environment.pin1.calculateSha256().pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + environment.encryptionKey = sessionKey + callback(CompletionResult.Success(true)) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ 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 index 2d1193e176..d99c57fcf9 100644 --- a/tangem-core/src/main/java/com/tangem/Config.kt +++ b/tangem-core/src/main/java/com/tangem/Config.kt @@ -1,6 +1,33 @@ package com.tangem class Config( + /** + * Enables or disables Linked Terminal feature. + + App can optionally generate ECDSA key pair Terminal_PrivateKey / Terminal_PublicKey. + And then submit Terminal_PublicKey to the card in any SIGN command. + Once SIGN is successfully executed by COS (Card Operation System), + including PIN2 verification and/or completion of security delay, the submitted + Terminal_PublicKey key is stored by COS. After that, the App instance is deemed trusted + by COS and COS will allow skipping security delay for subsequent SIGN operations + thus improving convenience without sacrificing security. + + In order to skip security delay, App should use Terminal_PrivateKey to compute the signature + of the data being submitted to SIGN command for signing and transmit this signature in + Terminal_Transaction_Signature parameter in the same SIGN command. COS will verify + the correctness of Terminal_Transaction_Signature using previously stored Terminal_PublicKey + and, if correct, will skip security delay for the current SIGN operation. + */ val linkedTerminal: Boolean = true, - val issuerPublicKey: ByteArray? = null + + /** + * If not null, it will be used to validate Issuer data and issuer extra data. + * If null, issuerPublicKey from current card will be used. + */ + val issuerPublicKey: ByteArray? = null, + + /** + * Level of encryption used in communication with a Tangem Card. + */ + val encryptionMode: EncryptionMode = EncryptionMode.NONE ) \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt similarity index 67% rename from tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt rename to tangem-core/src/main/java/com/tangem/SessionEnvironment.kt index 09cfd736b4..81a541184c 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt @@ -1,17 +1,22 @@ -package com.tangem.common +package com.tangem +import com.tangem.commands.Card import com.tangem.commands.EllipticCurve +import com.tangem.common.extensions.calculateSha256 import com.tangem.crypto.CryptoUtils.generatePublicKey /** * Contains data relating to a Tangem card. It is used in constructing all the commands, - * and commands can return modified [CardEnvironment]. + * and commands can return modified [SessionEnvironment]. + * + * @property card Current card, read by preflight [com.tangem.commands.ReadCommand]. + * @property terminalKeys generated terminal keys used in Linked Terminal feature. */ -data class CardEnvironment( +data class SessionEnvironment( val pin1: String = DEFAULT_PIN, val pin2: String = DEFAULT_PIN2, - val cardId: String? = null, + var card: Card? = null, val terminalKeys: KeyPair? = null, var encryptionMode: EncryptionMode = EncryptionMode.NONE, var encryptionKey: ByteArray? = null, @@ -24,6 +29,9 @@ data class CardEnvironment( } } +/** + * All possible encryption modes. + */ enum class EncryptionMode(val code: Byte) { NONE(0x0), FAST(0x1), diff --git a/tangem-core/src/main/java/com/tangem/SessionError.kt b/tangem-core/src/main/java/com/tangem/SessionError.kt new file mode 100644 index 0000000000..07209b6a3f --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/SessionError.kt @@ -0,0 +1,139 @@ +package com.tangem + +import com.tangem.commands.Card +import com.tangem.commands.ReadCommand +import com.tangem.common.apdu.StatusWord +import com.tangem.tasks.ScanTask + +/** + * An error class that represent typical errors that may occur when performing Tangem SDK tasks. + * Errors are propagated back to the caller in callbacks. + */ +sealed class SessionError(val code: Int) : Exception() { + + //Errors in serializing APDU + /** + * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] + * (this error is a wrapper around internal [com.tangem.common.tlv.TlvDecoder] errors). + */ + class SerializeCommandError : SessionError(1000) + + class DeserializeApduFailed : SessionError(1001) + class EncodingFailedTypeMismatch : SessionError(1002) + class EncodingFailed : SessionError(1003) + + class DecodingFailedMissingTag : SessionError(1004) + class DecodingFailedTypeMismatch : SessionError(1005) + class DecodingFailed : SessionError(1005) + + /** + * This error is returned when unknown [StatusWord] is received from a card. + */ + class UnknownStatus : SessionError(2001) + + /** + * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. + * The card sends this status in case of internal card error. + */ + class ErrorProcessingCommand : SessionError(2002) + + /** + * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] + * is executed before performing other commands. + */ + class MissingPreflightRead : SessionError(2003) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidState]. + * The card sends this status when command can not be executed in the current state of a card. + */ + class InvalidState : SessionError(2004) + + /** + * This error is returned when a card's reply is [StatusWord.InsNotSupported]. + * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. + */ + class InsNotSupported : SessionError(2005) + + /** + * This error is returned when a card's reply is [StatusWord.InvalidParams]. + * The card sends this status when there are wrong or not sufficient parameters in TLV request, + * or wrong PIN1/PIN2. + * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], + * mapping or serialization errors. + */ + class InvalidParams : SessionError(2006) + + /** + * This error is returned when a card's reply is [StatusWord.NeedEncryption] + * and the encryption was not established by TangemSdk. + */ + class NeedEncryption : SessionError(2007) + + //Scan errors + /** + * This error is returned when a [Task] checks unsuccessfully either + * a card's ability to sign with its private key, or the validity of issuer data. + */ + class VerificationFailed : SessionError(3000) + + /** + * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. + */ + class CardError : SessionError(3001) + + /** + * This error is returned when a [Task] expects a user to use a particular card, + * and a user tries to use a different card. + */ + class WrongCard : SessionError(3002) + + /** + * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. + * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. + */ + class TooMuchHashesInOneTransaction : SessionError(3003) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives only empty hashes for signature. + */ + class EmptyHashes : SessionError(3004) + + /** + * This error is returned when a [com.tangem.commands.SignCommand] + * receives hashes of different lengths for signature. + */ + class HashSizeMustBeEqual : SessionError(3005) + + /** + * This error is returned when [com.tangem.TangemSdk] was called with a new [Task], + * while a previous [Task] is still in progress. + */ + class Busy : SessionError(4000) + + /** + * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. + */ + class UserCancelled : SessionError(4001) + + //NFC errors + class NfcReaderError : SessionError(5002) + + /** + * This error is returned when Android NFC reader loses a tag + * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. + */ + class TagLost : SessionError(5003) + + class UnknownError : SessionError(6000) + + //Specific Command Errors + /** + * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter + * (when the card's requires it), but the counter is missing. + */ + class MissingCounter : SessionError(7001) + + class MissingIssuerPubicKey : SessionError(7002) +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt similarity index 78% rename from tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt rename to tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt index 5fce77186d..c372b1730c 100644 --- a/tangem-core/src/main/java/com/tangem/CardManagerDelegate.kt +++ b/tangem-core/src/main/java/com/tangem/SessionViewDelegate.kt @@ -1,19 +1,18 @@ package com.tangem import com.tangem.common.CompletionResult -import com.tangem.tasks.TaskError /** * Allows interaction with users and shows visual elements. * * Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module. */ -interface CardManagerDelegate { +interface SessionViewDelegate { /** * It is called when user is expected to scan a Tangem Card with an Android device. */ - fun onNfcSessionStarted(cardId: String?) + fun onNfcSessionStarted(cardId: String?, message: Message? = null) /** * It is called when security delay is triggered by the card. @@ -36,16 +35,21 @@ interface CardManagerDelegate { /** * It is called when NFC session was completed and a user can take the card away from the Android device. */ - fun onNfcSessionCompleted() + fun onNfcSessionCompleted(message: Message? = null) /** * It is called when some error occur during NFC session. */ - fun onError(error: TaskError) + fun onError(errorMessage: String) /** * It is called when a user is expected to enter pin code. */ fun onPinRequested(callback: (result: CompletionResult) -> Unit) -} \ No newline at end of file +} + +/** + * 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 new file mode 100644 index 0000000000..145efc088e --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/TangemSdk.kt @@ -0,0 +1,375 @@ +package com.tangem + +import com.tangem.commands.* +import com.tangem.commands.personalization.DepersonalizeCommand +import com.tangem.commands.personalization.DepersonalizeResponse +import com.tangem.commands.personalization.PersonalizeCommand +import com.tangem.commands.personalization.entities.Acquirer +import com.tangem.commands.personalization.entities.CardConfig +import com.tangem.commands.personalization.entities.Issuer +import com.tangem.commands.personalization.entities.Manufacturer +import com.tangem.common.CompletionResult +import com.tangem.common.TerminalKeysService +import com.tangem.crypto.CryptoUtils +import com.tangem.tasks.CreateWalletTask +import com.tangem.tasks.ScanTask + +/** + * The main interface of Tangem SDK that allows your app to communicate with Tangem cards. + * + * @property reader is an interface that is responsible for NFC connection and + * transfer of data to and from the Tangem Card. + * Its default implementation, NfcCardReader, is in our tangem-sdk module. + * @property viewDelegate An interface that allows interaction with users and shows relevant UI. + * Its default implementation, DefaultCardSessionViewDelegate, is in our tangem-sdk module. + * @property config allows to change a number of parameters for communication with Tangem cards. + * Do not change the default values unless you know what you are doing. + */ +class TangemSdk( + private val reader: CardReader, + private val viewDelegate: SessionViewDelegate, + var config: Config = Config() +) { + + private var terminalKeysService: TerminalKeysService? = null + + init { + CryptoUtils.initCrypto() + } + + /** + * This method launches a [ScanTask] on a new thread. + * + * To start using any card, you first need to read it using the scanCard() method. + * This method launches an NFC session, and once 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 [SessionError] in case of an error. + */ + fun scanCard(initialMessage: Message? = null, callback: (result: CompletionResult) -> Unit) { + startSession(ScanTask(), null, initialMessage, callback) + } + + /** + * This method launches a [SignCommand] on a new thread. + * + * It allows you to sign one or multiple hashes. + * Simultaneous signing of array of hashes in a single [SignCommand] is required to support + * Bitcoin-type multi-input blockchains (UTXO). + * The [SignCommand] will return a corresponding array of signatures. + * + * Please note that Tangem cards usually protect the signing with a security delay + * that may last up to 90 seconds, depending on a card. + * It is for [SessionViewDelegate] to notify users of security delay. + * + * @param hashes Array of transaction hashes. It can be from one or up to ten hashes of the same length. + * @param cardId CID, Unique Tangem card ID number + * @param callback is triggered on the completion of the [SignCommand] and provides card response + * in the form of [SignResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun sign(hashes: Array, cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(SignCommand(hashes), cardId, initialMessage, callback) + } + + /** + * This method launches a [ReadIssuerDataCommand] on a new thread. + + * This command returns 512-byte Issuer Data field and its 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 [SessionError] in case of an error. + */ + fun readIssuerData(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(ReadIssuerDataCommand(config.issuerPublicKey), cardId, initialMessage, callback) + } + + /** + * This method launches a [ReadIssuerExtraDataCommand] on a new thread. + * + * This command retrieves Issuer Extra Data field and its 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 [SessionError] in case of an error. + */ + fun readIssuerExtraData(cardId: String, + callback: (result: CompletionResult) -> Unit) { + startSession(ReadIssuerExtraDataCommand(config.issuerPublicKey), cardId, null, callback) + } + + /** + * This method launches a [WriteIssuerDataCommand] on a new thread. + * + * This command writes 512-byte Issuer Data field and its 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 [SessionError] in case of an error. + */ + fun writeIssuerData(cardId: String, + issuerData: ByteArray, + issuerDataSignature: ByteArray, + issuerDataCounter: Int? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = WriteIssuerDataCommand( + issuerData, + issuerDataSignature, + issuerDataCounter, + config.issuerPublicKey + ) + startSession(command, cardId, initialMessage, callback) + } + + /** + * This method launches a [WriteIssuerExtraDataCommand] on a new thread. + * + * This command writes Issuer Extra Data field and its 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 [SessionError] in case of an error. + */ + fun writeIssuerExtraData(cardId: String, + issuerData: ByteArray, + startingSignature: ByteArray, + finalizingSignature: ByteArray, + issuerDataCounter: Int? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = WriteIssuerExtraDataCommand( + issuerData, + startingSignature, finalizingSignature, + issuerDataCounter, + config.issuerPublicKey + ) + startSession(command, cardId, initialMessage, callback) + } + + /** + * This method launches a [WriteUserDataCommand] on a new thread. + * + * This command writes some of UserData, UserProtectedData, UserCounter and UserProtectedCounter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * Writing of UserCounter and UserData is protected only by PIN1. + * UserProtectedCounter and UserProtectedData need additionally PIN2 to confirmation. + */ + fun writeUserData( + cardId: String, + userData: ByteArray? = null, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit + ) { + val command = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) + startSession(command, cardId, initialMessage, callback) + } + + /** + * This method launches a [ReadUserDataCommand] on a new thread. + * + * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and + * User_Protected_Counter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [ReadUserDataCommand] and provides + * card response in the form of [ReadUserDataResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun readUserData(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(ReadUserDataCommand(), cardId, initialMessage, callback) + } + + /** + * This method launches a [CreateWalletTask] on a new thread. + * + * This this will create a new wallet on the card having ‘Empty’ state with [CreateWalletCommand] + * and will check the success of the operation by performing [CheckWalletCommand]. + * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card. + * App will need to obtain Wallet_PublicKey from the [CreateWalletResponse] or from the + * response of [ReadCommand] and then transform it into an address of corresponding + * blockchain wallet according to a specific blockchain algorithm. + * WalletPrivateKey is never revealed by the card and will be used by [SignCommand] and [CheckWalletCommand]. + * RemainingSignature is set to MaxSignatures. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [CreateWalletTask] and provides + * card response in the form of [CreateWalletResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun createWallet(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(CreateWalletTask(), cardId, initialMessage, callback) + } + + /** + * This method launches a [PurgeWalletCommand] on a new thread. + * + * This command deletes all wallet data. If IsReusable flag is enabled during personalization, + + * or [CreateWalletCommand]. + * If IsReusable flag is disabled, the card switches to ‘Purged’ state. + * ‘Purged’ state is final, it makes the card useless. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [PurgeWalletCommand] and provides + * card response in the form of [PurgeWalletResponse] if the task was performed successfully + * or [SessionError] in case of an error. + */ + fun purgeWallet(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(PurgeWalletCommand(), cardId, initialMessage, callback) + } + + /** + * Command available on SDK cards only + * + * This method launches a [DepersonalizeCommand] on a new thread. + * + * This command resets card to initial state, + * erasing all data written during personalization and usage. + * + * @param cardId CID, Unique Tangem card ID number. + * @param callback is triggered on the completion of the [DepersonalizeCommand] and provides + * card response in the form of [DepersonalizeResponse] if the task was performed successfully + * or [SessionError] in case of an error. + * */ + fun depersonalize(cardId: String, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + startSession(DepersonalizeCommand(), cardId, initialMessage, callback) + } + + /** + * Command available on SDK cards only + * + * This method launches a [PersonalizeCommand] on a new thread. + * + * Personalization is an initialization procedure, required before starting using a card. + * During this procedure a card setting is set up. + * During this procedure all data exchange is encrypted. + * @param config is a configuration file with all the card settings that are written on the card + * during personalization. + * @param issuer Issuer is a third-party team or company wishing to use Tangem cards. + * @param manufacturer Tangem Card Manufacturer. + * @param acquirer Acquirer is a trusted third-party company that operates proprietary + * (non-EMV) POS terminal infrastructure and transaction processing back-end. + * @param callback is triggered on the completion of the [PersonalizeCommand] and provides + * card response in the form of [Card] if the command was performed successfully + * or [SessionError] in case of an error. + */ + fun personalize(config: CardConfig, + issuer: Issuer, manufacturer: Manufacturer, acquirer: Acquirer? = null, + initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val command = PersonalizeCommand(config, issuer, manufacturer, acquirer) + startSession(command, null, initialMessage, callback) + } + + /** + * Allows running a custom bunch of commands in one [CardSession] by creating a custom task. + * [TangemSdk] will start a card session, perform preflight [ReadCommand], + * invoke [CardSessionRunnable.run] and close the session. + * You can find the current card in the [CardSession.environment]. + + * @runnable: A custom task, adopting [CardSessionRunnable] protocol + * @cardId: CID, Unique Tangem card ID number. If not null, the SDK will check that you the card + * with which you tapped a phone has this [cardId] and SDK will return + * the [SessionError.WrongCard] otherwise. + * @initialMessage: A custom description that shows at the beginning of the NFC session. + * If null, default message will be used. + * @callback: Standard [TangemSdk] callback. + */ + fun startSession( + runnable: CardSessionRunnable, cardId: String? = null, initialMessage: Message? = null, + callback: (result: CompletionResult) -> Unit) { + val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + Thread().run { cardSession.startWithRunnable(runnable, callback) } + } + + /** + * Allows running a custom bunch of commands in one [CardSession] with lightweight closure syntax. + * Tangem SDK will start a card sesion and perform preflight [ReadCommand]. + + * @cardId: CID, Unique Tangem card ID number. If not null, the SDK will check that you the card + * with which you tapped a phone has this [cardId] and SDK will return + * the [SessionError.WrongCard] otherwise. + * @initialMessage: A custom description that shows at the beginning of the NFC session. + * If null, default message will be used. + * @callback: At first, you should check that the [SessionError] is not null, + * then you can use the [CardSession] to interact with a card. + */ + fun startSession( + cardId: String? = null, initialMessage: Message? = null, + callback: (session: CardSession, error: SessionError?) -> Unit) { + val cardSession = CardSession(buildEnvironment(), reader, viewDelegate, cardId, initialMessage) + Thread().run { cardSession.start(callback) } + } + + /** + * Allows to set a particular [TerminalKeysService] to retrieve terminal keys. + * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. + */ + fun setTerminalKeysService(terminalKeysService: TerminalKeysService) { + this.terminalKeysService = terminalKeysService + } + + private fun buildEnvironment(): SessionEnvironment { + val terminalKeys = if (config.linkedTerminal) terminalKeysService?.getKeys() else null + return SessionEnvironment( + terminalKeys = terminalKeys + ) + } + + companion object +} \ 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 28f0cd2d43..4d9c3a53ff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -1,14 +1,16 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.CryptoUtils -import com.tangem.tasks.TaskError /** * Deserialized response from the Tangem card after [CheckWalletCommand]. @@ -40,33 +42,54 @@ class CheckWalletResponse( * @property cardId Unique Tangem card ID number * @property challenge Random challenge generated by application */ -class CheckWalletCommand : CommandSerializer() { +class CheckWalletCommand( + private val curve: EllipticCurve, private val publicKey: ByteArray +) : Command() { - val challenge = CryptoUtils.generateRandomBytes(16) + private val challenge = CryptoUtils.generateRandomBytes(16) - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + transceive(session) { result -> + when (result) { + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + is CompletionResult.Success -> { + val verified = result.data.verify( + curve, + publicKey, + challenge + ) + if (verified) { + callback(CompletionResult.Success(result.data)) + } else { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } + } + } + } + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) return CommandApdu( Instruction.CheckWallet, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CheckWalletResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - CheckWalletResponse( - cardId = mapper.map(TlvTag.CardId), - salt = mapper.map(TlvTag.Salt), - walletSignature = mapper.map(TlvTag.Signature) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return CheckWalletResponse( + cardId = decoder.decode(TlvTag.CardId), + salt = decoder.decode(TlvTag.Salt), + walletSignature = decoder.decode(TlvTag.Signature) + ) } } \ 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 new file mode 100644 index 0000000000..a44332d04f --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt @@ -0,0 +1,116 @@ +package com.tangem.commands + +import com.tangem.* +import com.tangem.common.CompletionResult +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.apdu.StatusWord +import com.tangem.common.apdu.toSessionError +import com.tangem.common.extensions.toInt +import com.tangem.common.tlv.TlvTag + +/** + * Basic interface for a parsed response from [Command]. + */ +interface CommandResponse + +/** + * Basic class for Tangem card commands + */ +abstract class Command : CardSessionRunnable { + + /** + * 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) { + transceive(session, callback) + } + + 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 response = deserialize(session.environment, result.data) + callback(CompletionResult.Success(response)) + } + } + } + } catch (error: SessionError) { + callback(CompletionResult.Failure(error)) + } + } + + private fun transceiveApdu(apdu: CommandApdu, session: CardSession, callback: (result: CompletionResult) -> Unit) { + session.send(apdu) { result -> + + when (result) { + is CompletionResult.Success -> { + val responseApdu = result.data + when (responseApdu.statusWord) { + StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged + -> callback(CompletionResult.Success(responseApdu)) + StatusWord.NeedPause -> { + // NeedPause is returned from the card whenever security delay is triggered. + val remainingTime = deserializeSecurityDelay(responseApdu, session.environment) + if (remainingTime != null) { + session.viewDelegate.onSecurityDelay( + remainingTime, + session.environment.card?.pauseBeforePin2 ?: 0) + } + Log.i(this::class.simpleName!!, "Nfc command ${this::class.simpleName!!} " + + "triggered security delay of $remainingTime milliseconds") + transceiveApdu(apdu, session, callback) + } + else -> { + val error = responseApdu.statusWord.toSessionError() + if (error != null && !tryHandleError(error)) { + callback(CompletionResult.Failure(error)) + } else { + callback(CompletionResult.Failure(SessionError.UnknownError())) + } + } + } + } + is CompletionResult.Failure -> + if (result.error == SessionError.TagLost()) { + session.viewDelegate.onTagLost() + } else { + callback(CompletionResult.Failure(result.error)) + } + } + } + } + + /** + * Helper method to parse security delay information received from a card. + * + * @return Remaining security delay in milliseconds. + */ + private fun deserializeSecurityDelay(responseApdu: ResponseApdu, environment: SessionEnvironment): Int? { + val tlv = responseApdu.getTlvData() + return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() + } + + private fun tryHandleError(error: SessionError): Boolean { + return false + } + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt deleted file mode 100644 index 134474fb23..0000000000 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.commands - -import com.tangem.common.CardEnvironment -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.toInt -import com.tangem.common.tlv.TlvTag - -/** - * Simple interface for responses received after sending commands to Tangem cards. - */ -interface CommandResponse - -/** - * Abstract class for all Tangem card commands. - */ -abstract class CommandSerializer { - - /** - * Serializes data into a [List] of [com.tangem.common.tlv.Tlv], - * then creates [CommandApdu] with this data. - * - * @return Command data that can be converted to raw bytes with a method [CommandApdu.toBytes]. - */ - abstract fun serialize(cardEnvironment: CardEnvironment): CommandApdu - - /** - * Deserializes data, received from a card and stored in [ResponseApdu], - * into a [List] of [com.tangem.common.tlv.Tlv]. Then this method maps it into a [CommandResponse]. - * - * @return Card response, converted to a [CommandResponse] of a type [T]. - */ - abstract fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): T? - - /** - * Helper method to parse security delay information received from a card. - * - * @return Remaining security delay in milliseconds. - */ - fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? { - val tlv = responseApdu.getTlvData() - return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() - } -} \ 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 18b20f1394..089a134702 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class CreateWalletResponse( /** @@ -35,32 +35,29 @@ class CreateWalletResponse( * * @property cardId CID, Unique Tangem card ID number. */ -class CreateWalletCommand : CommandSerializer() { +class CreateWalletCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) - tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.Cvc, environment.cvc) return CommandApdu( Instruction.CreateWallet, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): CreateWalletResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - CreateWalletResponse( - cardId = mapper.map(TlvTag.CardId), - status = mapper.map(TlvTag.Status), - walletPublicKey = mapper.map(TlvTag.WalletPublicKey) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return CreateWalletResponse( + cardId = decoder.decode(TlvTag.CardId), + status = decoder.decode(TlvTag.Status), + walletPublicKey = decoder.decode(TlvTag.WalletPublicKey) + ) } } \ 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 85d3ddf866..7484f9d48b 100644 --- a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class OpenSessionResponse( val sessionKeyB: ByteArray, @@ -20,27 +20,24 @@ class OpenSessionResponse( * to encrypt and decrypt commands’ payload. */ -class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { +class OpenSessionCommand(private val sessionKeyA: ByteArray) : Command() { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) return CommandApdu( Instruction.OpenSession, tlvBuilder.serialize(), - encryptionMode = cardEnvironment.encryptionMode + encryptionMode = environment.encryptionMode ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): OpenSessionResponse { + val tlvData = apdu.getTlvData() + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - OpenSessionResponse( - sessionKeyB = mapper.map(TlvTag.SessionKeyB), - uid = mapper.map(TlvTag.Uid) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return OpenSessionResponse( + sessionKeyB = decoder.decode(TlvTag.SessionKeyB), + uid = decoder.decode(TlvTag.Uid) + ) } } \ 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 4eb379e521..057ae8d084 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class PurgeWalletResponse( /** @@ -27,29 +27,26 @@ class PurgeWalletResponse( * ‘Purged’ state is final, it makes the card useless. * @property cardId CID, Unique Tangem card ID number. */ -class PurgeWalletCommand : CommandSerializer() { +class PurgeWalletCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) - tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.Pin2, environment.pin2) return CommandApdu( Instruction.PurgeWallet, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PurgeWalletResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - PurgeWalletResponse( - cardId = mapper.map(TlvTag.CardId), - status = mapper.map(TlvTag.Status)) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return PurgeWalletResponse( + cardId = decoder.decode(TlvTag.CardId), + status = decoder.decode(TlvTag.Status)) } } \ 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 index c861d73469..09c188072d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -1,14 +1,14 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError import java.util.* /** @@ -73,7 +73,7 @@ data class SigningMethod(val rawValue: Int) { if (signRawValidatedByIssuerAndWriteIssuerData) signingMethod += 0x01 shl SigningMethod.signRawValidatedByIssuerAndWriteIssuerData if (signPos) signingMethod += 0x01 shl SigningMethod.signPos } - return SigningMethod(signingMethod) + return SigningMethod(signingMethod) } } } @@ -354,6 +354,13 @@ class Card( */ val userCounter: Int?, + /** + * This value can be initialized by App (with PIN2 confirmation) and will be increased by COS + * with the execution of each [SignCommand]. For example, this field can store blockchain “nonce” + * for a quick one-touch transaction on POS terminals. Returned only if [SigningMethod.SignPos]. + */ + val userProtectedCounter: Int?, + /** * When this value is true, it means that the application is linked to the card, * and COS will not enforce security delay if [SignCommand] will be called @@ -373,58 +380,56 @@ class Card( * This command receives from the Tangem Card all the data about the card and the wallet, * including unique card number (CID or cardId) that has to be submitted while calling all other commands. */ -class ReadCommand : CommandSerializer() { +class ReadCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() /** - * [CardEnvironment] stores the pin1 value. If no pin1 value was set, it will contain + * [SessionEnvironment] stores the pin1 value. If no pin1 value was set, it will contain * default value of ‘000000’. * In order to obtain 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, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey) return CommandApdu( Instruction.Read, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val tlvMapper = TlvMapper(tlvData) + val decoder = TlvDecoder(tlvData) - Card( - cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", - manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", - status = tlvMapper.mapOptional(TlvTag.Status), + return Card( + cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", + manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", + status = decoder.decodeOptional(TlvTag.Status), - firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), - cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), - settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask), - issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey), - curve = tlvMapper.mapOptional(TlvTag.CurveId), - maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures), - signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod), - pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2), - walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey), - walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures), - walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes), - health = tlvMapper.mapOptional(TlvTag.Health), - isActivated = tlvMapper.map(TlvTag.IsActivated), - activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed), - paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion), - userCounter = tlvMapper.mapOptional(TlvTag.UserCounter), - terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked), + firmwareVersion = decoder.decodeOptional(TlvTag.Firmware), + cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey), + settingsMask = decoder.decodeOptional(TlvTag.SettingsMask), + issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey), + curve = decoder.decodeOptional(TlvTag.CurveId), + maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures), + signingMethod = decoder.decodeOptional(TlvTag.SigningMethod), + pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2), + walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey), + walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes), + health = decoder.decodeOptional(TlvTag.Health), + isActivated = decoder.decode(TlvTag.IsActivated), + activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed), + paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion), + userCounter = decoder.decodeOptional(TlvTag.UserCounter), + userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter), + terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked), - cardData = deserializeCardData(tlvData) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + cardData = deserializeCardData(tlvData) + ) } private fun deserializeCardData(tlvData: List): CardData? { @@ -433,18 +438,18 @@ class ReadCommand : CommandSerializer() { } if (cardDataTlvs.isNullOrEmpty()) return null - val tlvMapper = TlvMapper(cardDataTlvs) + val decoder = TlvDecoder(cardDataTlvs) return CardData( - batchId = tlvMapper.mapOptional(TlvTag.Batch), - manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime), - issuerName = tlvMapper.mapOptional(TlvTag.IssuerId), - blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId), - manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature), - productMask = tlvMapper.mapOptional(TlvTag.ProductMask), + batchId = decoder.decodeOptional(TlvTag.Batch), + manufactureDateTime = decoder.decodeOptional(TlvTag.ManufactureDateTime), + issuerName = decoder.decodeOptional(TlvTag.IssuerId), + blockchainName = decoder.decodeOptional(TlvTag.BlockchainId), + manufacturerSignature = decoder.decodeOptional(TlvTag.ManufacturerSignature), + productMask = decoder.decodeOptional(TlvTag.ProductMask), - tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol), - tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress), - tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal) + tokenSymbol = decoder.decodeOptional(TlvTag.TokenSymbol), + tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), + tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) ) } } \ 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 cb95fd0899..7055bc403f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -1,16 +1,19 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class ReadIssuerDataResponse( @@ -51,34 +54,63 @@ class ReadIssuerDataResponse( * @property cardId CID, Unique Tangem card ID number. */ class ReadIssuerDataCommand( + val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), - IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + super.run(session) { result -> + when (result) { + is CompletionResult.Failure -> callback(result) + is CompletionResult.Success -> { + if (result.data.issuerData.isEmpty()) { + callback(result) + return@run + } + val issuerDataToVerify = IssuerDataToVerify( + card.cardId, result.data.issuerData, result.data.issuerDataCounter + ) + if (verify(publicKey, result.data.issuerDataSignature, issuerDataToVerify)) { + callback(result) + } else { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } + } + } + } + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) return CommandApdu( Instruction.ReadIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - ReadIssuerDataResponse( - cardId = mapper.map(TlvTag.CardId), - issuerData = mapper.map(TlvTag.IssuerData), - issuerDataSignature = mapper.map(TlvTag.IssuerDataSignature), - issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return ReadIssuerDataResponse( + cardId = decoder.decode(TlvTag.CardId), + issuerData = decoder.decode(TlvTag.IssuerData), + issuerDataSignature = decoder.decode(TlvTag.IssuerDataSignature), + issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter) + ) } } \ 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 5672003477..4140000621 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -1,16 +1,20 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError +import java.io.ByteArrayOutputStream class ReadIssuerExtraDataResponse( @@ -56,38 +60,113 @@ class ReadIssuerExtraDataResponse( * a series of these commands have to be executed to read the entire Issuer_Extra_Data. */ class ReadIssuerExtraDataCommand( + private val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { - var offset: Int = 0 + private val issuerData = ByteArrayOutputStream() + private var offset: Int = 0 + private var issuerDataSize: Int = 0 - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + + readIssuerData(session, card.cardId, publicKey, callback) + } + + + private fun readIssuerData( + session: CardSession, + cardId: String, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit) { + + if (issuerDataSize != 0) { + session.viewDelegate.onDelay( + issuerDataSize, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + ) + } + + transceive(session) { result -> + when (result) { + is CompletionResult.Success -> { + if (result.data.size != null) { + if (result.data.size == 0) { + callback(CompletionResult.Success(result.data)) + return@transceive + } + issuerDataSize = result.data.size + } + issuerData.write(result.data.issuerData) + if (result.data.issuerDataSignature == null) { + offset = issuerData.size() + readIssuerData(session, cardId, publicKey, callback) + } else { + completeTask(result.data, cardId, publicKey, callback) + } + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } + + private fun completeTask(data: ReadIssuerExtraDataResponse, + cardId: String, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit) { + val dataToVerify = IssuerDataToVerify( + cardId, + issuerData.toByteArray(), + data.issuerDataCounter + ) + if (verify(publicKey, data.issuerDataSignature!!, dataToVerify)) { + val finalResult = ReadIssuerExtraDataResponse( + data.cardId, + issuerDataSize, + issuerData.toByteArray(), + data.issuerDataSignature, + data.issuerDataCounter + ) + callback(CompletionResult.Success(finalResult)) + } else { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) return CommandApdu( Instruction.ReadIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadIssuerExtraDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - ReadIssuerExtraDataResponse( - cardId = mapper.map(TlvTag.CardId), - size = mapper.mapOptional(TlvTag.Size), - issuerData = mapper.mapOptional(TlvTag.IssuerData) ?: byteArrayOf(), - issuerDataSignature = mapper.mapOptional(TlvTag.IssuerDataSignature), - issuerDataCounter = mapper.mapOptional(TlvTag.IssuerDataCounter) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + + val decoder = TlvDecoder(tlvData) + return ReadIssuerExtraDataResponse( + cardId = decoder.decode(TlvTag.CardId), + size = decoder.decodeOptional(TlvTag.Size), + issuerData = decoder.decodeOptional(TlvTag.IssuerData) ?: byteArrayOf(), + issuerDataSignature = decoder.decodeOptional(TlvTag.IssuerDataSignature), + issuerDataCounter = decoder.decodeOptional(TlvTag.IssuerDataCounter) + ) } companion object { 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 656c1750f0..528ba98fb0 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -1,13 +1,13 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class ReadUserDataResponse( /** @@ -47,33 +47,30 @@ class ReadUserDataResponse( * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. * For example, this fields may contain blockchain nonce value. */ -class ReadUserDataCommand: CommandSerializer() { +class ReadUserDataCommand: Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { val builder = TlvBuilder() - builder.append(TlvTag.CardId, cardEnvironment.cardId) - builder.append(TlvTag.Pin, cardEnvironment.pin1) + builder.append(TlvTag.CardId, environment.card?.cardId) + builder.append(TlvTag.Pin, environment.pin1) return CommandApdu( Instruction.ReadUserData, builder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): ReadUserDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - ReadUserDataResponse( - cardId = mapper.map(TlvTag.CardId), - userData = mapper.map(TlvTag.UserData), - userProtectedData = mapper.map(TlvTag.UserProtectedData), - userCounter = mapper.map(TlvTag.UserCounter), - userProtectedCounter = mapper.map(TlvTag.UserProtectedCounter) + val decoder = TlvDecoder(tlvData) + return ReadUserDataResponse( + cardId = decoder.decode(TlvTag.CardId), + userData = decoder.decode(TlvTag.UserData), + userProtectedData = decoder.decode(TlvTag.UserProtectedData), + userCounter = decoder.decode(TlvTag.UserCounter), + userProtectedCounter = decoder.decode(TlvTag.UserProtectedCounter) ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } } } \ 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 6f0b7bfacc..7707f82197 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -1,14 +1,14 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.sign -import com.tangem.tasks.TaskError /** * @param cardId CID, Unique Tangem card ID number @@ -31,10 +31,26 @@ class SignResponse( * @property cardId CID, Unique Tangem card ID number */ class SignCommand(private val hashes: Array) - : CommandSerializer() { + : Command() { private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 - private val dataToSign = flattenHashes() + + override fun serialize(environment: SessionEnvironment): CommandApdu { + val dataToSign = flattenHashes() + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.Pin2, environment.pin2) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) + tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) + tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) + tlvBuilder.append(TlvTag.Cvc, environment.cvc) + + addTerminalSignature(environment, dataToSign, tlvBuilder) + return CommandApdu( + Instruction.Sign, tlvBuilder.serialize(), + environment.encryptionMode, environment.encryptionKey + ) + } private fun flattenHashes(): ByteArray { checkForErrors() @@ -42,25 +58,9 @@ class SignCommand(private val hashes: Array) } private fun checkForErrors() { - if (hashes.isEmpty()) throw TaskError.EmptyHashes() - if (hashes.size > 10) throw TaskError.TooMuchHashesInOneTransaction() - if (hashes.any { it.size != hashSizes }) throw TaskError.HashSizeMustBeEqual() - } - - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) - tlvBuilder.append(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())) - tlvBuilder.append(TlvTag.TransactionOutHash, dataToSign) - tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - - addTerminalSignature(cardEnvironment, tlvBuilder) - return CommandApdu( - Instruction.Sign, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey - ) + if (hashes.isEmpty()) throw SessionError.EmptyHashes() + if (hashes.size > 10) throw SessionError.TooMuchHashesInOneTransaction() + if (hashes.any { it.size != hashSizes }) throw SessionError.HashSizeMustBeEqual() } /** @@ -70,23 +70,25 @@ class SignCommand(private val hashes: Array) * TerminalTransactionSignature parameter containing a correct signature of raw data to be signed made with TerminalPrivateKey * (this key should be generated and securily stored by the application). */ - private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvBuilder: TlvBuilder) { - cardEnvironment.terminalKeys?.let { terminalKeyPair -> + private fun addTerminalSignature( + environment: SessionEnvironment, dataToSign: ByteArray, tlvBuilder: TlvBuilder) { + environment.terminalKeys?.let { terminalKeyPair -> val signedData = dataToSign.sign(terminalKeyPair.privateKey) tlvBuilder.append(TlvTag.TerminalTransactionSignature, signedData) tlvBuilder.append(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey) } } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): SignResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - val tlvMapper = TlvMapper(tlvData) + val decoder = TlvDecoder(tlvData) return SignResponse( - cardId = tlvMapper.map(TlvTag.CardId), - signature = tlvMapper.map(TlvTag.Signature), - walletRemainingSignatures = tlvMapper.map(TlvTag.RemainingSignatures), - walletSignedHashes = tlvMapper.map(TlvTag.SignedHashes) + cardId = decoder.decode(TlvTag.CardId), + signature = decoder.decode(TlvTag.Signature), + walletRemainingSignatures = decoder.decode(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decode(TlvTag.SignedHashes) ) } } \ 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 dbc7f15640..a425f5a740 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -1,16 +1,19 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class WriteIssuerDataResponse( /** @@ -33,13 +36,50 @@ class WriteIssuerDataCommand( private val issuerData: ByteArray, private val issuerDataSignature: ByteArray, private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + + if (!isCounterValid(issuerDataCounter, card)) { + callback(CompletionResult.Failure(SessionError.MissingCounter())) + } else if (!verifySignature(publicKey, card.cardId)) { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } else { + super.run(session, callback) + } + } + + private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = + if (isCounterRequired(card)) issuerDataCounter != null else true + + private fun isCounterRequired(card: Card): Boolean = + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false + + private fun verifySignature(publicKey: ByteArray, cardId: String): Boolean { + return verify( + publicKey, + issuerDataSignature, + IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + ) + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.WriteData) tlvBuilder.append(TlvTag.IssuerData, issuerData) tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) @@ -47,20 +87,17 @@ class WriteIssuerDataCommand( return CommandApdu( Instruction.WriteIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - WriteIssuerDataResponse( - cardId = mapper.map(TlvTag.CardId) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + val decoder = TlvDecoder(tlvData) + return WriteIssuerDataResponse( + cardId = decoder.decode(TlvTag.CardId) + ) } } \ 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 index 753607f0a9..4a93ebd734 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -1,16 +1,19 @@ package com.tangem.commands +import com.tangem.CardSession +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.common.DefaultIssuerDataVerifier import com.tangem.commands.common.IssuerDataMode +import com.tangem.commands.common.IssuerDataToVerify import com.tangem.commands.common.IssuerDataVerifier -import com.tangem.common.CardEnvironment +import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError /** * This command writes Issuer Extra Data field and its issuer’s signature. @@ -33,17 +36,94 @@ class WriteIssuerExtraDataCommand( private val startingSignature: ByteArray, private val finalizingSignature: ByteArray, private val issuerDataCounter: Int? = null, + private val issuerPublicKey: ByteArray? = null, verifier: IssuerDataVerifier = DefaultIssuerDataVerifier() -) : CommandSerializer(), IssuerDataVerifier by verifier { +) : Command(), IssuerDataVerifier by verifier { var mode: IssuerDataMode = IssuerDataMode.InitializeWritingExtraData var offset: Int = 0 - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) + return + } + val publicKey = issuerPublicKey ?: card.issuerPublicKey + if (publicKey == null) { + callback(CompletionResult.Failure(SessionError.MissingIssuerPubicKey())) + return + } + + if (!isCounterValid(issuerDataCounter, card)) { + callback(CompletionResult.Failure(SessionError.MissingCounter())) + } else if (!verifySignatures(card.cardId, publicKey)) { + callback(CompletionResult.Failure(SessionError.VerificationFailed())) + } else { + writeIssuerData(session, card.cardId, publicKey, callback) + } + } + + private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean = + if (isCounterRequired(card)) issuerDataCounter != null else true + + private fun isCounterRequired(card: Card): Boolean = + card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false + + private fun verifySignatures(cardId: String, publicKey: ByteArray): Boolean { + + val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size) + val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter) + + return verify(publicKey, startingSignature, firstData) && + verify(publicKey, finalizingSignature, secondData) + } + + private fun writeIssuerData( + session: CardSession, + cardId: String, publicKey: ByteArray, + callback: (result: CompletionResult) -> Unit + ) { + + if (mode == IssuerDataMode.WriteExtraData) { + session.viewDelegate.onDelay(issuerData.size, offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE) + } + transceive(session) { result -> + when (result) { + is CompletionResult.Success -> { + when (mode) { + IssuerDataMode.InitializeWritingExtraData -> { + mode = IssuerDataMode.WriteExtraData + writeIssuerData(session, cardId, publicKey, callback) + return@transceive + } + IssuerDataMode.WriteExtraData -> { + offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE + if (offset >= issuerData.size) { + mode = IssuerDataMode.FinalizeExtraData + } + writeIssuerData(session, cardId, publicKey, callback) + return@transceive + } + IssuerDataMode.FinalizeExtraData -> { + callback(CompletionResult.Success(result.data)) + } + } + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + + + } + + override fun serialize(environment: SessionEnvironment): CommandApdu { val tlvBuilder = TlvBuilder() - tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) - tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) + tlvBuilder.append(TlvTag.Pin, environment.pin1) + tlvBuilder.append(TlvTag.CardId, environment.card?.cardId) tlvBuilder.append(TlvTag.Mode, mode) when (mode) { @@ -62,7 +142,7 @@ class WriteIssuerExtraDataCommand( } return CommandApdu( Instruction.WriteIssuerData, tlvBuilder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + environment.encryptionMode, environment.encryptionKey ) } @@ -74,17 +154,12 @@ class WriteIssuerExtraDataCommand( return if (bytesLeft < SINGLE_WRITE_SIZE) bytesLeft else SINGLE_WRITE_SIZE } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteIssuerDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val mapper = TlvMapper(tlvData) - WriteIssuerDataResponse( - cardId = mapper.map(TlvTag.CardId) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + return WriteIssuerDataResponse(cardId = TlvDecoder(tlvData).decode(TlvTag.CardId) + ) } companion object { 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 ac79cba45c..e9771d0b87 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -1,20 +1,20 @@ package com.tangem.commands -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag -import com.tangem.tasks.TaskError class WriteUserDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String -): CommandResponse + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String +) : CommandResponse /** * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. @@ -29,33 +29,29 @@ class WriteUserDataResponse( * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. */ class WriteUserDataCommand(private val userData: ByteArray? = null, private val userProtectedData: ByteArray? = null, - private val userCounter: Int? = null, - private val userProtectedCounter: Int? = null): CommandSerializer() { + private val userCounter: Int? = null, + private val userProtectedCounter: Int? = null) : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val builder = TlvBuilder() - builder.append(TlvTag.CardId, cardEnvironment.cardId) - builder.append(TlvTag.Pin, cardEnvironment.pin1) - builder.append(TlvTag.UserData, userData) - builder.append(TlvTag.UserCounter, userCounter) - builder.append(TlvTag.UserProtectedData, userProtectedData) - builder.append(TlvTag.UserProtectedCounter, userProtectedCounter) - if (userProtectedCounter != null || userProtectedData != null) - builder.append(TlvTag.Pin2, cardEnvironment.pin2) + override fun serialize(environment: SessionEnvironment): CommandApdu { + val builder = TlvBuilder() + builder.append(TlvTag.CardId, environment.card?.cardId) + builder.append(TlvTag.Pin, environment.pin1) + builder.append(TlvTag.UserData, userData) + builder.append(TlvTag.UserCounter, userCounter) + builder.append(TlvTag.UserProtectedData, userProtectedData) + builder.append(TlvTag.UserProtectedCounter, userProtectedCounter) + if (userProtectedCounter != null || userProtectedData != null) + builder.append(TlvTag.Pin2, environment.pin2) - return CommandApdu( - Instruction.WriteUserData, builder.serialize(), - cardEnvironment.encryptionMode, cardEnvironment.encryptionKey - ) - } - - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null - - return try { - WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() + return CommandApdu( + Instruction.WriteUserData, builder.serialize(), + environment.encryptionMode, environment.encryptionKey + ) + } + + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): WriteUserDataResponse { + val tlvData = apdu.getTlvData(environment.encryptionKey) + ?: throw SessionError.DeserializeApduFailed() + return WriteUserDataResponse(TlvDecoder(tlvData).decode(TlvTag.CardId)) } - } } \ 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 index 034b28dce5..2ed9366331 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 @@ -1,8 +1,8 @@ package com.tangem.commands.personalization +import com.tangem.SessionEnvironment +import com.tangem.commands.Command import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -14,17 +14,16 @@ data class DepersonalizeResponse(val success: Boolean) : CommandResponse * * This command resets card to initial state, * erasing all data written during personalization and usage. - * @param cardId CID, Unique Tangem card ID number. */ -class DepersonalizeCommand : CommandSerializer() { +class DepersonalizeCommand : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu( Instruction.Depersonalize, byteArrayOf() ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): DepersonalizeResponse? { + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): DepersonalizeResponse { return DepersonalizeResponse(true) } } \ 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 035963c21a..24a118322a 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,10 +1,11 @@ package com.tangem.commands.personalization +import com.tangem.SessionEnvironment +import com.tangem.SessionError import com.tangem.commands.Card import com.tangem.commands.CardData -import com.tangem.commands.CommandSerializer +import com.tangem.commands.Command import com.tangem.commands.personalization.entities.* -import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu @@ -12,10 +13,9 @@ import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import com.tangem.crypto.sign -import com.tangem.tasks.TaskError /** * Command available on SDK cards only @@ -34,9 +34,9 @@ class PersonalizeCommand( private val config: CardConfig, private val issuer: Issuer, private val manufacturer: Manufacturer, private val acquirer: Acquirer? = null -) : CommandSerializer() { +) : Command() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + override fun serialize(environment: SessionEnvironment): CommandApdu { return CommandApdu( Instruction.Personalize, serializePersonalizationData(config), @@ -44,39 +44,37 @@ class PersonalizeCommand( ) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData(devPersonalizationKey) ?: return null + override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card { + val tlvData = apdu.getTlvData(devPersonalizationKey) + ?: throw SessionError.DeserializeApduFailed() - return try { - val tlvMapper = TlvMapper(tlvData) - Card( - cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "", - manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "", - status = tlvMapper.mapOptional(TlvTag.Status), + val decoder = TlvDecoder(tlvData) + return Card( + cardId = decoder.decodeOptional(TlvTag.CardId) ?: "", + manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "", + status = decoder.decodeOptional(TlvTag.Status), - firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware), - cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey), - settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask), - issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey), - curve = tlvMapper.mapOptional(TlvTag.CurveId), - maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures), - signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod), - pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2), - walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey), - walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures), - walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes), - health = tlvMapper.mapOptional(TlvTag.Health), - isActivated = tlvMapper.map(TlvTag.IsActivated), - activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed), - paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion), - userCounter = tlvMapper.mapOptional(TlvTag.UserCounter), - terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked), + firmwareVersion = decoder.decodeOptional(TlvTag.Firmware), + cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey), + settingsMask = decoder.decodeOptional(TlvTag.SettingsMask), + issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey), + curve = decoder.decodeOptional(TlvTag.CurveId), + maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures), + signingMethod = decoder.decodeOptional(TlvTag.SigningMethod), + pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2), + walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey), + walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures), + walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes), + health = decoder.decodeOptional(TlvTag.Health), + isActivated = decoder.decode(TlvTag.IsActivated), + activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed), + paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion), + userCounter = decoder.decodeOptional(TlvTag.UserCounter), + userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter), + terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked), - cardData = deserializeCardData(tlvData) - ) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } + cardData = deserializeCardData(tlvData) + ) } private fun deserializeCardData(tlvData: List): CardData? { @@ -85,23 +83,23 @@ class PersonalizeCommand( } if (cardDataTlvs.isNullOrEmpty()) return null - val tlvMapper = TlvMapper(cardDataTlvs) + val decoder = TlvDecoder(cardDataTlvs) return CardData( - batchId = tlvMapper.mapOptional(TlvTag.Batch), - manufactureDateTime = tlvMapper.mapOptional(TlvTag.ManufactureDateTime), - issuerName = tlvMapper.mapOptional(TlvTag.IssuerId), - blockchainName = tlvMapper.mapOptional(TlvTag.BlockchainId), - manufacturerSignature = tlvMapper.mapOptional(TlvTag.ManufacturerSignature), - productMask = tlvMapper.mapOptional(TlvTag.ProductMask), + batchId = decoder.decodeOptional(TlvTag.Batch), + manufactureDateTime = decoder.decodeOptional(TlvTag.ManufactureDateTime), + issuerName = decoder.decodeOptional(TlvTag.IssuerId), + blockchainName = decoder.decodeOptional(TlvTag.BlockchainId), + manufacturerSignature = decoder.decodeOptional(TlvTag.ManufacturerSignature), + productMask = decoder.decodeOptional(TlvTag.ProductMask), - tokenSymbol = tlvMapper.mapOptional(TlvTag.TokenSymbol), - tokenContractAddress = tlvMapper.mapOptional(TlvTag.TokenContractAddress), - tokenDecimal = tlvMapper.mapOptional(TlvTag.TokenDecimal) + tokenSymbol = decoder.decodeOptional(TlvTag.TokenSymbol), + tokenContractAddress = decoder.decodeOptional(TlvTag.TokenContractAddress), + tokenDecimal = decoder.decodeOptional(TlvTag.TokenDecimal) ) } private fun serializePersonalizationData(config: CardConfig): ByteArray { - val cardId = config.createCardId() ?: throw TaskError.SerializeCommandError() + val cardId = config.createCardId() ?: throw SessionError.SerializeCommandError() val tlvBuilder = TlvBuilder() tlvBuilder.append(TlvTag.CardId, cardId) 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 index cda239b145..ec4cff04b0 100644 --- 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 @@ -1,6 +1,6 @@ package com.tangem.commands.personalization.entities -import com.tangem.common.KeyPair +import com.tangem.KeyPair data class Acquirer( val keyPair: KeyPair, 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 index f82889be6a..ee7ab58fa7 100644 --- 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 @@ -1,6 +1,6 @@ package com.tangem.commands.personalization.entities -import com.tangem.common.KeyPair +import com.tangem.KeyPair data class Issuer( val name: String, 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 index bffdde9384..9e30287bb0 100644 --- 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 @@ -1,6 +1,6 @@ package com.tangem.commands.personalization.entities -import com.tangem.common.KeyPair +import com.tangem.KeyPair data class Manufacturer( val keyPair: KeyPair, diff --git a/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt b/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt index afd76a39e8..71f460c7d7 100644 --- a/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt +++ b/tangem-core/src/main/java/com/tangem/common/CompletionResult.kt @@ -1,7 +1,7 @@ package com.tangem.common +import com.tangem.SessionError import com.tangem.common.CompletionResult.Success -import com.tangem.tasks.TaskError /** * Response class encapsulating successful and failed results. @@ -9,5 +9,5 @@ import com.tangem.tasks.TaskError */ sealed class CompletionResult { class Success(val data: T) : CompletionResult() - class Failure(val error: TaskError) : CompletionResult() + class Failure(val error: SessionError) : CompletionResult() } \ 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 index d389c172b3..3ba365247d 100644 --- a/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt +++ b/tangem-core/src/main/java/com/tangem/common/TerminalKeysService.kt @@ -1,9 +1,11 @@ package com.tangem.common +import com.tangem.KeyPair + /** * Interface for a service for managing Terminal keypair, used for Linked Terminal feature. - * Its implementation Needs to be provided to [com.tangem.CardManager] - * by calling [com.tangem.CardManager.setTerminalKeysService]. + * Its implementation Needs to be provided to [com.tangem.TangemSdk] + * by calling [com.tangem.TangemSdk.setTerminalKeysService]. * Default implementation is provided in tangem-sdk module: [TerminalKeysStorage]. * Linked Terminal feature can be disabled manually by editing [com.tangem.Config]. */ 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 1a2c2be076..e2d37df4bb 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 @@ -1,6 +1,6 @@ package com.tangem.common.apdu -import com.tangem.common.EncryptionMode +import com.tangem.EncryptionMode import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.extensions.toByteArray import com.tangem.crypto.encrypt 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 a43cfbe969..3938cee65f 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 @@ -1,5 +1,7 @@ package com.tangem.common.apdu +import com.tangem.SessionError + /** * Part of a response from the card, shows the status of the operation */ @@ -21,5 +23,18 @@ enum class StatusWord(val code: Int, val description: String) { private val values = values() fun byCode(code: Int): StatusWord = values.find { it.code == code } ?: Unknown } - +} + +fun StatusWord.toSessionError(): SessionError? { + return when (this) { + StatusWord.ProcessCompleted, StatusWord.Pin1Changed, + StatusWord.Pin2Changed, StatusWord.PinsChanged -> null + StatusWord.NeedPause -> null + StatusWord.InvalidParams -> SessionError.InvalidParams() + StatusWord.ErrorProcessingCommand -> SessionError.ErrorProcessingCommand() + StatusWord.InvalidState -> SessionError.InvalidState() + StatusWord.InsNotSupported -> SessionError.InsNotSupported() + StatusWord.NeedEncryption -> SessionError.NeedEncryption() + StatusWord.Unknown -> SessionError.UnknownStatus() + } } diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt similarity index 86% rename from tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt rename to tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt index 1713fd2e2d..3d63f0e27e 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt @@ -1,13 +1,13 @@ package com.tangem.common.tlv import com.tangem.Log +import com.tangem.SessionError import com.tangem.commands.* import com.tangem.commands.common.IssuerDataMode import com.tangem.common.extensions.toDate import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toInt import com.tangem.common.extensions.toUtf8 -import com.tangem.tasks.TaskError import java.util.* /** @@ -16,7 +16,7 @@ import java.util.* * * @property tlvList List of TLVs, which values are to be converted to particular classes. */ -class TlvMapper(val tlvList: List) { +class TlvDecoder(val tlvList: List) { /** * Finds [Tlv] by its [TlvTag]. @@ -26,10 +26,10 @@ class TlvMapper(val tlvList: List) { * * @return Value converted to a nullable type [T]. */ - inline fun mapOptional(tag: TlvTag): T? = + inline fun decodeOptional(tag: TlvTag): T? = try { - map(tag) - } catch (exception: TaskError.MissingTag) { + decode(tag) + } catch (exception: SessionError.DecodingFailedMissingTag) { null } @@ -44,13 +44,13 @@ class TlvMapper(val tlvList: List) { * * @throws [TaskError.MissingTag] exception if no [Tlv] is found by the Tag. */ - inline fun map(tag: TlvTag): T { + inline fun decode(tag: TlvTag): T { val tlvValue: ByteArray = tlvList.find { it.tag == tag }?.value ?: if (tag.valueType() == TlvValueType.BoolValue && T::class == Boolean::class) { return false as T } else { Log.e(this::class.simpleName!!, "Tag $tag not found") - throw TaskError.MissingTag() + throw SessionError.DecodingFailedMissingTag() } return when (tag.valueType()) { @@ -68,7 +68,7 @@ class TlvMapper(val tlvList: List) { tlvValue.toInt() as T } catch (exception: IllegalArgumentException) { Log.e(this::class.simpleName!!, exception.message ?: "") - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.BoolValue -> { @@ -85,7 +85,7 @@ class TlvMapper(val tlvList: List) { EllipticCurve.byName(tlvValue.toUtf8()) as T } catch (exception: Exception) { logException(tag, tlvValue.toUtf8(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } @@ -96,7 +96,7 @@ class TlvMapper(val tlvList: List) { tlvValue.toDate() as T } catch (exception: Exception) { logException(tag, tlvValue.toHexString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.ProductMask -> { @@ -113,7 +113,7 @@ class TlvMapper(val tlvList: List) { CardStatus.byCode(tlvValue.toInt()) as T } catch (exception: Exception) { logException(tag, tlvValue.toInt().toString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.SigningMethod -> { @@ -122,7 +122,7 @@ class TlvMapper(val tlvList: List) { SigningMethod(tlvValue.toInt()) as T } catch (exception: Exception) { logException(tag, tlvValue.toInt().toString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } TlvValueType.IssuerDataMode -> { @@ -131,7 +131,7 @@ class TlvMapper(val tlvList: List) { IssuerDataMode.byCode(tlvValue.toInt().toByte()) as T } catch (exception: Exception) { logException(tag, tlvValue.toInt().toString(), exception) - throw TaskError.ConvertError() + throw SessionError.DecodingFailed() } } } @@ -146,7 +146,7 @@ class TlvMapper(val tlvList: List) { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, "Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}") - throw TaskError.WrongType() + throw SessionError.DecodingFailedTypeMismatch() } } 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 index 58e4043450..1855badcab 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvEncoder.kt @@ -1,13 +1,12 @@ package com.tangem.common.tlv import com.tangem.Log +import com.tangem.SessionError import com.tangem.commands.* import com.tangem.commands.common.IssuerDataMode import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toByteArray -import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskError import java.util.* /** @@ -25,7 +24,7 @@ class TlvEncoder { return Tlv(tag, encodeValue(tag, value)) } else { Log.e(this::class.simpleName!!, "Encoding error. Value for tag $tag is null") - throw TaskError.SerializeCommandError() + throw SessionError.EncodingFailed() } } @@ -107,7 +106,7 @@ class TlvEncoder { if (T::class != ExpectedT::class) { Log.e(this::class.simpleName!!, "Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}") - throw TaskError.WrongType() + throw SessionError.EncodingFailedTypeMismatch() } } } \ 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 new file mode 100644 index 0000000000..6b21be102c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/tasks/CreateWalletTask.kt @@ -0,0 +1,43 @@ +package com.tangem.tasks + +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.SessionError +import com.tangem.commands.CardStatus +import com.tangem.commands.CheckWalletCommand +import com.tangem.commands.CreateWalletCommand +import com.tangem.commands.CreateWalletResponse +import com.tangem.common.CompletionResult + +class CreateWalletTask : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val curve = session.environment.card?.curve + if (curve == null) { + callback(CompletionResult.Failure(SessionError.CardError())) + return + } + + val command = CreateWalletCommand() + command.run(session) { createWalletResult -> + when (createWalletResult) { + is CompletionResult.Failure -> callback(createWalletResult) + is CompletionResult.Success -> { + if (createWalletResult.data.status != CardStatus.Loaded) { + callback(CompletionResult.Failure(SessionError.UnknownError())) + } else { + val checkWalletCommand = CheckWalletCommand( + curve, createWalletResult.data.walletPublicKey + ) + checkWalletCommand.run(session) { result -> + when (result) { + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + is CompletionResult.Success -> callback(createWalletResult) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt deleted file mode 100644 index d7c50dde31..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerDataTask.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.commands.ReadIssuerDataCommand -import com.tangem.commands.ReadIssuerDataResponse -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult - -class ReadIssuerDataTask(private val issuerPublicKey: ByteArray? = null) : Task() { - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { - - val command = ReadIssuerDataCommand() - - sendCommand(command, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - val data = result.data - if (data.issuerData.isEmpty()) { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - return@sendCommand - } - val publicKey = issuerPublicKey ?: currentCard!!.issuerPublicKey!! - val issuerDataToVerify = IssuerDataToVerify( - cardEnvironment.cardId!!, - data.issuerData, - data.issuerDataCounter - ) - if (command.verify(publicKey, data.issuerDataSignature, issuerDataToVerify)) { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } else { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt deleted file mode 100644 index 4f1397b8db..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/ReadIssuerExtraDataTask.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.commands.ReadIssuerExtraDataCommand -import com.tangem.commands.ReadIssuerExtraDataResponse -import com.tangem.commands.WriteIssuerExtraDataCommand -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult -import java.io.ByteArrayOutputStream - -/** - * This task performs [ReadIssuerExtraDataCommand] repeatedly until - * the issuer extra data is fully retrieved. - */ -internal class ReadIssuerExtraDataTask( - private val issuerPublicKey: ByteArray?) : Task() { - - private val issuerData = ByteArrayOutputStream() - private var issuerDataSize = 0 - private lateinit var card: Card - private lateinit var cardEnvironment: CardEnvironment - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit - ) { - card = currentCard!! - this.cardEnvironment = cardEnvironment - val command = ReadIssuerExtraDataCommand() - readIssuerData(command, callback) - } - - private fun readIssuerData( - command: ReadIssuerExtraDataCommand, - callback: (result: TaskEvent) -> Unit) { - - if (issuerDataSize != 0) { - delegate?.onDelay( - issuerDataSize, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE - ) - } - - sendCommand(command, cardEnvironment) { result -> - when (result) { - - is CompletionResult.Success -> { - if (result.data.size != null) { - if (result.data.size == 0) { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - return@sendCommand - } - issuerDataSize = result.data.size - } - issuerData.write(result.data.issuerData) - if (result.data.issuerDataSignature == null) { - command.offset = issuerData.size() - readIssuerData(command, callback) - } else { - completeTask(result.data, command, callback) - } - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } - - private fun completeTask(data: ReadIssuerExtraDataResponse, command: ReadIssuerExtraDataCommand, - callback: (result: TaskEvent) -> Unit) { - val publicKey = issuerPublicKey ?: card.issuerPublicKey!! - val dataToVerify = IssuerDataToVerify( - cardEnvironment.cardId!!, - issuerData.toByteArray(), - data.issuerDataCounter - ) - if (command.verify(publicKey, data.issuerDataSignature!!, dataToVerify)) { - completeNfcSession() - val finalResult = ReadIssuerExtraDataResponse( - data.cardId, - issuerDataSize, - issuerData.toByteArray(), - data.issuerDataSignature, - data.issuerDataCounter - ) - callback(TaskEvent.Event(finalResult)) - callback(TaskEvent.Completion()) - } else { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - } -} \ 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 f0160c48b3..207cb6f705 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -1,78 +1,42 @@ package com.tangem.tasks +import com.tangem.CardSession +import com.tangem.CardSessionRunnable +import com.tangem.SessionError import com.tangem.commands.* -import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult -/** - * Events that [ScanTask] returns on completion of its commands. - */ -sealed class ScanEvent { - - /** - * Contains data from a Tangem card after successful completion of [ReadCommand]. - */ - data class OnReadEvent(val card: Card) : ScanEvent() - - /** - * Shows whether the Tangem card was verified on completion of [CheckWalletCommand]. - */ - data class OnVerifyEvent(val isGenuine: Boolean) : ScanEvent() -} - /** * Task that allows to read Tangem card and verify its private key. * * It performs two commands, [ReadCommand] and [CheckWalletCommand], subsequently. */ -internal class ScanTask : Task() { +internal class ScanTask : CardSessionRunnable { - override fun onRun(cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { - if (currentCard != null) callback(TaskEvent.Event(ScanEvent.OnReadEvent(currentCard))) + val card = session.environment.card - if (currentCard == null) { - completeNfcSession(TaskError.MissingPreflightRead()) - callback(TaskEvent.Completion(TaskError.MissingPreflightRead())) + if (card == null) { + callback(CompletionResult.Failure(SessionError.MissingPreflightRead())) - } else if (currentCard.cardData?.productMask?.contains(ProductMask.tag) != false) { - completeNfcSession() - callback(TaskEvent.Completion()) + } else if (card.cardData?.productMask?.contains(ProductMask.tag) != false) { + callback(CompletionResult.Success(card)) - } else if (currentCard.status != CardStatus.Loaded) { - completeNfcSession() - callback(TaskEvent.Completion()) + } else if (card.status != CardStatus.Loaded) { + callback(CompletionResult.Success(card)) - } else if (currentCard.curve == null || currentCard.walletPublicKey == null) { - completeNfcSession(TaskError.CardError()) - callback(TaskEvent.Completion(TaskError.CardError())) + } else if (card.curve == null || card.walletPublicKey == null) { + callback(CompletionResult.Failure(SessionError.CardError())) } else { + val checkWalletCommand = CheckWalletCommand(card.curve, card.walletPublicKey) - val checkWalletCommand = CheckWalletCommand() - - sendCommand(checkWalletCommand, cardEnvironment) { result -> + checkWalletCommand.run(session) { result -> when (result) { - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - is CompletionResult.Success -> { - completeNfcSession() - val verified = result.data.verify( - currentCard.curve, - currentCard.walletPublicKey, - checkWalletCommand.challenge - ) - callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(verified))) - callback(TaskEvent.Completion()) - } + is CompletionResult.Success -> callback(CompletionResult.Success(card)) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) } - } } } diff --git a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt b/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt deleted file mode 100644 index 03efa615f8..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/SingleCommandTask.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.common.CardEnvironment -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.common.CompletionResult - -/** - * Allows to perform a single command. - * - * @property command A command that will be performed. - */ -class SingleCommandTask( - private val command: CommandSerializer -) : Task() { - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit - ) { - sendCommand(command, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } -} diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt deleted file mode 100644 index 224b163a9a..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ /dev/null @@ -1,358 +0,0 @@ -package com.tangem.tasks - -import com.tangem.CardManagerDelegate -import com.tangem.CardReader -import com.tangem.Log -import com.tangem.commands.* -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult -import com.tangem.common.EncryptionMode -import com.tangem.common.apdu.CommandApdu -import com.tangem.common.apdu.StatusWord -import com.tangem.common.extensions.calculateSha256 -import com.tangem.crypto.EncryptionHelper -import com.tangem.crypto.FastEncryptionHelper -import com.tangem.crypto.StrongEncryptionHelper -import com.tangem.crypto.pbkdf2Hash - -/** - * An error class that represent typical errors that may occur when performing Tangem SDK tasks. - * Errors are propagated back to the caller in callbacks. - */ -sealed class TaskError(val code: Int) : Exception() { - - //Errors in serializing APDU - /** - * This error is returned when there [CommandSerializer] cannot deserialize [com.tangem.common.tlv.Tlv] - * (this error is a wrapper around internal [com.tangem.common.tlv.TlvMapper] errors). - */ - class SerializeCommandError : TaskError(1001) - - class EncodingError : TaskError(1002) - class MissingTag : TaskError(1003) - class WrongType : TaskError(1004) - class ConvertError : TaskError(1005) - - /** - * This error is returned when unknown [StatusWord] is received from a card. - */ - class UnknownStatus : TaskError(2001) - - /** - * This error is returned when a card's reply is [StatusWord.ErrorProcessingCommand]. - * The card sends this status in case of internal card error. - */ - class ErrorProcessingCommand : TaskError(2002) - - /** - * This error is returned when a task (such as [ScanTask]) requires that [ReadCommand] - * is executed before performing other commands. - */ - class MissingPreflightRead : TaskError(2003) - - /** - * This error is returned when a card's reply is [StatusWord.InvalidState]. - * The card sends this status when command can not be executed in the current state of a card. - */ - class InvalidState : TaskError(2004) - - /** - * This error is returned when a card's reply is [StatusWord.InsNotSupported]. - * The card sends this status when the card cannot process the [com.tangem.common.apdu.Instruction]. - */ - class InsNotSupported : TaskError(2005) - - /** - * This error is returned when a card's reply is [StatusWord.InvalidParams]. - * The card sends this status when there are wrong or not sufficient parameters in TLV request, - * or wrong PIN1/PIN2. - * The error may be caused, for example, by wrong parameters of the [Task], [CommandSerializer], - * mapping or serialization errors. - */ - class InvalidParams : TaskError(2006) - - /** - * This error is returned when a card's reply is [StatusWord.NeedEncryption] - * and the encryption was not established by TangemSdk. - */ - class NeedEncryption : TaskError(2007) - - //Scan errors - /** - * This error is returned when a [Task] checks unsuccessfully either - * a card's ability to sign with its private key, or the validity of issuer data. - */ - class VerificationFailed : TaskError(3000) - - /** - * This error is returned when a [ScanTask] returns a [Card] without some of the essential fields. - */ - class CardError : TaskError(3001) - - /** - * This error is returned when a [Task] expects a user to use a particular card, - * and a user tries to use a different card. - */ - class WrongCard : TaskError(3002) - - /** - * Tangem cards can sign currently up to 10 hashes during one [com.tangem.commands.SignCommand]. - * This error is returned when a [com.tangem.commands.SignCommand] receives more than 10 hashes to sign. - */ - class TooMuchHashesInOneTransaction : TaskError(3003) - - /** - * This error is returned when a [com.tangem.commands.SignCommand] - * receives only empty hashes for signature. - */ - class EmptyHashes : TaskError(3004) - - /** - * This error is returned when a [com.tangem.commands.SignCommand] - * receives hashes of different lengths for signature. - */ - class HashSizeMustBeEqual : TaskError(3005) - - /** - * This error is returned when [com.tangem.CardManager] was called with a new [Task], - * while a previous [Task] is still in progress. - */ - class Busy : TaskError(4000) - - /** - * This error is returned when a user manually closes NFC Reading Bottom Sheet Dialog. - */ - class UserCancelled : TaskError(4001) - - //NFC errors - class NfcReaderError : TaskError(5002) - - /** - * This error is returned when Android NFC reader loses a tag - * (e.g. a user detaches card from the phone's NFC module) while the NFC session is in progress. - */ - class TagLost : TaskError(5003) - - class UnknownError : TaskError(6000) - - //Specific Command Errors - /** - * This error is returned when [ReadIssuerDataTask] or [ReadIssuerExtraDataTask] expects a counter - * (when the card's requires it), but the counter is missing. - */ - class MissingCounter : TaskError(7001) -} - -/** - * Events that are are sent in callbacks from [Task]. - */ -sealed class TaskEvent { - - /** - * A callback that is triggered by a Task. - */ - class Event(val data: T) : TaskEvent() - - /** - * A callback that is triggered when a [Task] is completed. - * - * @param error is null if it's a successful completion of a [Task] - */ - class Completion(val error: TaskError? = null) : TaskEvent() -} - -/** - * Allows to perform a group of commands interacting between the card and the application. - * A task opens an NFC session, sends commands to the card and receives its responses, - * repeats the commands if needed, and closes session after receiving the last answer. - */ -abstract class Task { - - var delegate: CardManagerDelegate? = null - var reader: CardReader? = null - var performPreflightRead: Boolean = true - var securityDelayDuration: Int = 0 - - /** - * This method should be called to run the [Task] and perform all its operations. - * - * @param cardEnvironment Relevant current version of a card environment - * @param callback It will be triggered during the performance of the [Task] - */ - fun run(cardEnvironment: CardEnvironment, - callback: (result: TaskEvent) -> Unit) { - delegate?.onNfcSessionStarted(cardEnvironment.cardId) - reader?.openSession() - Log.i(this::class.simpleName!!, "Nfc task is started") - - if (performPreflightRead) { - runWithPreflightRead(cardEnvironment, callback) - } else { - onRun(cardEnvironment, null, callback) - } - } - - /** - * Should be called on [Task] completion, whether it was successful or with failure. - * - * @param taskError The error to be shown by [CardManagerDelegate] - */ - protected fun completeNfcSession(taskError: TaskError? = null) { - reader?.closeSession() - if (taskError != null) { - delegate?.onError(taskError) - } else { - delegate?.onNfcSessionCompleted() - } - } - - /** - * In this method the individual Tasks' logic should be implemented. - */ - protected abstract fun onRun(cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) - - /** - * This method should be called by Tasks in their [onRun] method wherever - * they need to communicate with the Tangem Card by launching commands. - */ - protected fun sendCommand( - command: CommandSerializer, - cardEnvironment: CardEnvironment, - callback: (result: CompletionResult) -> Unit) { - - Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - - when (cardEnvironment.encryptionMode) { - EncryptionMode.NONE -> { - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) - } - EncryptionMode.FAST, EncryptionMode.STRONG -> { - if (cardEnvironment.encryptionKey != null ) { - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) - return - } - val encryptionHelper: EncryptionHelper = - if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { - StrongEncryptionHelper() - } else { - FastEncryptionHelper() - } - val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA) - val openSessionApdu = openSessionCommand.serialize(cardEnvironment) - sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - val uid = result.data.uid - val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) - val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) - val sessionKey = (secret + protocolKey).calculateSha256() - cardEnvironment.encryptionKey = sessionKey - - sendCommand(command, cardEnvironment, callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } - } - } - - private fun sendRequest(command: CommandSerializer, - commandApdu: CommandApdu, - cardEnvironment: CardEnvironment, - callback: (result: CompletionResult) -> Unit) { - - reader?.transceiveApdu(commandApdu) { result -> - - when (result) { - is CompletionResult.Success -> { - val responseApdu = result.data - when (responseApdu.statusWord) { - StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged - -> { - try { - val responseData = command.deserialize(cardEnvironment, responseApdu) - Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is completed") - callback(CompletionResult.Success(responseData as T)) - } catch (error: TaskError) { - callback(CompletionResult.Failure(error)) - } - } - StatusWord.InvalidParams -> callback(CompletionResult.Failure(TaskError.InvalidParams())) - StatusWord.Unknown -> { - Log.e(this::class.simpleName!!, "Unknown status error: ${result.data.sw}") - callback(CompletionResult.Failure(TaskError.UnknownStatus())) - } - StatusWord.ErrorProcessingCommand -> callback(CompletionResult.Failure(TaskError.ErrorProcessingCommand())) - StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState())) - - StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) - StatusWord.NeedEncryption -> { - when (cardEnvironment.encryptionMode) { - EncryptionMode.NONE -> { - cardEnvironment.encryptionKey = null - cardEnvironment.encryptionMode = EncryptionMode.FAST - } - EncryptionMode.FAST -> { - cardEnvironment.encryptionKey = null - cardEnvironment.encryptionMode = EncryptionMode.STRONG - } - EncryptionMode.STRONG -> { - Log.e(this::class.simpleName!!, "Encryption doesn't work") - callback(CompletionResult.Failure(TaskError.NeedEncryption())) - return@transceiveApdu - } - } - sendCommand(command, cardEnvironment, callback) - } - StatusWord.NeedPause -> { - // NeedPause is returned from the card whenever security delay is triggered. - val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) - if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) - Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") - sendRequest(command, commandApdu, cardEnvironment, callback) - } - } - } - is CompletionResult.Failure -> - if (result.error == TaskError.TagLost()) { - delegate?.onTagLost() - } else if (result.error is TaskError.UserCancelled) { - callback(CompletionResult.Failure(TaskError.UserCancelled())) - reader?.closeSession() - } - } - } - } - - private fun runWithPreflightRead( - environment: CardEnvironment, callback: (result: TaskEvent) -> Unit) { - sendCommand(ReadCommand(), environment) { readResult -> - when (readResult) { - is CompletionResult.Failure -> { - completeNfcSession(readResult.error) - callback(TaskEvent.Completion(readResult.error)) - } - is CompletionResult.Success -> { - val receivedCardId = readResult.data.cardId - securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 - - if (environment.cardId != null && environment.cardId != receivedCardId) { - completeNfcSession(TaskError.WrongCard()) - callback(TaskEvent.Completion(TaskError.WrongCard())) - return@sendCommand - } - - val newEnvironment = environment.copy(cardId = receivedCardId) - onRun(newEnvironment, readResult.data, callback) - } - } - } - } -} - - diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt deleted file mode 100644 index b22ed95049..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerDataTask.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.Card -import com.tangem.commands.Settings -import com.tangem.commands.WriteIssuerDataCommand -import com.tangem.commands.WriteIssuerDataResponse -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult - -class WriteIssuerDataTask( - private val issuerData: ByteArray, - private val issuerDataSignature: ByteArray, - private val issuerDataCounter: Int? = null, - private val issuerPublicKey: ByteArray? = null -) : Task() { - - private lateinit var card: Card - - override fun onRun( - cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { - - card = currentCard!! - val command = WriteIssuerDataCommand( - issuerData, issuerDataSignature, issuerDataCounter - ) - if (!isCounterValid(issuerDataCounter)) { - completeNfcSession(TaskError.MissingCounter()) - callback(TaskEvent.Completion(TaskError.MissingCounter())) - } else if (!verifySignature(command, cardEnvironment.cardId!!)) { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - - sendCommand(command, cardEnvironment) { result -> - when (result) { - is CompletionResult.Success -> { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } - - private fun isCounterValid(issuerDataCounter: Int?): Boolean = - if (isCounterRequired()) issuerDataCounter != null else true - - private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false - - private fun verifySignature(command: WriteIssuerDataCommand, cardId: String): Boolean { - return command.verify( - issuerPublicKey ?: card.issuerPublicKey!!, - issuerDataSignature, - IssuerDataToVerify(cardId, issuerData, issuerDataCounter) - ) - } -} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt b/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt deleted file mode 100644 index 887e832548..0000000000 --- a/tangem-core/src/main/java/com/tangem/tasks/WriteIssuerExtraDataTask.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.tangem.tasks - -import com.tangem.commands.* -import com.tangem.commands.common.IssuerDataMode -import com.tangem.commands.common.IssuerDataToVerify -import com.tangem.common.CardEnvironment -import com.tangem.common.CompletionResult - -/** - * This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully - * written on the card. - * @param issuerData Data provided by issuer. - * @param startingSignature 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. - */ -internal class WriteIssuerExtraDataTask( - private val issuerData: ByteArray, - private val startingSignature: ByteArray, - private val finalizingSignature: ByteArray, - private val issuerPublicKey: ByteArray? = null, - private val issuerDataCounter: Int? = null -) : Task() { - - private lateinit var card: Card - private lateinit var cardEnvironment: CardEnvironment - - override fun onRun(cardEnvironment: CardEnvironment, - currentCard: Card?, - callback: (result: TaskEvent) -> Unit) { - - card = currentCard!! - this.cardEnvironment = cardEnvironment - val command = WriteIssuerExtraDataCommand( - issuerData, startingSignature, finalizingSignature, issuerDataCounter - ) - if (!isCounterValid(issuerDataCounter)) { - completeNfcSession(TaskError.MissingCounter()) - callback(TaskEvent.Completion(TaskError.MissingCounter())) - } else if (!verifySignatures(command)) { - completeNfcSession(TaskError.VerificationFailed()) - callback(TaskEvent.Completion(TaskError.VerificationFailed())) - } - - writeIssuerData(command, callback) - } - - private fun writeIssuerData( - command: WriteIssuerExtraDataCommand, - callback: (result: TaskEvent) -> Unit) { - - if (command.mode == IssuerDataMode.WriteExtraData) { - delegate?.onDelay(issuerData.size, command.offset, WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE) - } - sendCommand(command, cardEnvironment) { result -> - when (result) { - - is CompletionResult.Success -> { - when (command.mode) { - IssuerDataMode.InitializeWritingExtraData -> { - command.mode = IssuerDataMode.WriteExtraData - writeIssuerData(command, callback) - return@sendCommand - } - IssuerDataMode.WriteExtraData -> { - command.offset += WriteIssuerExtraDataCommand.SINGLE_WRITE_SIZE - if (command.offset >= issuerData.size) { - command.mode = IssuerDataMode.FinalizeExtraData - } - writeIssuerData(command, callback) - return@sendCommand - } - IssuerDataMode.FinalizeExtraData -> { - completeNfcSession() - callback(TaskEvent.Event(result.data)) - callback(TaskEvent.Completion()) - } - } - } - is CompletionResult.Failure -> { - if (result.error !is TaskError.UserCancelled) { - completeNfcSession(result.error) - } - callback(TaskEvent.Completion(result.error)) - } - } - } - } - - private fun isCounterValid(issuerDataCounter: Int?): Boolean = - if (isCounterRequired()) issuerDataCounter != null else true - - private fun isCounterRequired(): Boolean = - card.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) != false - - private fun verifySignatures(command: WriteIssuerExtraDataCommand): Boolean { - val publicKey = issuerPublicKey ?: card.issuerPublicKey!! - val cardId = cardEnvironment.cardId!! - - val firstData = IssuerDataToVerify(cardId, null, issuerDataCounter, issuerData.size) - val secondData = IssuerDataToVerify(cardId, issuerData, issuerDataCounter) - - return command.verify(publicKey, startingSignature, firstData) && - command.verify(publicKey, finalizingSignature, secondData) - } -} \ No newline at end of file diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt similarity index 71% rename from tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt rename to tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt index 64cb5ce15d..f9915fc7b8 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvMapperTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvDecoderTest.kt @@ -1,69 +1,69 @@ package com.tangem.common.tlv import com.google.common.truth.Truth.assertThat +import com.tangem.SessionError import com.tangem.commands.* import com.tangem.common.extensions.hexToBytes -import com.tangem.tasks.TaskError import org.junit.Test import org.junit.jupiter.api.assertThrows import java.util.* -class TlvMapperTest { +class TlvDecoderTest { private val rawData = byteArrayOf(1, 8, -53, 34, 0, 0, 0, 2, 115, 116, 32, 11, 83, 77, 65, 82, 84, 32, 67, 65, 83, 72, 0, 2, 1, 2, -128, 6, 50, 46, 49, 49, 114, 0, 3, 65, 4, -49, 11, -50, -66, -121, -25, -2, 65, 65, -13, 14, 49, 27, -82, -33, -85, -113, 65, 20, 8, -39, -75, 57, 45, 65, -31, 35, 44, 38, 40, 63, -44, 113, -45, -75, -95, -118, 118, 29, 65, 117, -24, -53, 82, -72, 91, -20, -96, -77, -103, -14, -63, 52, -127, -123, -27, -16, -128, -67, -3, -104, -26, -22, 65, 10, 4, 0, 0, 126, 33, 12, 90, -127, 2, 0, 41, -126, 4, 7, -29, 5, 2, -125, 7, 84, 65, 78, 71, 69, 77, 0, -124, 3, 69, 84, 72, -122, 64, 111, -103, 48, -114, -40, 18, -103, 26, -102, -12, -38, -78, -90, -9, -98, 88, -47, -100, -24, 24, -105, -70, -72, 6, 94, -96, -77, 11, -123, -28, -118, 37, 63, 107, -55, -11, 23, -12, 13, -23, -121, -63, 36, -59, 70, 116, 91, -125, -34, -69, 23, -112, 6, 17, 4, -49, 68, -56, 29, -45, 81, 10, 97, 83, 48, 65, 4, -127, -106, -86, 75, 65, 10, -60, 74, 59, -100, -50, 24, -25, -66, 34, 106, -22, 7, 10, -52, -125, -87, -49, 103, 84, 15, -84, 73, -81, 37, 18, -97, 106, 83, -118, 40, -83, 99, 65, 53, -114, 60, 79, -103, 99, 6, 79, 126, 54, 83, 114, -90, 81, -45, 116, -27, -62, 60, -35, 55, -3, 9, -101, -14, 5, 10, 115, 101, 99, 112, 50, 53, 54, 107, 49, 0, 8, 4, 0, 15, 66, 64, 7, 1, 0, 9, 2, 11, -72, 96, 65, 4, -42, -5, -41, -84, -23, 88, 2, 86, -63, -118, -123, -10, -66, -82, -107, -68, -93, 111, 47, 93, -20, -86, 74, 28, 21, 81, 93, -21, -124, -57, -102, 55, 17, 84, -66, -68, -22, -128, 126, -99, -65, -54, -42, 59, -25, -21, -124, 5, 59, -16, -72, 73, 48, 16, -27, 103, -112, -73, 2, 96, -51, 41, -42, 116, 98, 4, 0, 15, 66, 52, 99, 4, 0, 0, 0, 13, 15, 1, 0) private val tlvData = Tlv.deserialize(rawData) - private val tlvMapper = TlvMapper(tlvData!!) + private val tlvMapper = TlvDecoder(tlvData!!) - private val cardDataRaw: ByteArray = tlvMapper.map(TlvTag.CardData) - private val cardDataMapper = TlvMapper(Tlv.deserialize(cardDataRaw)!!) + private val cardDataRaw: ByteArray = tlvMapper.decode(TlvTag.CardData) + private val cardDataMapper = TlvDecoder(Tlv.deserialize(cardDataRaw)!!) @Test fun `map optional when value is present`() { - val settingsMask: SettingsMask? = tlvMapper.mapOptional(TlvTag.SettingsMask) + val settingsMask: SettingsMask? = tlvMapper.decodeOptional(TlvTag.SettingsMask) assertThat(settingsMask) .isNotNull() } @Test fun `map optional when no tag returns null`() { - val tokenSymbol: String? = tlvMapper.mapOptional(TlvTag.TokenSymbol) + val tokenSymbol: String? = tlvMapper.decodeOptional(TlvTag.TokenSymbol) assertThat(tokenSymbol) .isNull() } @Test fun `map when value is null throws MissingTagException`() { - assertThrows { - tlvMapper.map(TlvTag.TokenSymbol) + assertThrows { + tlvMapper.decode(TlvTag.TokenSymbol) } } @Test fun `map optional to wrong type throws WrongTypeException`() { - assertThrows { - tlvMapper.mapOptional(TlvTag.CardData) + assertThrows { + tlvMapper.decodeOptional(TlvTag.CardData) } } @Test fun `map to wrong type throws WrongTypeException`() { - assertThrows { - tlvMapper.map(TlvTag.CardData) + assertThrows { + tlvMapper.decode(TlvTag.CardData) } } @Test fun `map boolean missing flag returns false`() { - val terminalIsLinked: Boolean = tlvMapper.map(TlvTag.TerminalIsLinked) + val terminalIsLinked: Boolean = tlvMapper.decode(TlvTag.TerminalIsLinked) assertThat(terminalIsLinked) .isFalse() } @Test fun `map SettingsMask returns correct value`() { - val settingsMask: SettingsMask = tlvMapper.map(TlvTag.SettingsMask) + val settingsMask: SettingsMask = tlvMapper.decode(TlvTag.SettingsMask) assertThat(settingsMask) .isNotNull() assertThat(settingsMask.rawValue) @@ -82,16 +82,16 @@ class TlvMapperTest { @Test fun `map SigningMethods single value returns correct value`() { - val signingMethods: SigningMethod = tlvMapper.map(TlvTag.SigningMethod) + val signingMethods: SigningMethod = tlvMapper.decode(TlvTag.SigningMethod) assertThat(signingMethods.contains(SigningMethod.signHash)) .isTrue() } @Test fun `map SigningMethods set of methods returns correct value`() { - val localMapper = TlvMapper(Tlv.deserialize("070195".hexToBytes())!!) + val localMapper = TlvDecoder(Tlv.deserialize("070195".hexToBytes())!!) - val signingMethod: SigningMethod = localMapper.map(TlvTag.SigningMethod) + val signingMethod: SigningMethod = localMapper.decode(TlvTag.SigningMethod) assertThat(signingMethod.contains(SigningMethod.signHash)) .isTrue() assertThat(signingMethod.contains(SigningMethod.signHashValidatedByIssuer)) @@ -110,38 +110,38 @@ class TlvMapperTest { @Test fun `map CardStatus returns correct value`() { - val cardStatus: CardStatus = tlvMapper.map(TlvTag.Status) + val cardStatus: CardStatus = tlvMapper.decode(TlvTag.Status) assertThat(cardStatus) .isEqualTo(CardStatus.Loaded) } @Test fun `map ProductMask with raw value 5 returns correct value`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5)))) - val productMask: ProductMask = localMapper.map(TlvTag.ProductMask) + val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(5)))) + val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask) assertThat(productMask.contains(ProductMask.note) && productMask.contains(ProductMask.idCard)) .isTrue() } @Test fun `map ProductMask with raw value 1 returns correct value`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1)))) - val productMask: ProductMask = localMapper.map(TlvTag.ProductMask) + val localMapper = TlvDecoder(listOf(Tlv(TlvTag.ProductMask, byteArrayOf(1)))) + val productMask: ProductMask = localMapper.decode(TlvTag.ProductMask) assertThat(productMask.contains(ProductMask.note)) .isTrue() } @Test fun `map Enum with unknown code throws ConversionException error`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.CurveId, "test".toByteArray()))) - assertThrows { - localMapper.map(TlvTag.CurveId) + 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.map(TlvTag.ManufactureDateTime) + val date: Date = cardDataMapper.decode(TlvTag.ManufactureDateTime) val expected = Calendar.getInstance().apply { this.set(2019, 4, 2, 0, 0, 0) }.time assertThat(date.toString()) .isEqualTo(expected.toString()) @@ -149,43 +149,43 @@ class TlvMapperTest { @Test fun `map EllipticCurve returns correct value`() { - val ellipticCurve: EllipticCurve = tlvMapper.map(TlvTag.CurveId) + val ellipticCurve: EllipticCurve = tlvMapper.decode(TlvTag.CurveId) assertThat(ellipticCurve) .isEqualTo(EllipticCurve.Secp256k1) } @Test fun `map ByteArray returns correctly`() { - val cardPublicKey: ByteArray = tlvMapper.map(TlvTag.CardPublicKey) + val cardPublicKey: ByteArray = tlvMapper.decode(TlvTag.CardPublicKey) assertThat(cardPublicKey) .isInstanceOf(ByteArray::class.java) } @Test fun `map Int returns correct value`() { - val signedHashes: Int = tlvMapper.map(TlvTag.SignedHashes) + val signedHashes: Int = tlvMapper.decode(TlvTag.SignedHashes) assertThat(signedHashes) .isEqualTo(13) } @Test fun `map Int with wrong value throws ConversionException`() { - val localMapper = TlvMapper(listOf(Tlv(TlvTag.SignedHashes, byteArrayOf(1, 2, 3, 4, 5)))) - assertThrows { - localMapper.map(TlvTag.SignedHashes) + 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.map(TlvTag.BlockchainId) + val blockchainId: String = cardDataMapper.decode(TlvTag.BlockchainId) assertThat(blockchainId) .isEqualTo("ETH") } @Test fun `map Hex returns correct value`() { - val cardId: String = tlvMapper.map(TlvTag.CardId) + val cardId: String = tlvMapper.decode(TlvTag.CardId) assertThat(cardId) .isEqualTo("cb22000000027374") } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt index f2c89bc9b4..40998ca19b 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt @@ -3,16 +3,14 @@ package com.tangem.tangemtest import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent import kotlinx.android.synthetic.main.old_activity_main.* class Old_MainActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager + private lateinit var tangemSdk: TangemSdk private lateinit var cardId: String private lateinit var issuerData: ByteArray private lateinit var issuerDataSignature: ByteArray @@ -22,66 +20,50 @@ class Old_MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.old_activity_main) - cardManager = CardManager.init(this) + tangemSdk = TangemSdk.init(this) btn_scan?.setOnClickListener { _ -> - cardManager.scanCard { taskEvent -> + tangemSdk.scanCard { taskEvent -> when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - // Handle returned card data - cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId - runOnUiThread { - tv_card_cid?.text = cardId - btn_create_wallet.isEnabled = true - } - } - is ScanEvent.OnVerifyEvent -> { - //Handle card verification - runOnUiThread { - tv_card_cid?.text = cardId - btn_sign.isEnabled = true - btn_read_issuer_data.isEnabled = true - btn_read_issuer_extra_data.isEnabled = true - btn_write_issuer_data.isEnabled = true - btn_purge_wallet.isEnabled = true - btn_create_wallet.isEnabled = true - } - } + is CompletionResult.Success -> { + // Handle returned card data + val card = taskEvent.data + cardId = card.cardId + runOnUiThread { + tv_card_cid?.text = cardId + btn_create_wallet.isEnabled = true + tv_card_cid?.text = cardId + btn_sign.isEnabled = true + btn_read_issuer_data.isEnabled = true + btn_read_issuer_extra_data.isEnabled = true + btn_write_issuer_data.isEnabled = true + btn_purge_wallet.isEnabled = true + btn_create_wallet.isEnabled = true + } } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error is TaskError.UserCancelled) { - // Handle case when user cancelled manually - } - // Handle other errors - } - // Handle completion - } } } } btn_sign?.setOnClickListener { _ -> - cardManager.sign( + tangemSdk.sign( createSampleHashes(), cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." } + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." } } } } btn_read_issuer_data?.setOnClickListener { _ -> - cardManager.readIssuerData(cardId) { + tangemSdk.readIssuerData(cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { btn_write_issuer_data.isEnabled = true tv_card_cid?.text = it.data.issuerData.contentToString() issuerData = it.data.issuerData @@ -91,27 +73,27 @@ class Old_MainActivity : AppCompatActivity() { } } btn_write_issuer_data?.setOnClickListener { _ -> - cardManager.writeIssuerData( + tangemSdk.writeIssuerData( cardId, issuerData, issuerDataSignature) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = it.data.cardId } } } } btn_read_issuer_extra_data?.setOnClickListener { _ -> - cardManager.readIssuerExtraData(cardId) { + tangemSdk.readIssuerExtraData(cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1 btn_write_issuer_data.isEnabled = true tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data." @@ -120,26 +102,26 @@ class Old_MainActivity : AppCompatActivity() { } } btn_purge_wallet?.setOnClickListener { _ -> - cardManager.purgeWallet( + tangemSdk.purgeWallet( cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = it.data.status.name } } } } btn_create_wallet?.setOnClickListener { _ -> - cardManager.createWallet( + tangemSdk.createWallet( cardId) { when (it) { - is TaskEvent.Completion -> { - if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName } + is CompletionResult.Failure -> { + runOnUiThread { tv_card_cid?.text = it.error::class.simpleName } } - is TaskEvent.Event -> runOnUiThread { + is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = it.data.status.name btn_sign.isEnabled = true btn_read_issuer_data.isEnabled = true diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt index 9ffcba3103..7f3ba0639d 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt @@ -5,14 +5,11 @@ import android.view.View import android.widget.CompoundButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import com.tangem.CardManager -import com.tangem.commands.ReadUserDataResponse -import com.tangem.commands.WriteUserDataResponse -import com.tangem.common.CardEnvironment +import com.tangem.SessionEnvironment +import com.tangem.SessionError +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.init -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskError -import com.tangem.tasks.TaskEvent import kotlinx.android.synthetic.main.activity_test_user_data.* import java.nio.charset.StandardCharsets @@ -21,7 +18,7 @@ import java.nio.charset.StandardCharsets */ class TestUserDataActivity : AppCompatActivity() { - private lateinit var cardManager: CardManager + private lateinit var tangemSdk: TangemSdk private lateinit var writeOptions: WriteOptions override fun onCreate(savedInstanceState: Bundle?) { @@ -33,31 +30,15 @@ class TestUserDataActivity : AppCompatActivity() { } private fun init() { - cardManager = CardManager.init(this) + tangemSdk = TangemSdk.init(this) btn_scan?.setOnClickListener { _ -> - cardManager.scanCard { taskEvent -> + tangemSdk.scanCard { taskEvent -> when (taskEvent) { - is TaskEvent.Event -> { - when (taskEvent.data) { - is ScanEvent.OnReadEvent -> { - // Handle returned card data - writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId - runOnUiThread { showReadWriteSection(true) } - } - is ScanEvent.OnVerifyEvent -> { - //Handle card verification - } - } - } - is TaskEvent.Completion -> { - if (taskEvent.error != null) { - if (taskEvent.error is TaskError.UserCancelled) { - // Handle case when user cancelled manually - } - // Handle other errors - } - // Handle completion + is CompletionResult.Success -> { + // Handle returned card data + writeOptions.cardId = taskEvent.data.cardId + runOnUiThread { showReadWriteSection(true) } } } } @@ -66,7 +47,7 @@ class TestUserDataActivity : AppCompatActivity() { btn_write.setOnClickListener { if (writeOptions.cardId == null) return@setOnClickListener - cardManager.writeUserData( + tangemSdk.writeUserData( writeOptions.cardId!!, writeOptions.userData, writeOptions.userProtectedData, @@ -74,16 +55,9 @@ class TestUserDataActivity : AppCompatActivity() { writeOptions.userProtectedCounter ) { when (it) { - is TaskEvent.Completion -> handleError(tv_write_result, it.error) - is TaskEvent.Event -> { - runOnUiThread { - val data = it.data as? WriteUserDataResponse - if (data == null) { - tv_write_result.text = "Response doesn't match" - return@runOnUiThread - } - tv_write_result?.text = "Success" - } + is CompletionResult.Failure -> handleError(tv_write_result, it.error) + is CompletionResult.Success -> { + runOnUiThread { tv_write_result?.text = "Success" } } } } @@ -92,28 +66,24 @@ class TestUserDataActivity : AppCompatActivity() { btn_read.setOnClickListener { if (writeOptions.cardId == null) return@setOnClickListener - cardManager.readUserData(writeOptions.cardId!!) { + tangemSdk.readUserData(writeOptions.cardId!!) { when (it) { - is TaskEvent.Completion -> handleError(tv_read_result, it.error) - is TaskEvent.Event -> { + is CompletionResult.Failure -> handleError(tv_write_result, it.error) + is CompletionResult.Success -> { runOnUiThread { - val data = it.data as? ReadUserDataResponse - if (data == null) { - tv_read_result.text = "Response doesn't match" - return@runOnUiThread - } + tv_read_result?.text = "Success" - writeOptions.userData = data.userData - writeOptions.userProtectedData = data.userProtectedData - writeOptions.userCounter = data.userCounter - writeOptions.userProtectedCounter = data.userProtectedCounter + writeOptions.userData = it.data.userData + writeOptions.userProtectedData = it.data.userProtectedData + writeOptions.userCounter = it.data.userCounter + writeOptions.userProtectedCounter = it.data.userProtectedCounter - tv_card_cid.text = data.cardId - tv_data.text = String(data.userData, StandardCharsets.US_ASCII) - tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) - tv_counter.text = data.userCounter.toString() - tv_protected_counter.text = data.userProtectedCounter.toString() + tv_card_cid.text = it.data.cardId + tv_data.text = String(it.data.userData, StandardCharsets.US_ASCII) + tv_protected_data.text = String(it.data.userProtectedData, StandardCharsets.US_ASCII) + tv_counter.text = it.data.userCounter.toString() + tv_protected_counter.text = it.data.userProtectedCounter.toString() } } @@ -122,11 +92,10 @@ class TestUserDataActivity : AppCompatActivity() { } } - private fun handleError(tv: TextView, error: TaskError?) { - val er = error ?: return - if (er is TaskError.UserCancelled) return + private fun handleError(tv: TextView, error: SessionError) { + if (error is SessionError.UserCancelled) return - runOnUiThread { tv.text = er::class.simpleName } + runOnUiThread { tv.text = error::class.simpleName } } private fun initWriteOptions() { @@ -174,7 +143,7 @@ class WriteOptions { } fun updatePin2(chbx: CompoundButton) { - val value = CardEnvironment.DEFAULT_PIN2 + val value = SessionEnvironment.DEFAULT_PIN2 pin2 = if (chbx.isChecked) value else null } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt index a599bcb479..a50da93a68 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainViewModel.kt @@ -2,7 +2,7 @@ package com.tangem.tangemtest._main import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.tangem.tasks.TaskEvent +import com.tangem.commands.CommandResponse import ru.dev.gbixahue.eu4d.lib.android.global.log.Log /** @@ -11,16 +11,16 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log class MainViewModel : ViewModel() { val ldDescriptionSwitch = MutableLiveData(false) - var responseEvent: TaskEvent<*>? = null + var commandResponse: CommandResponse? = null fun switchToggled(state: Boolean) { ldDescriptionSwitch.postValue(state) } - fun changeResponseEvent(event: TaskEvent<*>?) { + fun changeResponseEvent(commandResponse: CommandResponse?) { Log.d(this, "changeResponseEvent") - val reqEvent = event ?: return + val response = commandResponse ?: return - responseEvent = reqEvent + this.commandResponse = response } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt index a5f736fe1f..e8358b8071 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/Action.kt @@ -1,6 +1,7 @@ package com.tangem.tangemtest.ucase.domain.actions -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.PayloadHolder @@ -8,7 +9,6 @@ import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.AfterActionModification import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence -import com.tangem.tasks.TaskEvent /** [REDACTED_AUTHOR] @@ -17,7 +17,7 @@ import com.tangem.tasks.TaskEvent * and then processing the response. It also allows you to extract the main action as a lambda expression */ data class AttrForAction( - val cardManager: CardManager, + val tangemSdk: TangemSdk, val itemList: List, val payload: Payload, val consequence: ItemsChangeConsequence? @@ -31,17 +31,17 @@ interface Action { abstract class BaseAction : Action { protected open fun handleResult( payload: PayloadHolder, - taskEvent: TaskEvent<*>, + commandResult: CompletionResult<*>, modifier: AfterActionModification?, attrs: AttrForAction, callback: ActionCallback ) { val modifiedItems = mutableListOf() - modifier?.modify(payload, taskEvent, attrs.itemList)?.forEach { item -> + modifier?.modify(payload, commandResult, attrs.itemList)?.forEach { item -> modifiedItems.add(item) attrs.consequence?.affectChanges(payload, item, attrs.itemList)?.let { modifiedItems.addAll(it) } } - callback(taskEvent, modifiedItems) + callback(commandResult, modifiedItems) } override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? = null diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt index 5f49aa15aa..5dd9d80fc1 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/DepersonalizeAction.kt @@ -14,7 +14,7 @@ class DepersonalizeAction : BaseAction() { val item = attrs.itemList.findItem(TlvId.CardId) ?: return val cardId = item.viewModel.data as? String ?: return - attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) } + attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) } } override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt index 21143ddec8..612b728c24 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/PersonalizeAction.kt @@ -40,7 +40,7 @@ class PersonalizeAction : BaseAction() { val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig()) val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig) - attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) { + attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) { handleResult(payload, it, null, attrs, callback) } } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt index 5264be9aed..213fea8b63 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/ScanAction.kt @@ -9,6 +9,6 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction.Aft */ class ScanAction : BaseAction() { override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) { - attrs.cardManager.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } + attrs.tangemSdk.scanCard { handleResult(payload, it, AfterScanModifier(), attrs, callback) } } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt index d71eaeb900..375c2ce9a7 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/actions/SignAction.kt @@ -16,7 +16,7 @@ class SignAction : BaseAction() { val hash = dataForHashing.getData() as? ByteArray ?: return val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return - attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) } + attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) } } override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt index 42b92174ad..a43809684b 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/ItemsManager.kt @@ -1,14 +1,14 @@ package com.tangem.tangemtest.ucase.domain.paramsManager -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence -import com.tangem.tasks.TaskEvent -typealias ActionResponse = TaskEvent<*> +typealias ActionResponse = CompletionResult<*> typealias AffectedList = List typealias AffectedItemsCallback = (AffectedList) -> Unit typealias ActionCallback = (ActionResponse, AffectedList) -> Unit @@ -22,8 +22,8 @@ interface ItemsManager : PayloadHolder { fun setItems(items: List) fun getItems(): List fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) - fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) - fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? + fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) + fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? fun attachPayload(payload: Payload) } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt index 72729d11eb..6ddbc6a916 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/BaseItemsManager.kt @@ -1,7 +1,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.managers import androidx.lifecycle.LifecycleObserver -import com.tangem.CardManager +import com.tangem.TangemSdk import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.abstraction.Item @@ -47,12 +47,12 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy this.changeConsequence = consequence } - override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(cardManager), callback) + override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(tangemSdk), callback) } - override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? { - return action.getActionByTag(this, id, getAttrsForAction(cardManager)) + override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? { + return action.getActionByTag(this, id, getAttrsForAction(tangemSdk)) } override fun attachPayload(payload: Payload) { @@ -64,6 +64,6 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) } } - protected fun getAttrsForAction(cardManager: CardManager) - : AttrForAction = AttrForAction(cardManager, itemList, payload, changeConsequence) + protected fun getAttrsForAction(tangemSdk: TangemSdk) + : AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence) } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt index e67e278aca..1652cd2833 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/managers/PersonalizationItemsManager.kt @@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.managers import androidx.lifecycle.Lifecycle import androidx.lifecycle.OnLifecycleEvent -import com.tangem.CardManager +import com.tangem.TangemSdk import com.tangem.tangemtest.commons.Store import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback @@ -23,8 +23,8 @@ class PersonalizationItemsManager( setItems(converter.convert(config)) } - override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) { - action.executeMainAction(this, getAttrsForAction(cardManager), callback) + override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) { + action.executeMainAction(this, getAttrsForAction(tangemSdk), callback) } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt index 0b6cecbf64..48ab757ff0 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterActionModifiers.kt @@ -1,8 +1,8 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item -import com.tangem.tasks.TaskEvent /** [REDACTED_AUTHOR] @@ -12,5 +12,5 @@ import com.tangem.tasks.TaskEvent * Returns a list of items that have been modified */ interface AfterActionModification { - fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List + fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List): List } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt index 5cf644eb32..f63694fcc7 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/paramsManager/triggers/afterAction/AfterScanModifier.kt @@ -2,6 +2,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction import com.tangem.commands.Card import com.tangem.commands.CardStatus +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.PayloadHolder import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest._arch.structure.abstraction.findItem @@ -9,17 +10,16 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey import com.tangem.tangemtest.ucase.tunnel.ActionView import com.tangem.tangemtest.ucase.tunnel.CardError import com.tangem.tangemtest.ucase.variants.TlvId -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskEvent + import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI /** [REDACTED_AUTHOR] */ class AfterScanModifier : AfterActionModification { - override fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List): List { + override fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List): List { val foundItem = itemList.findItem(TlvId.CardId) ?: return listOf() - val card = smartCast(taskEvent)?.card ?: return listOf() + val card = smartCast(commandResult) ?: return listOf() val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf() return if (isNotPersonalized(card)) { @@ -34,8 +34,8 @@ class AfterScanModifier : AfterActionModification { } } - private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? { - return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent + private fun smartCast(commandResult: CompletionResult<*>): Card? { + return (commandResult as? CompletionResult.Success)?.data } private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt index 84695d90fb..d54bc08a5d 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/domain/responses/ResponseJsonConverter.kt @@ -1,12 +1,8 @@ package com.tangem.tangemtest.ucase.domain.responses import com.google.gson.* -import com.tangem.commands.ProductMask -import com.tangem.commands.Settings -import com.tangem.commands.SettingsMask -import com.tangem.commands.SigningMethod +import com.tangem.commands.* import com.tangem.common.extensions.toHexString -import com.tangem.tasks.TaskEvent import java.lang.reflect.Type import java.text.DateFormat import java.util.* @@ -29,12 +25,7 @@ class ResponseJsonConverter { return builder.create() } - fun convertTaskEvent(task: TaskEvent<*>?): String = gson.toJson(task) - - fun convertEvent(task: TaskEvent<*>?): String { - val event = task as? TaskEvent.Event ?: "" - return gson.toJson(event) - } + fun convertResponse(response: CommandResponse?): String = gson.toJson(response) } class ByteTypeAdapter : JsonSerializer { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt index fc4956bb70..b0b47e9450 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/ActionViewModel.kt @@ -4,13 +4,16 @@ import android.view.View import androidx.annotation.UiThread import androidx.lifecycle.* import com.google.gson.Gson -import com.tangem.CardManager +import com.tangem.SessionError +import com.tangem.TangemSdk +import com.tangem.commands.Card +import com.tangem.commands.CommandResponse +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.SingleLiveEvent import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.Payload import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest._arch.structure.abstraction.iterate -import com.tangem.tangemtest.commons.performAction import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager import com.tangem.tangemtest.ucase.domain.responses.ResponseJsonConverter import com.tangem.tangemtest.ucase.resources.ActionType @@ -30,19 +33,19 @@ class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvi class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver { - val seResponseEvent = SingleLiveEvent>() - val seReadResponse = SingleLiveEvent() - val seResponse = SingleLiveEvent() + 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 cardManager: CardManager + private lateinit var tangemSdk: TangemSdk - fun setCardManager(cardManager: CardManager) { - this.cardManager = cardManager + fun setCardManager(tangemSdk: TangemSdk) { + this.tangemSdk = tangemSdk } @Deprecated("Events must be send directly from the Widget") @@ -56,15 +59,17 @@ class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), Lif //invokes Scan, Sign etc... fun invokeMainAction() { - performAction(itemsManager, cardManager, { paramsManager, cardManager -> - paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams -> - notifier.handleActionResult(response, listOfChangedParams) - } - }) + if (!::tangemSdk.isInitialized) { + Log.e(this, "TangemSdk isn't initialized") + return + } + itemsManager.invokeMainAction(tangemSdk) { response, listOfChangedParams -> + notifier.handleActionResult(response, listOfChangedParams) + } } fun getItemAction(id: Id): (() -> Unit)? { - val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null + val itemFunction = itemsManager.getActionByTag(id, tangemSdk) ?: return null return { itemFunction { response, listOfChangedParams -> @@ -128,12 +133,12 @@ class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), Lif internal class Notifier(private val vm: ActionViewModel) { - private var notShowedError: TaskError? = null + private var notShowedError: SessionError? = null private val gson: Gson = ResponseJsonConverter().gson - fun handleActionResult(response: TaskEvent<*>, list: List) { + fun handleActionResult(result: CompletionResult<*>, list: List) { if (list.isNotEmpty()) notifyItemsChanged(list) - handleResponse(response) + handleCompletionResult(result) } @UiThread @@ -141,47 +146,35 @@ internal class Notifier(private val vm: ActionViewModel) { vm.seChangedItems.postValue(list) } - fun handleResponse(response: TaskEvent<*>) { - val taskEvent = response as? TaskEvent<*> ?: return + fun handleCompletionResult(result: CompletionResult<*>) { + val commandResponse = result as? CompletionResult ?: return - when (taskEvent) { - is TaskEvent.Completion -> handleCompletionEvent(taskEvent) - is TaskEvent.Event -> { - handleDataEvent(taskEvent.data) - vm.seResponseEvent.postValue(taskEvent) - } + when (commandResponse) { + is CompletionResult.Success -> handleData(commandResponse.data) + is CompletionResult.Failure -> handleError(commandResponse.error) } } - private fun handleDataEvent(event: Any?) { + private fun handleData(event: CommandResponse?) { + vm.seResponse.postValue(event) when (event) { - is ScanEvent.OnReadEvent -> vm.seReadResponse.postValue(gson.toJson(event)) - is ScanEvent.OnVerifyEvent -> { - } - else -> vm.seResponse.postValue(gson.toJson(event)) + is Card -> vm.seResponseCardData.postValue(event) + else -> vm.seResponseData.postValue(event) } } - private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) { - if (taskEvent.error == null) { - Log.d(this, "error = null") - if (notShowedError != null) { - vm.seError.postValue("${notShowedError!!::class.simpleName}") - notShowedError = null - } - } else { - Log.d(this, "error = ${taskEvent.error}") - when (taskEvent.error) { - is TaskError.UserCancelled -> { - if (notShowedError == null) { - vm.seError.postValue("User canceled the action") - } else { - vm.seError.postValue("${notShowedError!!::class.simpleName}") - notShowedError = null - } + private fun handleError(error: SessionError) { + Log.d(this, "error = $error") + when (error) { + is SessionError.UserCancelled -> { + if (notShowedError == null) { + vm.seError.postValue("User canceled the action") + } else { + vm.seError.postValue("${notShowedError!!::class.simpleName}") + notShowedError = null } - else -> notShowedError = taskEvent.error } + else -> notShowedError = error } } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt index 7148be7ee5..e9916b33f0 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/ui/BaseCardActionFragment.kt @@ -9,7 +9,8 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import com.google.android.material.floatingactionbutton.FloatingActionButton -import com.tangem.CardManager +import com.tangem.TangemSdk +import com.tangem.commands.Card import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tangemtest.R import com.tangem.tangemtest._arch.structure.Id @@ -43,7 +44,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView { bindViews() viewLifecycleOwner.lifecycle.addObserver(actionVM) - actionVM.setCardManager(CardManager.init(requireActivity())) + actionVM.setCardManager(TangemSdk.init(requireActivity())) actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView)) initFab() @@ -80,28 +81,37 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView { protected open fun subscribeToViewModelChanges() { Log.d(this, "subscribeToViewModelChanges") - listenEvent() - listenReadResponse() listenResponse() + listenResponseData() + listenResponseCardData() listenError() listenChangedItems() listenDescriptionSwitchChanges() } - protected open fun listenEvent() { - actionVM.seResponseEvent.observe(viewLifecycleOwner, Observer { + private fun listenResponse() { + actionVM.seResponse.observe(viewLifecycleOwner, Observer { + Log.d(this, "listen response: $it") mainActivityVM.changeResponseEvent(it) }) } - protected open fun listenReadResponse() {} - - protected open fun listenResponse() { - actionVM.seResponse.observe(viewLifecycleOwner, Observer { + protected open fun listenResponseData() { + actionVM.seResponseData.observe(viewLifecycleOwner, Observer { + Log.d(this, "listen responseData: $it") navigateTo(R.id.action_nav_card_action_to_response_screen) }) } + protected open fun listenResponseCardData() { + actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer { + Log.d(this, "listen responseCardData: $it") + responseCardDataHandled(it) + }) + } + + protected open fun responseCardDataHandled(card: Card?) {} + protected open fun listenError() { actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) }) } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt index d82099cab8..ace112c0af 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/dto/DefaultPersonalizationParams.kt @@ -1,9 +1,9 @@ package com.tangem.tangemtest.ucase.variants.personalize.dto +import com.tangem.KeyPair import com.tangem.commands.personalization.entities.Acquirer import com.tangem.commands.personalization.entities.Issuer import com.tangem.commands.personalization.entities.Manufacturer -import com.tangem.common.KeyPair /** [REDACTED_AUTHOR] diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt index 97d152d51b..d9d467df53 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/personalize/ui/PersonalizationFragment.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import androidx.lifecycle.Observer +import com.tangem.commands.Card import com.tangem.tangemtest.R import com.tangem.tangemtest._arch.structure.Id import com.tangem.tangemtest._arch.structure.StringId @@ -78,20 +79,6 @@ class PersonalizationFragment : BaseCardActionFragment() { }) } - override fun subscribeToViewModelChanges() { - Log.d(this, "subscribeToViewModelChanges") - listenEvent() - listenError() - listenDescriptionSwitchChanges() - } - - override fun listenEvent() { - actionVM.seResponseEvent.observe(viewLifecycleOwner, Observer { - mainActivityVM.changeResponseEvent(it) - navigateTo(R.id.action_nav_card_action_to_response_screen) - }) - } - override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) { super.showSnackbar(id) { when (id) { @@ -101,4 +88,9 @@ class PersonalizationFragment : BaseCardActionFragment() { } } } + + override fun responseCardDataHandled(card: Card?) { + super.responseCardDataHandled(card) + navigateTo(R.id.action_nav_card_action_to_response_screen) + } } \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ResponseViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ResponseViewModel.kt index e9e00c7186..488b8f014b 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ResponseViewModel.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ResponseViewModel.kt @@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.variants.responses import android.view.View import androidx.lifecycle.ViewModel import com.tangem.commands.Card +import com.tangem.commands.CommandResponse import com.tangem.commands.SignResponse import com.tangem.commands.personalization.DepersonalizeResponse import com.tangem.tangemtest.R @@ -10,8 +11,6 @@ import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems import com.tangem.tangemtest._arch.structure.abstraction.iterate import com.tangem.tangemtest.ucase.variants.responses.converter.ConvertersStore -import com.tangem.tasks.ScanEvent -import com.tangem.tasks.TaskEvent import ru.dev.gbixahue.eu4d.lib.android.global.log.Log /** @@ -22,13 +21,13 @@ class ResponseViewModel : ViewModel() { private val convertersHolder = ConvertersStore() private var itemList: List? = null - fun createItemList(taskEvent: TaskEvent<*>?): List { + fun createItemList(response: CommandResponse?): List { Log.d(this, "createItemList: itemList size: ${itemList?.size ?: 0}") - val event = taskEvent as? TaskEvent.Event ?: return emptyList() - val type = event.data::class.java + val responseEvent = response ?: return emptyList() + val type = responseEvent::class.java val converter = convertersHolder.get(type) as? ModelToItems ?: return emptyList() - itemList = converter.convert(event.data) + itemList = converter.convert(responseEvent) return itemList!! } @@ -38,13 +37,12 @@ class ResponseViewModel : ViewModel() { } } - fun determineTitleId(taskEvent: TaskEvent<*>?): Int { - val event = taskEvent as? TaskEvent.Event ?: return R.string.unknown + fun determineTitleId(response: CommandResponse?): Int { + val responseEvent = response ?: return R.string.unknown - return when (event.data) { - is ScanEvent.OnReadEvent -> R.string.fg_name_response_scan - is SignResponse -> R.string.fg_name_response_sign + return when (responseEvent) { is Card -> R.string.fg_name_response_personalization + is SignResponse -> R.string.fg_name_response_sign is DepersonalizeResponse -> R.string.fg_name_response_depersonalization else -> R.string.unknown } diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/ConvertersStore.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/ConvertersStore.kt index 9b1dc76a49..067d47027c 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/ConvertersStore.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/ConvertersStore.kt @@ -3,13 +3,12 @@ package com.tangem.tangemtest.ucase.variants.responses.converter import com.tangem.commands.Card import com.tangem.commands.SignResponse import com.tangem.commands.personalization.DepersonalizeResponse -import com.tangem.tasks.ScanEvent import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder import java.lang.reflect.Type class ConvertersStore : BaseTypedHolder() { init { - register(ScanEvent.OnReadEvent::class.java, ReadEventConverter()) +// register(CompletionResult.Success::class.java, ReadEventConverter()) register(SignResponse::class.java, SignResponseConverter()) register(Card::class.java, CardConverter()) register(DepersonalizeResponse::class.java, DepersonalizeResponseConverter()) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/SimpleConverters.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/SimpleConverters.kt index a1c997465b..b0c43734b1 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/SimpleConverters.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/converter/SimpleConverters.kt @@ -1,19 +1,20 @@ package com.tangem.tangemtest.ucase.variants.responses.converter +import com.tangem.commands.Card import com.tangem.commands.SignResponse import com.tangem.commands.personalization.DepersonalizeResponse +import com.tangem.common.CompletionResult import com.tangem.tangemtest._arch.structure.StringId import com.tangem.tangemtest._arch.structure.abstraction.Item import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems import com.tangem.tangemtest._arch.structure.impl.TextItem -import com.tangem.tasks.ScanEvent import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf /** [REDACTED_AUTHOR] */ -class ReadEventConverter : ModelToItems { - override fun convert(from: ScanEvent.OnReadEvent): List = CardConverter().convert(from.card) +class ReadEventConverter : ModelToItems> { + override fun convert(from: CompletionResult.Success): List = CardConverter().convert(from.data) } class SignResponseConverter : ModelToItems { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ui/ResponseFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ui/ResponseFragment.kt index 4477f12894..a42d51e237 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ui/ResponseFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/responses/ui/ResponseFragment.kt @@ -33,7 +33,7 @@ open class ResponseFragment : BaseFragment() { } private fun setTittle() { - val titleId = selfVM.determineTitleId(mainActivityVM.responseEvent) + val titleId = selfVM.determineTitleId(mainActivityVM.commandResponse) activity?.setTitle(titleId) } @@ -47,7 +47,7 @@ open class ResponseFragment : BaseFragment() { private fun buildWidgets() { val builder = WidgetBuilder(ResponseItemBuilder()) - val itemList = selfVM.createItemList(mainActivityVM.responseEvent) + val itemList = selfVM.createItemList(mainActivityVM.commandResponse) itemList.forEach { builder.build(it, itemContainer) } } @@ -67,7 +67,7 @@ open class ResponseFragment : BaseFragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_share -> { - shareText(ResponseJsonConverter().convertEvent(mainActivityVM.responseEvent)) + shareText(ResponseJsonConverter().convertResponse(mainActivityVM.commandResponse)) } } return super.onOptionsItemSelected(item) diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt index 1e5ed5eb9b..90e3931375 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/ucase/variants/scan/ui/ScanActionFragment.kt @@ -1,8 +1,6 @@ package com.tangem.tangemtest.ucase.variants.scan.ui -import android.os.Bundle -import android.view.View -import androidx.lifecycle.Observer +import com.tangem.commands.Card import com.tangem.tangemtest.R import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager @@ -17,14 +15,12 @@ class ScanActionFragment : BaseCardActionFragment() { override fun getLayoutId(): Int = R.layout.fg_action_card_scan - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - enableActionFab(true) + override fun initFab() { + actionFab.setOnClickListener { actionVM.invokeMainAction() } } - override fun listenReadResponse() { - actionVM.seReadResponse.observe(viewLifecycleOwner, Observer { - navigateTo(R.id.action_nav_card_action_to_response_screen) - }) + override fun responseCardDataHandled(card: Card?) { + super.responseCardDataHandled(card) + navigateTo(R.id.action_nav_card_action_to_response_screen) } } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt similarity index 87% rename from tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt rename to tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt index e1dd5bb69b..d9cdf14304 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultCardManagerDelegate.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/DefaultSessionViewDelegate.kt @@ -6,24 +6,24 @@ import android.view.View import android.view.animation.DecelerateInterpolator import androidx.fragment.app.FragmentActivity import com.google.android.material.bottomsheet.BottomSheetDialog -import com.tangem.CardManagerDelegate import com.tangem.Log import com.tangem.LoggerInterface +import com.tangem.Message +import com.tangem.SessionViewDelegate import com.tangem.common.CompletionResult import com.tangem.tangem_sdk_new.extensions.hide import com.tangem.tangem_sdk_new.extensions.show import com.tangem.tangem_sdk_new.nfc.NfcReader import com.tangem.tangem_sdk_new.ui.NfcEnableDialog import com.tangem.tangem_sdk_new.ui.TouchCardAnimation -import com.tangem.tasks.TaskError import kotlinx.android.synthetic.main.layout_touch_card.* import kotlinx.android.synthetic.main.nfc_bottom_sheet.* /** - * Default implementation of [CardManagerDelegate]. + * Default implementation of [SessionViewDelegate]. * If no customisation is required, this is the preferred way to use Tangem SDK. */ -class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDelegate { +class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDelegate { lateinit var activity: FragmentActivity private var readingDialog: BottomSheetDialog? = null @@ -33,13 +33,13 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel setLogger() } - override fun onNfcSessionStarted(cardId: String?) { + override fun onNfcSessionStarted(cardId: String?, message: Message?) { reader.readingCancelled = false - postUI { showReadingDialog(activity, cardId) } + postUI { showReadingDialog(activity, cardId, message) } if (!reader.nfcEnabled) showNFCEnableDialog() } - private fun showReadingDialog(activity: FragmentActivity, cardId: String?) { + private fun showReadingDialog(activity: FragmentActivity, cardId: String?, message: Message?) { val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null) readingDialog = BottomSheetDialog(activity) readingDialog?.setContentView(dialogView) @@ -56,6 +56,10 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel readingDialog?.tvCardId?.visibility = View.VISIBLE readingDialog?.tvCardId?.text = cardId } + if (message != null) { + if (message.body != null) readingDialog?.tvTaskText?.text = message.body + if (message.header != null) readingDialog?.tvTaskTitle?.text = message.header + } } readingDialog?.setOnCancelListener { reader.readingCancelled = true @@ -132,25 +136,29 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel } } - override fun onNfcSessionCompleted() { + override fun onNfcSessionCompleted(message: Message?) { postUI { readingDialog?.lTouchCard?.hide() readingDialog?.flSecurityDelay?.hide() readingDialog?.flCompletion?.show() readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp)) + if (message != null) { + if (message.body != null) readingDialog?.tvTaskText?.text = message.body + if (message.header != null) readingDialog?.tvTaskTitle?.text = message.header + } performHapticFeedback() } postUI(300) { readingDialog?.dismiss() } } - override fun onError(error: TaskError) { + override fun onError(errorMessage: String) { postUI { readingDialog?.lTouchCard?.hide() readingDialog?.flSecurityDelay?.hide() readingDialog?.flCompletion?.hide() readingDialog?.flError?.show() readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error) - readingDialog?.tvTaskText?.text = "${error::class.simpleName}: ${error.code}" + readingDialog?.tvTaskText?.text = errorMessage performHapticFeedback() } } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt index 70b2cfa431..0e4eabae9c 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/TerminalKeysStorage.kt @@ -3,7 +3,7 @@ package com.tangem.tangem_sdk_new import android.app.Application import android.content.SharedPreferences import at.favre.lib.armadillo.Armadillo -import com.tangem.common.KeyPair +import com.tangem.KeyPair import com.tangem.common.TerminalKeysService import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString @@ -12,7 +12,7 @@ import com.tangem.crypto.CryptoUtils /** * Service for managing Terminal keypair, used for Linked Terminal feature. - * Needs to be provided to [com.tangem.CardManager] by calling [com.tangem.CardManager.setTerminalKeysService] + * Needs to be provided to [com.tangem.TangemSdk] by calling [com.tangem.TangemSdk.setTerminalKeysService] * Linked Terminal feature can be disabled manually by editing [com.tangem.Config]. * @param applicationContext is required to retrieve an instance of [SharedPreferences] */ diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/CardManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt similarity index 62% rename from tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/CardManager.kt rename to tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt index b2364c13cf..f69b7c96cf 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/CardManager.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/extensions/TangemSdk.kt @@ -1,21 +1,21 @@ package com.tangem.tangem_sdk_new.extensions import androidx.fragment.app.FragmentActivity -import com.tangem.CardManager -import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate +import com.tangem.TangemSdk +import com.tangem.tangem_sdk_new.DefaultSessionViewDelegate import com.tangem.tangem_sdk_new.NfcLifecycleObserver import com.tangem.tangem_sdk_new.TerminalKeysStorage import com.tangem.tangem_sdk_new.nfc.NfcManager -fun CardManager.Companion.init(activity: FragmentActivity): CardManager { +fun TangemSdk.Companion.init(activity: FragmentActivity): TangemSdk { val nfcManager = NfcManager().apply { this.setCurrentActivity(activity) activity.lifecycle.addObserver(NfcLifecycleObserver(this)) } - val cardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader).apply { + val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader).apply { this.activity = activity } - return CardManager(nfcManager.reader, cardManagerDelegate).apply { + return TangemSdk(nfcManager.reader, viewDelegate).apply { this.setTerminalKeysService(TerminalKeysStorage(activity.application)) } } \ No newline at end of file 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 5ba6b11d4b..845dbb2abd 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 @@ -6,10 +6,10 @@ import android.nfc.tech.IsoDep import android.nfc.tech.NfcV import com.tangem.CardReader import com.tangem.Log +import com.tangem.SessionError import com.tangem.common.CompletionResult import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu -import com.tangem.tasks.TaskError /** * Provides NFC communication between an Android application and Tangem card. @@ -38,7 +38,7 @@ class NfcReader : CardReader { // Stops reading and sends failure callback to a task // if reading is cancelled (when user closes nfc bottom sheet dialog). closeSession() - callback?.invoke(CompletionResult.Failure(TaskError.UserCancelled())) + callback?.invoke(CompletionResult.Failure(SessionError.UserCancelled())) } } @@ -67,7 +67,7 @@ class NfcReader : CardReader { private fun transceiveData() { if (readingCancelled) { - callback?.invoke(CompletionResult.Failure(TaskError.UserCancelled())) + callback?.invoke(CompletionResult.Failure(SessionError.UserCancelled())) return } if (data == null) return @@ -76,7 +76,7 @@ class NfcReader : CardReader { try { rawResponse = isoDep?.transceive(data) } catch (exception: TagLostException) { - callback?.invoke(CompletionResult.Failure(TaskError.TagLost())) + callback?.invoke(CompletionResult.Failure(SessionError.TagLost())) isoDep = null return } catch (exception: Exception) { @@ -110,7 +110,7 @@ class NfcReader : CardReader { when (response) { is SlixReadResult.Failure -> { Log.e(this::class.simpleName!!, "${response.exception.message}") - callback?.invoke(CompletionResult.Failure(TaskError.ErrorProcessingCommand())) + callback?.invoke(CompletionResult.Failure(SessionError.ErrorProcessingCommand())) } is SlixReadResult.Success -> { callback?.invoke(CompletionResult.Success(ResponseApdu(response.data))) diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt index 587ded11a3..79f99b4eba 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt @@ -7,7 +7,7 @@ import com.tangem.common.apdu.StatusWord import com.tangem.common.extensions.toByteArray import com.tangem.common.extensions.toHexString import com.tangem.common.tlv.Tlv -import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvDecoder import com.tangem.common.tlv.TlvTag import java.io.ByteArrayOutputStream import java.io.IOException @@ -64,9 +64,9 @@ class SlixTagReader() { val areaBuf = readMultipleBlocks(1, blocksCount) - val tlvNdef = TlvMapper(Tlv.deserialize(areaBuf, true) ?: listOf()) + val tlvNdef = TlvDecoder(Tlv.deserialize(areaBuf, true) ?: listOf()) - return NdefMessage(tlvNdef.map(TlvTag.CardPublicKey)) + return NdefMessage(tlvNdef.decode(TlvTag.CardPublicKey)) } private fun readSingleBlock(blockNo: Int): ByteArray {