4 * Copyright (C) 2011 Eric Butler
7 * Eric Butler <eric@codebutler.com>
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 package com.codebutler.farebot.card.desfire;
25 import android.nfc.tech.IsoDep;
26 import com.codebutler.farebot.Utils;
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
31 import org.apache.commons.lang3.ArrayUtils;
33 public class DesfireProtocol {
35 static final byte GET_MANUFACTURING_DATA = (byte) 0x60;
36 static final byte GET_APPLICATION_DIRECTORY = (byte) 0x6A;
37 static final byte GET_ADDITIONAL_FRAME = (byte) 0xAF;
38 static final byte SELECT_APPLICATION = (byte) 0x5A;
39 static final byte READ_DATA = (byte) 0xBD;
40 static final byte READ_RECORD = (byte) 0xBB;
41 static final byte READ_VALUE = (byte) 0x6C;
42 static final byte GET_FILES = (byte) 0x6F;
43 static final byte GET_FILE_SETTINGS = (byte) 0xF5;
46 static final byte OPERATION_OK = (byte) 0x00;
47 static final byte PERMISSION_DENIED = (byte) 0x9D;
48 static final byte ADDITIONAL_FRAME = (byte) 0xAF;
50 private IsoDep mTagTech;
52 public DesfireProtocol(IsoDep tagTech) {
56 public DesfireManufacturingData getManufacturingData() throws DesfireException {
57 byte[] respBuffer = sendRequest(GET_MANUFACTURING_DATA);
59 if (respBuffer.length != 28)
60 throw new DesfireException("Invalid response");
62 return new DesfireManufacturingData(respBuffer);
65 public int[] getAppList() throws DesfireException {
66 byte[] appDirBuf = sendRequest(GET_APPLICATION_DIRECTORY);
68 int[] appIds = new int[appDirBuf.length / 3];
70 for (int app = 0; app < appDirBuf.length; app += 3) {
71 byte[] appId = new byte[3];
72 System.arraycopy(appDirBuf, app, appId, 0, 3);
74 appIds[app / 3] = Utils.byteArrayToInt(appId);
80 public void selectApp (int appId) throws DesfireException {
81 byte[] appIdBuff = new byte[3];
82 appIdBuff[0] = (byte) ((appId & 0xFF0000) >> 16);
83 appIdBuff[1] = (byte) ((appId & 0xFF00) >> 8);
84 appIdBuff[2] = (byte) (appId & 0xFF);
86 sendRequest(SELECT_APPLICATION, appIdBuff);
89 public int[] getFileList() throws DesfireException {
90 byte[] buf = sendRequest(GET_FILES);
91 int[] fileIds = new int[buf.length];
92 for (int x = 0; x < buf.length; x++) {
93 fileIds[x] = (int)buf[x];
98 public DesfireFileSettings getFileSettings (int fileNo) throws DesfireException {
99 byte[] data = new byte[0];
100 data = sendRequest(GET_FILE_SETTINGS, new byte[] { (byte) fileNo });
101 return DesfireFileSettings.Create(data);
104 public byte[] readFile (int fileNo) throws DesfireException {
105 return sendRequest(READ_DATA, new byte[] {
107 (byte) 0x0, (byte) 0x0, (byte) 0x0,
108 (byte) 0x0, (byte) 0x0, (byte) 0x0
112 public byte[] readRecord (int fileNum) throws DesfireException {
113 return sendRequest(READ_RECORD, new byte[]{
115 (byte) 0x0, (byte) 0x0, (byte) 0x0,
116 (byte) 0x0, (byte) 0x0, (byte) 0x0
120 public int readValue(int fileNum) throws DesfireException {
121 byte[] buf = sendRequest(READ_VALUE, new byte[]{
124 ArrayUtils.reverse(buf);
125 return Utils.byteArrayToInt(buf);
129 private byte[] sendRequest (byte command) throws DesfireException {
130 return sendRequest(command, null);
133 private byte[] sendRequest (byte command, byte[] parameters) throws DesfireException {
134 ByteArrayOutputStream output = new ByteArrayOutputStream();
136 byte[] recvBuffer = new byte[0];
138 recvBuffer = mTagTech.transceive(wrapMessage(command, parameters));
139 } catch (IOException e) {
140 throw new DesfireException(e);
144 if (recvBuffer[recvBuffer.length - 2] != (byte) 0x91)
145 throw new DesfireException("Invalid response");
147 output.write(recvBuffer, 0, recvBuffer.length - 2);
149 byte status = recvBuffer[recvBuffer.length - 1];
150 if (status == OPERATION_OK) {
152 } else if (status == ADDITIONAL_FRAME) {
154 recvBuffer = mTagTech.transceive(wrapMessage(GET_ADDITIONAL_FRAME, null));
155 } catch (IOException e) {
156 throw new DesfireException(e);
158 } else if (status == PERMISSION_DENIED) {
159 throw new DesfireException("Permission denied");
161 throw new DesfireException("Unknown status code: " + Integer.toHexString(status & 0xFF));
165 return output.toByteArray();
168 private byte[] wrapMessage (byte command, byte[] parameters) throws DesfireException {
169 ByteArrayOutputStream stream = new ByteArrayOutputStream();
171 stream.write((byte) 0x90);
172 stream.write(command);
173 stream.write((byte) 0x00);
174 stream.write((byte) 0x00);
175 if (parameters != null) {
176 stream.write((byte) parameters.length);
178 stream.write(parameters);
179 } catch (IOException e) {
180 throw new DesfireException(e);
183 stream.write((byte) 0x00);
185 return stream.toByteArray();