diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index bacd0a3023..e6e2558685 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -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() { 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), diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt index 2c20fa64e0..544317c358 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/Tlv.kt @@ -53,9 +53,9 @@ class Tlv { } - fun deserialize(mData: ByteArray): List? { + fun deserialize(data: ByteArray, nfcV: Boolean = false): List? { val tlvList = mutableListOf() - 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) diff --git a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt index b179960ddb..742adc0d3f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/ScanTask.kt @@ -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() { 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()) diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index e791190f9a..9f3bf8b836 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -190,6 +190,7 @@ abstract class Task { is CompletionResult.Success -> { val receivedCardId = readResult.data.cardId + securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 if (environment.cardId != null && environment.cardId != receivedCardId) { completeNfcSession(true, TaskError.WrongCard()) diff --git a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt index b0dc16ccc1..c2d1e0e09d 100644 --- a/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt +++ b/tangem-core/src/test/java/com/tangem/common/tlv/TlvTest.kt @@ -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() + } } \ No newline at end of file diff --git a/tangem-demo/src/main/res/xml/nfc_tech_filter.xml b/tangem-demo/src/main/res/xml/nfc_tech_filter.xml index 2b794e0d65..1773c9e9b4 100644 --- a/tangem-demo/src/main/res/xml/nfc_tech_filter.xml +++ b/tangem-demo/src/main/res/xml/nfc_tech_filter.xml @@ -3,5 +3,6 @@ android.nfc.tech.IsoDep android.nfc.tech.Ndef + android.nfc.tech.NfcV \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt index 2052890f91..d8436d8e3b 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcManager.kt @@ -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 } diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt index 9b54bd9ffe..02518ad5f9 100644 --- a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/NfcReader.kt @@ -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) -> Unit)? = null + private var data: ByteArray? = null + private var callback: ((response: CompletionResult) -> 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))) + } + } + } } \ No newline at end of file diff --git a/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt new file mode 100644 index 0000000000..587ded11a3 --- /dev/null +++ b/tangem-sdk/src/main/java/com/tangem/tangem_sdk_new/nfc/SlixTagReader.kt @@ -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 = 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(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() +} \ No newline at end of file