Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-11 13:08:03 +03:00
parent 7e7e3254f3
commit 0c419c3441
9 changed files with 167 additions and 17 deletions

View file

@ -183,7 +183,7 @@ class Card(
/**
* Current status of the card.
*/
val status: CardStatus,
val status: CardStatus?,
/**
* Version of Tangem COS.
@ -316,9 +316,9 @@ class ReadCommand : CommandSerializer<Card>() {
val tlvMapper = TlvMapper(tlvData)
Card(
cardId = tlvMapper.map(TlvTag.CardId),
manufacturerName = tlvMapper.map(TlvTag.ManufactureId),
status = tlvMapper.map(TlvTag.Status),
cardId = tlvMapper.mapOptional(TlvTag.CardId) ?: "",
manufacturerName = tlvMapper.mapOptional(TlvTag.ManufactureId) ?: "",
status = tlvMapper.mapOptional(TlvTag.Status),
firmwareVersion = tlvMapper.mapOptional(TlvTag.Firmware),
cardPublicKey = tlvMapper.mapOptional(TlvTag.CardPublicKey),

View file

@ -53,9 +53,9 @@ class Tlv {
}
fun deserialize(mData: ByteArray): List<Tlv>? {
fun deserialize(data: ByteArray, nfcV: Boolean = false): List<Tlv>? {
val tlvList = mutableListOf<Tlv>()
val stream = ByteArrayInputStream(mData)
val stream = ByteArrayInputStream(data)
var tlv: Tlv?
do {
try {
@ -63,7 +63,7 @@ class Tlv {
if (tlv != null) tlvList.add(tlv)
} catch (e: IOException) {
Log.e(this::class.java.simpleName,"TLVError: " + e.message)
return null
if (nfcV) break else return null
}
} while (tlv != null)

View file

@ -1,10 +1,7 @@
package com.tangem.tasks
import com.tangem.commands.*
import com.tangem.common.CardEnvironment
import com.tangem.commands.Card
import com.tangem.commands.CardStatus
import com.tangem.commands.CheckWalletCommand
import com.tangem.commands.ReadCommand
import com.tangem.common.CompletionResult
import com.tangem.crypto.CryptoUtils
@ -41,6 +38,10 @@ internal class ScanTask : Task<ScanEvent>() {
completeNfcSession(true, TaskError.MissingPreflightRead())
callback(TaskEvent.Completion(TaskError.MissingPreflightRead()))
} else if (currentCard.cardData?.productMask == ProductMask.Tag) {
completeNfcSession()
callback(TaskEvent.Completion())
} else if (currentCard.status != CardStatus.Loaded) {
completeNfcSession()
callback(TaskEvent.Completion())

View file

@ -190,6 +190,7 @@ abstract class Task<T> {
is CompletionResult.Success -> {
val receivedCardId = readResult.data.cardId
securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0
if (environment.cardId != null && environment.cardId != receivedCardId) {
completeNfcSession(true, TaskError.WrongCard())

View file

@ -51,7 +51,7 @@ class TlvTest {
assertThat(tlvs)
.isNotEmpty()
val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value
val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value
val pinExpected = "000000".calculateSha256()
assertThat(pin)
@ -72,7 +72,7 @@ class TlvTest {
assertThat(tlvs)
.isNotEmpty()
val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value
val pin = tlvs!!.find { it.tag == TlvTag.Pin }?.value
val pinExpected = "000000".calculateSha256()
assertThat(pin)
.isEqualTo(pinExpected)
@ -92,12 +92,20 @@ class TlvTest {
fun `Bytes to TLVs, wrong values`() {
val bytes = byteArrayOf(0)
val tlvs = Tlv.deserialize(bytes)
assertThat(tlvs)
.isNull()
assertThat(tlvs)
.isNull()
val bytes1 = byteArrayOf(0, 0, 0, 0, 0, 0, 0)
val tlvs1 = Tlv.deserialize(bytes1)
assertThat(tlvs1)
.isNull()
}
@Test
fun `parse Slix tag response`() {
val response = "03ff010f91010b550474616e67656d2e636f6d140f11616e64726f69642e636f6d3a706b67636f6d2e74616e67656d2e77616c6c65745411c974616e67656d2e636f6d3a77616c6c657490000c618102ffff8a0102820407e40109830b54414e47454d2053444b008403584c4d86400e71c1f060387029688254320b90abeae471bcafbbe8ea3880903bdb8d1cc389d032b982e1ffd7ef49e66f1780123b763dd2f3a9a9494eb0fad4ae8cf306672c60207c967a51077c14fc49d867f23b8d0eaf60cad479a56587e894571b7fb33690176140345fbe53f5be0ec871e91c317cde2bd0396d47e4b945c138c153b0271f636a73cf531df1bc54ac4fcdbce42f81b40d58e0265d34e28121a4c50fdfe329a97f6000fe000000000000000000000000000000000000000000000000000000000000000000000000000000"
val tlvs = Tlv.deserialize(response.hexToBytes(), true)
assertThat(tlvs)
.isNotEmpty()
}
}

View file

@ -3,5 +3,6 @@
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
</resources>

View file

@ -87,6 +87,7 @@ class NfcManager : NfcAdapter.ReaderCallback {
companion object {
// reader mode flags: listen for type A (not B), skipping ndef check
private const val READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A or
NfcAdapter.FLAG_READER_NFC_V or
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK or
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS
}

View file

@ -3,6 +3,7 @@ package com.tangem.tangem_sdk_new.nfc
import android.nfc.Tag
import android.nfc.TagLostException
import android.nfc.tech.IsoDep
import android.nfc.tech.NfcV
import com.tangem.CardReader
import com.tangem.Log
import com.tangem.common.CompletionResult
@ -41,8 +42,8 @@ class NfcReader : CardReader {
}
}
var data: ByteArray? = null
var callback: ((response: CompletionResult<ResponseApdu>) -> Unit)? = null
private var data: ByteArray? = null
private var callback: ((response: CompletionResult<ResponseApdu>) -> Unit)? = null
override fun openSession() {
readingActive = true
@ -90,6 +91,7 @@ class NfcReader : CardReader {
}
fun onTagDiscovered(tag: Tag?) {
NfcV.get(tag)?.let { onNfcVDiscovered(it) }
isoDep = IsoDep.get(tag)
transceiveData()
}
@ -103,4 +105,17 @@ class NfcReader : CardReader {
Log.i(this::class.simpleName!!, "Nfc session is started")
}
private fun onNfcVDiscovered(nfcV: NfcV) {
val response = SlixTagReader().transceive(nfcV)
when (response) {
is SlixReadResult.Failure -> {
callback?.invoke(CompletionResult.Failure(
TaskError.ErrorProcessingCommand(response.exception.message))
)
}
is SlixReadResult.Success -> {
callback?.invoke(CompletionResult.Success(ResponseApdu(response.data)))
}
}
}
}

View file

@ -0,0 +1,123 @@
package com.tangem.tangem_sdk_new.nfc
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.tech.NfcV
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.TlvTag
import java.io.ByteArrayOutputStream
import java.io.IOException
class SlixTagReader() {
private lateinit var nfcV: NfcV
fun transceive(nfcV: NfcV): SlixReadResult {
this.nfcV = nfcV
if (!nfcV.isConnected) {
try {
nfcV.connect()
} catch (e: Exception) {
return SlixReadResult.Failure(e)
}
}
return try {
runRead()
} catch (e: Exception) {
nfcV.close()
SlixReadResult.Failure(e)
}
}
private fun runRead(): SlixReadResult {
val ndefMessage = runReadNDEF()
val records: Array<NdefRecord> = ndefMessage.records
for (record in records) {
if (record.toUri() != null && record.toUri().toString() == "vnd.android.nfc://ext/tangem.com:wallet") {
val payload = record.payload
val status = StatusWord.ProcessCompleted.code.toByteArray()
val data = payload.copyOfRange(2, payload.size) + status
nfcV.close()
return SlixReadResult.Success(data)
}
}
return SlixReadResult.Failure(Exception("No relevant data was found."))
}
private fun runReadNDEF(): NdefMessage {
val answerCC = readSingleBlock(0x00)
if (answerCC.size != 4 || answerCC[0].toInt() != 0xE1 || ((answerCC[1].toInt() and 0xF0) != 0x40)) {
} else {
throw Exception("Failed! Invalid CC read " + answerCC.toHexString())
}
if ((answerCC[3].toInt() and 0x01) != 0x01) {
throw Exception("Multiple block read unsupported!")
}
val areaSize = 8 * answerCC[2]
val blocksCount = areaSize / 4
val areaBuf = readMultipleBlocks(1, blocksCount)
val tlvNdef = TlvMapper(Tlv.deserialize(areaBuf, true) ?: listOf())
return NdefMessage(tlvNdef.map<ByteArray>(TlvTag.CardPublicKey))
}
private fun readSingleBlock(blockNo: Int): ByteArray {
return performRead(0x20, blockNo)!!
}
private fun readMultipleBlocks(startBlock: Int, blocksCount: Int): ByteArray {
val resultBuf = ByteArrayOutputStream()
val maxBlocksAtOnce = 32
var blocksRemaining = blocksCount
var firstBlockToRead = startBlock
while (blocksRemaining > 0) {
val blocksToRead = if (blocksRemaining > maxBlocksAtOnce) {
maxBlocksAtOnce
} else {
blocksRemaining
}
val blocks = performRead(0x23.toByte(), firstBlockToRead, blocksToRead - 1)
blocksRemaining -= blocksToRead
firstBlockToRead += blocksToRead
resultBuf.write(blocks!!)
}
return resultBuf.toByteArray()
}
private fun performRead(cmd: Byte, p1: Int?, p2: Int? = null, params: ByteArray? = null): ByteArray? {
val command: ByteArray
val res: ByteArray?
val os = ByteArrayOutputStream()
os.write(REQ_FLAG.toInt())
os.write(cmd.toInt())
p1?.let { os.write(p1) }
p2?.let { os.write(it) }
params?.let { os.write(params, 0, params.size) }
command = os.toByteArray()
if (!nfcV.isConnected) throw IOException("Connection lost")
res = nfcV.transceive(command)
val errorCode = res[0].toInt()
if (errorCode != 0) {
throw IOException("Error! Code: " + String.format("0x%02x", errorCode))
}
return res.copyOfRange(1, res.size)
}
companion object {
// iso15693 flags
private const val REQ_FLAG: Byte = 0x02
}
}
sealed class SlixReadResult {
data class Success(val data: ByteArray) : SlixReadResult()
data class Failure(val exception: Exception) : SlixReadResult()
}