migrated all farebot code to kotlin
This commit is contained in:
parent
e74c307566
commit
b4ed1ca927
|
@ -1,268 +0,0 @@
|
|||
/*
|
||||
* Utils.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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.codebutler.farebot.card.desfire.DesfireException;
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings;
|
||||
import com.codebutler.farebot.card.desfire.DesfireProtocol;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
public class Utils {
|
||||
|
||||
private static final String TAG = Utils.class.getName();
|
||||
|
||||
public static void showError (final Activity activity, Exception ex) {
|
||||
Log.e(activity.getClass().getName(), ex.getMessage(), ex);
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(Utils.getErrorMessage(ex))
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void showErrorAndFinish (final Activity activity, Exception ex) {
|
||||
try {
|
||||
Log.e(activity.getClass().getName(), Utils.getErrorMessage(ex));
|
||||
ex.printStackTrace();
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setMessage(Utils.getErrorMessage(ex))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface arg0, int arg1) {
|
||||
activity.finish();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
} catch (WindowManager.BadTokenException unused) {
|
||||
/* Ignore... happens if the activity was destroyed */
|
||||
}
|
||||
}
|
||||
|
||||
public static String getHexString (byte[] b) throws Exception {
|
||||
String result = "";
|
||||
for (int i=0; i < b.length; i++) {
|
||||
result += Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String getHexString (byte[] b, String defaultResult) {
|
||||
try {
|
||||
return getHexString(b);
|
||||
} catch (Exception ex) {
|
||||
return defaultResult;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] hexStringToByteArray (String s) {
|
||||
if ((s.length() % 2) != 0) {
|
||||
throw new IllegalArgumentException("Bad input string: " + s);
|
||||
}
|
||||
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
public static byte[] intToByteArray(int value) {
|
||||
return new byte[] {
|
||||
(byte)(value >>> 24),
|
||||
(byte)(value >>> 16),
|
||||
(byte)(value >>> 8),
|
||||
(byte)value};
|
||||
}
|
||||
*/
|
||||
|
||||
public static int byteArrayToInt(byte[] b) {
|
||||
return byteArrayToInt(b, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] b, int offset) {
|
||||
return byteArrayToInt(b, offset, b.length);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] b, int offset, int length) {
|
||||
return (int) byteArrayToLong(b, offset, length);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] b, int offset, int length) {
|
||||
if (b.length < length)
|
||||
throw new IllegalArgumentException("length must be less than or equal to b.length");
|
||||
|
||||
long value = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int shift = (length - 1 - i) * 8;
|
||||
value += (b[i + offset] & 0x000000FF) << shift;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static byte[] byteArraySlice(byte[] b, int offset, int length) {
|
||||
byte[] ret = new byte[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
ret[i] = b[offset+i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static String xmlNodeToString (Node node) throws Exception {
|
||||
// The amount of code required to do simple things in Java is incredible.
|
||||
Source source = new DOMSource(node);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
Result result = new StreamResult(stringWriter);
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
Transformer transformer = factory.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
|
||||
transformer.setURIResolver(null);
|
||||
transformer.transform(source, result);
|
||||
return stringWriter.getBuffer().toString();
|
||||
}
|
||||
|
||||
public static String getErrorMessage (Throwable ex) {
|
||||
String errorMessage = ex.getLocalizedMessage();
|
||||
if (errorMessage == null)
|
||||
errorMessage = ex.getMessage();
|
||||
if (errorMessage == null)
|
||||
errorMessage = ex.toString();
|
||||
|
||||
if (ex.getCause() != null) {
|
||||
String causeMessage = ex.getCause().getLocalizedMessage();
|
||||
if (causeMessage == null)
|
||||
causeMessage = ex.getCause().getMessage();
|
||||
if (causeMessage == null)
|
||||
causeMessage = ex.getCause().toString();
|
||||
|
||||
if (causeMessage != null)
|
||||
errorMessage += ": " + causeMessage;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
|
||||
public static <T> T findInList(List<T> list, Matcher<T> matcher) {
|
||||
for (T item : list) {
|
||||
if (matcher.matches(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static interface Matcher<T> {
|
||||
public boolean matches(T t);
|
||||
}
|
||||
|
||||
public static int convertBCDtoInteger(byte data) {
|
||||
return (((data & (char)0xF0) >> 4) * 10) + ((data & (char)0x0F));
|
||||
}
|
||||
|
||||
public static int getBitsFromInteger(int buffer, int iStartBit, int iLength) {
|
||||
return (buffer >> (iStartBit)) & ((char)0xFF >> (8 - iLength));
|
||||
}
|
||||
|
||||
/* Based on function from mfocGUI by 'Huuf' (http://www.huuf.info/OV/) */
|
||||
public static int getBitsFromBuffer(byte[] buffer, int iStartBit, int iLength) {
|
||||
int iEndBit = iStartBit + iLength - 1;
|
||||
int iSByte = iStartBit / 8;
|
||||
int iSBit = iStartBit % 8;
|
||||
int iEByte = iEndBit / 8;
|
||||
int iEBit = iEndBit % 8;
|
||||
|
||||
if (iSByte == iEByte) {
|
||||
return (int)(((char)buffer[iEByte] >> (7 - iEBit)) & ((char)0xFF >> (8 - iLength)));
|
||||
} else {
|
||||
int uRet = (((char)buffer[iSByte] & (char)((char)0xFF >> iSBit)) << (((iEByte - iSByte - 1) * 8) + (iEBit + 1)));
|
||||
|
||||
for (int i = iSByte + 1; i < iEByte; i++) {
|
||||
uRet |= (((char)buffer[i] & (char)0xFF) << (((iEByte - i - 1) * 8) + (iEBit + 1)));
|
||||
}
|
||||
|
||||
uRet |= (((char)buffer[iEByte] & (char)0xFF)) >> (7 - iEBit);
|
||||
|
||||
return uRet;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static DesfireFileSettings selectAppFile(DesfireProtocol tag, int appID, int fileID) {
|
||||
try {
|
||||
tag.selectApp(appID);
|
||||
} catch (DesfireException e) {
|
||||
Log.w(TAG,"App not found");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return tag.getFileSettings(fileID);
|
||||
} catch (DesfireException e) {
|
||||
Log.w(TAG,"File not found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean arrayContains(int[] arr, int item) {
|
||||
for (int i: arr)
|
||||
if (i==item)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean containsAppFile(DesfireProtocol tag, int appID, int fileID) {
|
||||
try {
|
||||
tag.selectApp(appID);
|
||||
} catch (DesfireException e) {
|
||||
Log.w(TAG,"App not found");
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return arrayContains(tag.getFileList(),fileID);
|
||||
} catch (DesfireException e) {
|
||||
Log.w(TAG,"File not found");
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Utils.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
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import com.codebutler.farebot.card.desfire.DesfireException
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings
|
||||
import com.codebutler.farebot.card.desfire.DesfireProtocol
|
||||
import org.w3c.dom.Node
|
||||
import java.io.StringWriter
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import kotlin.experimental.and
|
||||
|
||||
class Utils {
|
||||
|
||||
companion object {
|
||||
private val TAG = Utils::class.java.name
|
||||
|
||||
@Suppress("unused")
|
||||
fun showError(activity: Activity, ex: Exception) {
|
||||
Log.e(activity.javaClass.name, ex.message, ex)
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(getErrorMessage(ex))
|
||||
.show()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun showErrorAndFinish(activity: Activity, ex: Exception) {
|
||||
try {
|
||||
Log.e(activity.javaClass.name, getErrorMessage(ex))
|
||||
ex.printStackTrace()
|
||||
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(getErrorMessage(ex))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> activity.finish() }
|
||||
.show()
|
||||
} catch (unused: WindowManager.BadTokenException) {
|
||||
/* Ignore... happens if the activity was destroyed */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun getHexString(b: ByteArray): String {
|
||||
var result = ""
|
||||
for (i in b.indices) {
|
||||
result += ((b[i] and 0xff.toByte()) + 0x100).toString(16).substring(1)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getHexString(b: ByteArray, defaultResult: String): String {
|
||||
return try {
|
||||
getHexString(b)
|
||||
} catch (ex: Exception) {
|
||||
defaultResult
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun hexStringToByteArray(s: String): ByteArray {
|
||||
if (s.length % 2 != 0) {
|
||||
throw IllegalArgumentException("Bad input string: $s")
|
||||
}
|
||||
|
||||
val len = s.length
|
||||
val data = ByteArray(len / 2)
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
data[i / 2] = ((Character.digit(s[i], 16) shl 4) + Character.digit(s[i + 1], 16)).toByte()
|
||||
i += 2
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun byteArrayToInt(b: ByteArray, offset: Int = 0, length: Int = b.size): Int {
|
||||
return byteArrayToLong(b, offset, length).toInt()
|
||||
}
|
||||
|
||||
fun byteArrayToLong(b: ByteArray, offset: Int, length: Int): Long {
|
||||
if (b.size < length)
|
||||
throw IllegalArgumentException("length must be less than or equal to b.length")
|
||||
|
||||
var value: Long = 0
|
||||
for (i in 0 until length) {
|
||||
val shift = (length - 1 - i) * 8
|
||||
value += ((b[i + offset].toInt() and 0x000000FF).toLong() shl shift)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun byteArraySlice(b: ByteArray, offset: Int, length: Int): ByteArray {
|
||||
val ret = ByteArray(length)
|
||||
for (i in 0 until length)
|
||||
ret[i] = b[offset + i]
|
||||
return ret
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@Throws(Exception::class)
|
||||
fun xmlNodeToString(node: Node): String {
|
||||
// The amount of code required to do simple things in Java is incredible.
|
||||
val source = DOMSource(node)
|
||||
val stringWriter = StringWriter()
|
||||
val result = StreamResult(stringWriter)
|
||||
val factory = TransformerFactory.newInstance()
|
||||
val transformer = factory.newTransformer()
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
|
||||
transformer.uriResolver = null
|
||||
transformer.transform(source, result)
|
||||
return stringWriter.buffer.toString()
|
||||
}
|
||||
|
||||
fun getErrorMessage(ex: Throwable): String {
|
||||
var errorMessage: String? = ex.localizedMessage
|
||||
if (errorMessage == null)
|
||||
errorMessage = ex.message
|
||||
if (errorMessage == null)
|
||||
errorMessage = ex.toString()
|
||||
|
||||
if (ex.cause != null) {
|
||||
var causeMessage: String? = ex.cause!!.localizedMessage
|
||||
if (causeMessage == null)
|
||||
causeMessage = ex.cause!!.message
|
||||
if (causeMessage == null)
|
||||
causeMessage = ex.cause.toString()
|
||||
|
||||
errorMessage += ": $causeMessage"
|
||||
}
|
||||
|
||||
return errorMessage
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun <T> findInList(list: List<T>, matcher: Matcher<T>): T? {
|
||||
for (item in list) {
|
||||
if (matcher.matches(item)) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
interface Matcher<T> {
|
||||
fun matches(t: T): Boolean
|
||||
}
|
||||
|
||||
fun selectAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): DesfireFileSettings? {
|
||||
try {
|
||||
tag.selectApp(appID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "App not found")
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
tag.getFileSettings(fileID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "File not found")
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun arrayContains(arr: IntArray, item: Int): Boolean {
|
||||
for (i in arr)
|
||||
if (i == item)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun containsAppFile(tag: DesfireProtocol, appID: Int, fileID: Int): Boolean {
|
||||
try {
|
||||
tag.selectApp(appID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "App not found")
|
||||
Log.w(TAG, e)
|
||||
return false
|
||||
}
|
||||
|
||||
return try {
|
||||
arrayContains(tag.fileList, fileID)
|
||||
} catch (e: DesfireException) {
|
||||
Log.w(TAG, "File not found")
|
||||
Log.w(TAG, e)
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* DesfireApplication.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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class DesfireApplication implements Parcelable {
|
||||
private int mId;
|
||||
private DesfireFile[] mFiles;
|
||||
|
||||
public DesfireApplication (int id, DesfireFile[] files) {
|
||||
mId = id;
|
||||
mFiles = files;
|
||||
}
|
||||
|
||||
public int getId () {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public DesfireFile[] getFiles () {
|
||||
return mFiles;
|
||||
}
|
||||
|
||||
public DesfireFile getFile (int fileId) {
|
||||
for (DesfireFile file : mFiles) {
|
||||
if (file.getId() == fileId)
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<DesfireApplication> CREATOR = new Parcelable.Creator<DesfireApplication>() {
|
||||
public DesfireApplication createFromParcel(Parcel source) {
|
||||
int id = source.readInt();
|
||||
|
||||
DesfireFile[] files = new DesfireFile[source.readInt()];
|
||||
source.readTypedArray(files, DesfireFile.CREATOR);
|
||||
|
||||
return new DesfireApplication(id, files);
|
||||
}
|
||||
|
||||
public DesfireApplication[] newArray (int size) {
|
||||
return new DesfireApplication[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void writeToParcel (Parcel parcel, int flags) {
|
||||
parcel.writeInt(mId);
|
||||
parcel.writeInt(mFiles.length);
|
||||
parcel.writeTypedArray(mFiles, flags);
|
||||
}
|
||||
|
||||
public int describeContents () {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package com.codebutler.farebot.card.desfire;
|
||||
|
||||
/**
|
||||
* Created by Jakob Wenzel on 16.11.13.
|
||||
*/
|
||||
public class DesfireException extends Exception {
|
||||
public DesfireException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public DesfireException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.codebutler.farebot.card.desfire
|
||||
|
||||
/**
|
||||
* Created by Jakob Wenzel on 16.11.13.
|
||||
*/
|
||||
class DesfireException : Exception {
|
||||
constructor(message: String) : super(message) {}
|
||||
constructor(cause: Throwable) : super(cause) {}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* DesfireFile.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 org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings.RecordDesfireFileSettings;
|
||||
|
||||
public class DesfireFile implements Parcelable {
|
||||
private int mId;
|
||||
private DesfireFileSettings mSettings;
|
||||
private byte[] mData;
|
||||
|
||||
public static DesfireFile create (int fileId, DesfireFileSettings fileSettings, byte[] fileData) {
|
||||
if (fileSettings instanceof RecordDesfireFileSettings)
|
||||
return new RecordDesfireFile(fileId, fileSettings, fileData);
|
||||
else
|
||||
return new DesfireFile(fileId, fileSettings, fileData);
|
||||
}
|
||||
|
||||
private DesfireFile (int fileId, DesfireFileSettings fileSettings, byte[] fileData) {
|
||||
mId = fileId;
|
||||
mSettings = fileSettings;
|
||||
mData = fileData;
|
||||
}
|
||||
|
||||
public DesfireFileSettings getFileSettings () {
|
||||
return mSettings;
|
||||
}
|
||||
|
||||
public int getId () {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public byte[] getData () {
|
||||
return mData;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<DesfireFile> CREATOR = new Parcelable.Creator<DesfireFile>() {
|
||||
public DesfireFile createFromParcel(Parcel source) {
|
||||
int fileId = source.readInt();
|
||||
|
||||
boolean isError = (source.readInt() == 1);
|
||||
|
||||
if (!isError) {
|
||||
DesfireFileSettings fileSettings = (DesfireFileSettings) source.readParcelable(DesfireFileSettings.class.getClassLoader());
|
||||
int dataLength = source.readInt();
|
||||
byte[] fileData = new byte[dataLength];
|
||||
source.readByteArray(fileData);
|
||||
|
||||
return DesfireFile.create(fileId, fileSettings, fileData);
|
||||
} else {
|
||||
return new InvalidDesfireFile(fileId, source.readString());
|
||||
}
|
||||
}
|
||||
|
||||
public DesfireFile[] newArray (int size) {
|
||||
return new DesfireFile[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void writeToParcel (Parcel parcel, int flags) {
|
||||
parcel.writeInt(mId);
|
||||
if (this instanceof InvalidDesfireFile) {
|
||||
parcel.writeInt(1);
|
||||
parcel.writeString(((InvalidDesfireFile)this).getErrorMessage());
|
||||
} else {
|
||||
parcel.writeInt(0);
|
||||
parcel.writeParcelable(mSettings, 0);
|
||||
parcel.writeInt(mData.length);
|
||||
parcel.writeByteArray(mData);
|
||||
}
|
||||
}
|
||||
|
||||
public int describeContents () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class RecordDesfireFile extends DesfireFile {
|
||||
private DesfireRecord[] mRecords;
|
||||
|
||||
private RecordDesfireFile (int fileId, DesfireFileSettings fileSettings, byte[] fileData) {
|
||||
super(fileId, fileSettings, fileData);
|
||||
|
||||
RecordDesfireFileSettings settings = (RecordDesfireFileSettings) fileSettings;
|
||||
|
||||
DesfireRecord[] records = new DesfireRecord[settings.curRecords];
|
||||
for (int i = 0; i < settings.curRecords; i++) {
|
||||
int offset = settings.recordSize * i;
|
||||
records[i] = new DesfireRecord(ArrayUtils.subarray(getData(), offset, offset + settings.recordSize));
|
||||
}
|
||||
mRecords = records;
|
||||
}
|
||||
|
||||
public DesfireRecord[] getRecords () {
|
||||
return mRecords;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidDesfireFile extends DesfireFile {
|
||||
private String mErrorMessage;
|
||||
|
||||
public InvalidDesfireFile (int fileId, String errorMessage) {
|
||||
super(fileId, null, new byte[0]);
|
||||
mErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public String getErrorMessage () {
|
||||
return mErrorMessage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* DesfireFile.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.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.codebutler.farebot.card.desfire.DesfireFileSettings.RecordDesfireFileSettings
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
|
||||
|
||||
open class DesfireFile private constructor(val id: Int, private val fileSettings: DesfireFileSettings?, val data: ByteArray) :
|
||||
Parcelable {
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeInt(id)
|
||||
if (this is InvalidDesfireFile) {
|
||||
parcel.writeInt(1)
|
||||
parcel.writeString(this.errorMessage)
|
||||
} else {
|
||||
parcel.writeInt(0)
|
||||
parcel.writeParcelable(fileSettings, 0)
|
||||
parcel.writeInt(data.size)
|
||||
parcel.writeByteArray(data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
class RecordDesfireFile(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray) :
|
||||
DesfireFile(fileId, fileSettings, fileData) {
|
||||
private val records: Array<DesfireRecord?>
|
||||
|
||||
init {
|
||||
|
||||
val settings = fileSettings as RecordDesfireFileSettings
|
||||
|
||||
val records = arrayOfNulls<DesfireRecord>(settings.curRecords)
|
||||
for (i in 0 until settings.curRecords) {
|
||||
val offset = settings.recordSize * i
|
||||
records[i] = DesfireRecord(ArrayUtils.subarray(data, offset, offset + settings.recordSize))
|
||||
}
|
||||
this.records = records
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidDesfireFile(fileId: Int, val errorMessage: String?) : DesfireFile(fileId, null, ByteArray(0))
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(fileId: Int, fileSettings: DesfireFileSettings, fileData: ByteArray): DesfireFile {
|
||||
return (fileSettings as? RecordDesfireFileSettings)?.let { RecordDesfireFile(fileId, it, fileData) }
|
||||
?: DesfireFile(fileId, fileSettings, fileData)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<DesfireFile> = object : Parcelable.Creator<DesfireFile> {
|
||||
override fun createFromParcel(source: Parcel): DesfireFile {
|
||||
val fileId = source.readInt()
|
||||
|
||||
val isError = source.readInt() == 1
|
||||
|
||||
return if (!isError) {
|
||||
val fileSettings =
|
||||
source.readParcelable<Parcelable>(DesfireFileSettings::class.java.classLoader) as DesfireFileSettings
|
||||
val dataLength = source.readInt()
|
||||
val fileData = ByteArray(dataLength)
|
||||
source.readByteArray(fileData)
|
||||
|
||||
create(fileId, fileSettings, fileData)
|
||||
} else {
|
||||
InvalidDesfireFile(fileId, source.readString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DesfireFile?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
/*
|
||||
* DesfireFileSettings.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 java.io.ByteArrayInputStream;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.codebutler.farebot.Utils;
|
||||
|
||||
public abstract class DesfireFileSettings implements Parcelable {
|
||||
public final byte fileType;
|
||||
public final byte commSetting;
|
||||
public final byte[] accessRights;
|
||||
|
||||
/* DesfireFile Types */
|
||||
static final byte STANDARD_DATA_FILE = (byte) 0x00;
|
||||
static final byte BACKUP_DATA_FILE = (byte) 0x01;
|
||||
static final byte VALUE_FILE = (byte) 0x02;
|
||||
static final byte LINEAR_RECORD_FILE = (byte) 0x03;
|
||||
static final byte CYCLIC_RECORD_FILE = (byte) 0x04;
|
||||
|
||||
public static DesfireFileSettings Create (byte[] data) throws DesfireException {
|
||||
byte fileType = (byte) data[0];
|
||||
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(data);
|
||||
|
||||
if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
|
||||
return new StandardDesfireFileSettings(stream);
|
||||
else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
|
||||
return new RecordDesfireFileSettings(stream);
|
||||
else if (fileType == VALUE_FILE)
|
||||
return new ValueDesfireFileSettings(stream);
|
||||
else
|
||||
throw new DesfireException("Unknown file type: " + Integer.toHexString(fileType));
|
||||
}
|
||||
|
||||
private DesfireFileSettings (ByteArrayInputStream stream) {
|
||||
fileType = (byte) stream.read();
|
||||
commSetting = (byte) stream.read();
|
||||
|
||||
accessRights = new byte[2];
|
||||
stream.read(accessRights, 0, accessRights.length);
|
||||
}
|
||||
|
||||
private DesfireFileSettings (byte fileType, byte commSetting, byte[] accessRights) {
|
||||
this.fileType = fileType;
|
||||
this.commSetting = commSetting;
|
||||
this.accessRights = accessRights;
|
||||
}
|
||||
|
||||
public String getFileTypeName () {
|
||||
switch (fileType) {
|
||||
case STANDARD_DATA_FILE:
|
||||
return "Standard";
|
||||
case BACKUP_DATA_FILE:
|
||||
return "Backup";
|
||||
case VALUE_FILE:
|
||||
return "Value";
|
||||
case LINEAR_RECORD_FILE:
|
||||
return "Linear Record";
|
||||
case CYCLIC_RECORD_FILE:
|
||||
return "Cyclic Record";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<DesfireFileSettings> CREATOR = new Parcelable.Creator<DesfireFileSettings>() {
|
||||
public DesfireFileSettings createFromParcel(Parcel source) {
|
||||
byte fileType = source.readByte();
|
||||
byte commSetting = source.readByte();
|
||||
byte[] accessRights = new byte[source.readInt()];
|
||||
source.readByteArray(accessRights);
|
||||
|
||||
if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE) {
|
||||
int fileSize = source.readInt();
|
||||
return new StandardDesfireFileSettings(fileType, commSetting, accessRights, fileSize);
|
||||
} else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE) {
|
||||
int recordSize = source.readInt();
|
||||
int maxRecords = source.readInt();
|
||||
int curRecords = source.readInt();
|
||||
return new RecordDesfireFileSettings(fileType, commSetting, accessRights, recordSize, maxRecords, curRecords);
|
||||
} else {
|
||||
return new UnsupportedDesfireFileSettings(fileType);
|
||||
}
|
||||
}
|
||||
|
||||
public DesfireFileSettings[] newArray(int size) {
|
||||
return new DesfireFileSettings[size];
|
||||
}
|
||||
};
|
||||
|
||||
public void writeToParcel (Parcel parcel, int flags) {
|
||||
parcel.writeByte(fileType);
|
||||
parcel.writeByte(commSetting);
|
||||
parcel.writeInt(accessRights.length);
|
||||
parcel.writeByteArray(accessRights);
|
||||
}
|
||||
|
||||
public int describeContents () {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class StandardDesfireFileSettings extends DesfireFileSettings {
|
||||
public final int fileSize;
|
||||
|
||||
private StandardDesfireFileSettings (ByteArrayInputStream stream) {
|
||||
super(stream);
|
||||
byte[] buf = new byte[3];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
fileSize = Utils.byteArrayToInt(buf);
|
||||
}
|
||||
|
||||
StandardDesfireFileSettings (byte fileType, byte commSetting, byte[] accessRights, int fileSize) {
|
||||
super(fileType, commSetting, accessRights);
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel (Parcel parcel, int flags) {
|
||||
super.writeToParcel(parcel, flags);
|
||||
parcel.writeInt(fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RecordDesfireFileSettings extends DesfireFileSettings {
|
||||
public final int recordSize;
|
||||
public final int maxRecords;
|
||||
public final int curRecords;
|
||||
|
||||
public RecordDesfireFileSettings(ByteArrayInputStream stream) {
|
||||
super(stream);
|
||||
|
||||
byte[] buf = new byte[3];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
recordSize = Utils.byteArrayToInt(buf);
|
||||
|
||||
buf = new byte[3];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
maxRecords = Utils.byteArrayToInt(buf);
|
||||
|
||||
buf = new byte[3];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
curRecords = Utils.byteArrayToInt(buf);
|
||||
}
|
||||
|
||||
RecordDesfireFileSettings (byte fileType, byte commSetting, byte[] accessRights, int recordSize, int maxRecords, int curRecords) {
|
||||
super(fileType, commSetting, accessRights);
|
||||
this.recordSize = recordSize;
|
||||
this.maxRecords = maxRecords;
|
||||
this.curRecords = curRecords;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel (Parcel parcel, int flags) {
|
||||
super.writeToParcel(parcel, flags);
|
||||
parcel.writeInt(recordSize);
|
||||
parcel.writeInt(maxRecords);
|
||||
parcel.writeInt(curRecords);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static class ValueDesfireFileSettings extends DesfireFileSettings {
|
||||
public final int lowerLimit;
|
||||
public final int upperLimit;
|
||||
public final int value;
|
||||
public final byte limitedCreditEnabled;
|
||||
|
||||
public ValueDesfireFileSettings(ByteArrayInputStream stream) {
|
||||
super(stream);
|
||||
|
||||
byte[] buf = new byte[4];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
lowerLimit = Utils.byteArrayToInt(buf);
|
||||
|
||||
buf = new byte[4];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
upperLimit = Utils.byteArrayToInt(buf);
|
||||
|
||||
buf = new byte[4];
|
||||
stream.read(buf, 0, buf.length);
|
||||
ArrayUtils.reverse(buf);
|
||||
value = Utils.byteArrayToInt(buf);
|
||||
|
||||
|
||||
buf = new byte[1];
|
||||
stream.read(buf, 0, buf.length);
|
||||
limitedCreditEnabled = buf[0];
|
||||
|
||||
//http://www.skyetek.com/docs/m2/desfire.pdf
|
||||
//http://neteril.org/files/M075031_desfire.pdf
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel (Parcel parcel, int flags) {
|
||||
super.writeToParcel(parcel, flags);
|
||||
parcel.writeInt(lowerLimit);
|
||||
parcel.writeInt(upperLimit);
|
||||
parcel.writeInt(value);
|
||||
parcel.writeByte(limitedCreditEnabled);
|
||||
}
|
||||
}
|
||||
public static class UnsupportedDesfireFileSettings extends DesfireFileSettings {
|
||||
public UnsupportedDesfireFileSettings(byte fileType) {
|
||||
super(fileType, Byte.MIN_VALUE, new byte[0]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* DesfireFileSettings.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.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.codebutler.farebot.Utils
|
||||
import org.apache.commons.lang3.ArrayUtils
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
abstract class DesfireFileSettings : Parcelable {
|
||||
private val fileType: Byte
|
||||
private val commSetting: Byte
|
||||
private val accessRights: ByteArray
|
||||
|
||||
@Suppress("unused")
|
||||
val fileTypeName: String
|
||||
get() {
|
||||
return when (fileType) {
|
||||
STANDARD_DATA_FILE -> "Standard"
|
||||
BACKUP_DATA_FILE -> "Backup"
|
||||
VALUE_FILE -> "Value"
|
||||
LINEAR_RECORD_FILE -> "Linear Record"
|
||||
CYCLIC_RECORD_FILE -> "Cyclic Record"
|
||||
else -> "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(stream: ByteArrayInputStream) {
|
||||
fileType = stream.read().toByte()
|
||||
commSetting = stream.read().toByte()
|
||||
|
||||
accessRights = ByteArray(2)
|
||||
stream.read(accessRights, 0, accessRights.size)
|
||||
}
|
||||
|
||||
private constructor(fileType: Byte, commSetting: Byte, accessRights: ByteArray) {
|
||||
this.fileType = fileType
|
||||
this.commSetting = commSetting
|
||||
this.accessRights = accessRights
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeByte(fileType)
|
||||
parcel.writeByte(commSetting)
|
||||
parcel.writeInt(accessRights.size)
|
||||
parcel.writeByteArray(accessRights)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
class StandardDesfireFileSettings : DesfireFileSettings {
|
||||
private val fileSize: Int
|
||||
|
||||
internal constructor(stream: ByteArrayInputStream) : super(stream) {
|
||||
val buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
fileSize = Utils.byteArrayToInt(buf)
|
||||
}
|
||||
|
||||
internal constructor(
|
||||
fileType: Byte,
|
||||
commSetting: Byte,
|
||||
accessRights: ByteArray,
|
||||
fileSize: Int
|
||||
) : super(fileType, commSetting, accessRights) {
|
||||
this.fileSize = fileSize
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
super.writeToParcel(parcel, flags)
|
||||
parcel.writeInt(fileSize)
|
||||
}
|
||||
}
|
||||
|
||||
class RecordDesfireFileSettings : DesfireFileSettings {
|
||||
private val maxRecords: Int
|
||||
val recordSize: Int
|
||||
val curRecords: Int
|
||||
|
||||
constructor(stream: ByteArrayInputStream) : super(stream) {
|
||||
|
||||
var buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
recordSize = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
maxRecords = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(3)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
curRecords = Utils.byteArrayToInt(buf)
|
||||
}
|
||||
|
||||
internal constructor(
|
||||
fileType: Byte,
|
||||
commSetting: Byte,
|
||||
accessRights: ByteArray,
|
||||
recordSize: Int,
|
||||
maxRecords: Int,
|
||||
curRecords: Int
|
||||
) : super(fileType, commSetting, accessRights) {
|
||||
this.recordSize = recordSize
|
||||
this.maxRecords = maxRecords
|
||||
this.curRecords = curRecords
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
super.writeToParcel(parcel, flags)
|
||||
parcel.writeInt(recordSize)
|
||||
parcel.writeInt(maxRecords)
|
||||
parcel.writeInt(curRecords)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ValueDesfireFileSettings(stream: ByteArrayInputStream) : DesfireFileSettings(stream) {
|
||||
private val lowerLimit: Int
|
||||
private val upperLimit: Int
|
||||
val value: Int
|
||||
private val limitedCreditEnabled: Byte
|
||||
|
||||
init {
|
||||
|
||||
var buf = ByteArray(4)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
lowerLimit = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(4)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
upperLimit = Utils.byteArrayToInt(buf)
|
||||
|
||||
buf = ByteArray(4)
|
||||
stream.read(buf, 0, buf.size)
|
||||
ArrayUtils.reverse(buf)
|
||||
value = Utils.byteArrayToInt(buf)
|
||||
|
||||
|
||||
buf = ByteArray(1)
|
||||
stream.read(buf, 0, buf.size)
|
||||
limitedCreditEnabled = buf[0]
|
||||
|
||||
//http://www.skyetek.com/docs/m2/desfire.pdf
|
||||
//http://neteril.org/files/M075031_desfire.pdf
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
super.writeToParcel(parcel, flags)
|
||||
parcel.writeInt(lowerLimit)
|
||||
parcel.writeInt(upperLimit)
|
||||
parcel.writeInt(value)
|
||||
parcel.writeByte(limitedCreditEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
class UnsupportedDesfireFileSettings(fileType: Byte) :
|
||||
DesfireFileSettings(fileType, java.lang.Byte.MIN_VALUE, ByteArray(0))
|
||||
|
||||
companion object {
|
||||
|
||||
/* DesfireFile Types */
|
||||
internal const val STANDARD_DATA_FILE = 0x00.toByte()
|
||||
internal const val BACKUP_DATA_FILE = 0x01.toByte()
|
||||
internal const val VALUE_FILE = 0x02.toByte()
|
||||
internal const val LINEAR_RECORD_FILE = 0x03.toByte()
|
||||
internal const val CYCLIC_RECORD_FILE = 0x04.toByte()
|
||||
|
||||
@Throws(DesfireException::class)
|
||||
fun create(data: ByteArray): DesfireFileSettings {
|
||||
val fileType = data[0]
|
||||
|
||||
val stream = ByteArrayInputStream(data)
|
||||
|
||||
return if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
|
||||
StandardDesfireFileSettings(stream)
|
||||
else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
|
||||
RecordDesfireFileSettings(stream)
|
||||
else if (fileType == VALUE_FILE)
|
||||
ValueDesfireFileSettings(stream)
|
||||
else
|
||||
throw DesfireException("Unknown file type: " + Integer.toHexString(fileType.toInt()))
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<DesfireFileSettings> = object : Parcelable.Creator<DesfireFileSettings> {
|
||||
override fun createFromParcel(source: Parcel): DesfireFileSettings {
|
||||
val fileType = source.readByte()
|
||||
val commSetting = source.readByte()
|
||||
val accessRights = ByteArray(source.readInt())
|
||||
source.readByteArray(accessRights)
|
||||
|
||||
if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE) {
|
||||
val fileSize = source.readInt()
|
||||
return StandardDesfireFileSettings(fileType, commSetting, accessRights, fileSize)
|
||||
} else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE) {
|
||||
val recordSize = source.readInt()
|
||||
val maxRecords = source.readInt()
|
||||
val curRecords = source.readInt()
|
||||
return RecordDesfireFileSettings(
|
||||
fileType,
|
||||
commSetting,
|
||||
accessRights,
|
||||
recordSize,
|
||||
maxRecords,
|
||||
curRecords
|
||||
)
|
||||
} else {
|
||||
return UnsupportedDesfireFileSettings(fileType)
|
||||
}
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DesfireFileSettings?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* DesfireManufacturingData.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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import com.codebutler.farebot.Utils;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class DesfireManufacturingData implements Parcelable {
|
||||
public final int hwVendorID;
|
||||
public final int hwType;
|
||||
public final int hwSubType;
|
||||
public final int hwMajorVersion;
|
||||
public final int hwMinorVersion;
|
||||
public final int hwStorageSize;
|
||||
public final int hwProtocol;
|
||||
|
||||
public final int swVendorID;
|
||||
public final int swType;
|
||||
public final int swSubType;
|
||||
public final int swMajorVersion;
|
||||
public final int swMinorVersion;
|
||||
public final int swStorageSize;
|
||||
public final int swProtocol;
|
||||
|
||||
public final int uid;
|
||||
public final int batchNo;
|
||||
public final int weekProd;
|
||||
public final int yearProd;
|
||||
|
||||
public DesfireManufacturingData (byte[] data) {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(data);
|
||||
hwVendorID = stream.read();
|
||||
hwType = stream.read();
|
||||
hwSubType = stream.read();
|
||||
hwMajorVersion = stream.read();
|
||||
hwMinorVersion = stream.read();
|
||||
hwStorageSize = stream.read();
|
||||
hwProtocol = stream.read();
|
||||
|
||||
swVendorID = stream.read();
|
||||
swType = stream.read();
|
||||
swSubType = stream.read();
|
||||
swMajorVersion = stream.read();
|
||||
swMinorVersion = stream.read();
|
||||
swStorageSize = stream.read();
|
||||
swProtocol = stream.read();
|
||||
|
||||
// FIXME: This has fewer digits than what's contained in EXTRA_ID, why?
|
||||
byte[] buf = new byte[7];
|
||||
stream.read(buf, 0, buf.length);
|
||||
uid = Utils.byteArrayToInt(buf);
|
||||
|
||||
// FIXME: This is returning a negative number. Probably is unsigned.
|
||||
buf = new byte[5];
|
||||
stream.read(buf, 0, buf.length);
|
||||
batchNo = Utils.byteArrayToInt(buf);
|
||||
|
||||
// FIXME: These numbers aren't making sense.
|
||||
weekProd = stream.read();
|
||||
yearProd = stream.read();
|
||||
}
|
||||
|
||||
public static DesfireManufacturingData fromXml (Element element) {
|
||||
return new DesfireManufacturingData(element);
|
||||
}
|
||||
|
||||
private DesfireManufacturingData (Element element) {
|
||||
hwVendorID = Integer.parseInt(element.getElementsByTagName("hw-vendor-id").item(0).getTextContent());
|
||||
hwType = Integer.parseInt(element.getElementsByTagName("hw-type").item(0).getTextContent());
|
||||
hwSubType = Integer.parseInt(element.getElementsByTagName("hw-sub-type").item(0).getTextContent());
|
||||
hwMajorVersion = Integer.parseInt(element.getElementsByTagName("hw-major-version").item(0).getTextContent());
|
||||
hwMinorVersion = Integer.parseInt(element.getElementsByTagName("hw-minor-version").item(0).getTextContent());
|
||||
hwStorageSize = Integer.parseInt(element.getElementsByTagName("hw-storage-size").item(0).getTextContent());
|
||||
hwProtocol = Integer.parseInt(element.getElementsByTagName("hw-protocol").item(0).getTextContent());
|
||||
|
||||
swVendorID = Integer.parseInt(element.getElementsByTagName("sw-vendor-id").item(0).getTextContent());
|
||||
swType = Integer.parseInt(element.getElementsByTagName("sw-type").item(0).getTextContent());
|
||||
swSubType = Integer.parseInt(element.getElementsByTagName("sw-sub-type").item(0).getTextContent());
|
||||
swMajorVersion = Integer.parseInt(element.getElementsByTagName("sw-major-version").item(0).getTextContent());
|
||||
swMinorVersion = Integer.parseInt(element.getElementsByTagName("sw-minor-version").item(0).getTextContent());
|
||||
swStorageSize = Integer.parseInt(element.getElementsByTagName("sw-storage-size").item(0).getTextContent());
|
||||
swProtocol = Integer.parseInt(element.getElementsByTagName("sw-protocol").item(0).getTextContent());
|
||||
|
||||
uid = Integer.parseInt(element.getElementsByTagName("uid").item(0).getTextContent());
|
||||
batchNo = Integer.parseInt(element.getElementsByTagName("batch-no").item(0).getTextContent());
|
||||
weekProd = Integer.parseInt(element.getElementsByTagName("week-prod").item(0).getTextContent());
|
||||
yearProd = Integer.parseInt(element.getElementsByTagName("year-prod").item(0).getTextContent());
|
||||
}
|
||||
|
||||
private DesfireManufacturingData (Parcel parcel) {
|
||||
hwVendorID = parcel.readInt();
|
||||
hwType = parcel.readInt();
|
||||
hwSubType = parcel.readInt();
|
||||
hwMajorVersion = parcel.readInt();
|
||||
hwMinorVersion = parcel.readInt();
|
||||
hwStorageSize = parcel.readInt();
|
||||
hwProtocol = parcel.readInt();
|
||||
|
||||
swVendorID = parcel.readInt();
|
||||
swType = parcel.readInt();
|
||||
swSubType = parcel.readInt();
|
||||
swMajorVersion = parcel.readInt();
|
||||
swMinorVersion = parcel.readInt();
|
||||
swStorageSize = parcel.readInt();
|
||||
swProtocol = parcel.readInt();
|
||||
|
||||
uid = parcel.readInt();
|
||||
batchNo = parcel.readInt();
|
||||
weekProd = parcel.readInt();
|
||||
yearProd = parcel.readInt();
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeInt(hwVendorID);
|
||||
parcel.writeInt(hwType);
|
||||
parcel.writeInt(hwSubType);
|
||||
parcel.writeInt(hwMajorVersion);
|
||||
parcel.writeInt(hwMinorVersion);
|
||||
parcel.writeInt(hwStorageSize);
|
||||
parcel.writeInt(hwProtocol);
|
||||
|
||||
parcel.writeInt(swVendorID);
|
||||
parcel.writeInt(swType);
|
||||
parcel.writeInt(swSubType);
|
||||
parcel.writeInt(swMajorVersion);
|
||||
parcel.writeInt(swMinorVersion);
|
||||
parcel.writeInt(swStorageSize);
|
||||
parcel.writeInt(swProtocol);
|
||||
|
||||
parcel.writeInt(uid);
|
||||
parcel.writeInt(batchNo);
|
||||
parcel.writeInt(weekProd);
|
||||
parcel.writeInt(yearProd);
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<DesfireManufacturingData> CREATOR = new Parcelable.Creator<DesfireManufacturingData>() {
|
||||
public DesfireManufacturingData createFromParcel(Parcel source) {
|
||||
return new DesfireManufacturingData(source);
|
||||
}
|
||||
|
||||
public DesfireManufacturingData[] newArray(int size) {
|
||||
return new DesfireManufacturingData[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* DesfireManufacturingData.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.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.codebutler.farebot.Utils
|
||||
import org.w3c.dom.Element
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
class DesfireManufacturingData : Parcelable {
|
||||
private val hwVendorID: Int
|
||||
private val hwType: Int
|
||||
private val hwSubType: Int
|
||||
private val hwMajorVersion: Int
|
||||
private val hwMinorVersion: Int
|
||||
private val hwStorageSize: Int
|
||||
private val hwProtocol: Int
|
||||
|
||||
private val swVendorID: Int
|
||||
private val swType: Int
|
||||
private val swSubType: Int
|
||||
private val swMajorVersion: Int
|
||||
private val swMinorVersion: Int
|
||||
private val swStorageSize: Int
|
||||
private val swProtocol: Int
|
||||
|
||||
private val uid: Int
|
||||
private val batchNo: Int
|
||||
private val weekProd: Int
|
||||
private val yearProd: Int
|
||||
|
||||
constructor(data: ByteArray) {
|
||||
val stream = ByteArrayInputStream(data)
|
||||
hwVendorID = stream.read()
|
||||
hwType = stream.read()
|
||||
hwSubType = stream.read()
|
||||
hwMajorVersion = stream.read()
|
||||
hwMinorVersion = stream.read()
|
||||
hwStorageSize = stream.read()
|
||||
hwProtocol = stream.read()
|
||||
|
||||
swVendorID = stream.read()
|
||||
swType = stream.read()
|
||||
swSubType = stream.read()
|
||||
swMajorVersion = stream.read()
|
||||
swMinorVersion = stream.read()
|
||||
swStorageSize = stream.read()
|
||||
swProtocol = stream.read()
|
||||
|
||||
// FIXME: This has fewer digits than what's contained in EXTRA_ID, why?
|
||||
var buf = ByteArray(7)
|
||||
stream.read(buf, 0, buf.size)
|
||||
uid = Utils.byteArrayToInt(buf)
|
||||
|
||||
// FIXME: This is returning a negative number. Probably is unsigned.
|
||||
buf = ByteArray(5)
|
||||
stream.read(buf, 0, buf.size)
|
||||
batchNo = Utils.byteArrayToInt(buf)
|
||||
|
||||
// FIXME: These numbers aren't making sense.
|
||||
weekProd = stream.read()
|
||||
yearProd = stream.read()
|
||||
}
|
||||
|
||||
private constructor(element: Element) {
|
||||
hwVendorID = Integer.parseInt(element.getElementsByTagName("hw-vendor-id").item(0).textContent)
|
||||
hwType = Integer.parseInt(element.getElementsByTagName("hw-type").item(0).textContent)
|
||||
hwSubType = Integer.parseInt(element.getElementsByTagName("hw-sub-type").item(0).textContent)
|
||||
hwMajorVersion = Integer.parseInt(element.getElementsByTagName("hw-major-version").item(0).textContent)
|
||||
hwMinorVersion = Integer.parseInt(element.getElementsByTagName("hw-minor-version").item(0).textContent)
|
||||
hwStorageSize = Integer.parseInt(element.getElementsByTagName("hw-storage-size").item(0).textContent)
|
||||
hwProtocol = Integer.parseInt(element.getElementsByTagName("hw-protocol").item(0).textContent)
|
||||
|
||||
swVendorID = Integer.parseInt(element.getElementsByTagName("sw-vendor-id").item(0).textContent)
|
||||
swType = Integer.parseInt(element.getElementsByTagName("sw-type").item(0).textContent)
|
||||
swSubType = Integer.parseInt(element.getElementsByTagName("sw-sub-type").item(0).textContent)
|
||||
swMajorVersion = Integer.parseInt(element.getElementsByTagName("sw-major-version").item(0).textContent)
|
||||
swMinorVersion = Integer.parseInt(element.getElementsByTagName("sw-minor-version").item(0).textContent)
|
||||
swStorageSize = Integer.parseInt(element.getElementsByTagName("sw-storage-size").item(0).textContent)
|
||||
swProtocol = Integer.parseInt(element.getElementsByTagName("sw-protocol").item(0).textContent)
|
||||
|
||||
uid = Integer.parseInt(element.getElementsByTagName("uid").item(0).textContent)
|
||||
batchNo = Integer.parseInt(element.getElementsByTagName("batch-no").item(0).textContent)
|
||||
weekProd = Integer.parseInt(element.getElementsByTagName("week-prod").item(0).textContent)
|
||||
yearProd = Integer.parseInt(element.getElementsByTagName("year-prod").item(0).textContent)
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
hwVendorID = parcel.readInt()
|
||||
hwType = parcel.readInt()
|
||||
hwSubType = parcel.readInt()
|
||||
hwMajorVersion = parcel.readInt()
|
||||
hwMinorVersion = parcel.readInt()
|
||||
hwStorageSize = parcel.readInt()
|
||||
hwProtocol = parcel.readInt()
|
||||
|
||||
swVendorID = parcel.readInt()
|
||||
swType = parcel.readInt()
|
||||
swSubType = parcel.readInt()
|
||||
swMajorVersion = parcel.readInt()
|
||||
swMinorVersion = parcel.readInt()
|
||||
swStorageSize = parcel.readInt()
|
||||
swProtocol = parcel.readInt()
|
||||
|
||||
uid = parcel.readInt()
|
||||
batchNo = parcel.readInt()
|
||||
weekProd = parcel.readInt()
|
||||
yearProd = parcel.readInt()
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeInt(hwVendorID)
|
||||
parcel.writeInt(hwType)
|
||||
parcel.writeInt(hwSubType)
|
||||
parcel.writeInt(hwMajorVersion)
|
||||
parcel.writeInt(hwMinorVersion)
|
||||
parcel.writeInt(hwStorageSize)
|
||||
parcel.writeInt(hwProtocol)
|
||||
|
||||
parcel.writeInt(swVendorID)
|
||||
parcel.writeInt(swType)
|
||||
parcel.writeInt(swSubType)
|
||||
parcel.writeInt(swMajorVersion)
|
||||
parcel.writeInt(swMinorVersion)
|
||||
parcel.writeInt(swStorageSize)
|
||||
parcel.writeInt(swProtocol)
|
||||
|
||||
parcel.writeInt(uid)
|
||||
parcel.writeInt(batchNo)
|
||||
parcel.writeInt(weekProd)
|
||||
parcel.writeInt(yearProd)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@Suppress("unused")
|
||||
fun fromXml(element: Element): DesfireManufacturingData {
|
||||
return DesfireManufacturingData(element)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<DesfireManufacturingData> =
|
||||
object : Parcelable.Creator<DesfireManufacturingData> {
|
||||
override fun createFromParcel(source: Parcel): DesfireManufacturingData {
|
||||
return DesfireManufacturingData(source)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DesfireManufacturingData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
public class DesfireProtocol {
|
||||
/* Commands */
|
||||
static final byte GET_MANUFACTURING_DATA = (byte) 0x60;
|
||||
static final byte GET_APPLICATION_DIRECTORY = (byte) 0x6A;
|
||||
static final byte GET_ADDITIONAL_FRAME = (byte) 0xAF;
|
||||
static final byte SELECT_APPLICATION = (byte) 0x5A;
|
||||
static final byte READ_DATA = (byte) 0xBD;
|
||||
static final byte READ_RECORD = (byte) 0xBB;
|
||||
static final byte READ_VALUE = (byte) 0x6C;
|
||||
static final byte GET_FILES = (byte) 0x6F;
|
||||
static final byte GET_FILE_SETTINGS = (byte) 0xF5;
|
||||
|
||||
/* Status codes */
|
||||
static final byte OPERATION_OK = (byte) 0x00;
|
||||
static final byte PERMISSION_DENIED = (byte) 0x9D;
|
||||
static final byte ADDITIONAL_FRAME = (byte) 0xAF;
|
||||
|
||||
private IsoDep mTagTech;
|
||||
|
||||
public DesfireProtocol(IsoDep tagTech) {
|
||||
mTagTech = tagTech;
|
||||
}
|
||||
|
||||
public DesfireManufacturingData getManufacturingData() throws DesfireException {
|
||||
byte[] respBuffer = sendRequest(GET_MANUFACTURING_DATA);
|
||||
|
||||
if (respBuffer.length != 28)
|
||||
throw new DesfireException("Invalid response");
|
||||
|
||||
return new DesfireManufacturingData(respBuffer);
|
||||
}
|
||||
|
||||
public int[] getAppList() throws DesfireException {
|
||||
byte[] appDirBuf = sendRequest(GET_APPLICATION_DIRECTORY);
|
||||
|
||||
int[] appIds = new int[appDirBuf.length / 3];
|
||||
|
||||
for (int app = 0; app < appDirBuf.length; app += 3) {
|
||||
byte[] appId = new byte[3];
|
||||
System.arraycopy(appDirBuf, app, appId, 0, 3);
|
||||
|
||||
appIds[app / 3] = Utils.byteArrayToInt(appId);
|
||||
}
|
||||
|
||||
return appIds;
|
||||
}
|
||||
|
||||
public void selectApp (int appId) throws DesfireException {
|
||||
byte[] appIdBuff = new byte[3];
|
||||
appIdBuff[0] = (byte) ((appId & 0xFF0000) >> 16);
|
||||
appIdBuff[1] = (byte) ((appId & 0xFF00) >> 8);
|
||||
appIdBuff[2] = (byte) (appId & 0xFF);
|
||||
|
||||
sendRequest(SELECT_APPLICATION, appIdBuff);
|
||||
}
|
||||
|
||||
public int[] getFileList() throws DesfireException {
|
||||
byte[] buf = sendRequest(GET_FILES);
|
||||
int[] fileIds = new int[buf.length];
|
||||
for (int x = 0; x < buf.length; x++) {
|
||||
fileIds[x] = (int)buf[x];
|
||||
}
|
||||
return fileIds;
|
||||
}
|
||||
|
||||
public DesfireFileSettings getFileSettings (int fileNo) throws DesfireException {
|
||||
byte[] data = new byte[0];
|
||||
data = sendRequest(GET_FILE_SETTINGS, new byte[] { (byte) fileNo });
|
||||
return DesfireFileSettings.Create(data);
|
||||
}
|
||||
|
||||
public byte[] readFile (int fileNo) throws DesfireException {
|
||||
return sendRequest(READ_DATA, new byte[] {
|
||||
(byte) fileNo,
|
||||
(byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||
(byte) 0x0, (byte) 0x0, (byte) 0x0
|
||||
});
|
||||
}
|
||||
|
||||
public byte[] readRecord (int fileNum) throws DesfireException {
|
||||
return sendRequest(READ_RECORD, new byte[]{
|
||||
(byte) fileNum,
|
||||
(byte) 0x0, (byte) 0x0, (byte) 0x0,
|
||||
(byte) 0x0, (byte) 0x0, (byte) 0x0
|
||||
});
|
||||
}
|
||||
|
||||
public int readValue(int fileNum) throws DesfireException {
|
||||
byte[] buf = sendRequest(READ_VALUE, new byte[]{
|
||||
(byte) fileNum
|
||||
});
|
||||
ArrayUtils.reverse(buf);
|
||||
return Utils.byteArrayToInt(buf);
|
||||
}
|
||||
|
||||
|
||||
private byte[] sendRequest (byte command) throws DesfireException {
|
||||
return sendRequest(command, null);
|
||||
}
|
||||
|
||||
private byte[] sendRequest (byte command, byte[] parameters) throws DesfireException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
byte[] recvBuffer = new byte[0];
|
||||
try {
|
||||
recvBuffer = mTagTech.transceive(wrapMessage(command, parameters));
|
||||
} catch (IOException e) {
|
||||
throw new DesfireException(e);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (recvBuffer[recvBuffer.length - 2] != (byte) 0x91)
|
||||
throw new DesfireException("Invalid response");
|
||||
|
||||
output.write(recvBuffer, 0, recvBuffer.length - 2);
|
||||
|
||||
byte status = recvBuffer[recvBuffer.length - 1];
|
||||
if (status == OPERATION_OK) {
|
||||
break;
|
||||
} else if (status == ADDITIONAL_FRAME) {
|
||||
try {
|
||||
recvBuffer = mTagTech.transceive(wrapMessage(GET_ADDITIONAL_FRAME, null));
|
||||
} catch (IOException e) {
|
||||
throw new DesfireException(e);
|
||||
}
|
||||
} else if (status == PERMISSION_DENIED) {
|
||||
throw new DesfireException("Permission denied");
|
||||
} else {
|
||||
throw new DesfireException("Unknown status code: " + Integer.toHexString(status & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] wrapMessage (byte command, byte[] parameters) throws DesfireException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
stream.write((byte) 0x90);
|
||||
stream.write(command);
|
||||
stream.write((byte) 0x00);
|
||||
stream.write((byte) 0x00);
|
||||
if (parameters != null) {
|
||||
stream.write((byte) parameters.length);
|
||||
try {
|
||||
stream.write(parameters);
|
||||
} catch (IOException e) {
|
||||
throw new DesfireException(e);
|
||||
}
|
||||
}
|
||||
stream.write((byte) 0x00);
|
||||
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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()
|
||||
}
|
||||
|
||||
}
|
|
@ -20,16 +20,6 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.codebutler.farebot.card.desfire;
|
||||
package com.codebutler.farebot.card.desfire
|
||||
|
||||
public class DesfireRecord {
|
||||
private byte[] mData;
|
||||
|
||||
public DesfireRecord (byte[] data) {
|
||||
mData = data;
|
||||
}
|
||||
|
||||
public byte[] getData () {
|
||||
return mData;
|
||||
}
|
||||
}
|
||||
class DesfireRecord(val data: ByteArray)
|
Loading…
Reference in New Issue