diff --git a/.gitignore b/.gitignore
index ae9ed9c524..39d65ab0a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,10 @@ local.properties
.idea/caches/
.idea/libraries/
.idea/*.xml
+.idea/codeStyles
+
+# LayoutInspector files
+.captures
# OS-specific files
.DS_Store
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 681f41ae2a..0000000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index a55e7a179b..0000000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
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/app/src/main/java/com/tangem/App.kt b/app/src/main/java/com/tangem/App.kt
index b5a4451cb7..ef34464937 100644
--- a/app/src/main/java/com/tangem/App.kt
+++ b/app/src/main/java/com/tangem/App.kt
@@ -80,8 +80,10 @@ class App : Application() {
)
}
- FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(Analytics.isEnabled())
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(Analytics.isEnabled())
+ FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(
+ Analytics.isEnabled() && !BuildConfig.DEBUG
+ )
}
private fun buildToastHelperComponent(): ToastHelperComponent {
diff --git a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt
index 98b57d7910..0ff8fef384 100644
--- a/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/SettingsFragment.kt
@@ -53,7 +53,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
if (!Analytics.isEnabled()) {
analytics.logEvent(AnalyticsEvent.ANALYTICS_TURNED_OFF.event, bundleOf())
}
- analytics.setAnalyticsCollectionEnabled(Analytics.isEnabled())
+ analytics.setAnalyticsCollectionEnabled(Analytics.isEnabled() && !BuildConfig.DEBUG)
}
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(Analytics.isEnabled())
@@ -71,10 +71,4 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
preferenceScreen.sharedPreferences
.unregisterOnSharedPreferenceChangeListener(this);
}
-
- override fun onDestroyView() {
- (activity as AppCompatActivity).setSupportActionBar(toolbar)
- super.onDestroyView()
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/util/Analytics.kt b/app/src/main/java/com/tangem/util/Analytics.kt
index c01d40e340..d1120edf6e 100644
--- a/app/src/main/java/com/tangem/util/Analytics.kt
+++ b/app/src/main/java/com/tangem/util/Analytics.kt
@@ -22,6 +22,7 @@ enum class AnalyticsParam(val param: String) {
FIRMWARE("firmware"),
;
}
+
class Analytics {
companion object {
fun setCardData(ctx: TangemContext): Bundle {
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/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt
index 102e7ef8d9..ed9371767b 100644
--- a/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt
+++ b/server-android/src/main/java/com/tangem/server_android/data/LocalStorage.kt
@@ -47,10 +47,11 @@ class LocalStorage
artworks = HashMap()
}
- if (artworks.count() < 43) {
+ if (artworks.count() < 44) {
// forceSave=true only on the last one
putResourceArtworkToCatalog(R.drawable.card_default, false)
putResourceArtworkToCatalog(R.drawable.card_default_nft, false)
+ putResourceArtworkToCatalog(R.drawable.card_id_issuer, false)
putResourceArtworkToCatalog(R.drawable.card_ru006, false)
putResourceArtworkToCatalog(R.drawable.card_ru007, false)
putResourceArtworkToCatalog(R.drawable.card_ru011, false)
@@ -229,7 +230,7 @@ class LocalStorage
fun getCardArtworkBitmap(card: TangemCard): Bitmap {
// special cases (first series of cards, hardcode CID->artwork), on new series batch<->artwork
- if (card.isIDIssuer) {
+ if (card.isIDIssuer || isIdSergio(card)) {
val artworkId = context.resources.getResourceEntryName(R.drawable.card_id_issuer)
return getArtworkBitmap(artworkId) ?: return getDefaultArtworkBitmap(card)
}
@@ -374,4 +375,10 @@ class LocalStorage
}
}
+}
+
+fun LocalStorage.isIdSergio(card: TangemCard): Boolean {
+ val key = card.walletPublicKey ?: return false
+ val hexString = Util.bytesToHex(key)
+ return hexString == "04EAD74FEEE4061044F46B19EB654CEEE981E9318F0C8FE99AF5CDB9D779D2E52BB51EA2D14545E0B323F7A90CF4CC72753C973149009C10DB2D83DCEC28487729"
}
\ No newline at end of file
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..9c338ca586
--- /dev/null
+++ b/tangem-core/src/main/java/com/tangem/CardSession.kt
@@ -0,0 +1,226 @@
+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
+) {
+
+ private val tag = this.javaClass.simpleName
+ /**
+ * 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) {
+ Log.e("tag", "Finishing with error: $errorMessage")
+ 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 -> {
+ Log.i(tag, "Establishing encryption")
+ when (environment.encryptionMode) {
+ EncryptionMode.NONE -> {
+ environment.encryptionKey = null
+ environment.encryptionMode = EncryptionMode.FAST
+ }
+ EncryptionMode.FAST -> {
+ environment.encryptionKey = null
+ environment.encryptionMode = EncryptionMode.STRONG
+ }
+ EncryptionMode.STRONG -> {
+ Log.e(tag, "Encryption doesn't work")
+ callback(CompletionResult.Failure(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.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 53%
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..0dabbc74d6 100644
--- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt
+++ b/tangem-core/src/main/java/com/tangem/SessionEnvironment.kt
@@ -1,29 +1,45 @@
-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(
- val pin1: String = DEFAULT_PIN,
- val pin2: String = DEFAULT_PIN2,
- val cardId: String? = null,
+data class SessionEnvironment(
+ var pin1: ByteArray = DEFAULT_PIN.calculateSha256(),
+ var pin2: ByteArray = DEFAULT_PIN2.calculateSha256(),
+ var card: Card? = null,
val terminalKeys: KeyPair? = null,
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
var encryptionKey: ByteArray? = null,
val cvc: ByteArray? = null
) {
+ fun setPin1(pin1: String) {
+ this.pin1 = pin1.calculateSha256()
+ }
+
+ fun setPin2(pin2: String) {
+ this.pin2 = pin2.calculateSha256()
+ }
+
companion object {
const val DEFAULT_PIN = "000000"
const val DEFAULT_PIN2 = "000"
}
}
+/**
+ * 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..f78e5663cb
--- /dev/null
+++ b/tangem-core/src/main/java/com/tangem/commands/Command.kt
@@ -0,0 +1,117 @@
+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) {
+ Log.i("Command", "Sending ${this::class.java.simpleName}")
+ 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/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt
index 544317c358..5b0f5a47c5 100644
--- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt
+++ b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt
@@ -1,6 +1,7 @@
package com.tangem.common.tlv
import com.tangem.Log
+import com.tangem.common.extensions.toHexString
import java.io.ByteArrayInputStream
import java.io.IOException
@@ -71,6 +72,10 @@ class Tlv {
}
}
+ override fun toString(): String {
+ return "${this.tag} ($tagRaw): ${value.toHexString()}"
+ }
+
}
fun List.serialize(): ByteArray =
diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt
index 6ec643d00a..3e3c8557f2 100644
--- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt
+++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvBuilder.kt
@@ -1,5 +1,7 @@
package com.tangem.common.tlv
+import com.tangem.Log
+
class TlvBuilder {
private val tlvs = mutableListOf()
private val encoder = TlvEncoder()
@@ -10,6 +12,10 @@ class TlvBuilder {
tlvs.add(encoder.encode(tag, value))
}
- fun serialize(): ByteArray = tlvs.serialize()
+ fun serialize(): ByteArray {
+ Log.v("TLV",
+ "List of TLV that are being sent to a card:\n${tlvs.joinToString("\n")}")
+ return tlvs.serialize()
+ }
}
\ No newline at end of file
diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvMapper.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvDecoder.kt
similarity index 85%
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..48c14edec4 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,12 @@ 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) {
+
+ init {
+ Log.v("TLV",
+ "List of TLV received from card:\n${tlvList.joinToString("\n")}")
+ }
/**
* Finds [Tlv] by its [TlvTag].
@@ -26,10 +31,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 +49,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 +73,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 +90,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 +101,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 +118,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 +127,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 +136,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 +151,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/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt
index 44200cd20a..e3c9122c59 100644
--- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt
+++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt
@@ -125,7 +125,7 @@ enum class TlvTag(val code: Int) {
fun valueType(): TlvValueType {
return when (this) {
CardId, Batch, CrExKey -> TlvValueType.HexString
- Pin, Pin2, NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash
+ NewPin, NewPin2, NewPin3 -> TlvValueType.HexStringToHash
ManufactureId, Firmware, IssuerId, BlockchainId, TokenSymbol, TokenContractAddress ->
TlvValueType.Utf8String
CurveId -> TlvValueType.EllipticCurve
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/apdu/CommandApduTest.kt b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt
index 61d6ea71d3..f879f65e6f 100644
--- a/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt
+++ b/tangem-core/src/test/java/com/tangem/common/apdu/CommandApduTest.kt
@@ -1,9 +1,7 @@
package com.tangem.common.apdu
import com.google.common.truth.Truth.assertThat
-import com.tangem.common.extensions.calculateSha256
-import com.tangem.common.extensions.toHexString
-import com.tangem.common.tlv.Tlv
+import com.tangem.SessionEnvironment
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvTag
import org.junit.Test
@@ -13,9 +11,9 @@ class CommandApduTest {
@Test
fun `simple READ command to bytes`() {
- val pin = "000000"
+ val sessionEnvironment = SessionEnvironment()
val tlvBuilder = TlvBuilder()
- tlvBuilder.append(TlvTag.Pin, pin)
+ tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1)
val commandApdu = CommandApdu(
Instruction.Read,
tlvBuilder.serialize()
@@ -30,13 +28,13 @@ class CommandApduTest {
@Test
fun `READ with terminal key to bytes`() {
- val pin = "000000"
+ val sessionEnvironment = SessionEnvironment()
val terminalPublicKey = byteArrayOf(4, 80, -122, 58, -42, 74, -121, -82, -118, 47, -24, 60,
26, -15, -88, 64, 60, -75, 63, 83, -28, -122, -40, 81, 29, -83, -118, 4, -120, 126,
91, 35, 82, 44, -44, 112, 36, 52, 83, -94, -103, -6, -98, 119, 35, 119, 22, 16, 58,
-68, 17, -95, -33, 56, -123, 94, -42, -14, -18, 24, 126, -100, 88, 43, -90)
val tlvBuilder = TlvBuilder()
- tlvBuilder.append(TlvTag.Pin, pin)
+ tlvBuilder.append(TlvTag.Pin, sessionEnvironment.pin1)
tlvBuilder.append(TlvTag.TerminalPublicKey, terminalPublicKey)
val commandApdu = CommandApdu(
Instruction.Read,
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/AppTangemDemo.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt
index c9fc162b6a..ab604ee506 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/AppTangemDemo.kt
@@ -1,6 +1,8 @@
package com.tangem.tangemtest
import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
import com.tangem.tangemtest._arch.structure.ILog
import com.tangem.tangemtest._arch.structure.ItemLogger
import com.tangem.tangemtest.commons.TangemLogger
@@ -23,6 +25,10 @@ class AppTangemDemo : Application() {
ILog.setLogger(ItemLogger())
}
+ fun sharedPreferences(name: String = "DevKitApp", mode: Int = Context.MODE_PRIVATE): SharedPreferences {
+ return getSharedPreferences(name, mode)
+ }
+
companion object {
lateinit var appInstance: AppTangemDemo
}
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/Old_MainActivity.kt
index b39081b6b0..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,17 +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.tangemtest.extensions.create
-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
@@ -23,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
@@ -92,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."
@@ -121,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/_arch/structure/HelpAbstracts.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt
index 98704005f5..309309c947 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/HelpAbstracts.kt
@@ -3,10 +3,6 @@ package com.tangem.tangemtest._arch.structure
/**
[REDACTED_AUTHOR]
*/
-interface DataHolder {
- var viewModel: D
-}
-
typealias Payload = MutableMap
interface PayloadHolder {
@@ -17,12 +13,4 @@ interface PayloadHolder {
fun set(key: String, value: Any?) {
payload[key] = value
}
-}
-
-interface ItemListHolder {
- fun setItems(list: MutableList)
- fun getItems(): MutableList
- fun addItem(item: I)
- fun removeItem(item: I)
- fun clear()
}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt
index 3b40edcc77..5e517808f2 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/Ids.kt
@@ -5,6 +5,10 @@ package com.tangem.tangemtest._arch.structure
*/
interface Id
+class StringId(val value: String) : Id
+
+class StringResId(val value: Int) : Id
+
enum class Additional : Id {
UNDEFINED,
JSON_INCOMING,
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt
index 71924302a3..76b43ce50a 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Extension.kt
@@ -5,11 +5,11 @@ import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
-fun List- .findDataItem(id: Id): BaseItem? {
- var foundItem: BaseItem? = null
+fun List
- .findItem(id: Id): Item? {
+ var foundItem: Item? = null
iterate {
if (it.id == id) {
- foundItem = it as? BaseItem
+ foundItem = it
return@iterate
}
}
@@ -19,8 +19,8 @@ fun List
- .findDataItem(id: Id): BaseItem? {
fun List
- .iterate(func: (Item) -> Unit) {
forEach {
when (it) {
- is BaseItem<*> -> func(it)
- is Block -> it.itemList.iterate(func)
+ is BaseItem -> func(it)
+ is ItemGroup -> it.itemList.iterate(func)
}
}
}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt
index 2cfbc69173..eaf3d7afa4 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Item.kt
@@ -1,31 +1,39 @@
package com.tangem.tangemtest._arch.structure.abstraction
-import com.tangem.tangemtest._arch.structure.DataHolder
import com.tangem.tangemtest._arch.structure.Id
-import com.tangem.tangemtest._arch.structure.Payload
-import com.tangem.tangemtest._arch.structure.PayloadHolder
/**
[REDACTED_AUTHOR]
*/
-interface Item : PayloadHolder {
+interface Item {
val id: Id
var parent: Item?
-}
+ var viewModel: ItemViewModel
-abstract class BaseItem(
- override var viewModel: ItemViewModel
-) : Item, DataHolder> {
- override var parent: Item? = null
- override val payload: Payload = mutableMapOf()
+ fun added(parent: Item) {
+ this.parent = parent
+ }
- fun getData(): D? = viewModel.data
+ fun removed(parent: Item) {
+ this.parent = null
+ }
- fun setData(value: D?) {
+ fun getData(): D? = viewModel.data as? D
+
+ fun setData(value: Any?) {
viewModel.data = value
}
- fun restoreDefaultData(){
+ fun restoreDefaultData() {
setData(viewModel.defaultData)
}
+}
+
+open class BaseItem(
+ override val id: Id,
+ override var viewModel: ItemViewModel
+) : Item {
+
+ override var parent: Item? = null
+
}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemGroup.kt
similarity index 61%
rename from tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt
rename to tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemGroup.kt
index 11086e6f93..61a43a0d1c 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/Block.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemGroup.kt
@@ -2,45 +2,47 @@ package com.tangem.tangemtest._arch.structure.abstraction
import com.tangem.tangemtest._arch.structure.ILog
import com.tangem.tangemtest._arch.structure.Id
-import com.tangem.tangemtest._arch.structure.ItemListHolder
/**
[REDACTED_AUTHOR]
*/
-interface Block : Item {
+interface ItemGroup : Item {
val itemList: MutableList
-
+
+ fun setItems(list: MutableList
- )
+ fun getItems(): MutableList
-
+ fun addItem(item: Item)
+ fun removeItem(item: Item)
+ fun clear()
}
-abstract class BaseBlock : Block {
+open class SimpleItemGroup(
+ override val id: Id,
+ override var viewModel: ItemViewModel = BaseItemViewModel()
+) : ItemGroup {
+
override var parent: Item? = null
override val itemList: MutableList
- = mutableListOf()
- override val payload: MutableMap = mutableMapOf()
-}
-
-open class ListItemBlock(
- override val id: Id
-) : BaseBlock(), ItemListHolder
- {
override fun setItems(list: MutableList
- ) {
ILog.d(this, "setItems into: $id, count: ${list.size}")
+ itemList.forEach { it.removed(this) }
itemList.clear()
- itemList.addAll(list)
- itemList.forEach { it.parent = this }
+ list.forEach { addItem(it) }
}
- override fun getItems(): MutableList
- {
- return itemList
- }
+ override fun getItems(): MutableList
- = itemList
override fun addItem(item: Item) {
ILog.d(this, "addItem into: $id, who: ${item.id}")
- item.parent = this
itemList.add(item)
+ item.added(this)
}
override fun removeItem(item: Item) {
ILog.d(this, "removeItem from: $id, which: ${item.id}")
itemList.remove(item)
+ item.removed(this)
}
override fun clear() {
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemViewModel.kt
new file mode 100644
index 0000000000..a542a3be4d
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ItemViewModel.kt
@@ -0,0 +1,101 @@
+package com.tangem.tangemtest._arch.structure.abstraction
+
+import com.tangem.tangemtest._arch.structure.ILog
+import com.tangem.tangemtest._arch.structure.Payload
+import com.tangem.tangemtest._arch.structure.PayloadHolder
+
+
+/**
+[REDACTED_AUTHOR]
+ */
+typealias ValueChanged = (V?) -> Unit
+typealias SafeValueChanged = (V) -> Unit
+
+class KeyValue(val key: String, val value: Any)
+
+class ViewState(
+ isVisible: Boolean? = null,
+ bgColor: Int? = -1
+) {
+
+ class State(
+ stateValue: T,
+ var onValueChanged: SafeValueChanged? = null
+ ) {
+ var value = stateValue
+ set(value) {
+ if (preventSameChanges && field == value) return
+
+ field = value
+ onValueChanged?.invoke(value)
+ }
+
+ internal var preventSameChanges = true
+ }
+
+ var isVisibleState = State(isVisible)
+ var backgroundColor = State(bgColor)
+ var descriptionVisibility = State(0x00000008)
+
+ internal fun preventSameChanges(isPrevented: Boolean) {
+ val states = listOf(isVisibleState, backgroundColor, descriptionVisibility)
+ states.forEach { it.preventSameChanges = isPrevented }
+ }
+}
+
+interface ItemViewModel : PayloadHolder {
+ val viewState: ViewState
+ var data: Any?
+ var defaultData: Any?
+ var onDataUpdated: ValueChanged?
+
+ fun updateDataByView(data: Any?)
+}
+
+open class BaseItemViewModel(
+ value: Any? = null,
+ override val viewState: ViewState = ViewState()
+) : ItemViewModel {
+
+ override val payload: Payload = mutableMapOf()
+
+ // Don't update it directly from a View. Use for it updateDataByView()
+ override var data: Any? = value
+ set(value) {
+ if (handleDataUpdates(value)) field = value
+ }
+
+ // Data for restoring initial value
+ override var defaultData: Any? = value
+ set(value) {
+ field = value
+ data = value
+ }
+
+ // Use it for handling data updates in View
+ override var onDataUpdated: ValueChanged? = null
+
+ // When data updates directly it invokes onDataUpdated
+ // return true = data will update
+ // return false = data won't update
+ protected open fun handleDataUpdates(value: Any?): Boolean {
+ ILog.d(this, "handleDateUpdates: $value")
+ onDataUpdated?.invoke(value)
+ return true
+ }
+
+ // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop
+ override fun updateDataByView(data: Any?) {
+ ILog.d(this, "data changed: $data")
+ val callback = onDataUpdated
+ onDataUpdated = null
+ this.data = data
+ onDataUpdated = callback
+ }
+}
+
+class ListViewModel(
+ val itemList: List,
+ var selectedItem: Any?,
+ override val viewState: ViewState = ViewState()
+) : BaseItemViewModel(selectedItem)
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt
deleted file mode 100644
index 86d1d1b345..0000000000
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/abstraction/ViewModel.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.tangem.tangemtest._arch.structure.abstraction
-
-import com.tangem.tangemtest._arch.structure.ILog
-import com.tangem.tangemtest._arch.structure.Payload
-import com.tangem.tangemtest._arch.structure.PayloadHolder
-
-
-/**
-[REDACTED_AUTHOR]
- */
-typealias ValueChange = (V?) -> Unit
-typealias SafeValueChange = (V) -> Unit
-
-interface ItemViewModel : PayloadHolder {
- val viewState: ViewState
- var data: D?
- var defaultData: D?
- var onDataUpdated: ValueChange?
-
- fun updateDataByView(data: D?)
-}
-
-open class BaseItemViewModel : ItemViewModel {
-
- override val viewState: ViewState = ViewState()
- override val payload: Payload = mutableMapOf()
-
- // Don't update it directly from a View. Use for it updateDataByView()
- override var data: D? = null
- set(value) {
- if (handleDataUpdates(value)) field = value
- }
-
- // Data for restoring initial value
- override var defaultData: D? = null
- set(value) {
- field = value
- data = value
- }
-
- // Use it for handling data updates in View
- override var onDataUpdated: ValueChange? = null
-
- // When data updates directly it invokes onDataUpdated
- // return true = data will update
- // return false = data won't update
- protected open fun handleDataUpdates(value: D?): Boolean {
- ILog.d(this, "handleDateUpdates: $value")
- onDataUpdated?.invoke(value)
- return true
- }
-
- // Use it to update the data from a View. It disables onDataUpdated to prevent a callback loop
- override fun updateDataByView(data: D?) {
- ILog.d(this, "data changed: $data")
- val callback = onDataUpdated
- onDataUpdated = null
- this.data = data
- onDataUpdated = callback
- }
-}
-
-class ViewState {
- var isHiddenField = false
-
- var descriptionVisibility: Int = 0x00000008
- set(value) {
- field = value
- onDescriptionVisibilityChanged?.invoke(value)
- }
-
- var onDescriptionVisibilityChanged: SafeValueChange? = null
-}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt
index 11ea5510c0..e402650802 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/Items.kt
@@ -1,19 +1,39 @@
package com.tangem.tangemtest._arch.structure.impl
import com.tangem.tangemtest._arch.structure.Id
-import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
+import com.tangem.tangemtest._arch.structure.abstraction.*
/**
[REDACTED_AUTHOR]
*/
-class AnyItem(override val id: Id, value: Any? = null) : BaseItem(AnyViewModel(value))
-class TextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value))
-class EditTextItem(override val id: Id, value: String? = null) : BaseItem(StringViewModel(value))
-class NumberItem(override val id: Id, value: Number? = null) : BaseItem(NumberViewModel(value))
-class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem(BoolViewModel(value))
-class ListItem(override val id: Id, value: List, selectedValue: Any?)
- : BaseItem(ListViewModel(ListValueWrapper(selectedValue, value))
-)
+open class TypedItem(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewModel) {
+ open fun getTypedData(): D? = viewModel.data as? D
+}
-inline fun BaseItem.getData(): T? = viewModel.data
\ No newline at end of file
+class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) {
+ constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
+ : this(id, BaseItemViewModel(value, viewState))
+}
+
+class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) {
+ constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState())
+ : this(id, BaseItemViewModel(value, viewState))
+}
+
+class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) {
+ constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState())
+ : this(id, BaseItemViewModel(value, viewState))
+}
+
+
+class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem(id, viewModel) {
+ constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
+ : this(id, BaseItemViewModel(value, viewState))
+}
+
+
+class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem(id, viewModel) {
+ constructor(id: Id, list: List, selectedValue: Any?, viewState: ViewState = ViewState())
+ : this(id, ListViewModel(list, selectedValue, viewState))
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt
deleted file mode 100644
index f082577e70..0000000000
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/structure/impl/ViewModels.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.tangem.tangemtest._arch.structure.impl
-
-import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
-
-/**
-[REDACTED_AUTHOR]
- */
-open class TransitiveViewModel(value: D?) : BaseItemViewModel() {
- init {
- defaultData = value
- }
-}
-
-class AnyViewModel(value: Any? = null) : TransitiveViewModel(value)
-class StringViewModel(value: String? = null) : TransitiveViewModel(value)
-class NumberViewModel(value: Number? = null) : TransitiveViewModel(value)
-class BoolViewModel(value: Boolean? = null) : TransitiveViewModel(value)
-class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel(value) {
-
- override fun handleDataUpdates(value: ListValueWrapper?): Boolean {
- val newValue = value ?: return false
- if (newValue.selectedItem == data?.selectedItem) return false
-
- return super.handleDataUpdates(value)
- }
-}
-
-class KeyValue(val key: String, val value: Any)
-class ListValueWrapper(var selectedItem: Any?, val itemList: List)
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/ItemWidgetBuilder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/ItemWidgetBuilder.kt
new file mode 100644
index 0000000000..2ed1e1ab8f
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/ItemWidgetBuilder.kt
@@ -0,0 +1,12 @@
+package com.tangem.tangemtest._arch.widget
+
+import android.view.ViewGroup
+import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
+import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface ItemWidgetBuilder {
+ fun build(item: BaseItem, parent: ViewGroup): ViewWidget?
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/WidgetBuilder.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/WidgetBuilder.kt
new file mode 100644
index 0000000000..3d9a3493a5
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/WidgetBuilder.kt
@@ -0,0 +1,36 @@
+package com.tangem.tangemtest._arch.widget
+
+import android.view.ViewGroup
+import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
+import com.tangem.tangemtest._arch.structure.abstraction.Item
+import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup
+import com.tangem.tangemtest._arch.widget.abstraction.ViewWidget
+import com.tangem.tangemtest._arch.widget.impl.LinearGroupWidget
+import com.tangem.tangemtest._arch.widget.impl.StubWidget
+
+/**
+[REDACTED_AUTHOR]
+ */
+class WidgetBuilder(
+ private val itemBuilder: ItemWidgetBuilder
+) {
+
+ fun build(item: Item, parent: ViewGroup): ViewWidget? {
+ return when (item) {
+ is ItemGroup -> buildBlock(item, parent)
+ is BaseItem -> itemBuilder.build(item, parent)
+ else -> StubWidget(item.id, parent)
+ }
+ }
+
+ private fun buildBlock(itemGroup: ItemGroup, parent: ViewGroup): ViewWidget {
+ return when (itemGroup) {
+ is ItemGroup -> {
+ val linearBlock = LinearGroupWidget(parent, itemGroup)
+ itemGroup.getItems().forEach { build(it, linearBlock.view as ViewGroup) }
+ linearBlock
+ }
+ else -> StubWidget(itemGroup.id, parent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/abstraction/ViewWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/abstraction/ViewWidget.kt
new file mode 100644
index 0000000000..9b6c5f9d2c
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/abstraction/ViewWidget.kt
@@ -0,0 +1,79 @@
+package com.tangem.tangemtest._arch.widget.abstraction
+
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.ColorRes
+import com.tangem.tangemtest.R
+import com.tangem.tangemtest._arch.structure.StringId
+import com.tangem.tangemtest._arch.structure.StringResId
+import com.tangem.tangemtest._arch.structure.abstraction.Item
+import com.tangem.tangemtest._arch.structure.abstraction.ViewState
+import ru.dev.gbixahue.eu4d.lib.android._android.views.colorFrom
+import ru.dev.gbixahue.eu4d.lib.kotlin.common.LayoutHolder
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface ViewWidget : LayoutHolder {
+ val view: View
+ var item: Item
+
+ fun getName(): String
+ fun setBackgroundColor(@ColorRes colorId: Int?)
+}
+
+abstract class BaseViewWidget(
+ parent: ViewGroup,
+ override var item: Item
+) : ViewWidget {
+
+ override val view: View = inflate(getLayoutId(), parent)
+
+ private var defaultBackground: Drawable? = view.background
+
+ init {
+ subscribeToViewStateChanges(item.viewModel.viewState)
+ initViewState(item.viewModel.viewState)
+ }
+
+ protected open fun subscribeToViewStateChanges(viewState: ViewState) {
+ viewState.isVisibleState.onValueChanged = { state ->
+ state?.let { view.visibility = if (it) View.VISIBLE else View.GONE }
+ }
+ viewState.backgroundColor.onValueChanged = { setBackgroundColor(it) }
+ }
+
+ protected open fun initViewState(viewState: ViewState) {
+ viewState.preventSameChanges(false)
+ viewState.isVisibleState.value = viewState.isVisibleState.value
+ if (viewState.backgroundColor.value != -1) setBackgroundColor(viewState.backgroundColor.value)
+ viewState.preventSameChanges(true)
+ }
+
+ override fun getName(): String {
+ return when (val id = item.id) {
+ is StringId -> id.value
+ is StringResId -> view.resources.getString(id.value)
+ else -> view.resources.getString(R.string.unknown)
+ }
+ }
+
+ override fun setBackgroundColor(colorId: Int?) {
+ val background = when {
+ colorId == null -> null
+ colorId == -1 -> defaultBackground
+ else -> ColorDrawable(view.colorFrom(colorId))
+ }
+ view.background = background
+ }
+}
+
+internal fun inflate(id: Int, parent: ViewGroup): View {
+ val layoutId = if (id <= 0) R.layout.w_empty else id
+ val inflatedView = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
+ parent.addView(inflatedView)
+ return inflatedView
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/LinearGroupWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/LinearGroupWidget.kt
new file mode 100644
index 0000000000..97c8d36a35
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/LinearGroupWidget.kt
@@ -0,0 +1,18 @@
+package com.tangem.tangemtest._arch.widget.impl
+
+import android.view.ViewGroup
+import com.tangem.tangemtest.R
+import com.tangem.tangemtest._arch.structure.abstraction.ItemGroup
+import com.tangem.tangemtest._arch.widget.abstraction.BaseViewWidget
+
+/**
+[REDACTED_AUTHOR]
+ */
+class LinearGroupWidget(
+ parent: ViewGroup,
+ itemGroup: ItemGroup
+) : BaseViewWidget(parent, itemGroup) {
+
+ override fun getLayoutId(): Int = R.layout.w_personilize_block
+
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/StubWidget.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/StubWidget.kt
new file mode 100644
index 0000000000..1b832e362a
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_arch/widget/impl/StubWidget.kt
@@ -0,0 +1,15 @@
+package com.tangem.tangemtest._arch.widget.impl
+
+import android.view.ViewGroup
+import com.tangem.tangemtest.R
+import com.tangem.tangemtest._arch.structure.Id
+import com.tangem.tangemtest._arch.structure.abstraction.BaseItem
+import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
+import com.tangem.tangemtest._arch.widget.abstraction.BaseViewWidget
+
+/**
+[REDACTED_AUTHOR]
+ */
+class StubWidget(id: Id, parent: ViewGroup) : BaseViewWidget(parent, BaseItem(id, BaseItemViewModel())) {
+ override fun getLayoutId(): Int = R.layout.w_empty
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt
index 4e774f0be7..92e093d2c8 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/MainActivity.kt
@@ -2,7 +2,6 @@ package com.tangem.tangemtest._main
import android.content.res.Resources
import android.os.Bundle
-import android.util.Log
import android.view.Menu
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@@ -12,8 +11,9 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
-import androidx.navigation.ui.setupActionBarWithNavController
+import androidx.navigation.ui.setupWithNavController
import com.tangem.tangemtest.R
+import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
@@ -34,8 +34,8 @@ class MainActivity : AppCompatActivity() {
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return
val navController = host.navController
- appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_entry_point))
- setupActionBarWithNavController(navController, appBarConfiguration)
+ appBarConfiguration = AppBarConfiguration(navController.graph)
+ toolbar.setupWithNavController(navController, appBarConfiguration)
navController.addOnDestinationChangedListener { _, destination, _ ->
val dest: String = try {
@@ -43,7 +43,7 @@ class MainActivity : AppCompatActivity() {
} catch (e: Resources.NotFoundException) {
destination.id.toString()
}
- Log.d("NavigationActivity", "Navigated to $dest")
+ Log.d(this, "Navigated to $dest")
}
}
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 17ce97d8f9..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,6 +2,8 @@ package com.tangem.tangemtest._main
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import com.tangem.commands.CommandResponse
+import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
@@ -9,7 +11,16 @@ import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
val ldDescriptionSwitch = MutableLiveData(false)
+ var commandResponse: CommandResponse? = null
+
fun switchToggled(state: Boolean) {
ldDescriptionSwitch.postValue(state)
}
+
+ fun changeResponseEvent(commandResponse: CommandResponse?) {
+ Log.d(this, "changeResponseEvent")
+ val response = commandResponse ?: return
+
+ this.commandResponse = response
+ }
}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt
index 313c4aaffe..ea17194439 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/_main/entryPoint/ActionListFragment.kt
@@ -1,14 +1,10 @@
package com.tangem.tangemtest._main.entryPoint
import android.os.Bundle
-import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
-import androidx.navigation.NavController
-import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -19,21 +15,19 @@ import com.tangem.tangemtest._main.MainViewModel
import com.tangem.tangemtest.ucase.getDefaultNavigationOptions
import com.tangem.tangemtest.ucase.resources.ActionType
import com.tangem.tangemtest.ucase.resources.MainResourceHolder
+import com.tangem.tangemtest.ucase.ui.BaseFragment
import kotlinx.android.synthetic.main.fg_entry_point.*
/**
[REDACTED_AUTHOR]
*/
-class ActionListFragment : Fragment() {
+class ActionListFragment : BaseFragment() {
- private val navController: NavController by lazy { findNavController() }
private lateinit var rvActions: RecyclerView
private val mainActivityVM: MainViewModel by activityViewModels()
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.fg_entry_point, container, false)
- }
+ override fun getLayoutId(): Int = R.layout.fg_entry_point
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -48,7 +42,9 @@ class ActionListFragment : Fragment() {
val vhDataWrapper = VhExDataWrapper(MainResourceHolder, false)
- rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data -> navigate(data) }
+ rvActions.adapter = RvActionsAdapter(vhDataWrapper) { type, position, data ->
+ navigateTo(data, options = getDefaultNavigationOptions())
+ }
mainActivityVM.ldDescriptionSwitch.observe(viewLifecycleOwner, Observer {
vhDataWrapper.descriptionIsVisible = it
TransitionManager.beginDelayedTransition(rvActions as ViewGroup, AutoTransition())
@@ -64,10 +60,6 @@ class ActionListFragment : Fragment() {
adapter.notifyDataSetChanged()
}
- private fun navigate(destinationId: Int) {
- navController.navigate(destinationId, null, getDefaultNavigationOptions())
- }
-
private fun getNavigateOptions(): MutableList {
return mutableListOf(
ActionType.Scan,
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Abstractions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Abstractions.kt
new file mode 100644
index 0000000000..f5f02c0ace
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/Abstractions.kt
@@ -0,0 +1,9 @@
+package com.tangem.tangemtest.commons
+
+/**
+[REDACTED_AUTHOR]
+ */
+interface Store {
+ fun save(config: M)
+ fun restore(): M
+}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt
index 7a19cf981f..59ae0da2b9 100644
--- a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/GlobalFunctions.kt
@@ -3,6 +3,7 @@ package com.tangem.tangemtest.commons
/**
[REDACTED_AUTHOR]
*/
-fun performAction(a: A?, b: B?, action: (A, B) -> Unit) {
+fun performAction(a: A?, b: B?, action: (A, B) -> Unit, onFail: (() -> Unit)? = null) {
if (a != null && b != null) action(a, b)
+ else onFail?.invoke()
}
\ No newline at end of file
diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/commons/view/MultiActionView.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/view/MultiActionView.kt
new file mode 100644
index 0000000000..ce21b9fae6
--- /dev/null
+++ b/tangem-demo/src/main/java/com/tangem/tangemtest/commons/view/MultiActionView.kt
@@ -0,0 +1,85 @@
+package com.tangem.tangemtest.commons.view
+
+import android.widget.TextView
+import androidx.annotation.StringRes
+import com.tangem.tangemtest._arch.structure.Id
+import com.tangem.tangemtest._arch.structure.StringId
+import com.tangem.tangemtest._arch.structure.abstraction.SafeValueChanged
+import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
+import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
+
+/**
+[REDACTED_AUTHOR]
+ */
+open class MultiActionView(
+ stateActionList: MutableList,
+ val child: V
+) {
+
+ interface State {
+ val id: Id
+
+ fun getAction(): SimpleFunction
+ fun getResNameId(): Int
+ }
+
+ var afterAction: SafeValueChanged