Updated on 2026-08-14

This commit is contained in:
Tangem 2020-04-13 14:51:17 +03:00
commit bafca42f6e
174 changed files with 4471 additions and 3455 deletions

4
.gitignore vendored
View file

@ -13,6 +13,10 @@ local.properties
.idea/caches/
.idea/libraries/
.idea/*.xml
.idea/codeStyles
# LayoutInspector files
.captures
# OS-specific files
.DS_Store

View file

@ -1,116 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

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

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

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -22,6 +22,7 @@ enum class AnalyticsParam(val param: String) {
FIRMWARE("firmware"),
;
}
class Analytics {
companion object {
fun setCardData(ctx: TangemContext): Bundle {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
}

View file

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

View file

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

View file

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

View file

@ -1,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),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,11 @@
package com.tangem.common
import com.tangem.KeyPair
/**
* Interface for a service for managing Terminal keypair, used for Linked Terminal feature.
* Its implementation Needs to be provided to [com.tangem.CardManager]
* by calling [com.tangem.CardManager.setTerminalKeysService].
* Its implementation Needs to be provided to [com.tangem.TangemSdk]
* by calling [com.tangem.TangemSdk.setTerminalKeysService].
* Default implementation is provided in tangem-sdk module: [TerminalKeysStorage].
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
*/

View file

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

View file

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

View file

@ -1,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<Tlv>.serialize(): ByteArray =

View file

@ -1,5 +1,7 @@
package com.tangem.common.tlv
import com.tangem.Log
class TlvBuilder {
private val tlvs = mutableListOf<Tlv>()
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()
}
}

View file

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

View file

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

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,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,

View file

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

View file

@ -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
}

View file

@ -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

View file

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

View file

@ -3,10 +3,6 @@ package com.tangem.tangemtest._arch.structure
/**
[REDACTED_AUTHOR]
*/
interface DataHolder<D> {
var viewModel: D
}
typealias Payload = MutableMap<String, Any?>
interface PayloadHolder {
@ -17,12 +13,4 @@ interface PayloadHolder {
fun set(key: String, value: Any?) {
payload[key] = value
}
}
interface ItemListHolder<I> {
fun setItems(list: MutableList<I>)
fun getItems(): MutableList<I>
fun addItem(item: I)
fun removeItem(item: I)
fun clear()
}

View file

@ -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,

View file

@ -5,11 +5,11 @@ import com.tangem.tangemtest._arch.structure.Id
/**
[REDACTED_AUTHOR]
*/
fun List<Item>.findDataItem(id: Id): BaseItem<Any?>? {
var foundItem: BaseItem<Any?>? = null
fun List<Item>.findItem(id: Id): Item? {
var foundItem: Item? = null
iterate {
if (it.id == id) {
foundItem = it as? BaseItem<Any?>
foundItem = it
return@iterate
}
}
@ -19,8 +19,8 @@ fun List<Item>.findDataItem(id: Id): BaseItem<Any?>? {
fun List<Item>.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)
}
}
}

View file

@ -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<D>(
override var viewModel: ItemViewModel<D>
) : Item, DataHolder<ItemViewModel<D>> {
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 <D> 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
}

View file

@ -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<Item>
fun setItems(list: MutableList<Item>)
fun getItems(): MutableList<Item>
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<Item> = mutableListOf()
override val payload: MutableMap<String, Any?> = mutableMapOf()
}
open class ListItemBlock(
override val id: Id
) : BaseBlock(), ItemListHolder<Item> {
override fun setItems(list: MutableList<Item>) {
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<Item> {
return itemList
}
override fun getItems(): MutableList<Item> = 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() {

View file

@ -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> = (V?) -> Unit
typealias SafeValueChanged<V> = (V) -> Unit
class KeyValue(val key: String, val value: Any)
class ViewState(
isVisible: Boolean? = null,
bgColor: Int? = -1
) {
class State<T>(
stateValue: T,
var onValueChanged: SafeValueChanged<T>? = 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<Any?>?
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<Any?>? = 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<KeyValue>,
var selectedItem: Any?,
override val viewState: ViewState = ViewState()
) : BaseItemViewModel(selectedItem)

View file

@ -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> = (V?) -> Unit
typealias SafeValueChange<V> = (V) -> Unit
interface ItemViewModel<D : Any?> : PayloadHolder {
val viewState: ViewState
var data: D?
var defaultData: D?
var onDataUpdated: ValueChange<D>?
fun updateDataByView(data: D?)
}
open class BaseItemViewModel<D> : ItemViewModel<D> {
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<D>? = 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<Int>? = null
}

View file

@ -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<Any>(AnyViewModel(value))
class TextItem(override val id: Id, value: String? = null) : BaseItem<String>(StringViewModel(value))
class EditTextItem(override val id: Id, value: String? = null) : BaseItem<String>(StringViewModel(value))
class NumberItem(override val id: Id, value: Number? = null) : BaseItem<Number>(NumberViewModel(value))
class BoolItem(override val id: Id, value: Boolean? = null) : BaseItem<Boolean>(BoolViewModel(value))
class ListItem(override val id: Id, value: List<KeyValue>, selectedValue: Any?)
: BaseItem<ListValueWrapper>(ListViewModel(ListValueWrapper(selectedValue, value))
)
open class TypedItem<D>(id: Id, viewModel: ItemViewModel) : BaseItem(id, viewModel) {
open fun getTypedData(): D? = viewModel.data as? D
}
inline fun<reified T> BaseItem<T>.getData(): T? = viewModel.data
class TextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class NumberItem(id: Id, viewModel: ItemViewModel) : TypedItem<Number>(id, viewModel) {
constructor(id: Id, value: Number? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class BoolItem(id: Id, viewModel: ItemViewModel) : TypedItem<Boolean>(id, viewModel) {
constructor(id: Id, value: Boolean? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class EditTextItem(id: Id, viewModel: ItemViewModel) : TypedItem<String>(id, viewModel) {
constructor(id: Id, value: String? = null, viewState: ViewState = ViewState())
: this(id, BaseItemViewModel(value, viewState))
}
class SpinnerItem(id: Id, viewModel: ListViewModel) : TypedItem<ListViewModel>(id, viewModel) {
constructor(id: Id, list: List<KeyValue>, selectedValue: Any?, viewState: ViewState = ViewState())
: this(id, ListViewModel(list, selectedValue, viewState))
}

View file

@ -1,29 +0,0 @@
package com.tangem.tangemtest._arch.structure.impl
import com.tangem.tangemtest._arch.structure.abstraction.BaseItemViewModel
/**
[REDACTED_AUTHOR]
*/
open class TransitiveViewModel<D>(value: D?) : BaseItemViewModel<D>() {
init {
defaultData = value
}
}
class AnyViewModel(value: Any? = null) : TransitiveViewModel<Any>(value)
class StringViewModel(value: String? = null) : TransitiveViewModel<String>(value)
class NumberViewModel(value: Number? = null) : TransitiveViewModel<Number>(value)
class BoolViewModel(value: Boolean? = null) : TransitiveViewModel<Boolean>(value)
class ListViewModel(value: ListValueWrapper? = null) : TransitiveViewModel<ListValueWrapper>(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<KeyValue>)

View file

@ -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?
}

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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<Boolean>(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
}
}

View file

@ -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<ActionType> {
return mutableListOf(
ActionType.Scan,

View file

@ -0,0 +1,9 @@
package com.tangem.tangemtest.commons
/**
[REDACTED_AUTHOR]
*/
interface Store<M> {
fun save(config: M)
fun restore(): M
}

View file

@ -3,6 +3,7 @@ package com.tangem.tangemtest.commons
/**
[REDACTED_AUTHOR]
*/
fun <A, B> performAction(a: A?, b: B?, action: (A, B) -> Unit) {
fun <A, B> performAction(a: A?, b: B?, action: (A, B) -> Unit, onFail: (() -> Unit)? = null) {
if (a != null && b != null) action(a, b)
else onFail?.invoke()
}

View file

@ -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<V : TextView>(
stateActionList: MutableList<ViewAction>,
val child: V
) {
interface State {
val id: Id
fun getAction(): SimpleFunction
fun getResNameId(): Int
}
var afterAction: SafeValueChanged<Id>? = null
var state: Id = DefaultId.default
set(value) {
if (field == value) return
field = value
Log.d(this, "state changed to: ${getKey(value)}")
btnState = stateHolder[getKey(value)]
}
init {
child.setOnClickListener {
val state = btnState ?: return@setOnClickListener
Log.d(this, "child handled OnClick for: ${getKey(state.id)}")
state.getAction().invoke()
afterAction?.invoke(state.id)
}
}
protected var btnState: State? = null
set(value) {
if (value == null) return
field = value
Log.d(this, "btnState changed to: ${getKey(value.id)}")
child.setText(value.getResNameId())
}
protected val stateHolder: MutableMap<String, State> = stateActionList.associateBy { getKey(it.id) }.toMutableMap()
fun performAction(id: Id) {
Log.d(this, "performAction ${getKey(id)}")
state = id
child.performClick()
}
protected open fun getKey(id: Id): String {
return when (id) {
is StringId -> id.value
else -> stringOf(id)
}
}
}
enum class DefaultId : Id { default }
typealias SimpleFunction = () -> Unit
class ViewAction(
override val id: Id,
@StringRes val name: Int,
private val action: SimpleFunction
) : MultiActionView.State {
override fun getAction(): SimpleFunction = action
override fun getResNameId(): Int = name
}

View file

@ -0,0 +1,22 @@
package com.tangem.tangemtest.extensions
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.fragment.app.Fragment
/**
[REDACTED_AUTHOR]
*/
fun Fragment.shareText(text: String) {
requireActivity().shareText(text)
}
fun ComponentActivity.shareText(text: String) {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, text)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
startActivity(shareIntent)
}

View file

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

View file

@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest._arch.structure.abstraction.findItem
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.TlvId
@ -11,10 +11,10 @@ import com.tangem.tangemtest.ucase.variants.TlvId
*/
class DepersonalizeAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
val item = attrs.itemList.findDataItem(TlvId.CardId) ?: return
val item = attrs.itemList.findItem(TlvId.CardId) ?: return
val cardId = item.viewModel.data as? String ?: return
attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {

View file

@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest._arch.structure.abstraction.findItem
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest._arch.structure.impl.NumberItem
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
@ -10,10 +10,10 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
import com.tangem.tangemtest.ucase.tunnel.ActionView
import com.tangem.tangemtest.ucase.tunnel.ItemError
import com.tangem.tangemtest.ucase.variants.personalize.CardNumber
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizeConfigConverter
import com.tangem.tangemtest.ucase.variants.personalize.converter.fromTo.PersonalizeConfigToCardConfig
import com.tangem.tangemtest.ucase.variants.personalize.dto.DefaultPersonalizeParams
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizeConfig
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigToCardConfig
import com.tangem.tangemtest.ucase.variants.personalize.dto.DefaultPersonalizationParams
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
/**
@ -33,31 +33,31 @@ class PersonalizeAction : BaseAction() {
return
}
val issuer = DefaultPersonalizeParams.issuer()
val acquirer = DefaultPersonalizeParams.acquirer()
val manufacturer = DefaultPersonalizeParams.manufacturer()
val issuer = DefaultPersonalizationParams.issuer()
val acquirer = DefaultPersonalizationParams.acquirer()
val manufacturer = DefaultPersonalizationParams.manufacturer()
val personalizeConfig = PersonalizeConfigConverter().convert(itemList, PersonalizeConfig())
val cardConfig = PersonalizeConfigToCardConfig().convert(personalizeConfig)
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig())
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) {
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {
handleResult(payload, it, null, attrs, callback)
}
}
private fun checkSeries(itemList: List<Item>): Boolean {
val seriesItem = itemList.findDataItem(CardNumber.Series) as? EditTextItem ?: return false
val data = seriesItem.getData() ?: return false
val seriesItem = itemList.findItem(CardNumber.Series) as? EditTextItem ?: return false
val data = seriesItem.getData() as? String ?: return false
seriesItem.setData(data.toUpperCase())
return data.length == 2 || data.length == 4
}
private fun checkNumber(itemList: List<Item>): Boolean {
val seriesItem = itemList.findDataItem(CardNumber.Series) as? EditTextItem ?: return false
val numberItem = itemList.findDataItem(CardNumber.Number) as? NumberItem ?: return false
val seriesData = seriesItem.getData() ?: return false
val numberData = stringOf(numberItem.getData())
val seriesItem = itemList.findItem(CardNumber.Series) as? EditTextItem ?: return false
val numberItem = itemList.findItem(CardNumber.Number) as? NumberItem ?: return false
val seriesData = seriesItem.getData() as? String ?: return false
val numberData = stringOf(numberItem.getData() as? Number)
return if (seriesData.length == 2 && numberData.length > 13) false
else !(seriesData.length == 4 && numberData.length > 11)

View file

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

View file

@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.actions
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.PayloadHolder
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest._arch.structure.abstraction.findItem
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.TlvId
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
@ -12,11 +12,11 @@ import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
*/
class SignAction : BaseAction() {
override fun executeMainAction(payload: PayloadHolder, attrs: AttrForAction, callback: ActionCallback) {
val dataForHashing = attrs.itemList.findDataItem(TlvId.TransactionOutHash) ?: return
val dataForHashing = attrs.itemList.findItem(TlvId.TransactionOutHash) ?: return
val hash = dataForHashing.getData() as? ByteArray ?: return
val cardId = attrs.itemList.findDataItem(TlvId.CardId)?.viewModel?.data ?: return
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
}
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {

View file

@ -1,25 +0,0 @@
package com.tangem.tangemtest.ucase.domain.paramsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.DepersonalizeItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.PersonalizeItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.SignItemsManager
import com.tangem.tangemtest.ucase.resources.ActionType
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
/**
[REDACTED_AUTHOR]
*/
class ParamsManagerFactory : BaseTypedHolder<ActionType, ItemsManager>() {
companion object {
fun createFactory(): ParamsManagerFactory {
return ParamsManagerFactory().apply {
register(ActionType.Scan, ScanItemsManager())
register(ActionType.Sign, SignItemsManager())
register(ActionType.Personalize, PersonalizeItemsManager())
register(ActionType.Depersonalize, DepersonalizeItemsManager())
}
}
}
}

View file

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

View file

@ -1,39 +1,59 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import androidx.lifecycle.LifecycleObserver
import com.tangem.TangemSdk
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.Payload
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.abstraction.findDataItem
import com.tangem.tangemtest._arch.structure.abstraction.findItem
import com.tangem.tangemtest.ucase.domain.actions.Action
import com.tangem.tangemtest.ucase.domain.actions.AttrForAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.AffectedItemsCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
/**
[REDACTED_AUTHOR]
*/
abstract class BaseItemsManager(protected val action: Action) : ItemsManager {
open class BaseItemsManager(protected val action: Action) : ItemsManager, LifecycleObserver {
protected val itemList: List<Item> by lazy { createItemsList() }
override val consequence: ItemsChangeConsequence? = null
init {
Log.d(this, "new instance created")
}
override val payload: MutableMap<String, Any?> = mutableMapOf()
protected var changeConsequence: ItemsChangeConsequence? = null
protected val itemList: MutableList<Item> = mutableListOf()
override fun itemChanged(id: Id, value: Any?, callback: AffectedItemsCallback?) {
if (itemList.isEmpty()) return
val foundItem = itemList.findDataItem(id) ?: return
val foundItem = itemList.findItem(id) ?: return
foundItem.setData(value)
applyChangesByAffectedItems(foundItem, callback)
}
override fun getItems(): MutableList<Item> = itemList.toMutableList()
override fun setItems(items: List<Item>) {
itemList.clear()
itemList.addAll(items)
}
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? = null
override fun getItems(): List<Item> = itemList
override fun setItemChangeConsequences(consequence: ItemsChangeConsequence?) {
this.changeConsequence = consequence
}
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
}
override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(tangemSdk))
}
override fun attachPayload(payload: Payload) {
payload.forEach { this.payload[it.key] = it.value }
@ -41,11 +61,9 @@ abstract class BaseItemsManager(protected val action: Action) : ItemsManager {
// Use it if current item needs to be affect any other items
protected open fun applyChangesByAffectedItems(param: Item, callback: AffectedItemsCallback?) {
consequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) }
changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) }
}
protected fun getAttrsForAction(cardManager: CardManager)
: AttrForAction = AttrForAction(cardManager, itemList, payload, consequence)
abstract fun createItemsList(): List<Item>
protected fun getAttrsForAction(tangemSdk: TangemSdk)
: AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence)
}

View file

@ -1,26 +0,0 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) {
override fun createItemsList(): List<Item> {
return listOf(EditTextItem(TlvId.CardId, null))
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(cardManager))
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.OnLifecycleEvent
import com.tangem.TangemSdk
import com.tangem.tangemtest.commons.Store
import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.variants.personalize.converter.PersonalizationConfigConverter
import com.tangem.tangemtest.ucase.variants.personalize.dto.PersonalizationConfig
/**
[REDACTED_AUTHOR]
*/
class PersonalizationItemsManager(
private val store: Store<PersonalizationConfig>
) : BaseItemsManager(PersonalizeAction()) {
private val converter = PersonalizationConfigConverter()
init {
val config = store.restore()
setItems(converter.convert(config))
}
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
val config = converter.convert(itemList, PersonalizationConfig())
store.save(config)
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
/**
[REDACTED_AUTHOR]
*/
class PersonalizeItemsManager : BaseItemsManager(PersonalizeAction()) {
override fun createItemsList(): List<Item> = listOf()
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest.ucase.domain.actions.ScanAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
/**
[REDACTED_AUTHOR]
*/
class ScanItemsManager : BaseItemsManager(ScanAction()) {
override fun createItemsList(): List<Item> = listOf()
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
}

View file

@ -1,34 +0,0 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.CardManager
import com.tangem.tangemtest._arch.structure.Id
import com.tangem.tangemtest._arch.structure.abstraction.Item
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.ucase.domain.actions.SignAction
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.ItemsChangeConsequence
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class SignItemsManager : BaseItemsManager(SignAction()) {
override val consequence: ItemsChangeConsequence? by lazy { SignScanConsequence() }
override fun createItemsList(): List<Item> {
return listOf(
EditTextItem(TlvId.CardId, null),
EditTextItem(TlvId.TransactionOutHash, "Data used for hashing")
)
}
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
}
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? {
return action.getActionByTag(this, id, getAttrsForAction(cardManager))
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
import com.tangem.tangemtest._arch.structure.impl.EditTextItem
import com.tangem.tangemtest.ucase.domain.actions.DepersonalizeAction
import com.tangem.tangemtest.ucase.domain.actions.ScanAction
import com.tangem.tangemtest.ucase.domain.actions.SignAction
import com.tangem.tangemtest.ucase.domain.paramsManager.triggers.changeConsequence.SignScanConsequence
import com.tangem.tangemtest.ucase.variants.TlvId
/**
[REDACTED_AUTHOR]
*/
class ScanItemsManager : BaseItemsManager(ScanAction())
class DepersonalizeItemsManager : BaseItemsManager(DepersonalizeAction()) {
init {
setItems(listOf(EditTextItem(TlvId.CardId, null)))
}
}
class SignItemsManager : BaseItemsManager(SignAction()) {
init {
setItemChangeConsequences(SignScanConsequence())
setItems(listOf(
EditTextItem(TlvId.CardId, null),
EditTextItem(TlvId.TransactionOutHash, "Data used for hashing")
))
}
}

Some files were not shown because too many files have changed in this diff Show more