Updated on 2026-08-14
This commit is contained in:
commit
bafca42f6e
174 changed files with 4471 additions and 3455 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -13,6 +13,10 @@ local.properties
|
|||
.idea/caches/
|
||||
.idea/libraries/
|
||||
.idea/*.xml
|
||||
.idea/codeStyles
|
||||
|
||||
# LayoutInspector files
|
||||
.captures
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
|
|
|
|||
116
.idea/codeStyles/Project.xml
generated
116
.idea/codeStyles/Project.xml
generated
|
|
@ -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>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
|
|
@ -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
16
.idea/dictionaries/tangem.xml
generated
Normal 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>
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ enum class AnalyticsParam(val param: String) {
|
|||
FIRMWARE("firmware"),
|
||||
;
|
||||
}
|
||||
|
||||
class Analytics {
|
||||
companion object {
|
||||
fun setCardData(ctx: TangemContext): Bundle {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -1,330 +0,0 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.personalization.DepersonalizeCommand
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.commands.personalization.PersonalizeCommand
|
||||
import com.tangem.commands.personalization.entities.Acquirer
|
||||
import com.tangem.commands.personalization.entities.CardConfig
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.commands.personalization.entities.Manufacturer
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.TerminalKeysService
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.tasks.*
|
||||
|
||||
/**
|
||||
* The main interface of Tangem SDK that allows your app to communicate with Tangem cards.
|
||||
*
|
||||
* @property reader is an interface that is responsible for NFC connection and
|
||||
* transfer of data to and from the Tangem Card.
|
||||
* Its default implementation, NfcCardReader, is in our tangem-sdk module.
|
||||
* @property cardManagerDelegate An interface that allows interaction with users and shows relevant UI.
|
||||
* Its default implementation, DefaultCardManagerDelegate, is in our tangem-sdk module.
|
||||
*/
|
||||
class CardManager(
|
||||
private val reader: CardReader,
|
||||
private val cardManagerDelegate: CardManagerDelegate? = null,
|
||||
private val config: Config = Config()
|
||||
) {
|
||||
|
||||
private var terminalKeysService: TerminalKeysService? = null
|
||||
private var isBusy = false
|
||||
|
||||
init {
|
||||
CryptoUtils.initCrypto()
|
||||
}
|
||||
|
||||
/**
|
||||
* To start using any card, you first need to read it using the scanCard() method.
|
||||
* This method launches an NFC session, and once it’s connected with the card,
|
||||
* it obtains the card data. Optionally, if the card contains a wallet (private and public key pair),
|
||||
* it proves that the wallet owns a private key that corresponds to a public one.
|
||||
*
|
||||
* It launches on the new thread a [ScanTask] that will send the following events in a callback:
|
||||
* [ScanEvent.OnReadEvent] after completing [com.tangem.commands.ReadCommand]
|
||||
* [ScanEvent.OnVerifyEvent] after completing [com.tangem.commands.CheckWalletCommand]
|
||||
* [TaskEvent.Completion] with an error field null after successful completion of a task or
|
||||
* [TaskEvent.Completion] with a [TaskError] if some error occurs.
|
||||
* @param callback is triggered on events during a performance of the task,
|
||||
* provides data in form of [ScanEvent] subclasses.
|
||||
*/
|
||||
fun scanCard(callback: (result: TaskEvent<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 issuer’s signature.
|
||||
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. For example, this field may contain information about
|
||||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param callback is triggered on the completion of the [ReadIssuerDataCommand],
|
||||
* provides card response in the form of [ReadIssuerDataResponse].
|
||||
*/
|
||||
fun readIssuerData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerDataResponse>) -> Unit) {
|
||||
val task = ReadIssuerDataTask(config.issuerPublicKey)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This task retrieves Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. . For example, this field may contain photo or
|
||||
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
|
||||
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param callback is triggered on the completion of the [ReadIssuerExtraDataTask],
|
||||
* provides card response in the form of [ReadIssuerExtraDataResponse].
|
||||
*/
|
||||
fun readIssuerExtraData(cardId: String,
|
||||
callback: (result: TaskEvent<ReadIssuerExtraDataResponse>) -> Unit) {
|
||||
val task = ReadIssuerExtraDataTask(config.issuerPublicKey)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This command writes 512-byte Issuer Data field and its issuer’s signature.
|
||||
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. For example, this field may contain information about
|
||||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key.
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
* @param callback is triggered on the completion of the [WriteIssuerDataCommand],
|
||||
* provides card response in the form of [WriteIssuerDataResponse].
|
||||
*/
|
||||
fun writeIssuerData(cardId: String,
|
||||
issuerData: ByteArray,
|
||||
issuerDataSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
callback: (result: TaskEvent<WriteIssuerDataResponse>) -> Unit) {
|
||||
val task = WriteIssuerDataTask(
|
||||
issuerData,
|
||||
issuerDataSignature,
|
||||
issuerDataCounter,
|
||||
config.issuerPublicKey
|
||||
)
|
||||
runTask(task, cardId, callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* This task writes Issuer Extra Data field and its issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
|
||||
* The issuer defines purpose of use, format and payload of Issuer Data.
|
||||
* For example, this field may contain a photo or biometric information for ID card products.
|
||||
* Because of the large size of Issuer_Extra_Data, a series of these commands have to be executed
|
||||
* to write entire Issuer_Extra_Data.
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
|
||||
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
|
||||
* @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
|
||||
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
* @param callback is triggered on the completion of the [WriteIssuerDataCommand],
|
||||
* provides card response in the form of [WriteIssuerDataResponse].
|
||||
*/
|
||||
fun writeIssuerExtraData(cardId: String,
|
||||
issuerData: ByteArray,
|
||||
startingSignature: ByteArray,
|
||||
finalizingSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
callback: (result: TaskEvent<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
|
||||
}
|
||||
226
tangem-core/src/main/java/com/tangem/CardSession.kt
Normal file
226
tangem-core/src/main/java/com/tangem/CardSession.kt
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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),
|
||||
139
tangem-core/src/main/java/com/tangem/SessionError.kt
Normal file
139
tangem-core/src/main/java/com/tangem/SessionError.kt
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
375
tangem-core/src/main/java/com/tangem/TangemSdk.kt
Normal file
375
tangem-core/src/main/java/com/tangem/TangemSdk.kt
Normal 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 it’s connected with the card,
|
||||
* it obtains the card data. Optionally, if the card contains a wallet (private and public key pair),
|
||||
* it proves that the wallet owns a private key that corresponds to a public one.
|
||||
*
|
||||
* @param callback is triggered on the completion of the [ScanTask] and provides card response
|
||||
* in the form of [Card] if the task was performed successfully or [SessionError] in case of an error.
|
||||
*/
|
||||
fun scanCard(initialMessage: Message? = null, callback: (result: CompletionResult<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 issuer’s signature.
|
||||
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. For example, this field may contain information about
|
||||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
*
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param callback is triggered on the completion of the [ReadIssuerDataCommand] and provides
|
||||
* card response in the form of [ReadIssuerDataResponse] if the task was performed successfully
|
||||
* or [SessionError] in case of an error.
|
||||
*/
|
||||
fun readIssuerData(cardId: String, initialMessage: Message? = null,
|
||||
callback: (result: CompletionResult<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 issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. . For example, this field may contain photo or
|
||||
* biometric information for ID card product. Because of the large size of Issuer_Extra_Data,
|
||||
* a series of these commands have to be executed to read the entire Issuer_Extra_Data.
|
||||
*
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param callback is triggered on the completion of the [ReadIssuerExtraDataCommand] and provides
|
||||
* card response in the form of [ReadIssuerExtraDataResponse] if the task was performed successfully
|
||||
* or [SessionError] in case of an error.
|
||||
*/
|
||||
fun readIssuerExtraData(cardId: String,
|
||||
callback: (result: CompletionResult<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 issuer’s signature.
|
||||
* Issuer Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use,
|
||||
* format and payload of Issuer Data. For example, this field may contain information about
|
||||
* wallet balance signed by the issuer or additional issuer’s attestation data.
|
||||
*
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param issuerDataSignature Issuer’s signature of [issuerData] with Issuer Data Private Key.
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
* @param callback is triggered on the completion of the [WriteIssuerDataCommand] and provides
|
||||
* card response in the form of [WriteIssuerDataResponse] if the task was performed successfully
|
||||
* or [SessionError] in case of an error.
|
||||
*/
|
||||
fun writeIssuerData(cardId: String,
|
||||
issuerData: ByteArray,
|
||||
issuerDataSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
initialMessage: Message? = null,
|
||||
callback: (result: CompletionResult<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 issuer’s signature.
|
||||
* Issuer Extra Data is never changed or parsed from within the Tangem COS.
|
||||
* The issuer defines purpose of use, format and payload of Issuer Data.
|
||||
* For example, this field may contain a photo or biometric information for ID card products.
|
||||
* Because of the large size of IssuerExtraData, a series of these commands have to be executed
|
||||
* to write entire IssuerExtraData.
|
||||
*
|
||||
* @param cardId CID, Unique Tangem card ID number.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
|
||||
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
|
||||
* @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
|
||||
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
* @param callback is triggered on the completion of the [WriteIssuerExtraDataCommand] and provides
|
||||
* card response in the form of [WriteIssuerDataResponse] if the task was performed successfully
|
||||
* or [SessionError] in case of an error.
|
||||
*/
|
||||
fun writeIssuerExtraData(cardId: String,
|
||||
issuerData: ByteArray,
|
||||
startingSignature: ByteArray,
|
||||
finalizingSignature: ByteArray,
|
||||
issuerDataCounter: Int? = null,
|
||||
initialMessage: Message? = null,
|
||||
callback: (result: CompletionResult<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
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
117
tangem-core/src/main/java/com/tangem/commands/Command.kt
Normal file
117
tangem-core/src/main/java/com/tangem/commands/Command.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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 card’s data, [ReadCommand] should use the correct pin 1 value.
|
||||
* The card will not respond if wrong pin 1 has been submitted.
|
||||
*/
|
||||
tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1)
|
||||
tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey)
|
||||
tlvBuilder.append(TlvTag.Pin, environment.pin1)
|
||||
tlvBuilder.append(TlvTag.TerminalPublicKey, environment.terminalKeys?.publicKey)
|
||||
return CommandApdu(
|
||||
Instruction.Read, tlvBuilder.serialize(),
|
||||
cardEnvironment.encryptionMode, cardEnvironment.encryptionKey
|
||||
environment.encryptionMode, environment.encryptionKey
|
||||
)
|
||||
}
|
||||
|
||||
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? {
|
||||
val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card {
|
||||
val tlvData = apdu.getTlvData(environment.encryptionKey)
|
||||
?: throw SessionError.DeserializeApduFailed()
|
||||
|
||||
return try {
|
||||
val tlvMapper = TlvMapper(tlvData)
|
||||
val decoder = TlvDecoder(tlvData)
|
||||
|
||||
Card(
|
||||
cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "",
|
||||
manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "",
|
||||
status = tlvMapper.mapOptional(TlvTag.Status),
|
||||
return Card(
|
||||
cardId = decoder.decodeOptional(TlvTag.CardId) ?: "",
|
||||
manufacturerName = decoder.decodeOptional(TlvTag.ManufactureId) ?: "",
|
||||
status = decoder.decodeOptional(TlvTag.Status),
|
||||
|
||||
firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware),
|
||||
cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey),
|
||||
settingsMask = tlvMapper.mapOptional(TlvTag.SettingsMask),
|
||||
issuerPublicKey = tlvMapper.mapOptional(TlvTag.IssuerDataPublicKey),
|
||||
curve = tlvMapper.mapOptional(TlvTag.CurveId),
|
||||
maxSignatures = tlvMapper.mapOptional(TlvTag.MaxSignatures),
|
||||
signingMethod = tlvMapper.mapOptional(TlvTag.SigningMethod),
|
||||
pauseBeforePin2 = tlvMapper.mapOptional(TlvTag.PauseBeforePin2),
|
||||
walletPublicKey = tlvMapper.mapOptional(TlvTag.WalletPublicKey),
|
||||
walletRemainingSignatures = tlvMapper.mapOptional(TlvTag.RemainingSignatures),
|
||||
walletSignedHashes = tlvMapper.mapOptional(TlvTag.SignedHashes),
|
||||
health = tlvMapper.mapOptional(TlvTag.Health),
|
||||
isActivated = tlvMapper.map(TlvTag.IsActivated),
|
||||
activationSeed = tlvMapper.mapOptional(TlvTag.ActivationSeed),
|
||||
paymentFlowVersion = tlvMapper.mapOptional(TlvTag.PaymentFlowVersion),
|
||||
userCounter = tlvMapper.mapOptional(TlvTag.UserCounter),
|
||||
terminalIsLinked = tlvMapper.map(TlvTag.TerminalIsLinked),
|
||||
firmwareVersion = decoder.decodeOptional(TlvTag.Firmware),
|
||||
cardPublicKey = decoder.decodeOptional(TlvTag.CardPublicKey),
|
||||
settingsMask = decoder.decodeOptional(TlvTag.SettingsMask),
|
||||
issuerPublicKey = decoder.decodeOptional(TlvTag.IssuerDataPublicKey),
|
||||
curve = decoder.decodeOptional(TlvTag.CurveId),
|
||||
maxSignatures = decoder.decodeOptional(TlvTag.MaxSignatures),
|
||||
signingMethod = decoder.decodeOptional(TlvTag.SigningMethod),
|
||||
pauseBeforePin2 = decoder.decodeOptional(TlvTag.PauseBeforePin2),
|
||||
walletPublicKey = decoder.decodeOptional(TlvTag.WalletPublicKey),
|
||||
walletRemainingSignatures = decoder.decodeOptional(TlvTag.RemainingSignatures),
|
||||
walletSignedHashes = decoder.decodeOptional(TlvTag.SignedHashes),
|
||||
health = decoder.decodeOptional(TlvTag.Health),
|
||||
isActivated = decoder.decode(TlvTag.IsActivated),
|
||||
activationSeed = decoder.decodeOptional(TlvTag.ActivationSeed),
|
||||
paymentFlowVersion = decoder.decodeOptional(TlvTag.PaymentFlowVersion),
|
||||
userCounter = decoder.decodeOptional(TlvTag.UserCounter),
|
||||
userProtectedCounter = decoder.decodeOptional(TlvTag.UserProtectedCounter),
|
||||
terminalIsLinked = decoder.decode(TlvTag.TerminalIsLinked),
|
||||
|
||||
cardData = deserializeCardData(tlvData)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw TaskError.SerializeCommandError()
|
||||
}
|
||||
cardData = deserializeCardData(tlvData)
|
||||
)
|
||||
}
|
||||
|
||||
private fun deserializeCardData(tlvData: List<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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.SessionError
|
||||
import com.tangem.commands.common.DefaultIssuerDataVerifier
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.commands.common.IssuerDataVerifier
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.tlv.TlvBuilder
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvDecoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* This command writes Issuer Extra Data field and its issuer’s signature.
|
||||
|
|
@ -33,17 +36,94 @@ class WriteIssuerExtraDataCommand(
|
|||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerDataCounter: Int? = null,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
verifier: IssuerDataVerifier = DefaultIssuerDataVerifier()
|
||||
) : CommandSerializer<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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
}
|
||||
|
|
@ -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].
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package com.tangem.tasks
|
||||
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.commands.common.IssuerDataToVerify
|
||||
import com.tangem.common.CardEnvironment
|
||||
import com.tangem.common.CompletionResult
|
||||
|
||||
/**
|
||||
* This task performs [WriteIssuerExtraDataCommand] repeatedly until the issuer extra data is fully
|
||||
* written on the card.
|
||||
* @param issuerData Data provided by issuer.
|
||||
* @param startingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerDataCounter] (if flags Protect_Issuer_Data_Against_Replay and
|
||||
* Restrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]) and size of [issuerData].
|
||||
* @param finalizingSignature Issuer’s signature with Issuer Data Private Key of [cardId],
|
||||
* [issuerData] and [issuerDataCounter] (the latter one only if flags Protect_Issuer_Data_Against_Replay
|
||||
* andRestrict_Overwrite_Issuer_Extra_Data are set in [SettingsMask]).
|
||||
* @param issuerDataCounter An optional counter that protect issuer data against replay attack.
|
||||
*/
|
||||
internal class WriteIssuerExtraDataTask(
|
||||
private val issuerData: ByteArray,
|
||||
private val startingSignature: ByteArray,
|
||||
private val finalizingSignature: ByteArray,
|
||||
private val issuerPublicKey: ByteArray? = null,
|
||||
private val issuerDataCounter: Int? = null
|
||||
) : Task<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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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>)
|
||||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.tangemtest.commons
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface Store<M> {
|
||||
fun save(config: M)
|
||||
fun restore(): M
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)? {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)? {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue