v1
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
@file:Suppress("PrivatePropertyName", "Typo")
|
||||
|
||||
package fr.ar2000.ndefemulator
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NdefRecord
|
||||
@@ -7,92 +11,17 @@ import android.nfc.cardemulation.HostApduService
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.LinkedHashMap
|
||||
import java.util.LinkedList
|
||||
import java.util.Stack
|
||||
|
||||
|
||||
class MyHostApduService : HostApduService() {
|
||||
|
||||
private val TAG = "HostApduService"
|
||||
|
||||
private val APDU_SELECT = byteArrayOf(
|
||||
0x00.toByte(), // CLA - Class - Class of instruction
|
||||
0xA4.toByte(), // INS - Instruction - Instruction code
|
||||
0x04.toByte(), // P1 - Parameter 1 - Instruction parameter 1
|
||||
0x00.toByte(), // P2 - Parameter 2 - Instruction parameter 2
|
||||
0x07.toByte(), // Lc field - Number of bytes present in the data field of the command
|
||||
0xD2.toByte(),
|
||||
0x76.toByte(),
|
||||
0x00.toByte(),
|
||||
0x00.toByte(),
|
||||
0x85.toByte(),
|
||||
0x01.toByte(),
|
||||
0x01.toByte(), // NDEF Tag Application name
|
||||
0x00.toByte(), // Le field - Maximum number of bytes expected in the data field of the response to the command
|
||||
)
|
||||
|
||||
private val CAPABILITY_CONTAINER_OK = byteArrayOf(
|
||||
0x00.toByte(), // CLA - Class - Class of instruction
|
||||
0xa4.toByte(), // INS - Instruction - Instruction code
|
||||
0x00.toByte(), // P1 - Parameter 1 - Instruction parameter 1
|
||||
0x0c.toByte(), // P2 - Parameter 2 - Instruction parameter 2
|
||||
0x02.toByte(), // Lc field - Number of bytes present in the data field of the command
|
||||
0xe1.toByte(),
|
||||
0x03.toByte(), // file identifier of the CC file
|
||||
)
|
||||
|
||||
private val READ_CAPABILITY_CONTAINER = byteArrayOf(
|
||||
0x00.toByte(), // CLA - Class - Class of instruction
|
||||
0xb0.toByte(), // INS - Instruction - Instruction code
|
||||
0x00.toByte(), // P1 - Parameter 1 - Instruction parameter 1
|
||||
0x00.toByte(), // P2 - Parameter 2 - Instruction parameter 2
|
||||
0x0f.toByte(), // Lc field - Number of bytes present in the data field of the command
|
||||
)
|
||||
|
||||
// In the scenario that we have done a CC read, the same byte[] match
|
||||
// for ReadBinary would trigger and we don't want that in succession
|
||||
private var READ_CAPABILITY_CONTAINER_CHECK = false
|
||||
|
||||
private val READ_CAPABILITY_CONTAINER_RESPONSE = byteArrayOf(
|
||||
0x00.toByte(), 0x11.toByte(), // CCLEN length of the CC file
|
||||
0x20.toByte(), // Mapping Version 2.0
|
||||
0xFF.toByte(), 0xFF.toByte(), // MLe maximum
|
||||
0xFF.toByte(), 0xFF.toByte(), // MLc maximum
|
||||
0x04.toByte(), // T field of the NDEF File Control TLV
|
||||
0x06.toByte(), // L field of the NDEF File Control TLV
|
||||
0xE1.toByte(), 0x04.toByte(), // File Identifier of NDEF file
|
||||
0x08.toByte(), 0x00.toByte(), // Maximum NDEF file size of 65534 bytes
|
||||
0x00.toByte(), // Read access without any security
|
||||
0xFF.toByte(), // Write access without any security
|
||||
0x90.toByte(), 0x00.toByte(), // A_OKAY
|
||||
)
|
||||
|
||||
private val NDEF_SELECT_OK = byteArrayOf(
|
||||
0x00.toByte(), // CLA - Class - Class of instruction
|
||||
0xa4.toByte(), // Instruction byte (INS) for Select command
|
||||
0x00.toByte(), // Parameter byte (P1), select by identifier
|
||||
0x0c.toByte(), // Parameter byte (P1), select by identifier
|
||||
0x02.toByte(), // Lc field - Number of bytes present in the data field of the command
|
||||
0xE1.toByte(),
|
||||
0x04.toByte(), // file identifier of the NDEF file retrieved from the CC file
|
||||
)
|
||||
|
||||
private val NDEF_READ_BINARY = byteArrayOf(
|
||||
0x00.toByte(), // Class byte (CLA)
|
||||
0xb0.toByte(), // Instruction byte (INS) for ReadBinary command
|
||||
)
|
||||
|
||||
private val NDEF_READ_BINARY_NLEN = byteArrayOf(
|
||||
0x00.toByte(), // Class byte (CLA)
|
||||
0xb0.toByte(), // Instruction byte (INS) for ReadBinary command
|
||||
0x00.toByte(),
|
||||
0x00.toByte(), // Parameter byte (P1, P2), offset inside the CC file
|
||||
0x02.toByte(), // Le field
|
||||
)
|
||||
|
||||
private val A_OKAY = byteArrayOf(
|
||||
0x90.toByte(), // SW1 Status byte 1 - Command processing status
|
||||
0x00.toByte(), // SW2 Status byte 2 - Command processing qualifier
|
||||
@@ -103,28 +32,11 @@ class MyHostApduService : HostApduService() {
|
||||
0x82.toByte(), // SW2 Status byte 2 - Command processing qualifier
|
||||
)
|
||||
|
||||
private val NDEF_ID = byteArrayOf(0xE1.toByte(), 0x04.toByte())
|
||||
|
||||
private var NDEF_URI = NdefMessage(createUrlRecord("https://www.youtube.com/@fredericjubault1906"))
|
||||
private var NDEF_URI_BYTES = NDEF_URI.toByteArray()
|
||||
private var NDEF_URI_LEN = fillByteArrayToFixedDimension(
|
||||
BigInteger.valueOf(NDEF_URI_BYTES.size.toLong()).toByteArray(),
|
||||
2,
|
||||
)
|
||||
private var selectedNdefFile = "0000"
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent?.hasExtra("ndefMessage")!!) {
|
||||
NDEF_URI =
|
||||
NdefMessage(createTextRecord("en", intent.getStringExtra("ndefMessage")!!, NDEF_ID))
|
||||
|
||||
NDEF_URI_BYTES = NDEF_URI.toByteArray()
|
||||
NDEF_URI_LEN = fillByteArrayToFixedDimension(
|
||||
BigInteger.valueOf(NDEF_URI_BYTES.size.toLong()).toByteArray(),
|
||||
2,
|
||||
)
|
||||
}
|
||||
|
||||
Log.i(TAG, "onStartCommand() | NDEF$NDEF_URI")
|
||||
Log.i(TAG, "onStartCommand()")
|
||||
selectedNdefFile = "0000"
|
||||
|
||||
return Service.START_STICKY
|
||||
}
|
||||
@@ -135,139 +47,116 @@ class MyHostApduService : HostApduService() {
|
||||
// in the NFC Forum specification
|
||||
//
|
||||
Log.i(TAG, "processCommandApdu() | incoming commandApdu: " + commandApdu.toHex())
|
||||
val ApduDissect = HashMap<String,String>()
|
||||
ApduDissect["CLA"] = commandApdu.copyOfRange(0,1).toHex()
|
||||
ApduDissect["INS"] = commandApdu.copyOfRange(1,2).toHex()
|
||||
ApduDissect["P1"] = commandApdu.copyOfRange(2,3).toHex()
|
||||
ApduDissect["P2"] = commandApdu.copyOfRange(3,4).toHex()
|
||||
if(commandApdu.size > 5) {
|
||||
ApduDissect["Lc"] = commandApdu.copyOfRange(4, 5).toHex()
|
||||
ApduDissect["Data"] = commandApdu.copyOfRange(5, 5 + commandApdu[4].toInt()).toHex()
|
||||
}
|
||||
ApduDissect["Le"] = commandApdu.copyOfRange(commandApdu.size-1,commandApdu.size).toHex()
|
||||
Log.i(TAG,ApduDissect.toString())
|
||||
val apduDissect = parseApdu(commandApdu)
|
||||
Log.i(TAG, apduDissect.toString())
|
||||
|
||||
//
|
||||
// First command: NDEF Tag Application select (Section 5.5.2 in NFC Forum spec)
|
||||
//
|
||||
if (APDU_SELECT.contentEquals(commandApdu)) {
|
||||
Log.i(TAG, "APDU_SELECT triggered. Our Response: " + A_OKAY.toHex())
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
//
|
||||
// Second command: Capability Container select (Section 5.5.3 in NFC Forum spec)
|
||||
//
|
||||
if (CAPABILITY_CONTAINER_OK.contentEquals(commandApdu)) {
|
||||
Log.i(TAG, "CAPABILITY_CONTAINER_OK triggered. Our Response: " + A_OKAY.toHex())
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
//
|
||||
// Third command: ReadBinary data from CC file (Section 5.5.4 in NFC Forum spec)
|
||||
//
|
||||
if (READ_CAPABILITY_CONTAINER.contentEquals(commandApdu) && !READ_CAPABILITY_CONTAINER_CHECK
|
||||
) {
|
||||
Log.i(TAG, "READ_CAPABILITY_CONTAINER triggered. Our Response: " + READ_CAPABILITY_CONTAINER_RESPONSE.toHex(),)
|
||||
READ_CAPABILITY_CONTAINER_CHECK = true
|
||||
val xrp = resources.getXml(R.xml.ccfile)
|
||||
val parentTag = Stack<String>()
|
||||
var currentTag = ""
|
||||
val ccFile = CCFile()
|
||||
val ccHashMap = LinkedHashMap<String,String>()
|
||||
while(xrp.eventType != XmlPullParser.END_DOCUMENT) {
|
||||
when (xrp.eventType) {
|
||||
XmlPullParser.START_TAG -> {
|
||||
parentTag.push(currentTag)
|
||||
currentTag = xrp.name
|
||||
when (apduDissect["INS"]) {
|
||||
"A4" -> { //ADPU_SELECT
|
||||
Log.i(TAG, "APDU_SELECT triggered.")
|
||||
when (apduDissect["P1"]) {
|
||||
"04" -> { //Select by name
|
||||
when (apduDissect["Data"]) {
|
||||
resources.getString(R.string.NDEF_AID) -> {
|
||||
Log.i(TAG, "NDEF App")
|
||||
return A_OKAY
|
||||
}
|
||||
}
|
||||
}
|
||||
XmlPullParser.TEXT -> {
|
||||
ccHashMap[currentTag] = xrp.text
|
||||
|
||||
"00" -> { //Select by ID
|
||||
when (apduDissect["Data"]) {
|
||||
"E103" -> { //CCFile
|
||||
Log.i(TAG, "NDEF file " + apduDissect["Data"] + "(CCF) selected")
|
||||
selectedNdefFile = apduDissect["Data"].toString()
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
"E104" -> {
|
||||
Log.i(TAG, "NDEF file " + apduDissect["Data"] + "(NDEF File) selected")
|
||||
selectedNdefFile = apduDissect["Data"].toString()
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "NDEF file " + apduDissect["Data"] + "select request denied")
|
||||
return A_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
XmlPullParser.END_TAG -> currentTag = parentTag.pop()
|
||||
XmlPullParser.END_DOCUMENT -> {
|
||||
xrp.close()
|
||||
break
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Unknown P1 " + apduDissect["P1"])
|
||||
return A_ERROR
|
||||
}
|
||||
}
|
||||
xrp.next()
|
||||
}
|
||||
|
||||
ccFile.cclen = Integer.decode(ccHashMap["CCLEN"] ?: "0x00")
|
||||
ccFile.t4tVno = Integer.decode(ccHashMap["T4T_VNo"] ?: "0x00")
|
||||
ccFile.mle = Integer.decode(ccHashMap["MLe"] ?: "0x00")
|
||||
ccFile.mlc = Integer.decode(ccHashMap["MLc"] ?: "0x00")
|
||||
var tlv = ccHashMap["T"]?.drop(2)?:"00"
|
||||
tlv += ccHashMap["L"]?.drop(2)?:"00"
|
||||
tlv += ccHashMap["NDEF_FILE_ID"]?.drop(2) ?: "0000"
|
||||
tlv += ccHashMap["NDEF_FILE_SIZE"]?.drop(2) ?: "0000"
|
||||
tlv += ccHashMap["ENDEF_File_READ_AC"]?.drop(2) ?: "00"
|
||||
tlv += ccHashMap["ENDEF_File_WRITE_AC"]?.drop(2) ?: "00"
|
||||
val tlvByteList = LinkedList<Byte>()
|
||||
tlv.chunked(2).forEach {
|
||||
tlvByteList.add(Integer.decode("0x$it").toByte())
|
||||
"B0" -> { //Read data
|
||||
if (selectedNdefFile == "E103") { //CCfile
|
||||
Log.i(TAG, "CCfile read")
|
||||
//TODO do better here
|
||||
return makeCCFileApdu()
|
||||
} else if (selectedNdefFile == "E104") {
|
||||
|
||||
val offset = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
||||
val length = apduDissect["Le"]?.toInt(16) ?: 0
|
||||
|
||||
val ndefFileContent: ByteArray = ByteArray(length + 2)
|
||||
val ndefFile = File(applicationContext.filesDir, selectedNdefFile)
|
||||
if (!ndefFile.exists()) {
|
||||
applicationContext.openFileOutput(selectedNdefFile, Context.MODE_PRIVATE)
|
||||
.use {
|
||||
val ndefMsg = NdefMessage(createUrlRecord("https://example.com"))
|
||||
val ndefBytes = ndefMsg.toByteArray()
|
||||
val ndefLen = fillByteArrayToFixedDimension(
|
||||
BigInteger.valueOf(ndefBytes.size.toLong()).toByteArray(),
|
||||
2,
|
||||
)
|
||||
it.write(ndefLen)
|
||||
it.write(ndefBytes)
|
||||
}
|
||||
}
|
||||
applicationContext.openFileInput(selectedNdefFile).use {
|
||||
it.skip(offset.toLong())
|
||||
it.read(ndefFileContent, 0, length)
|
||||
}
|
||||
|
||||
A_OKAY.copyInto(ndefFileContent, ndefFileContent.size - 2)
|
||||
Log.i(
|
||||
TAG,
|
||||
"Reading from $selectedNdefFile offset : $offset, length : $length, answer : " + ndefFileContent.toHex()
|
||||
)
|
||||
|
||||
return ndefFileContent
|
||||
|
||||
}
|
||||
}
|
||||
ccFile.tlv = tlvByteList.toByteArray()
|
||||
|
||||
val t = ccFile.toByteArray()
|
||||
"D6" -> {//Write
|
||||
val offset = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
||||
val length = apduDissect["Lc"]?.toInt(16) ?: 0
|
||||
Log.i(TAG, "Write offset : $offset, length : $length")
|
||||
|
||||
//return READ_CAPABILITY_CONTAINER_RESPONSE
|
||||
val msg = ByteArray(t.size+2)
|
||||
t.copyInto(msg)
|
||||
msg[t.size] = 0x90.toByte()
|
||||
return msg
|
||||
}
|
||||
val ccFile = parseCCFile()
|
||||
|
||||
//
|
||||
// Fourth command: NDEF Select command (Section 5.5.5 in NFC Forum spec)
|
||||
//
|
||||
if (NDEF_SELECT_OK.contentEquals(commandApdu)) {
|
||||
Log.i(TAG, "NDEF_SELECT_OK triggered. Our Response: " + A_OKAY.toHex())
|
||||
return A_OKAY
|
||||
}
|
||||
val ndefFileContent: ByteArray =
|
||||
ByteArray(Integer.decode(ccFile["NDEF_FILE_SIZE"] ?: "0000"))
|
||||
val ndefFile = File(applicationContext.filesDir, selectedNdefFile)
|
||||
if (ndefFile.exists()) {
|
||||
applicationContext.openFileInput(selectedNdefFile).use {
|
||||
it.read(ndefFileContent, 0, ndefFileContent.size)
|
||||
}
|
||||
}
|
||||
commandApdu.copyInto(ndefFileContent, offset, 5)
|
||||
applicationContext.openFileOutput(selectedNdefFile, Context.MODE_PRIVATE).use {
|
||||
it.write(ndefFileContent)
|
||||
}
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
if (NDEF_READ_BINARY_NLEN.contentEquals(commandApdu)) {
|
||||
// Build our response
|
||||
val response = ByteArray(NDEF_URI_LEN.size + A_OKAY.size)
|
||||
System.arraycopy(NDEF_URI_LEN, 0, response, 0, NDEF_URI_LEN.size)
|
||||
System.arraycopy(A_OKAY, 0, response, NDEF_URI_LEN.size, A_OKAY.size)
|
||||
|
||||
Log.i(TAG, "NDEF_READ_BINARY_NLEN triggered. Our Response: " + response.toHex())
|
||||
|
||||
READ_CAPABILITY_CONTAINER_CHECK = false
|
||||
return response
|
||||
}
|
||||
|
||||
if (commandApdu.sliceArray(0..1).contentEquals(NDEF_READ_BINARY)) {
|
||||
val offset = commandApdu.sliceArray(2..3).toHex().toInt(16)
|
||||
val length = commandApdu.sliceArray(4..4).toHex().toInt(16)
|
||||
|
||||
val fullResponse = ByteArray(NDEF_URI_LEN.size + NDEF_URI_BYTES.size)
|
||||
System.arraycopy(NDEF_URI_LEN, 0, fullResponse, 0, NDEF_URI_LEN.size)
|
||||
System.arraycopy(
|
||||
NDEF_URI_BYTES,
|
||||
0,
|
||||
fullResponse,
|
||||
NDEF_URI_LEN.size,
|
||||
NDEF_URI_BYTES.size,
|
||||
)
|
||||
|
||||
Log.i(TAG, "NDEF_READ_BINARY triggered. Full data: " + fullResponse.toHex())
|
||||
Log.i(TAG, "READ_BINARY - OFFSET: $offset - LEN: $length")
|
||||
|
||||
val slicedResponse = fullResponse.sliceArray(offset until fullResponse.size)
|
||||
|
||||
// Build our response
|
||||
val realLength = if (slicedResponse.size <= length) slicedResponse.size else length
|
||||
val response = ByteArray(realLength + A_OKAY.size)
|
||||
|
||||
System.arraycopy(slicedResponse, 0, response, 0, realLength)
|
||||
System.arraycopy(A_OKAY, 0, response, realLength, A_OKAY.size)
|
||||
|
||||
Log.i(TAG, "NDEF_READ_BINARY triggered. Our Response: " + response.toHex())
|
||||
|
||||
READ_CAPABILITY_CONTAINER_CHECK = false
|
||||
return response
|
||||
else -> {
|
||||
Log.w(TAG, "Unknown INS : " + apduDissect["INS"])
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -277,8 +166,81 @@ class MyHostApduService : HostApduService() {
|
||||
return A_ERROR
|
||||
}
|
||||
|
||||
private fun makeCCFileApdu(): ByteArray {
|
||||
val ccFile = CCFile()
|
||||
val ccHashMap = parseCCFile()
|
||||
|
||||
ccFile.cclen = Integer.decode(ccHashMap["CCLEN"] ?: "0x00")
|
||||
ccFile.t4tVno = Integer.decode(ccHashMap["T4T_VNo"] ?: "0x00")
|
||||
ccFile.mle = Integer.decode(ccHashMap["MLe"] ?: "0x00")
|
||||
ccFile.mlc = Integer.decode(ccHashMap["MLc"] ?: "0x00")
|
||||
var tlv = ccHashMap["T"]?.drop(2) ?: "00"
|
||||
tlv += ccHashMap["L"]?.drop(2) ?: "00"
|
||||
tlv += ccHashMap["NDEF_FILE_ID"]?.drop(2) ?: "0000"
|
||||
tlv += ccHashMap["NDEF_FILE_SIZE"]?.drop(2) ?: "0000"
|
||||
tlv += ccHashMap["ENDEF_File_READ_AC"]?.drop(2) ?: "00"
|
||||
tlv += ccHashMap["ENDEF_File_WRITE_AC"]?.drop(2) ?: "00"
|
||||
val tlvByteList = LinkedList<Byte>()
|
||||
tlv.chunked(2).forEach {
|
||||
tlvByteList.add(Integer.decode("0x$it").toByte())
|
||||
}
|
||||
ccFile.tlv = tlvByteList.toByteArray()
|
||||
|
||||
val t = ccFile.toByteArray()
|
||||
|
||||
//return READ_CAPABILITY_CONTAINER_RESPONSE
|
||||
val msg = ByteArray(t.size + 2)
|
||||
t.copyInto(msg)
|
||||
msg[t.size] = 0x90.toByte()
|
||||
return msg
|
||||
}
|
||||
|
||||
private fun parseCCFile(): LinkedHashMap<String, String> {
|
||||
val xrp = resources.getXml(R.xml.ccfile)
|
||||
val parentTag = Stack<String>()
|
||||
var currentTag = ""
|
||||
|
||||
val ccHashMap = LinkedHashMap<String, String>()
|
||||
while (xrp.eventType != XmlPullParser.END_DOCUMENT) {
|
||||
when (xrp.eventType) {
|
||||
XmlPullParser.START_TAG -> {
|
||||
parentTag.push(currentTag)
|
||||
currentTag = xrp.name
|
||||
}
|
||||
|
||||
XmlPullParser.TEXT -> {
|
||||
ccHashMap[currentTag] = xrp.text
|
||||
}
|
||||
|
||||
XmlPullParser.END_TAG -> currentTag = parentTag.pop()
|
||||
XmlPullParser.END_DOCUMENT -> {
|
||||
xrp.close()
|
||||
break
|
||||
}
|
||||
}
|
||||
xrp.next()
|
||||
}
|
||||
return ccHashMap
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun parseApdu(commandApdu: ByteArray): HashMap<String, String> {
|
||||
val dissected = HashMap<String, String>()
|
||||
dissected["CLA"] = commandApdu.copyOfRange(0, 1).toHex()
|
||||
dissected["INS"] = commandApdu.copyOfRange(1, 2).toHex()
|
||||
dissected["P1"] = commandApdu.copyOfRange(2, 3).toHex()
|
||||
dissected["P2"] = commandApdu.copyOfRange(3, 4).toHex()
|
||||
if (commandApdu.size > 5) {
|
||||
dissected["Lc"] = commandApdu.copyOfRange(4, 5).toHex()
|
||||
dissected["Data"] = commandApdu.copyOfRange(5, 5 + commandApdu[4].toUByte().toInt()).toHex()
|
||||
}
|
||||
dissected["Le"] = commandApdu.copyOfRange(commandApdu.size - 1, commandApdu.size).toHex()
|
||||
return dissected
|
||||
}
|
||||
|
||||
override fun onDeactivated(reason: Int) {
|
||||
Log.i(TAG, "onDeactivated() Fired! Reason: $reason")
|
||||
selectedNdefFile = "0000"
|
||||
}
|
||||
|
||||
private val HEX_CHARS = "0123456789ABCDEF".toCharArray()
|
||||
@@ -336,7 +298,7 @@ class MyHostApduService : HostApduService() {
|
||||
return NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, id, recordPayload)
|
||||
}
|
||||
|
||||
private fun createUrlRecord(url:String):NdefRecord {
|
||||
private fun createUrlRecord(url: String): NdefRecord {
|
||||
|
||||
return NdefRecord.createUri(url)
|
||||
}
|
||||
@@ -352,4 +314,17 @@ class MyHostApduService : HostApduService() {
|
||||
System.arraycopy(array, 0, filledArray, start.size, array.size)
|
||||
return fillByteArrayToFixedDimension(filledArray, fixedSize)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun Int.toByteArray(len: Int = 1): ByteArray {
|
||||
var str = toHexString(HexFormat.UpperCase).drop(2)
|
||||
str = str.padStart(len * 2, '0').takeLast(len * 2)
|
||||
val res = ByteArray(len)
|
||||
var i = 0
|
||||
str.chunked(2).forEach {
|
||||
res[i] = Integer.decode("0x$it").toByte()
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
@@ -6,18 +6,18 @@
|
||||
<!--Indicates the Mapping Version that is implemented on the T4T (see Section 4.5). Section 4.3.2 defines how the Reader/Writer needs to handle different Mapping Versions.-->
|
||||
<T4T_VNo>0x20</T4T_VNo>
|
||||
<!--Defines the maximum data size that can be read from the T4T using a single READ_BINARY Command.-->
|
||||
<MLe>0xFFFF</MLe>
|
||||
<MLe>0x00FF</MLe>
|
||||
<!--Defines the maximum data size that can be sent to the T4T using a single Command.-->
|
||||
<MLc>0xFFFF</MLc>
|
||||
<MLc>0x00FF</MLc>
|
||||
<!--Section 4.7.3 specifies the content of the NDEF-File_Ctrl_TLV block that contains information to control and manage the NDEF File for Mapping Version 2.0 (see Section 4.5).-->
|
||||
<NDEF-File_Ctrl_TLV>
|
||||
<T>0x04</T>
|
||||
<L>0x06</L>
|
||||
<V>
|
||||
<NDEF_FILE_ID>0xE104</NDEF_FILE_ID>
|
||||
<NDEF_FILE_SIZE>0x0800</NDEF_FILE_SIZE>
|
||||
<NDEF_FILE_SIZE>0x7FFF</NDEF_FILE_SIZE>
|
||||
<ENDEF_File_READ_AC>0x00</ENDEF_File_READ_AC>
|
||||
<ENDEF_File_WRITE_AC>0xFF</ENDEF_File_WRITE_AC>
|
||||
<ENDEF_File_WRITE_AC>0x00</ENDEF_File_WRITE_AC>
|
||||
</V>
|
||||
</NDEF-File_Ctrl_TLV>
|
||||
</CapabilityContainerFile>
|
||||
|
Reference in New Issue
Block a user