blob: 6874f67f281a8fbd0965a69f4b8c664b9d57a7fd [file] [log] [blame]
Jeff Hamilton6be655c2010-11-12 12:28:16 -06001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.nfc.technology;
18
19import android.nfc.NfcAdapter;
20import android.nfc.Tag;
21import android.os.Bundle;
22import android.os.RemoteException;
23
24import java.io.IOException;
25
26/**
27 * Concrete class for TagTechnology.MIFARE_CLASSIC
28 *
Jeff Hamiltonbe372d62010-12-22 19:20:26 -060029 * MIFARE Classic has n sectors, with varying sizes, although
30 * they are at least the same pattern for any one MIFARE Classic
Jeff Hamilton6be655c2010-11-12 12:28:16 -060031 * product. Each sector has two keys. Authentication with the correct
32 * key is needed before access to any sector.
33 *
34 * Each sector has k blocks.
Jeff Hamiltonbe372d62010-12-22 19:20:26 -060035 * Block size is constant across the whole MIFARE classic family.
Jeff Hamilton6be655c2010-11-12 12:28:16 -060036 */
37public final class MifareClassic extends BasicTagTechnology {
38 /**
Jan Brands65c3f982010-12-06 21:05:52 +010039 * The well-known, default MIFARE read key.
Jeff Hamilton6be655c2010-11-12 12:28:16 -060040 * Use this key to effectively make the payload in this sector
41 * public.
42 */
Jan Brands65c3f982010-12-06 21:05:52 +010043 public static final byte[] KEY_DEFAULT =
Jeff Hamilton6be655c2010-11-12 12:28:16 -060044 {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF};
Jeff Hamilton6be655c2010-11-12 12:28:16 -060045 /**
Jeff Hamiltonbe372d62010-12-22 19:20:26 -060046 * The well-known, default MIFARE Application Directory read key.
Jeff Hamilton6be655c2010-11-12 12:28:16 -060047 */
Jan Brands65c3f982010-12-06 21:05:52 +010048 public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY =
Jeff Hamilton6be655c2010-11-12 12:28:16 -060049 {(byte)0xA0,(byte)0xA1,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5};
50 /**
Jeff Hamiltonbe372d62010-12-22 19:20:26 -060051 * The well-known, default read key for NDEF data on a MIFARE Classic
Jeff Hamilton6be655c2010-11-12 12:28:16 -060052 */
Jan Brands65c3f982010-12-06 21:05:52 +010053 public static final byte[] KEY_NFC_FORUM =
Jeff Hamilton6be655c2010-11-12 12:28:16 -060054 {(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7};
55
56 public static final int TYPE_CLASSIC = 0;
57 public static final int TYPE_PLUS = 1;
58 public static final int TYPE_PRO = 2;
59 public static final int TYPE_DESFIRE = 3;
60 public static final int TYPE_ULTRALIGHT = 4;
61 public static final int TYPE_UNKNOWN = 5;
62
63 public static final int SIZE_1K = 1024;
64 public static final int SIZE_2K = 2048;
65 public static final int SIZE_4K = 4096;
66 public static final int SIZE_MINI = 320;
67 public static final int SIZE_UNKNOWN = 0;
68
69 private boolean mIsEmulated;
70 private int mType;
71 private int mSize;
72
73 public MifareClassic(NfcAdapter adapter, Tag tag, Bundle extras) throws RemoteException {
74 super(adapter, tag, TagTechnology.MIFARE_CLASSIC);
75
76 // Check if this could actually be a Mifare
Nick Pelly50b4d8f2010-12-07 22:40:28 -080077 NfcA a = (NfcA) tag.getTechnology(adapter, TagTechnology.NFC_A);
Jeff Hamilton6be655c2010-11-12 12:28:16 -060078 //short[] ATQA = getATQA(tag);
79
80 mIsEmulated = false;
81 mType = TYPE_UNKNOWN;
82 mSize = SIZE_UNKNOWN;
83
84 switch (a.getSak()) {
85 case 0x00:
86 // could be UL or UL-C
87 mType = TYPE_ULTRALIGHT;
88 break;
89 case 0x08:
90 // Type == classic
91 // Size = 1K
92 mType = TYPE_CLASSIC;
93 mSize = SIZE_1K;
94 break;
95 case 0x09:
96 // Type == classic mini
97 // Size == ?
98 mType = TYPE_CLASSIC;
99 mSize = SIZE_MINI;
100 break;
101 case 0x10:
102 // Type == MF+
103 // Size == 2K
104 // SecLevel = SL2
105 mType = TYPE_PLUS;
106 mSize = SIZE_2K;
107 break;
108 case 0x11:
109 // Type == MF+
110 // Size == 4K
111 // Seclevel = SL2
112 mType = TYPE_PLUS;
113 mSize = SIZE_4K;
114 break;
115 case 0x18:
116 // Type == classic
117 // Size == 4k
118 mType = TYPE_CLASSIC;
119 mSize = SIZE_4K;
120 break;
121 case 0x20:
122 // TODO this really should be a short, not byte
123 if (a.getAtqa()[0] == 0x03) {
124 // Type == DESFIRE
125 mType = TYPE_DESFIRE;
126 } else {
127 // Type == MF+
128 // SL = SL3
129 mType = TYPE_PLUS;
130 mSize = SIZE_UNKNOWN;
131 }
132 break;
133 case 0x28:
134 // Type == MF Classic
135 // Size == 1K
136 // Emulated == true
137 mType = TYPE_CLASSIC;
138 mSize = SIZE_1K;
139 mIsEmulated = true;
140 break;
141 case 0x38:
142 // Type == MF Classic
143 // Size == 4K
144 // Emulated == true
145 mType = TYPE_CLASSIC;
146 mSize = SIZE_4K;
147 mIsEmulated = true;
148 break;
149 case 0x88:
150 // Type == MF Classic
151 // Size == 1K
152 // NXP-tag: false
153 mType = TYPE_CLASSIC;
154 mSize = SIZE_1K;
155 break;
156 case 0x98:
157 case 0xB8:
158 // Type == MF Pro
159 // Size == 4K
160 mType = TYPE_PRO;
161 mSize = SIZE_4K;
162 break;
163 default:
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100164 // Unknown mifare
165 mType = TYPE_UNKNOWN;
166 mSize = SIZE_UNKNOWN;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600167 break;
168 }
169 }
170
171 // Immutable data known at discovery time
172 public int getSize() {
173 return mSize;
174 }
175
176 public int getType() {
177 return mType;
178 }
179
180 public boolean isEmulated() {
181 return mIsEmulated;
182 }
183
184 public int getSectorCount() {
185 switch (mSize) {
186 case SIZE_1K: {
187 return 16;
188 }
189 case SIZE_2K: {
190 return 32;
191 }
192 case SIZE_4K: {
193 return 40;
194 }
195 case SIZE_MINI: {
196 return 5;
197 }
198 default: {
199 return 0;
200 }
201 }
202 }
203
204 public int getSectorSize(int sector) {
205 return getBlockCount(sector) * 16;
206 }
207
Martijn Coenena42b3522010-12-18 12:59:53 +0100208 public int getTotalBlockCount() {
209 int totalBlocks = 0;
210 for (int sec = 0; sec < getSectorCount(); sec++) {
211 totalBlocks += getSectorSize(sec);
212 }
213
214 return totalBlocks;
215 }
216
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600217 public int getBlockCount(int sector) {
218 if (sector >= getSectorCount()) {
219 throw new IllegalArgumentException("this card only has " + getSectorCount() +
220 " sectors");
221 }
222
223 if (sector <= 32) {
224 return 4;
225 } else {
226 return 16;
227 }
228 }
229
230 private byte firstBlockInSector(int sector) {
231 if (sector < 32) {
232 return (byte) ((sector * 4) & 0xff);
233 } else {
234 return (byte) ((32 * 4 + ((sector - 32) * 16)) & 0xff);
235 }
236 }
237
238 // Methods that require connect()
239 /**
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100240 * Authenticate for a given block.
241 * Note that this will authenticate the entire sector the block belongs to.
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600242 */
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100243 public boolean authenticateBlock(int block, byte[] key, boolean keyA) {
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100244 checkConnected();
245
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600246 byte[] cmd = new byte[12];
247
248 // First byte is the command
249 if (keyA) {
250 cmd[0] = 0x60; // phHal_eMifareAuthentA
251 } else {
252 cmd[0] = 0x61; // phHal_eMifareAuthentB
253 }
254
255 // Second byte is block address
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100256 cmd[1] = (byte) block;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600257
258 // Next 4 bytes are last 4 bytes of UID
259 byte[] uid = getTag().getId();
260 System.arraycopy(uid, uid.length - 4, cmd, 2, 4);
261
262 // Next 6 bytes are key
263 System.arraycopy(key, 0, cmd, 6, 6);
264
265 try {
266 if ((transceive(cmd) != null)) {
267 return true;
268 }
269 } catch (IOException e) {
270 // No need to deal with, will return false anyway
271 }
272 return false;
273 }
274
275 /**
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100276 * Authenticate for a given sector.
277 */
278 public boolean authenticateSector(int sector, byte[] key, boolean keyA) {
279 checkConnected();
280
281 byte addr = (byte) ((firstBlockInSector(sector)) & 0xff);
282
283 // Note that authenticating a block of a sector, will authenticate
284 // the entire sector.
285 return authenticateBlock(addr, key, keyA);
286 }
287
288 /**
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600289 * Sector indexing starts at 0.
290 * Block indexing starts at 0, and resets in each sector.
291 * @throws IOException
292 */
293 public byte[] readBlock(int sector, int block) throws IOException {
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100294 checkConnected();
295
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600296 byte addr = (byte) ((firstBlockInSector(sector) + block) & 0xff);
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100297 return readBlock(addr);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600298
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100299 }
300
301 /**
302 * Reads absolute block index.
303 * @throws IOException
304 */
305 public byte[] readBlock(int block) throws IOException {
306 checkConnected();
307
308 byte addr = (byte) block;
309 byte[] blockread_cmd = { 0x30, addr };
310
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600311 return transceive(blockread_cmd);
312 }
313
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100314 /**
315 * Writes absolute block index.
316 * @throws IOException
317 */
318 public void writeBlock(int block, byte[] data) throws IOException {
319 checkConnected();
320
321 byte addr = (byte) block;
322 byte[] blockwrite_cmd = new byte[data.length + 2];
323 blockwrite_cmd[0] = (byte) 0xA0; // MF write command
324 blockwrite_cmd[1] = addr;
325 System.arraycopy(data, 0, blockwrite_cmd, 2, data.length);
326
327 transceive(blockwrite_cmd);
328 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600329
330 /**
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100331 * Writes relative block in sector.
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600332 * @throws IOException
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600333 */
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100334 public void writeBlock(int sector, int block, byte[] data) throws IOException {
335 checkConnected();
Martijn Coenenfc5a3b62010-12-10 10:46:56 -0800336
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100337 byte addr = (byte) ((firstBlockInSector(sector) + block) & 0xff);
338
339 writeBlock(addr, data);
340 }
341
342 public void increment(int block) throws IOException {
343 checkConnected();
344
345 byte addr = (byte) block;
346 byte[] incr_cmd = { (byte) 0xC1, (byte) block };
347
348 transceive(incr_cmd);
349 }
350
351 public void decrement(int block) throws IOException {
352 checkConnected();
353
354 byte addr = (byte) block;
Martijn Coenena42b3522010-12-18 12:59:53 +0100355 byte[] decr_cmd = { (byte) 0xC0, (byte) block };
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100356
Martijn Coenena42b3522010-12-18 12:59:53 +0100357 transceive(decr_cmd);
358 }
359
360 public void transfer(int block) throws IOException {
361 checkConnected();
362
363 byte addr = (byte) block;
364 byte[] trans_cmd = { (byte) 0xB0, (byte) block };
365
366 transceive(trans_cmd);
367 }
368
369 public void restore(int block) throws IOException {
370 checkConnected();
371
372 byte addr = (byte) block;
373 byte[] rest_cmd = { (byte) 0xC2, (byte) block };
374
375 transceive(rest_cmd);
Martijn Coenenab82a5b2010-12-17 19:31:39 +0100376 }
377
Martijn Coenenfc5a3b62010-12-10 10:46:56 -0800378 /**
379 * Send data to a tag and receive the response.
380 * <p>
381 * This method will block until the response is received. It can be canceled
382 * with {@link #close}.
383 * <p>Requires {@link android.Manifest.permission#NFC} permission.
384 *
385 * @param data bytes to send
386 * @return bytes received in response
387 * @throws IOException if the target is lost or connection closed
388 */
389 @Override
390 public byte[] transceive(byte[] data) throws IOException {
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100391 checkConnected();
392
Martijn Coenenfc5a3b62010-12-10 10:46:56 -0800393 try {
394 byte[] response = mTagService.transceive(mTag.getServiceHandle(), data, false);
395 if (response == null) {
396 throw new IOException("transceive failed");
397 }
398 return response;
399 } catch (RemoteException e) {
400 attemptDeadServiceRecovery(e);
401 throw new IOException("NFC service died");
402 }
403 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600404}