/** * DesfireProtocol.kt * * Copyright (C) 2011 Eric Butler * Copyright (C) 2019 * * Authors: * Eric Butler * * 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 . */ 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() } }