Updated on 2026-08-14

This commit is contained in:
Tangem 2019-11-14 08:09:19 +00:00
commit 587f7d2913
26 changed files with 239 additions and 292 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 onTagLost()
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

@ -18,6 +18,7 @@ enum class Instruction(var code: Int) {
companion object {
fun byCode(code: Int): Instruction = values().find { it.code == code } ?: Unknown
private val values = values()
fun byCode(code: Int): Instruction = values.find { it.code == code } ?: Unknown
}
}

View file

@ -6,16 +6,17 @@ 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 {
fun byCode(code: Int): StatusWord = values().find { it.code == code } ?: InvalidParams
private val values = values()
fun byCode(code: Int): StatusWord = values.find { it.code == code } ?: Unknown
}
}

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,14 +1,10 @@
package com.tangem.common.tlv
import com.tangem.commands.CardStatus
import com.tangem.commands.EllipticCurve
import com.tangem.commands.ProductMask
import com.tangem.commands.SigningMethod
import com.tangem.commands.*
import com.tangem.common.extentions.toDate
import com.tangem.common.extentions.toHexString
import com.tangem.common.extentions.toInt
import com.tangem.common.extentions.toUtf8
import com.tangem.data.SettingsMask
import java.util.*
@ -16,7 +12,7 @@ open class TlvMapperException(message: String?) : Exception(message)
class MissingTagException(message: String? = null) : TlvMapperException(message)
class WrongTypeException(message: String? = null) : TlvMapperException(message)
class ConvertionException(message: String? = null) : TlvMapperException(message)
class ConversionException(message: String? = null) : TlvMapperException(message)
class TlvMapper(val tlvList: List<Tlv>) {
@ -49,7 +45,11 @@ class TlvMapper(val tlvList: List<Tlv>) {
TlvValueType.IntValue -> {
if (T::class != Integer::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
tlvValue.toInt() as T
try {
tlvValue.toInt() as T
} catch (exception: IllegalArgumentException) {
throw ConversionException(exception.message)
}
}
TlvValueType.BoolValue -> {
if (T::class != Boolean::class)
@ -64,34 +64,40 @@ class TlvMapper(val tlvList: List<Tlv>) {
TlvValueType.EllipticCurve -> {
if (T::class != EllipticCurve::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
EllipticCurve.byName(tlvValue.toUtf8()) as T
EllipticCurve.byName(tlvValue.toUtf8()) as? T
?: throw ConversionException("Unknown Elliptic Curve value: ${tlvValue.toUtf8()}")
}
TlvValueType.DateTime -> {
if (T::class != Date::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
tlvValue.toDate() as T
try {
tlvValue.toDate() as T
} catch (exception: Exception) {
throw ConversionException("Converting to date with the following exception: " + exception.message)
}
}
TlvValueType.ProductMask -> {
if (T::class != ProductMask::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
ProductMask.byCode(tlvValue.first()) as T
ProductMask.byCode(tlvValue.first()) as? T
?: throw ConversionException("Unknown Product Mask Code: ${tlvValue.first()}.")
}
TlvValueType.SettingsMask -> {
if (T::class != SettingsMask::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
SettingsMask(tlvValue.toInt()) as T
}
TlvValueType.CardStatus -> {
if (T::class != CardStatus::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
CardStatus.byCode(tlvValue.toInt()) as T
?: throw ConversionException("Unknown Card Status with code of: ${tlvValue.toInt()}")
}
TlvValueType.SigningMethod -> {
if (T::class != SigningMethod::class)
throw WrongTypeException("Mapping error. Type for tag: $tag must be ${tag.valueType()}. It is ${T::class}")
SigningMethod.byCode(tlvValue.toInt()) as T
?: throw ConversionException("Unknown Signing Method with code of: ${tlvValue.toInt()}")
}
}
}

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),
@ -108,9 +122,9 @@ enum class TlvTag(val code: Int) {
}
}
companion object {
fun byCode(code: Int): TlvTag = values().find { it.code == code } ?: Unknown
private val values = values()
fun byCode(code: Int): TlvTag = values.find { it.code == code } ?: Unknown
}
}

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

View file

@ -25,8 +25,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
setLogger()
}
override fun onTaskStarted() {
// reader.isoDep = null
override fun onNfcSessionStarted() {
reader.readingCancelled = false
postUI { showReadingDialog(activity) }
if (!reader.nfcEnabled) showNFCEnableDialog()
@ -58,7 +57,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
activity.supportFragmentManager.let { nfcEnableDialog?.show(it, NfcEnableDialog.TAG) }
}
override fun showSecurityDelay(ms: Int) {
override fun onSecurityDelay(ms: Int) {
postUI {
readingDialog?.lTouchCard?.visibility = View.GONE
readingDialog?.tvRemainingTime?.text = ms.div(100).toString()
@ -69,7 +68,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
}
}
override fun hideSecurityDelay() {
override fun onTagLost() {
postUI {
readingDialog?.lTouchCard?.visibility = View.VISIBLE
readingDialog?.flSecurityDelay?.visibility = View.GONE
@ -78,7 +77,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
}
}
override fun onTaskCompleted() {
override fun onNfcSessionCompleted() {
postUI {
readingDialog?.lTouchCard?.visibility = View.GONE
readingDialog?.flSecurityDelay?.visibility = View.GONE
@ -88,7 +87,7 @@ class DefaultCardManagerDelegate(private val reader: NfcReader) : CardManagerDel
postUI(300) { readingDialog?.dismiss() }
}
override fun onTaskError(error: TaskError?) {
override fun onError(error: TaskError?) {
postUI {
readingDialog?.lTouchCard?.visibility = View.GONE
readingDialog?.flSecurityDelay?.visibility = View.GONE

View file

@ -5,6 +5,6 @@ import android.os.Looper
val uiHandler = Handler(Looper.getMainLooper())
internal fun postUI(msTime: Long = 0, func: () -> Unit) {
internal fun postUI(msTime: Long = 0, func: () -> Unit) {
if (msTime > 0) uiHandler.postDelayed({ func() }, msTime) else uiHandler.post(func)
}

View file

@ -1,5 +1,6 @@
package com.tangem.tangem_sdk_new.nfc
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@ -9,16 +10,15 @@ import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.tangem.Log
class NfcManager : NfcAdapter.ReaderCallback {
val reader = NfcReader()
private var activity: FragmentActivity? = null
private var activity: Activity? = null
private var nfcAdapter: NfcAdapter? = null
fun setCurrentActivity(activity: FragmentActivity) {
fun setCurrentActivity(activity: Activity) {
this.activity = activity
nfcAdapter = NfcAdapter.getDefaultAdapter(activity)
}
@ -49,11 +49,13 @@ class NfcManager : NfcAdapter.ReaderCallback {
reader.nfcEnabled = true
enableReaderMode()
}
reader.manager = this
}
fun onPause() {
activity?.unregisterReceiver(mBroadcastReceiver)
disableReaderMode()
reader.manager = null
}
fun onDestroy() {
@ -61,12 +63,11 @@ class NfcManager : NfcAdapter.ReaderCallback {
nfcAdapter = null
}
private fun enableReaderMode() {
val options = Bundle()
nfcAdapter?.enableReaderMode(activity, this, READER_FLAGS, options)
fun enableReaderMode() {
nfcAdapter?.enableReaderMode(activity, this, READER_FLAGS, Bundle())
}
private fun disableReaderMode() {
fun disableReaderMode() {
nfcAdapter?.disableReaderMode(activity)
}

View file

@ -1,6 +1,7 @@
package com.tangem.tangem_sdk_new.nfc
import android.nfc.Tag
import android.nfc.TagLostException
import android.nfc.tech.IsoDep
import com.tangem.CardReader
import com.tangem.Log
@ -13,14 +14,15 @@ class NfcReader : CardReader {
override var readingActive = false
var nfcEnabled = false
var isoDep: IsoDep? = null
var manager: NfcManager? = null
private var isoDep: IsoDep? = null
set(value) {
// if tag is received, call connect first before transceiving data
// don't reassign when there's an active tag already
if (field == null) {
field = value
// if tag is received, call connect first before transceiving data
connect()
}
// don't reassign when there's an active tag already
if (value == null) field = value
}
@ -38,9 +40,11 @@ class NfcReader : CardReader {
var data: ByteArray? = null
var callback: ((response: CompletionResult<ResponseApdu>) -> Unit)? = null
override fun setStartSession() {
override fun startNfcSession() {
readingActive = true
readingCancelled = false
manager?.disableReaderMode()
manager?.enableReaderMode()
}
override fun closeSession() {
@ -93,7 +97,6 @@ class NfcReader : CardReader {
isoDep?.connect()
isoDep?.timeout = 240000
Log.i(this::class.simpleName!!, "Nfc session is started")
}
}

View file

@ -15,7 +15,7 @@ class NfcEnableDialog : DialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
val builder = AlertDialog.Builder(requireContext())
builder.setCancelable(false)
.setIcon(R.drawable.ic_action_nfc_gray)
.setTitle(R.string.dialog_nfc_enable_title)

View file

@ -11,10 +11,10 @@ import android.widget.RelativeLayout
import androidx.appcompat.widget.LinearLayoutCompat
class TouchCardAnimation(private var context: Context,
private var ivHandCardHorizontal: ImageView,
private var ivHandCardVertical: ImageView,
private var llHand: LinearLayoutCompat,
private var llNfc: LinearLayoutCompat) {
private var ivHandCardHorizontal: ImageView,
private var ivHandCardVertical: ImageView,
private var llHand: LinearLayoutCompat,
private var llNfc: LinearLayoutCompat) {
companion object {
const val CARD_ON_BACK = 0