Updated on 2026-08-14
This commit is contained in:
commit
c725b5a7ca
79 changed files with 1922 additions and 1932 deletions
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>
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
221
tangem-core/src/main/java/com/tangem/CardSession.kt
Normal file
221
tangem-core/src/main/java/com/tangem/CardSession.kt
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
package com.tangem
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.OpenSessionCommand
|
||||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.crypto.EncryptionHelper
|
||||
import com.tangem.crypto.FastEncryptionHelper
|
||||
import com.tangem.crypto.StrongEncryptionHelper
|
||||
import com.tangem.crypto.pbkdf2Hash
|
||||
|
||||
/**
|
||||
* Basic interface for running tasks and [com.tangem.commands.Command] in a [CardSession]
|
||||
*/
|
||||
interface CardSessionRunnable<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
|
||||
) {
|
||||
|
||||
/**
|
||||
* 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) 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 -> {
|
||||
when (environment.encryptionMode) {
|
||||
EncryptionMode.NONE -> {
|
||||
environment.encryptionKey = null
|
||||
environment.encryptionMode = EncryptionMode.FAST
|
||||
}
|
||||
EncryptionMode.FAST -> {
|
||||
environment.encryptionKey = null
|
||||
environment.encryptionMode = EncryptionMode.STRONG
|
||||
}
|
||||
EncryptionMode.STRONG -> {
|
||||
Log.e(this::class.simpleName!!, "Encryption doesn't work")
|
||||
callback(CompletionResult.Failure(SessionError.NeedEncryption()))
|
||||
}
|
||||
}
|
||||
return establishEncryption(callback)
|
||||
}
|
||||
else -> callback(CompletionResult.Failure(SessionError.UnknownError()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun establishEncryption(callback: (result: CompletionResult<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.calculateSha256().pbkdf2Hash(uid, 50)
|
||||
val secret = encryptionHelper.generateSecret(result.data.sessionKeyB)
|
||||
val sessionKey = (secret + protocolKey).calculateSha256()
|
||||
environment.encryptionKey = sessionKey
|
||||
callback(CompletionResult.Success(true))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,17 +1,22 @@
|
|||
package com.tangem.common
|
||||
package com.tangem
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.EllipticCurve
|
||||
import com.tangem.common.extensions.calculateSha256
|
||||
import com.tangem.crypto.CryptoUtils.generatePublicKey
|
||||
|
||||
|
||||
/**
|
||||
* Contains data relating to a Tangem card. It is used in constructing all the commands,
|
||||
* and commands can return modified [CardEnvironment].
|
||||
* and commands can return modified [SessionEnvironment].
|
||||
*
|
||||
* @property card Current card, read by preflight [com.tangem.commands.ReadCommand].
|
||||
* @property terminalKeys generated terminal keys used in Linked Terminal feature.
|
||||
*/
|
||||
data class CardEnvironment(
|
||||
data class SessionEnvironment(
|
||||
val pin1: String = DEFAULT_PIN,
|
||||
val pin2: String = DEFAULT_PIN2,
|
||||
val cardId: String? = null,
|
||||
var card: Card? = null,
|
||||
val terminalKeys: KeyPair? = null,
|
||||
var encryptionMode: EncryptionMode = EncryptionMode.NONE,
|
||||
var encryptionKey: ByteArray? = null,
|
||||
|
|
@ -24,6 +29,9 @@ data class CardEnvironment(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All possible encryption modes.
|
||||
*/
|
||||
enum class EncryptionMode(val code: Byte) {
|
||||
NONE(0x0),
|
||||
FAST(0x1),
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
116
tangem-core/src/main/java/com/tangem/commands/Command.kt
Normal file
116
tangem-core/src/main/java/com/tangem/commands/Command.kt
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package com.tangem.commands
|
||||
|
||||
import com.tangem.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.common.apdu.StatusWord
|
||||
import com.tangem.common.apdu.toSessionError
|
||||
import com.tangem.common.extensions.toInt
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
|
||||
/**
|
||||
* Basic interface for a parsed response from [Command].
|
||||
*/
|
||||
interface CommandResponse
|
||||
|
||||
/**
|
||||
* Basic class for Tangem card commands
|
||||
*/
|
||||
abstract class Command<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) {
|
||||
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,13 +1,13 @@
|
|||
package com.tangem.common.tlv
|
||||
|
||||
import com.tangem.Log
|
||||
import com.tangem.SessionError
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.commands.common.IssuerDataMode
|
||||
import com.tangem.common.extensions.toDate
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.extensions.toInt
|
||||
import com.tangem.common.extensions.toUtf8
|
||||
import com.tangem.tasks.TaskError
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
|
|
@ -16,7 +16,7 @@ import java.util.*
|
|||
*
|
||||
* @property tlvList List of TLVs, which values are to be converted to particular classes.
|
||||
*/
|
||||
class TlvMapper(val tlvList: List<Tlv>) {
|
||||
class TlvDecoder(val tlvList: List<Tlv>) {
|
||||
|
||||
/**
|
||||
* Finds [Tlv] by its [TlvTag].
|
||||
|
|
@ -26,10 +26,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 +44,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 +68,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 +85,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 +96,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 +113,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 +122,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 +131,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 +146,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,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")
|
||||
}
|
||||
|
|
@ -3,16 +3,14 @@ package com.tangem.tangemtest
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskError
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.android.synthetic.main.old_activity_main.*
|
||||
|
||||
class Old_MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var cardManager: CardManager
|
||||
private lateinit var tangemSdk: TangemSdk
|
||||
private lateinit var cardId: String
|
||||
private lateinit var issuerData: ByteArray
|
||||
private lateinit var issuerDataSignature: ByteArray
|
||||
|
|
@ -22,66 +20,50 @@ class Old_MainActivity : AppCompatActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.old_activity_main)
|
||||
|
||||
cardManager = CardManager.init(this)
|
||||
tangemSdk = TangemSdk.init(this)
|
||||
|
||||
btn_scan?.setOnClickListener { _ ->
|
||||
cardManager.scanCard { taskEvent ->
|
||||
tangemSdk.scanCard { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is TaskEvent.Event -> {
|
||||
when (taskEvent.data) {
|
||||
is ScanEvent.OnReadEvent -> {
|
||||
// Handle returned card data
|
||||
cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
|
||||
runOnUiThread {
|
||||
tv_card_cid?.text = cardId
|
||||
btn_create_wallet.isEnabled = true
|
||||
}
|
||||
}
|
||||
is ScanEvent.OnVerifyEvent -> {
|
||||
//Handle card verification
|
||||
runOnUiThread {
|
||||
tv_card_cid?.text = cardId
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_read_issuer_extra_data.isEnabled = true
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = true
|
||||
}
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
// Handle returned card data
|
||||
val card = taskEvent.data
|
||||
cardId = card.cardId
|
||||
runOnUiThread {
|
||||
tv_card_cid?.text = cardId
|
||||
btn_create_wallet.isEnabled = true
|
||||
tv_card_cid?.text = cardId
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_read_issuer_extra_data.isEnabled = true
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = true
|
||||
|
||||
}
|
||||
}
|
||||
is TaskEvent.Completion -> {
|
||||
if (taskEvent.error != null) {
|
||||
if (taskEvent.error is TaskError.UserCancelled) {
|
||||
// Handle case when user cancelled manually
|
||||
}
|
||||
// Handle other errors
|
||||
}
|
||||
// Handle completion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_sign?.setOnClickListener { _ ->
|
||||
cardManager.sign(
|
||||
tangemSdk.sign(
|
||||
createSampleHashes(),
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." }
|
||||
is CompletionResult.Success -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." }
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_data?.setOnClickListener { _ ->
|
||||
cardManager.readIssuerData(cardId) {
|
||||
tangemSdk.readIssuerData(cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = it.data.issuerData.contentToString()
|
||||
issuerData = it.data.issuerData
|
||||
|
|
@ -91,27 +73,27 @@ class Old_MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
btn_write_issuer_data?.setOnClickListener { _ ->
|
||||
cardManager.writeIssuerData(
|
||||
tangemSdk.writeIssuerData(
|
||||
cardId,
|
||||
issuerData,
|
||||
issuerDataSignature) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.cardId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_extra_data?.setOnClickListener { _ ->
|
||||
cardManager.readIssuerExtraData(cardId) {
|
||||
tangemSdk.readIssuerExtraData(cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data."
|
||||
|
|
@ -120,26 +102,26 @@ class Old_MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
btn_purge_wallet?.setOnClickListener { _ ->
|
||||
cardManager.purgeWallet(
|
||||
tangemSdk.purgeWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_create_wallet?.setOnClickListener { _ ->
|
||||
cardManager.createWallet(
|
||||
tangemSdk.createWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
is CompletionResult.Failure -> {
|
||||
runOnUiThread { tv_card_cid?.text = it.error::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
is CompletionResult.Success -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.tangemtest._main
|
|||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import com.tangem.commands.CommandResponse
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
|
|
@ -11,16 +11,16 @@ import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
|||
class MainViewModel : ViewModel() {
|
||||
val ldDescriptionSwitch = MutableLiveData<Boolean>(false)
|
||||
|
||||
var responseEvent: TaskEvent<*>? = null
|
||||
var commandResponse: CommandResponse? = null
|
||||
|
||||
fun switchToggled(state: Boolean) {
|
||||
ldDescriptionSwitch.postValue(state)
|
||||
}
|
||||
|
||||
fun changeResponseEvent(event: TaskEvent<*>?) {
|
||||
fun changeResponseEvent(commandResponse: CommandResponse?) {
|
||||
Log.d(this, "changeResponseEvent")
|
||||
val reqEvent = event ?: return
|
||||
val response = commandResponse ?: return
|
||||
|
||||
responseEvent = reqEvent
|
||||
this.commandResponse = response
|
||||
}
|
||||
}
|
||||
|
|
@ -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,17 +31,17 @@ interface Action {
|
|||
abstract class BaseAction : Action {
|
||||
protected open fun handleResult(
|
||||
payload: PayloadHolder,
|
||||
taskEvent: TaskEvent<*>,
|
||||
commandResult: CompletionResult<*>,
|
||||
modifier: AfterActionModification?,
|
||||
attrs: AttrForAction,
|
||||
callback: ActionCallback
|
||||
) {
|
||||
val modifiedItems = mutableListOf<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
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class DepersonalizeAction : BaseAction() {
|
|||
val item = attrs.itemList.findItem(TlvId.CardId) ?: return
|
||||
val cardId = item.viewModel.data as? String ?: return
|
||||
|
||||
attrs.cardManager.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
|
||||
attrs.tangemSdk.depersonalize(cardId) { handleResult(payload, it, null, attrs, callback) }
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class PersonalizeAction : BaseAction() {
|
|||
val personalizeConfig = PersonalizationConfigConverter().convert(itemList, PersonalizationConfig())
|
||||
val cardConfig = PersonalizationConfigToCardConfig().convert(personalizeConfig)
|
||||
|
||||
attrs.cardManager.personalize(cardConfig, issuer, manufacturer, acquirer) {
|
||||
attrs.tangemSdk.personalize(cardConfig, issuer, manufacturer, acquirer) {
|
||||
handleResult(payload, it, null, attrs, callback)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ class SignAction : BaseAction() {
|
|||
val hash = dataForHashing.getData() as? ByteArray ?: return
|
||||
val cardId = attrs.itemList.findItem(TlvId.CardId)?.viewModel?.data ?: return
|
||||
|
||||
attrs.cardManager.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
|
||||
attrs.tangemSdk.sign(arrayOf(hash), stringOf(cardId)) { handleResult(payload, it, null, attrs, callback) }
|
||||
}
|
||||
|
||||
override fun getActionByTag(payload: PayloadHolder, id: Id, attrs: AttrForAction): ((ActionCallback) -> Unit)? {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -22,8 +22,8 @@ interface ItemsManager : PayloadHolder {
|
|||
fun setItems(items: List<Item>)
|
||||
fun getItems(): List<Item>
|
||||
fun setItemChangeConsequences(consequence: ItemsChangeConsequence?)
|
||||
fun invokeMainAction(cardManager: CardManager, callback: ActionCallback)
|
||||
fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)?
|
||||
fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback)
|
||||
fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)?
|
||||
fun attachPayload(payload: Payload)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tangemtest.ucase.domain.paramsManager.managers
|
||||
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.Payload
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
|
|
@ -47,12 +47,12 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
|
|||
this.changeConsequence = consequence
|
||||
}
|
||||
|
||||
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
|
||||
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
|
||||
}
|
||||
|
||||
override fun getActionByTag(id: Id, cardManager: CardManager): ((ActionCallback) -> Unit)? {
|
||||
return action.getActionByTag(this, id, getAttrsForAction(cardManager))
|
||||
override fun getActionByTag(id: Id, tangemSdk: TangemSdk): ((ActionCallback) -> Unit)? {
|
||||
return action.getActionByTag(this, id, getAttrsForAction(tangemSdk))
|
||||
}
|
||||
|
||||
override fun attachPayload(payload: Payload) {
|
||||
|
|
@ -64,6 +64,6 @@ open class BaseItemsManager(protected val action: Action) : ItemsManager, Lifecy
|
|||
changeConsequence?.affectChanges(this, param, itemList)?.let { callback?.invoke(it) }
|
||||
}
|
||||
|
||||
protected fun getAttrsForAction(cardManager: CardManager)
|
||||
: AttrForAction = AttrForAction(cardManager, itemList, payload, changeConsequence)
|
||||
protected fun getAttrsForAction(tangemSdk: TangemSdk)
|
||||
: AttrForAction = AttrForAction(tangemSdk, itemList, payload, changeConsequence)
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.managers
|
|||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.tangemtest.commons.Store
|
||||
import com.tangem.tangemtest.ucase.domain.actions.PersonalizeAction
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ActionCallback
|
||||
|
|
@ -23,8 +23,8 @@ class PersonalizationItemsManager(
|
|||
setItems(converter.convert(config))
|
||||
}
|
||||
|
||||
override fun invokeMainAction(cardManager: CardManager, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(cardManager), callback)
|
||||
override fun invokeMainAction(tangemSdk: TangemSdk, callback: ActionCallback) {
|
||||
action.executeMainAction(this, getAttrsForAction(tangemSdk), callback)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangemtest._arch.structure.PayloadHolder
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tasks.TaskEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -12,5 +12,5 @@ import com.tangem.tasks.TaskEvent
|
|||
* Returns a list of items that have been modified
|
||||
*/
|
||||
interface AfterActionModification {
|
||||
fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List<Item>): List<Item>
|
||||
fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List<Item>): List<Item>
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.tangemtest.ucase.domain.paramsManager.triggers.afterAction
|
|||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CardStatus
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangemtest._arch.structure.PayloadHolder
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.findItem
|
||||
|
|
@ -9,17 +10,16 @@ import com.tangem.tangemtest.ucase.domain.paramsManager.PayloadKey
|
|||
import com.tangem.tangemtest.ucase.tunnel.ActionView
|
||||
import com.tangem.tangemtest.ucase.tunnel.CardError
|
||||
import com.tangem.tangemtest.ucase.variants.TlvId
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskEvent
|
||||
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.threading.postUI
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AfterScanModifier : AfterActionModification {
|
||||
override fun modify(payload: PayloadHolder, taskEvent: TaskEvent<*>, itemList: List<Item>): List<Item> {
|
||||
override fun modify(payload: PayloadHolder, commandResult: CompletionResult<*>, itemList: List<Item>): List<Item> {
|
||||
val foundItem = itemList.findItem(TlvId.CardId) ?: return listOf()
|
||||
val card = smartCast(taskEvent)?.card ?: return listOf()
|
||||
val card = smartCast(commandResult) ?: return listOf()
|
||||
val actionView = payload.get(PayloadKey.actionView) as? ActionView ?: return listOf()
|
||||
|
||||
return if (isNotPersonalized(card)) {
|
||||
|
|
@ -34,8 +34,8 @@ class AfterScanModifier : AfterActionModification {
|
|||
}
|
||||
}
|
||||
|
||||
private fun smartCast(taskEvent: TaskEvent<*>): ScanEvent.OnReadEvent? {
|
||||
return (taskEvent as? TaskEvent.Event)?.data as? ScanEvent.OnReadEvent
|
||||
private fun smartCast(commandResult: CompletionResult<*>): Card? {
|
||||
return (commandResult as? CompletionResult.Success<Card>)?.data
|
||||
}
|
||||
|
||||
private fun isNotPersonalized(card: Card): Boolean = card.status == CardStatus.NotPersonalized
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
package com.tangem.tangemtest.ucase.domain.responses
|
||||
|
||||
import com.google.gson.*
|
||||
import com.tangem.commands.ProductMask
|
||||
import com.tangem.commands.Settings
|
||||
import com.tangem.commands.SettingsMask
|
||||
import com.tangem.commands.SigningMethod
|
||||
import com.tangem.commands.*
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import java.lang.reflect.Type
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
|
@ -29,12 +25,7 @@ class ResponseJsonConverter {
|
|||
return builder.create()
|
||||
}
|
||||
|
||||
fun convertTaskEvent(task: TaskEvent<*>?): String = gson.toJson(task)
|
||||
|
||||
fun convertEvent(task: TaskEvent<*>?): String {
|
||||
val event = task as? TaskEvent.Event ?: ""
|
||||
return gson.toJson(event)
|
||||
}
|
||||
fun convertResponse(response: CommandResponse?): String = gson.toJson(response)
|
||||
}
|
||||
|
||||
class ByteTypeAdapter : JsonSerializer<ByteArray> {
|
||||
|
|
|
|||
|
|
@ -4,19 +4,19 @@ import android.view.View
|
|||
import androidx.annotation.UiThread
|
||||
import androidx.lifecycle.*
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.SessionError
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangemtest._arch.SingleLiveEvent
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.Payload
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.iterate
|
||||
import com.tangem.tangemtest.commons.performAction
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.responses.ResponseJsonConverter
|
||||
import com.tangem.tangemtest.ucase.tunnel.ViewScreen
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskError
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
|
|
@ -28,19 +28,19 @@ class ActionViewModelFactory(private val manager: ItemsManager) : ViewModelProvi
|
|||
|
||||
class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), LifecycleObserver {
|
||||
|
||||
val seResponseEvent = SingleLiveEvent<TaskEvent<*>>()
|
||||
val seReadResponse = SingleLiveEvent<String>()
|
||||
val seResponse = SingleLiveEvent<String>()
|
||||
val seResponse = SingleLiveEvent<CommandResponse>()
|
||||
val seResponseData = SingleLiveEvent<CommandResponse>()
|
||||
val seResponseCardData = SingleLiveEvent<Card>()
|
||||
|
||||
val ldItemList = MutableLiveData(itemsManager.getItems())
|
||||
val seError: MutableLiveData<String> = SingleLiveEvent()
|
||||
val seChangedItems: MutableLiveData<List<Item>> = SingleLiveEvent()
|
||||
|
||||
private val notifier: Notifier = Notifier(this)
|
||||
private lateinit var cardManager: CardManager
|
||||
private lateinit var tangemSdk: TangemSdk
|
||||
|
||||
fun setCardManager(cardManager: CardManager) {
|
||||
this.cardManager = cardManager
|
||||
fun setCardManager(tangemSdk: TangemSdk) {
|
||||
this.tangemSdk = tangemSdk
|
||||
}
|
||||
|
||||
@Deprecated("Events must be send directly from the Widget")
|
||||
|
|
@ -54,15 +54,17 @@ class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), Lif
|
|||
|
||||
//invokes Scan, Sign etc...
|
||||
fun invokeMainAction() {
|
||||
performAction(itemsManager, cardManager, { paramsManager, cardManager ->
|
||||
paramsManager.invokeMainAction(cardManager) { response, listOfChangedParams ->
|
||||
notifier.handleActionResult(response, listOfChangedParams)
|
||||
}
|
||||
})
|
||||
if (!::tangemSdk.isInitialized) {
|
||||
Log.e(this, "TangemSdk isn't initialized")
|
||||
return
|
||||
}
|
||||
itemsManager.invokeMainAction(tangemSdk) { response, listOfChangedParams ->
|
||||
notifier.handleActionResult(response, listOfChangedParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun getItemAction(id: Id): (() -> Unit)? {
|
||||
val itemFunction = itemsManager.getActionByTag(id, cardManager) ?: return null
|
||||
val itemFunction = itemsManager.getActionByTag(id, tangemSdk) ?: return null
|
||||
|
||||
return {
|
||||
itemFunction { response, listOfChangedParams ->
|
||||
|
|
@ -91,12 +93,12 @@ class ActionViewModel(private val itemsManager: ItemsManager) : ViewModel(), Lif
|
|||
|
||||
internal class Notifier(private val vm: ActionViewModel) {
|
||||
|
||||
private var notShowedError: TaskError? = null
|
||||
private var notShowedError: SessionError? = null
|
||||
private val gson: Gson = ResponseJsonConverter().gson
|
||||
|
||||
fun handleActionResult(response: TaskEvent<*>, list: List<Item>) {
|
||||
fun handleActionResult(result: CompletionResult<*>, list: List<Item>) {
|
||||
if (list.isNotEmpty()) notifyItemsChanged(list)
|
||||
handleResponse(response)
|
||||
handleCompletionResult(result)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
|
|
@ -104,47 +106,35 @@ internal class Notifier(private val vm: ActionViewModel) {
|
|||
vm.seChangedItems.postValue(list)
|
||||
}
|
||||
|
||||
fun handleResponse(response: TaskEvent<*>) {
|
||||
val taskEvent = response as? TaskEvent<*> ?: return
|
||||
fun handleCompletionResult(result: CompletionResult<*>) {
|
||||
val commandResponse = result as? CompletionResult<CommandResponse> ?: return
|
||||
|
||||
when (taskEvent) {
|
||||
is TaskEvent.Completion -> handleCompletionEvent(taskEvent)
|
||||
is TaskEvent.Event -> {
|
||||
handleDataEvent(taskEvent.data)
|
||||
vm.seResponseEvent.postValue(taskEvent)
|
||||
}
|
||||
when (commandResponse) {
|
||||
is CompletionResult.Success -> handleData(commandResponse.data)
|
||||
is CompletionResult.Failure -> handleError(commandResponse.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDataEvent(event: Any?) {
|
||||
private fun handleData(event: CommandResponse?) {
|
||||
vm.seResponse.postValue(event)
|
||||
when (event) {
|
||||
is ScanEvent.OnReadEvent -> vm.seReadResponse.postValue(gson.toJson(event))
|
||||
is ScanEvent.OnVerifyEvent -> {
|
||||
}
|
||||
else -> vm.seResponse.postValue(gson.toJson(event))
|
||||
is Card -> vm.seResponseCardData.postValue(event)
|
||||
else -> vm.seResponseData.postValue(event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) {
|
||||
if (taskEvent.error == null) {
|
||||
Log.d(this, "error = null")
|
||||
if (notShowedError != null) {
|
||||
vm.seError.postValue("${notShowedError!!::class.simpleName}")
|
||||
notShowedError = null
|
||||
}
|
||||
} else {
|
||||
Log.d(this, "error = ${taskEvent.error}")
|
||||
when (taskEvent.error) {
|
||||
is TaskError.UserCancelled -> {
|
||||
if (notShowedError == null) {
|
||||
vm.seError.postValue("User canceled the action")
|
||||
} else {
|
||||
vm.seError.postValue("${notShowedError!!::class.simpleName}")
|
||||
notShowedError = null
|
||||
}
|
||||
private fun handleError(error: SessionError) {
|
||||
Log.d(this, "error = $error")
|
||||
when (error) {
|
||||
is SessionError.UserCancelled -> {
|
||||
if (notShowedError == null) {
|
||||
vm.seError.postValue("User canceled the action")
|
||||
} else {
|
||||
vm.seError.postValue("${notShowedError!!::class.simpleName}")
|
||||
notShowedError = null
|
||||
}
|
||||
else -> notShowedError = taskEvent.error
|
||||
}
|
||||
else -> notShowedError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,8 @@ import androidx.fragment.app.activityViewModels
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
|
|
@ -43,7 +44,7 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
|
||||
bindViews()
|
||||
viewLifecycleOwner.lifecycle.addObserver(actionVM)
|
||||
actionVM.setCardManager(CardManager.init(requireActivity()))
|
||||
actionVM.setCardManager(TangemSdk.init(requireActivity()))
|
||||
actionVM.attachToPayload(mutableMapOf(PayloadKey.actionView to this as ActionView))
|
||||
|
||||
initFab()
|
||||
|
|
@ -75,28 +76,37 @@ abstract class BaseCardActionFragment : BaseFragment(), ActionView {
|
|||
|
||||
protected open fun subscribeToViewModelChanges() {
|
||||
Log.d(this, "subscribeToViewModelChanges")
|
||||
listenEvent()
|
||||
listenReadResponse()
|
||||
listenResponse()
|
||||
listenResponseData()
|
||||
listenResponseCardData()
|
||||
listenError()
|
||||
listenChangedItems()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
||||
protected open fun listenEvent() {
|
||||
actionVM.seResponseEvent.observe(viewLifecycleOwner, Observer {
|
||||
private fun listenResponse() {
|
||||
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "listen response: $it")
|
||||
mainActivityVM.changeResponseEvent(it)
|
||||
})
|
||||
}
|
||||
|
||||
protected open fun listenReadResponse() {}
|
||||
|
||||
protected open fun listenResponse() {
|
||||
actionVM.seResponse.observe(viewLifecycleOwner, Observer {
|
||||
protected open fun listenResponseData() {
|
||||
actionVM.seResponseData.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "listen responseData: $it")
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
})
|
||||
}
|
||||
|
||||
protected open fun listenResponseCardData() {
|
||||
actionVM.seResponseCardData.observe(viewLifecycleOwner, Observer {
|
||||
Log.d(this, "listen responseCardData: $it")
|
||||
responseCardDataHandled(it)
|
||||
})
|
||||
}
|
||||
|
||||
protected open fun responseCardDataHandled(card: Card?) {}
|
||||
|
||||
protected open fun listenError() {
|
||||
actionVM.seError.observe(viewLifecycleOwner, Observer { showSnackbar(it) })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.tangemtest.ucase.variants.personalize.dto
|
||||
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.commands.personalization.entities.Acquirer
|
||||
import com.tangem.commands.personalization.entities.Issuer
|
||||
import com.tangem.commands.personalization.entities.Manufacturer
|
||||
import com.tangem.common.KeyPair
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.variants.personalize.ui
|
|||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest._arch.structure.Id
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
|
|
@ -52,20 +53,6 @@ class PersonalizationFragment : BaseCardActionFragment() {
|
|||
})
|
||||
}
|
||||
|
||||
override fun subscribeToViewModelChanges() {
|
||||
Log.d(this, "subscribeToViewModelChanges")
|
||||
listenEvent()
|
||||
listenError()
|
||||
listenDescriptionSwitchChanges()
|
||||
}
|
||||
|
||||
override fun listenEvent() {
|
||||
actionVM.seResponseEvent.observe(viewLifecycleOwner, Observer {
|
||||
mainActivityVM.changeResponseEvent(it)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
})
|
||||
}
|
||||
|
||||
override fun showSnackbar(id: Id, additionalHandler: ((Id) -> Int)?) {
|
||||
super.showSnackbar(id) {
|
||||
when (id) {
|
||||
|
|
@ -75,4 +62,9 @@ class PersonalizationFragment : BaseCardActionFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun responseCardDataHandled(card: Card?) {
|
||||
super.responseCardDataHandled(card)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tangemtest.ucase.variants.responses
|
|||
import android.view.View
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.CommandResponse
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.tangemtest.R
|
||||
|
|
@ -10,8 +11,6 @@ import com.tangem.tangemtest._arch.structure.abstraction.Item
|
|||
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.iterate
|
||||
import com.tangem.tangemtest.ucase.variants.responses.converter.ConvertersStore
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import ru.dev.gbixahue.eu4d.lib.android.global.log.Log
|
||||
|
||||
/**
|
||||
|
|
@ -22,13 +21,13 @@ class ResponseViewModel : ViewModel() {
|
|||
private val convertersHolder = ConvertersStore()
|
||||
private var itemList: List<Item>? = null
|
||||
|
||||
fun createItemList(taskEvent: TaskEvent<*>?): List<Item> {
|
||||
fun createItemList(response: CommandResponse?): List<Item> {
|
||||
Log.d(this, "createItemList: itemList size: ${itemList?.size ?: 0}")
|
||||
|
||||
val event = taskEvent as? TaskEvent.Event<Any> ?: return emptyList()
|
||||
val type = event.data::class.java
|
||||
val responseEvent = response ?: return emptyList()
|
||||
val type = responseEvent::class.java
|
||||
val converter = convertersHolder.get(type) as? ModelToItems<Any> ?: return emptyList()
|
||||
itemList = converter.convert(event.data)
|
||||
itemList = converter.convert(responseEvent)
|
||||
return itemList!!
|
||||
}
|
||||
|
||||
|
|
@ -38,13 +37,12 @@ class ResponseViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun determineTitleId(taskEvent: TaskEvent<*>?): Int {
|
||||
val event = taskEvent as? TaskEvent.Event<Any> ?: return R.string.unknown
|
||||
fun determineTitleId(response: CommandResponse?): Int {
|
||||
val responseEvent = response ?: return R.string.unknown
|
||||
|
||||
return when (event.data) {
|
||||
is ScanEvent.OnReadEvent -> R.string.fg_name_response_scan
|
||||
is SignResponse -> R.string.fg_name_response_sign
|
||||
return when (responseEvent) {
|
||||
is Card -> R.string.fg_name_response_personalization
|
||||
is SignResponse -> R.string.fg_name_response_sign
|
||||
is DepersonalizeResponse -> R.string.fg_name_response_depersonalization
|
||||
else -> R.string.unknown
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ package com.tangem.tangemtest.ucase.variants.responses.converter
|
|||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.common.BaseTypedHolder
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class ConvertersStore : BaseTypedHolder<Type, Any>() {
|
||||
init {
|
||||
register(ScanEvent.OnReadEvent::class.java, ReadEventConverter())
|
||||
// register(CompletionResult.Success<Card>::class.java, ReadEventConverter())
|
||||
register(SignResponse::class.java, SignResponseConverter())
|
||||
register(Card::class.java, CardConverter())
|
||||
register(DepersonalizeResponse::class.java, DepersonalizeResponseConverter())
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
package com.tangem.tangemtest.ucase.variants.responses.converter
|
||||
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.commands.personalization.DepersonalizeResponse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangemtest._arch.structure.StringId
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.Item
|
||||
import com.tangem.tangemtest._arch.structure.abstraction.ModelToItems
|
||||
import com.tangem.tangemtest._arch.structure.impl.TextItem
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import ru.dev.gbixahue.eu4d.lib.kotlin.stringOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ReadEventConverter : ModelToItems<ScanEvent.OnReadEvent> {
|
||||
override fun convert(from: ScanEvent.OnReadEvent): List<Item> = CardConverter().convert(from.card)
|
||||
class ReadEventConverter : ModelToItems<CompletionResult.Success<Card>> {
|
||||
override fun convert(from: CompletionResult.Success<Card>): List<Item> = CardConverter().convert(from.data)
|
||||
}
|
||||
|
||||
class SignResponseConverter : ModelToItems<SignResponse> {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ open class ResponseFragment : BaseFragment() {
|
|||
}
|
||||
|
||||
private fun setTittle() {
|
||||
val titleId = selfVM.determineTitleId(mainActivityVM.responseEvent)
|
||||
val titleId = selfVM.determineTitleId(mainActivityVM.commandResponse)
|
||||
activity?.setTitle(titleId)
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ open class ResponseFragment : BaseFragment() {
|
|||
|
||||
private fun buildWidgets() {
|
||||
val builder = WidgetBuilder(ResponseItemBuilder())
|
||||
val itemList = selfVM.createItemList(mainActivityVM.responseEvent)
|
||||
val itemList = selfVM.createItemList(mainActivityVM.commandResponse)
|
||||
itemList.forEach { builder.build(it, itemContainer) }
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ open class ResponseFragment : BaseFragment() {
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_share -> {
|
||||
shareText(ResponseJsonConverter().convertEvent(mainActivityVM.responseEvent))
|
||||
shareText(ResponseJsonConverter().convertResponse(mainActivityVM.commandResponse))
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.tangemtest.ucase.variants.scan.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Observer
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.ItemsManager
|
||||
import com.tangem.tangemtest.ucase.domain.paramsManager.managers.ScanItemsManager
|
||||
|
|
@ -17,14 +15,12 @@ class ScanActionFragment : BaseCardActionFragment() {
|
|||
|
||||
override fun getLayoutId(): Int = R.layout.fg_action_card_scan
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
enableActionFab(true)
|
||||
override fun initFab() {
|
||||
actionFab.setOnClickListener { actionVM.invokeMainAction() }
|
||||
}
|
||||
|
||||
override fun listenReadResponse() {
|
||||
actionVM.seReadResponse.observe(viewLifecycleOwner, Observer {
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
})
|
||||
override fun responseCardDataHandled(card: Card?) {
|
||||
super.responseCardDataHandled(card)
|
||||
navigateTo(R.id.action_nav_card_action_to_response_screen)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,24 +6,24 @@ import android.view.View
|
|||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.CardManagerDelegate
|
||||
import com.tangem.Log
|
||||
import com.tangem.LoggerInterface
|
||||
import com.tangem.Message
|
||||
import com.tangem.SessionViewDelegate
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.tangem_sdk_new.extensions.hide
|
||||
import com.tangem.tangem_sdk_new.extensions.show
|
||||
import com.tangem.tangem_sdk_new.nfc.NfcReader
|
||||
import com.tangem.tangem_sdk_new.ui.NfcEnableDialog
|
||||
import com.tangem.tangem_sdk_new.ui.TouchCardAnimation
|
||||
import com.tangem.tasks.TaskError
|
||||
import kotlinx.android.synthetic.main.layout_touch_card.*
|
||||
import kotlinx.android.synthetic.main.nfc_bottom_sheet.*
|
||||
|
||||
/**
|
||||
* Default implementation of [CardManagerDelegate].
|
||||
* Default implementation of [SessionViewDelegate].
|
||||
* If no customisation is required, this is the preferred way to use Tangem SDK.
|
||||
*/
|
||||
class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDelegate {
|
||||
class DefaultSessionViewDelegate(private val reader: NfcReader) : SessionViewDelegate {
|
||||
|
||||
lateinit var activity: FragmentActivity
|
||||
private var readingDialog: BottomSheetDialog? = null
|
||||
|
|
@ -33,13 +33,13 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
setLogger()
|
||||
}
|
||||
|
||||
override fun onNfcSessionStarted(cardId: String?) {
|
||||
override fun onNfcSessionStarted(cardId: String?, message: Message?) {
|
||||
reader.readingCancelled = false
|
||||
postUI { showReadingDialog(activity, cardId) }
|
||||
postUI { showReadingDialog(activity, cardId, message) }
|
||||
if (!reader.nfcEnabled) showNFCEnableDialog()
|
||||
}
|
||||
|
||||
private fun showReadingDialog(activity: FragmentActivity, cardId: String?) {
|
||||
private fun showReadingDialog(activity: FragmentActivity, cardId: String?, message: Message?) {
|
||||
val dialogView = activity.layoutInflater.inflate(R.layout.nfc_bottom_sheet, null)
|
||||
readingDialog = BottomSheetDialog(activity)
|
||||
readingDialog?.setContentView(dialogView)
|
||||
|
|
@ -56,6 +56,10 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
readingDialog?.tvCardId?.visibility = View.VISIBLE
|
||||
readingDialog?.tvCardId?.text = cardId
|
||||
}
|
||||
if (message != null) {
|
||||
if (message.body != null) readingDialog?.tvTaskText?.text = message.body
|
||||
if (message.header != null) readingDialog?.tvTaskTitle?.text = message.header
|
||||
}
|
||||
}
|
||||
readingDialog?.setOnCancelListener {
|
||||
reader.readingCancelled = true
|
||||
|
|
@ -132,25 +136,29 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
|
|||
}
|
||||
}
|
||||
|
||||
override fun onNfcSessionCompleted() {
|
||||
override fun onNfcSessionCompleted(message: Message?) {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.flCompletion?.show()
|
||||
readingDialog?.ivCompletion?.setImageDrawable(activity.getDrawable(R.drawable.ic_done_135dp))
|
||||
if (message != null) {
|
||||
if (message.body != null) readingDialog?.tvTaskText?.text = message.body
|
||||
if (message.header != null) readingDialog?.tvTaskTitle?.text = message.header
|
||||
}
|
||||
performHapticFeedback()
|
||||
}
|
||||
postUI(300) { readingDialog?.dismiss() }
|
||||
}
|
||||
|
||||
override fun onError(error: TaskError) {
|
||||
override fun onError(errorMessage: String) {
|
||||
postUI {
|
||||
readingDialog?.lTouchCard?.hide()
|
||||
readingDialog?.flSecurityDelay?.hide()
|
||||
readingDialog?.flCompletion?.hide()
|
||||
readingDialog?.flError?.show()
|
||||
readingDialog?.tvTaskTitle?.text = activity.getText(R.string.dialog_error)
|
||||
readingDialog?.tvTaskText?.text = "${error::class.simpleName}: ${error.code}"
|
||||
readingDialog?.tvTaskText?.text = errorMessage
|
||||
performHapticFeedback()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ package com.tangem.tangem_sdk_new
|
|||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import at.favre.lib.armadillo.Armadillo
|
||||
import com.tangem.common.KeyPair
|
||||
import com.tangem.KeyPair
|
||||
import com.tangem.common.TerminalKeysService
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
|
|
@ -12,7 +12,7 @@ import com.tangem.crypto.CryptoUtils
|
|||
|
||||
/**
|
||||
* Service for managing Terminal keypair, used for Linked Terminal feature.
|
||||
* Needs to be provided to [com.tangem.CardManager] by calling [com.tangem.CardManager.setTerminalKeysService]
|
||||
* Needs to be provided to [com.tangem.TangemSdk] by calling [com.tangem.TangemSdk.setTerminalKeysService]
|
||||
* Linked Terminal feature can be disabled manually by editing [com.tangem.Config].
|
||||
* @param applicationContext is required to retrieve an instance of [SharedPreferences]
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
package com.tangem.tangem_sdk_new.extensions
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.tangem_sdk_new.DefaultCardManagerDelegate
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.tangem_sdk_new.DefaultSessionViewDelegate
|
||||
import com.tangem.tangem_sdk_new.NfcLifecycleObserver
|
||||
import com.tangem.tangem_sdk_new.TerminalKeysStorage
|
||||
import com.tangem.tangem_sdk_new.nfc.NfcManager
|
||||
|
||||
fun CardManager.Companion.init(activity: FragmentActivity): CardManager {
|
||||
fun TangemSdk.Companion.init(activity: FragmentActivity): TangemSdk {
|
||||
val nfcManager = NfcManager().apply {
|
||||
this.setCurrentActivity(activity)
|
||||
activity.lifecycle.addObserver(NfcLifecycleObserver(this))
|
||||
}
|
||||
val cardManagerDelegate = DefaultCardManagerDelegate(nfcManager.reader).apply {
|
||||
val viewDelegate = DefaultSessionViewDelegate(nfcManager.reader).apply {
|
||||
this.activity = activity
|
||||
}
|
||||
return CardManager(nfcManager.reader, cardManagerDelegate).apply {
|
||||
return TangemSdk(nfcManager.reader, viewDelegate).apply {
|
||||
this.setTerminalKeysService(TerminalKeysStorage(activity.application))
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ import android.nfc.tech.IsoDep
|
|||
import android.nfc.tech.NfcV
|
||||
import com.tangem.CardReader
|
||||
import com.tangem.Log
|
||||
import com.tangem.SessionError
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
import com.tangem.tasks.TaskError
|
||||
|
||||
/**
|
||||
* Provides NFC communication between an Android application and Tangem card.
|
||||
|
|
@ -38,7 +38,7 @@ class NfcReader : CardReader {
|
|||
// Stops reading and sends failure callback to a task
|
||||
// if reading is cancelled (when user closes nfc bottom sheet dialog).
|
||||
closeSession()
|
||||
callback?.invoke(CompletionResult.Failure(TaskError.UserCancelled()))
|
||||
callback?.invoke(CompletionResult.Failure(SessionError.UserCancelled()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class NfcReader : CardReader {
|
|||
|
||||
private fun transceiveData() {
|
||||
if (readingCancelled) {
|
||||
callback?.invoke(CompletionResult.Failure(TaskError.UserCancelled()))
|
||||
callback?.invoke(CompletionResult.Failure(SessionError.UserCancelled()))
|
||||
return
|
||||
}
|
||||
if (data == null) return
|
||||
|
|
@ -76,7 +76,7 @@ class NfcReader : CardReader {
|
|||
try {
|
||||
rawResponse = isoDep?.transceive(data)
|
||||
} catch (exception: TagLostException) {
|
||||
callback?.invoke(CompletionResult.Failure(TaskError.TagLost()))
|
||||
callback?.invoke(CompletionResult.Failure(SessionError.TagLost()))
|
||||
isoDep = null
|
||||
return
|
||||
} catch (exception: Exception) {
|
||||
|
|
@ -110,7 +110,7 @@ class NfcReader : CardReader {
|
|||
when (response) {
|
||||
is SlixReadResult.Failure -> {
|
||||
Log.e(this::class.simpleName!!, "${response.exception.message}")
|
||||
callback?.invoke(CompletionResult.Failure(TaskError.ErrorProcessingCommand()))
|
||||
callback?.invoke(CompletionResult.Failure(SessionError.ErrorProcessingCommand()))
|
||||
}
|
||||
is SlixReadResult.Success -> {
|
||||
callback?.invoke(CompletionResult.Success(ResponseApdu(response.data)))
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.common.apdu.StatusWord
|
|||
import com.tangem.common.extensions.toByteArray
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.tlv.Tlv
|
||||
import com.tangem.common.tlv.TlvMapper
|
||||
import com.tangem.common.tlv.TlvDecoder
|
||||
import com.tangem.common.tlv.TlvTag
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
|
|
@ -64,9 +64,9 @@ class SlixTagReader() {
|
|||
|
||||
val areaBuf = readMultipleBlocks(1, blocksCount)
|
||||
|
||||
val tlvNdef = TlvMapper(Tlv.deserialize(areaBuf, true) ?: listOf())
|
||||
val tlvNdef = TlvDecoder(Tlv.deserialize(areaBuf, true) ?: listOf())
|
||||
|
||||
return NdefMessage(tlvNdef.map<ByteArray>(TlvTag.CardPublicKey))
|
||||
return NdefMessage(tlvNdef.decode<ByteArray>(TlvTag.CardPublicKey))
|
||||
}
|
||||
|
||||
private fun readSingleBlock(blockNo: Int): ByteArray {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue