Updated on 2026-08-14

This commit is contained in:
Tangem 2019-11-13 14:27:46 +03:00
parent ff4c151e90
commit bbdcad1410
18 changed files with 188 additions and 252 deletions

View file

@ -4,7 +4,7 @@ import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.commands.SignCommand
import com.tangem.commands.SignResponse
import com.tangem.crypto.initCrypto
import com.tangem.crypto.CryptoUtils
import com.tangem.tasks.*
import java.util.concurrent.Executors
@ -17,7 +17,7 @@ class CardManager(
private val cardManagerExecutor = Executors.newSingleThreadExecutor()
init {
initCrypto()
CryptoUtils.initCrypto()
}
fun scanCard(callback: (result: TaskEvent<ScanEvent>) -> Unit) {
@ -56,26 +56,21 @@ class CardManager(
task.delegate = cardManagerDelegate
cardManagerExecutor.execute {
task.run(environment) {
when (it) {
is TaskEvent.Event -> callback(it)
is TaskEvent.Completion -> {
isBusy = false
callback(it)
}
}
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)
}
private fun fetchCardEnvironment(cardId: String?): CardEnvironment {
return cardEnvironmentRepository[cardId] ?: CardEnvironment()
}
fun <T : CommandResponse> runCommand(commandSerializer: CommandSerializer<T>,
cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
val task = SingleCommandTask(commandSerializer)
runTask(task, cardId, callback)
}
}

View file

@ -5,10 +5,11 @@ import com.tangem.tasks.TaskError
interface CardManagerDelegate {
fun onTaskStarted()
fun showSecurityDelay(ms: Int)
fun onTaskCompleted()
fun onTaskError(error: TaskError? = null)
fun onNfcSessionStarted()
fun onSecurityDelay(ms: Int)
fun hideSecurityDelay()
fun onNfcSessionCompleted()
fun onError(error: TaskError? = null)
fun requestPin(callback: (result: CompletionResult<String>) -> Unit)

View file

@ -7,6 +7,6 @@ import com.tangem.common.apdu.ResponseApdu
interface CardReader {
var readingActive: Boolean
fun transceiveApdu(apdu: CommandApdu, callback: (response: CompletionResult<ResponseApdu>) -> Unit)
fun setStartSession()
fun startNfcSession()
fun closeSession()
}

View file

@ -1,10 +0,0 @@
package com.tangem
interface DataStorage {
fun getTerminalPublicKey(): ByteArray?
fun getTerminalPrivateKey(): ByteArray?
fun getPin1(): String?
fun getPin2(): String?
}

View file

@ -1,16 +0,0 @@
package com.tangem
//
//data class SignResponse(
// val cid: String,
// val signature: ByteArray,
// val remainingSignatures: Int,
// val signedHashes: Int
//)
//
//
//data class CardError(
// val code: Int = 0
//)
//

View file

@ -19,20 +19,19 @@ class CheckWalletResponse(
class CheckWalletCommand(
val pin1: String, val cid: String,
val challenge: ByteArray, val publicKeyChallenge: ByteArray) : CommandSerializer<CheckWalletResponse>() {
override val instruction = Instruction.CheckWallet
override val instructionCode = instruction.code
private val pin1: String,
private val cardId: String,
private val challenge: ByteArray
) : CommandSerializer<CheckWalletResponse>() {
override fun serialize(cardEnvironment: CardEnvironment): CommandApdu {
val tlvData = listOf(
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.CardId, cid.hexToBytes()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.Challenge, challenge)
)
return CommandApdu(instructionCode, tlvData)
return CommandApdu(Instruction.CheckWallet, tlvData)
}
override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? {

View file

@ -23,9 +23,6 @@ class SignResponse(
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
: CommandSerializer<SignResponse>() {
override val instruction = Instruction.Sign
override val instructionCode = instruction.code
private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0
private val dataToSign = flattenHashes()
@ -36,7 +33,7 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
private fun checkForErrors() {
if (hashes.isEmpty()) throw TaskError.EmptyHashes()
if (hashes.size > 10) throw TaskError.TooMuchHashes()
if (hashes.size > 10) throw TaskError.TooMuchHashes()
if (hashes.any { it.size != hashSizes }) throw TaskError.HashSizeMustBeEqual()
}
@ -45,20 +42,20 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
Tlv(TlvTag.Pin, cardEnvironment.pin1.calculateSha256()),
Tlv(TlvTag.Pin2, cardEnvironment.pin2.calculateSha256()),
Tlv(TlvTag.CardId, cardId.hexToBytes()),
Tlv(TlvTag.TransactionOutHashSize,byteArrayOf(hashSizes.toByte())),
Tlv(TlvTag.TransactionOutHashSize, byteArrayOf(hashSizes.toByte())),
Tlv(TlvTag.TransactionOutHash, dataToSign)
)
addTerminalSignature(cardEnvironment, tlvData)
return CommandApdu(instructionCode, tlvData)
return CommandApdu(Instruction.Sign, tlvData)
}
private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList<Tlv>) {
cardEnvironment.terminalKeys?.let {
val signedData = dataToSign.sign(it.privateKey)
cardEnvironment.terminalKeys?.let { terminalKeyPair ->
val signedData = dataToSign.sign(terminalKeyPair.privateKey)
tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, signedData))
tlvData.add(Tlv(TlvTag.TerminalPublicKey, it.publicKey))
tlvData.add(Tlv(TlvTag.TerminalPublicKey, terminalKeyPair.publicKey))
}
}
@ -67,7 +64,7 @@ class SignCommand(private val hashes: Array<ByteArray>, private val cardId: Stri
val tlvMapper = TlvMapper(tlvData)
return SignResponse(
cardId= tlvMapper.map(TlvTag.CardId),
cardId = tlvMapper.map(TlvTag.CardId),
signature = tlvMapper.map(TlvTag.Signature),
remainingSignatures = tlvMapper.map(TlvTag.RemainingSignatures),
signedHashes = tlvMapper.map(TlvTag.SignedHashes)

View file

@ -7,7 +7,7 @@ import java.io.ByteArrayOutputStream
class CommandApdu(
private val instruction: Int,
private val ins: Int,
private val tlvList: List<Tlv>,
private val cla: Byte = ISO_CLA,
@ -19,6 +19,18 @@ class CommandApdu(
private val encryptionMode: EncryptionMode = EncryptionMode.NONE,
private val encryptionKey: ByteArray? = null) {
constructor(
instruction: Instruction,
tlvList: List<Tlv>,
encryptionMode: EncryptionMode = EncryptionMode.NONE,
encryptionKey: ByteArray? = null
) : this(
instruction.code,
tlvList,
encryptionMode = encryptionMode,
encryptionKey = encryptionKey
)
val apduData: ByteArray
init {
@ -39,7 +51,7 @@ class CommandApdu(
val byteStream = ByteArrayOutputStream()
byteStream.write(cla.toInt())
byteStream.write(instruction)
byteStream.write(ins)
byteStream.write(p1.toInt())
byteStream.write(p2.toInt())
if (lc != 0) {
@ -58,7 +70,7 @@ class CommandApdu(
private fun encrypt() {
TODO("not implemented")
}
companion object {

View file

@ -6,13 +6,13 @@ enum class StatusWord (val code: Int, val description: String){
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"),
InvalidState(0x6985, "SW_INVALID_STATE"),
// PinsNotChanged(ProcessCompleted.code, ProcessCompleted.description),
Pin1Changed(ProcessCompleted.code + 0x0001, "SW_PIN1_CHANGED"),
Pin2Changed(ProcessCompleted.code + 0x0002, "SW_PIN2_CHANGED"),
PinsChanged(ProcessCompleted.code + 0x0003, "SW_PINS_CHANGED"),
InsNotSupported(0x6D00, "SW_INS_NOT_SUPPORTED"),
NeedEncryption(0x6982, "SW_NEED_ENCRYPTION"),
NeedPause(0x9789, "SW_NEED_PAUSE");
NeedPause(0x9789, "SW_NEED_PAUSE"),
Unknown(0x0000, "SW_UNKNOWN");
companion object {
private val values = values()

View file

@ -7,17 +7,17 @@ class Tlv {
val tag: TlvTag
val value: ByteArray
val tagCode: Int
val tagRaw: Int
constructor(tagCode: Int, value: ByteArray = byteArrayOf()) {
this.tag = TlvTag.byCode(tagCode)
this.tagCode = tagCode
this.tagRaw = tagCode
this.value = value
}
constructor(tag: TlvTag, value: ByteArray = byteArrayOf()) {
this.tag = tag
this.tagCode = tag.code
this.tagRaw = tag.code
this.value = value
}

View file

@ -1,5 +1,19 @@
package com.tangem.common.tlv
enum class TlvValueType {
HexString,
Utf8String,
IntValue,
BoolValue,
ByteArray,
EllipticCurve,
DateTime,
ProductMask,
SettingsMask,
CardStatus,
SigningMethod
}
enum class TlvTag(val code: Int) {
Unknown(0x00),
CardId(0x01),

View file

@ -1,15 +0,0 @@
package com.tangem.common.tlv
enum class TlvValueType {
HexString,
Utf8String,
IntValue,
BoolValue,
ByteArray,
EllipticCurve,
DateTime,
ProductMask,
SettingsMask,
CardStatus,
SigningMethod
}

View file

@ -5,35 +5,34 @@ import net.i2p.crypto.eddsa.EdDSASecurityProvider
import java.security.SecureRandom
import java.security.Security
fun generateRandomBytes(length: Int): ByteArray {
val bytes = ByteArray(length)
SecureRandom().nextBytes(bytes)
return bytes
}
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
return when (curve) {
EllipticCurve.Secp256k1 -> verifySecp256k1(publicKey, message, signature)
EllipticCurve.Ed25519 -> verifyEd25519(publicKey, message, signature)
object CryptoUtils {
fun initCrypto() {
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
Security.addProvider(EdDSASecurityProvider())
}
fun generateRandomBytes(length: Int): ByteArray {
val bytes = ByteArray(length)
SecureRandom().nextBytes(bytes)
return bytes
}
fun verify(publicKey: ByteArray, message: ByteArray, signature: ByteArray,
curve: EllipticCurve = EllipticCurve.Secp256k1): Boolean {
return when (curve) {
EllipticCurve.Secp256k1 -> verifySecp256k1(publicKey, message, signature)
EllipticCurve.Ed25519 -> verifyEd25519(publicKey, message, signature)
}
}
}
fun initCrypto() {
Security.insertProviderAt(org.spongycastle.jce.provider.BouncyCastleProvider(), 1)
Security.addProvider(EdDSASecurityProvider())
}
fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1): ByteArray {
return when (curve) {
EllipticCurve.Secp256k1 -> signSecp256k1(this,privateKeyArray)
EllipticCurve.Secp256k1 -> signSecp256k1(this, privateKeyArray)
EllipticCurve.Ed25519 -> signEd25519(this, privateKeyArray)
}
}

View file

@ -1,32 +0,0 @@
package com.tangem.data
data class SettingsMask(val rawValue: Int) {
companion object{
const val IsReusable = 0x0001
const val UseActivation = 0x0002
const val ForbidPurgeWallet = 0x0004
const val UseBlock = 0x0008
const val AllowSwapPIN = 0x0010
const val AllowSwapPIN2 = 0x0020
const val UseCVC = 0x0040
const val ForbidDefaultPIN = 0x0080
const val UseOneCommandAtTime = 0x0100
const val UseNDEF = 0x0200
const val UseDynamicNDEF = 0x0400
const val SmartSecurityDelay = 0x0800
const val Protocol_AllowUnencrypted = 0x1000
const val Protocol_AllowStaticEncryption = 0x2000
const val ProtectIssuerDataAgainstReplay = 0x4000
const val AllowSelectBlockchain = 0x8000
const val DisablePrecomputedNDEF = 0x00010000
const val SkipSecurityDelayIfValidatedByLinkedTerminal = 0x00080000
}
}

View file

@ -3,11 +3,9 @@ package com.tangem.tasks
import com.tangem.CardEnvironment
import com.tangem.commands.Card
import com.tangem.commands.CheckWalletCommand
import com.tangem.commands.EllipticCurve
import com.tangem.commands.ReadCardCommand
import com.tangem.commands.ReadCommand
import com.tangem.common.CompletionResult
import com.tangem.crypto.generateRandomBytes
import com.tangem.crypto.verify
import com.tangem.crypto.CryptoUtils
sealed class ScanEvent {
data class OnReadEvent(val card: Card) : ScanEvent()
@ -17,76 +15,67 @@ sealed class ScanEvent {
internal class ScanTask : Task<ScanEvent>() {
private lateinit var cardData: Card
private lateinit var challenge: ByteArray
private lateinit var curve: EllipticCurve
private lateinit var walletPublickKey: ByteArray
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<ScanEvent>) -> Unit) {
val readCommand = ReadCardCommand()
sendCommand(readCommand, cardEnvironment) { readEvent ->
val readCommand = ReadCommand()
sendCommand(readCommand, cardEnvironment) { readResult ->
when (readResult) {
is CompletionResult.Failure -> {
if (readResult.error !is TaskError.UserCancelledError) {
completeNfcSession(true, readResult.error)
}
callback(TaskEvent.Completion(readResult.error))
}
when (readEvent) {
is CompletionResult.Success -> {
cardData = readEvent.data
val card = readResult.data
callback(TaskEvent.Event(ScanEvent.OnReadEvent(cardData)))
callback(TaskEvent.Event(ScanEvent.OnReadEvent(card)))
if (cardData.curve != null && cardData.walletPublicKey != null) {
curve = cardData.curve!!
walletPublickKey = cardData.walletPublicKey!!
} else {
onTaskCompleted(true)
if (card.curve == null || card.walletPublicKey == null) {
completeNfcSession(true)
callback(TaskEvent.Completion(TaskError.CardError()))
return@sendCommand
}
val checkWalletCommand = prepareCheckWalletCommand(cardEnvironment)
val challenge = CryptoUtils.generateRandomBytes(16)
val checkWalletCommand = CheckWalletCommand(
cardEnvironment.pin1,
card.cardId,
challenge)
sendCommand(checkWalletCommand, cardEnvironment) { result ->
when (result) {
is CompletionResult.Failure -> {
if (result.error !is TaskError.UserCancelledError) {
completeNfcSession(true, result.error)
}
callback(TaskEvent.Completion(result.error))
}
sendCommand(checkWalletCommand, cardEnvironment) { checkWalletEvent ->
when (checkWalletEvent) {
is CompletionResult.Success -> {
val checkWalletResponse = checkWalletEvent.data
val verified = verify(walletPublickKey,
completeNfcSession()
val checkWalletResponse = result.data
val verified = CryptoUtils.verify(
card.walletPublicKey,
challenge + checkWalletResponse.salt,
checkWalletResponse.walletSignature,
curve)
card.curve)
if (verified) {
onTaskCompleted()
callback(TaskEvent.Completion())
callback(TaskEvent.Event(ScanEvent.OnVerifyEvent(true)))
callback(TaskEvent.Completion())
} else {
onTaskCompleted(true)
callback(TaskEvent.Completion(TaskError.VefificationFailed()))
}
}
is CompletionResult.Failure -> {
if (checkWalletEvent.error !is TaskError.UserCancelledError) {
onTaskCompleted(true, checkWalletEvent.error)
}
callback(TaskEvent.Completion(checkWalletEvent.error))
}
}
}
}
is CompletionResult.Failure -> {
if (readEvent.error !is TaskError.UserCancelledError) {
onTaskCompleted(true, readEvent.error)
}
callback(TaskEvent.Completion(readEvent.error))
}
}
}
}
private fun prepareCheckWalletCommand(cardEnvironment: CardEnvironment): CheckWalletCommand {
challenge = generateRandomBytes(16)
return CheckWalletCommand(
cardEnvironment.pin1,
cardData.cardId,
challenge,
byteArrayOf())
}
}

View file

@ -6,22 +6,23 @@ import com.tangem.commands.CommandSerializer
import com.tangem.common.CompletionResult
class SingleCommandTask<Event : CommandResponse>(
private val commandSerializer: CommandSerializer<Event>) : Task<Event>() {
private val command: CommandSerializer<Event>
) : Task<Event>() {
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<Event>) -> Unit) {
sendCommand(commandSerializer, cardEnvironment) { completionResult ->
when (completionResult) {
sendCommand(command, cardEnvironment) { result ->
when (result) {
is CompletionResult.Success -> {
onTaskCompleted()
callback(TaskEvent.Event(completionResult.data))
completeNfcSession()
callback(TaskEvent.Event(result.data))
callback(TaskEvent.Completion())
}
is CompletionResult.Failure -> {
if (completionResult.error !is TaskError.UserCancelledError) {
onTaskCompleted(true, completionResult.error)
if (result.error !is TaskError.UserCancelledError) {
completeNfcSession(true, result.error)
}
callback(TaskEvent.Completion(completionResult.error))
callback(TaskEvent.Completion(result.error))
}
}
}

View file

@ -7,8 +7,40 @@ import com.tangem.Log
import com.tangem.commands.CommandResponse
import com.tangem.commands.CommandSerializer
import com.tangem.common.CompletionResult
import com.tangem.common.apdu.CommandApdu
import com.tangem.common.apdu.StatusWord
sealed class TaskError(description: String? = null) : Exception(description) {
class UnknownStatus(sw: Int) : TaskError("Unknown StatusWord: $sw")
class MappingError : TaskError()
class GenericError(description: String? = null) : TaskError(description)
class UserCancelledError() : TaskError()
class Busy() : TaskError()
class TagLost() : TaskError()
class ErrorProcessingCommand : TaskError()
class InvalidState : TaskError()
class InsNotSupported : TaskError()
class InvalidParams : TaskError()
class NeedEncryption : TaskError()
class NeedPause : TaskError()
class VefificationFailed : TaskError()
class CardError : TaskError()
class ReaderError() : TaskError()
class SerializeCommandError() : TaskError()
class CardIsMissing() : TaskError()
class EmptyHashes() : TaskError()
class TooMuchHashes() : TaskError()
class HashSizeMustBeEqual() : TaskError()
}
sealed class TaskEvent<T> {
class Event<T>(val data: T) : TaskEvent<T>()
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
}
abstract class Task<T> {
var delegate: CardManagerDelegate? = null
@ -16,18 +48,18 @@ abstract class Task<T> {
fun run(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<T>) -> Unit) {
delegate?.onTaskStarted()
reader?.setStartSession()
delegate?.onNfcSessionStarted()
reader?.startNfcSession()
Log.i(this::class.simpleName!!, "Nfc task is started")
onRun(cardEnvironment, callback)
}
protected fun onTaskCompleted(withError: Boolean = false, taskError: TaskError? = null) {
protected fun completeNfcSession(withError: Boolean = false, taskError: TaskError? = null) {
reader?.closeSession()
if (withError) {
delegate?.onTaskError(taskError)
delegate?.onError(taskError)
} else {
delegate?.onTaskCompleted()
delegate?.onNfcSessionCompleted()
}
}
@ -35,15 +67,22 @@ abstract class Task<T> {
callback: (result: TaskEvent<T>) -> Unit)
protected fun <T : CommandResponse> sendCommand(
commandSerializer: CommandSerializer<T>,
command: CommandSerializer<T>,
cardEnvironment: CardEnvironment,
callback: (result: CompletionResult<T>) -> Unit) {
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is initiated")
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated")
val commandApdu = command.serialize(cardEnvironment)
sendRequest(command, commandApdu, cardEnvironment, callback)
}
reader?.transceiveApdu(
commandSerializer.serialize(cardEnvironment)) { result ->
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 -> {
@ -52,24 +91,26 @@ abstract class Task<T> {
StatusWord.ProcessCompleted, StatusWord.Pin1Changed, StatusWord.Pin2Changed, StatusWord.PinsChanged
-> {
try {
val responseData = commandSerializer.deserialize(cardEnvironment, responseApdu)
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} is completed")
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 -> callback(CompletionResult.Failure(TaskError.UnknownStatus(result.data.sw)))
StatusWord.ErrorProcessingCommand -> callback(CompletionResult.Failure(TaskError.ErrorProcessingCommand()))
StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState()))
StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported()))
StatusWord.NeedEncryption -> callback(CompletionResult.Failure(TaskError.NeedEncryption()))
StatusWord.NeedPause -> {
val remainingTime = commandSerializer.deserializeSecurityDelay(responseApdu, cardEnvironment)
if (remainingTime != null) delegate?.showSecurityDelay(remainingTime)
Log.i(this::class.simpleName!!, "Nfc command ${commandSerializer::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
sendCommand(commandSerializer, cardEnvironment, callback)
// When NeedPause is returned from the card whenever security delay is triggered.
val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment)
if (remainingTime != null) delegate?.onSecurityDelay(remainingTime)
Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds")
sendRequest(command, commandApdu, cardEnvironment, callback)
}
}
}
@ -80,18 +121,9 @@ abstract class Task<T> {
callback(CompletionResult.Failure(TaskError.UserCancelledError()))
reader?.readingActive = false
}
}
}
}
}
/**
* Returns in callback from tasks
*/
sealed class TaskEvent<T> {
class Event<T>(val data: T) : TaskEvent<T>()
class Completion<T>(val error: TaskError? = null) : TaskEvent<T>()
}

View file

@ -1,30 +0,0 @@
package com.tangem.tasks
/**
* An error class that covers typical errors that may occur when performing Tangem SDK tasks.
* Errors are got propagated back within callbacks.
*/
sealed class TaskError(description: String? = null) : Exception(description) {
class UnknownStatus(sw: Int) : TaskError()
class MappingError : TaskError()
class GenericError(description: String? = null) : TaskError(description)
class UserCancelledError() : TaskError()
class Busy() : TaskError()
class ErrorProcessingCommand : TaskError()
class InvalidState : TaskError()
class InsNotSupported : TaskError()
class InvalidParams : TaskError()
class NeedEncryption : TaskError()
class NeedPause : TaskError()
class VefificationFailed : TaskError()
class CardError : TaskError()
class ReaderError() : TaskError()
class SerializeCommandError() : TaskError()
class CardIsMissing() : TaskError()
class EmptyHashes() : TaskError()
class TooMuchHashes() : TaskError()
class HashSizeMustBeEqual() : TaskError()
}