213 lines
6.6 KiB
Kotlin
213 lines
6.6 KiB
Kotlin
/*
|
|
* DesfireProtocol.java
|
|
*
|
|
* Copyright (C) 2011 Eric Butler
|
|
*
|
|
* Authors:
|
|
* Eric Butler <eric@codebutler.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.codebutler.farebot.card.desfire
|
|
|
|
import android.nfc.tech.IsoDep
|
|
import com.codebutler.farebot.Utils
|
|
|
|
import java.io.ByteArrayOutputStream
|
|
import java.io.IOException
|
|
|
|
import org.apache.commons.lang3.ArrayUtils
|
|
import kotlin.experimental.and
|
|
|
|
class DesfireProtocol(private val mTagTech: IsoDep) {
|
|
|
|
@Suppress("unused")
|
|
val manufacturingData: DesfireManufacturingData
|
|
@Throws(DesfireException::class)
|
|
get() {
|
|
val respBuffer = sendRequest(GET_MANUFACTURING_DATA)
|
|
|
|
if (respBuffer.size != 28)
|
|
throw DesfireException("Invalid response")
|
|
|
|
return DesfireManufacturingData(respBuffer)
|
|
}
|
|
|
|
@Suppress("unused")
|
|
val appList: IntArray
|
|
@Throws(DesfireException::class)
|
|
get() {
|
|
val appDirBuf = sendRequest(GET_APPLICATION_DIRECTORY)
|
|
|
|
val appIds = IntArray(appDirBuf.size / 3)
|
|
|
|
var app = 0
|
|
while (app < appDirBuf.size) {
|
|
val appId = ByteArray(3)
|
|
System.arraycopy(appDirBuf, app, appId, 0, 3)
|
|
|
|
appIds[app / 3] = Utils.byteArrayToInt(appId)
|
|
app += 3
|
|
}
|
|
|
|
return appIds
|
|
}
|
|
|
|
val fileList: IntArray
|
|
@Throws(DesfireException::class)
|
|
get() {
|
|
val buf = sendRequest(GET_FILES)
|
|
val fileIds = IntArray(buf.size)
|
|
for (x in buf.indices) {
|
|
fileIds[x] = buf[x].toInt()
|
|
}
|
|
return fileIds
|
|
}
|
|
|
|
@Throws(DesfireException::class)
|
|
fun selectApp(appId: Int) {
|
|
val appIdBuff = ByteArray(3)
|
|
appIdBuff[0] = (appId and 0xFF0000 shr 16).toByte()
|
|
appIdBuff[1] = (appId and 0xFF00 shr 8).toByte()
|
|
appIdBuff[2] = (appId and 0xFF).toByte()
|
|
|
|
sendRequest(SELECT_APPLICATION, appIdBuff)
|
|
}
|
|
|
|
@Throws(DesfireException::class)
|
|
fun getFileSettings(fileNo: Int): DesfireFileSettings {
|
|
val data = sendRequest(GET_FILE_SETTINGS, byteArrayOf(fileNo.toByte()))
|
|
return DesfireFileSettings.create(data)
|
|
}
|
|
|
|
@Suppress("unused")
|
|
@Throws(DesfireException::class)
|
|
fun readFile(fileNo: Int): ByteArray {
|
|
return sendRequest(
|
|
READ_DATA,
|
|
byteArrayOf(
|
|
fileNo.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte()
|
|
)
|
|
)
|
|
}
|
|
|
|
@Suppress("unused")
|
|
@Throws(DesfireException::class)
|
|
fun readRecord(fileNum: Int): ByteArray {
|
|
return sendRequest(
|
|
READ_RECORD,
|
|
byteArrayOf(
|
|
fileNum.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte(),
|
|
0x0.toByte()
|
|
)
|
|
)
|
|
}
|
|
|
|
@Throws(DesfireException::class)
|
|
fun readValue(fileNum: Int): Int {
|
|
val buf = sendRequest(READ_VALUE, byteArrayOf(fileNum.toByte()))
|
|
ArrayUtils.reverse(buf)
|
|
return Utils.byteArrayToInt(buf)
|
|
}
|
|
|
|
@Throws(DesfireException::class)
|
|
private fun sendRequest(command: Byte, parameters: ByteArray? = null): ByteArray {
|
|
val output = ByteArrayOutputStream()
|
|
|
|
var recvBuffer: ByteArray
|
|
try {
|
|
recvBuffer = mTagTech.transceive(wrapMessage(command, parameters))
|
|
} catch (e: IOException) {
|
|
throw DesfireException(e)
|
|
}
|
|
|
|
while (true) {
|
|
if (recvBuffer[recvBuffer.size - 2] != 0x91.toByte())
|
|
throw DesfireException("Invalid response")
|
|
|
|
output.write(recvBuffer, 0, recvBuffer.size - 2)
|
|
|
|
val status = recvBuffer[recvBuffer.size - 1]
|
|
if (status == OPERATION_OK) {
|
|
break
|
|
} else if (status == ADDITIONAL_FRAME) {
|
|
try {
|
|
recvBuffer = mTagTech.transceive(wrapMessage(GET_ADDITIONAL_FRAME, null))
|
|
} catch (e: IOException) {
|
|
throw DesfireException(e)
|
|
}
|
|
|
|
} else if (status == PERMISSION_DENIED) {
|
|
throw DesfireException("Permission denied")
|
|
} else {
|
|
throw DesfireException("Unknown status code: " + Integer.toHexString((status and 0xFF.toByte()).toInt()))
|
|
}
|
|
}
|
|
|
|
return output.toByteArray()
|
|
}
|
|
|
|
@Throws(DesfireException::class)
|
|
private fun wrapMessage(command: Byte, parameters: ByteArray?): ByteArray {
|
|
val stream = ByteArrayOutputStream()
|
|
|
|
stream.write(0x90.toByte().toInt())
|
|
stream.write(command.toInt())
|
|
stream.write(0x00.toByte().toInt())
|
|
stream.write(0x00.toByte().toInt())
|
|
if (parameters != null) {
|
|
stream.write(parameters.size.toByte().toInt())
|
|
try {
|
|
stream.write(parameters)
|
|
} catch (e: IOException) {
|
|
throw DesfireException(e)
|
|
}
|
|
|
|
}
|
|
stream.write(0x00.toByte().toInt())
|
|
|
|
return stream.toByteArray()
|
|
}
|
|
|
|
companion object {
|
|
/* Commands */
|
|
internal const val GET_MANUFACTURING_DATA = 0x60.toByte()
|
|
internal const val GET_APPLICATION_DIRECTORY = 0x6A.toByte()
|
|
internal const val GET_ADDITIONAL_FRAME = 0xAF.toByte()
|
|
internal const val SELECT_APPLICATION = 0x5A.toByte()
|
|
internal const val READ_DATA = 0xBD.toByte()
|
|
internal const val READ_RECORD = 0xBB.toByte()
|
|
internal const val READ_VALUE = 0x6C.toByte()
|
|
internal const val GET_FILES = 0x6F.toByte()
|
|
internal const val GET_FILE_SETTINGS = 0xF5.toByte()
|
|
|
|
/* Status codes */
|
|
internal const val OPERATION_OK = 0x00.toByte()
|
|
internal const val PERMISSION_DENIED = 0x9D.toByte()
|
|
internal const val ADDITIONAL_FRAME = 0xAF.toByte()
|
|
}
|
|
|
|
} |