MifareClassic.java revision 20e62c9f1466ace5771e244f03a995dc0939b11b
16be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton/* 26be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * Copyright (C) 2010 The Android Open Source Project 36be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * 46be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * Licensed under the Apache License, Version 2.0 (the "License"); 56be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * you may not use this file except in compliance with the License. 66be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * You may obtain a copy of the License at 76be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * 86be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * http://www.apache.org/licenses/LICENSE-2.0 96be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * 106be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * Unless required by applicable law or agreed to in writing, software 116be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * distributed under the License is distributed on an "AS IS" BASIS, 126be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * See the License for the specific language governing permissions and 146be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * limitations under the License. 156be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 166be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 174e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamiltonpackage android.nfc.tech; 186be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 19112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenenimport android.nfc.ErrorCodes; 206be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.nfc.Tag; 214e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamiltonimport android.nfc.TagLostException; 226be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport android.os.RemoteException; 23112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenenimport android.util.Log; 246be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 256be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonimport java.io.IOException; 261e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pellyimport java.nio.ByteBuffer; 27b134223f91c8801d577cb72e92a37cb65fec717aNick Pellyimport java.nio.ByteOrder; 286be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 296be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton/** 3074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Provides access to MIFARE Classic properties and I/O operations on a {@link Tag}. 316be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * 3274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Acquire a {@link MifareClassic} object using {@link #get}. 336be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton * 3474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>MIFARE Classic is also known as MIFARE Standard. 3574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>MIFARE Classic tags are divided into sectors, and each sector is sub-divided into 3674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * blocks. Block size is always 16 bytes ({@link #BLOCK_SIZE}. Sector size varies. 3774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <ul> 3874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>MIFARE Classic Mini are 320 bytes ({@link #SIZE_MINI}), with 5 sectors each of 4 blocks. 3974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>MIFARE Classic 1k are 1024 bytes ({@link #SIZE_1K}), with 16 sectors each of 4 blocks. 4074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>MIFARE Classic 2k are 2048 bytes ({@link #SIZE_2K}), with 32 sectors each of 4 blocks. 4174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>MIFARE Classic 4k} are 4096 bytes ({@link #SIZE_4K}). The first 32 sectors contain 4 blocks 4274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * and the last 8 sectors contain 16 blocks. 4374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * </ul> 4474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 4574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>MIFARE Classic tags require authentication on a per-sector basis before any 4674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * other I/O operations on that sector can be performed. There are two keys per sector, 4774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * and ACL bits determine what I/O operations are allowed on that sector after 4874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * authenticating with a key. {@see #authenticateSectorWithKeyA} and 4974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@see #authenticateSectorWithKeyB}. 5074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 5174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Three well-known authentication keys are defined in this class: 5274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link #KEY_DEFAULT}, {@link #KEY_MIFARE_APPLICATION_DIRECTORY}, 5374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link #KEY_NFC_FORUM}. 5474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <ul> 5574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>{@link #KEY_DEFAULT} is the default factory key for MIFARE Classic. 5674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>{@link #KEY_MIFARE_APPLICATION_DIRECTORY} is the well-known key for 5774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * MIFARE Classic cards that have been formatted according to the 5874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * MIFARE Application Directory (MAD) specification. 5974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <li>{@link #KEY_NFC_FORUM} is the well-known key for MIFARE Classic cards that 6039cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * have been formatted according to the NXP specification for NDEF on MIFARE Classic. 6174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 6274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Implementation of this class on a Android NFC device is optional. 6374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * If it is not implemented, then 6474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link MifareClassic} will never be enumerated in {@link Tag#getTechList}. 6574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * If it is enumerated, then all {@link MifareClassic} I/O operations will be supported, 6674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * and {@link Ndef#MIFARE_CLASSIC} NDEF tags will also be supported. In either case, 6774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link NfcA} will also be enumerated on the tag, because all MIFARE Classic tags are also 6874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link NfcA}. 6939cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 7039cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note"><strong>Note:</strong> Methods that perform I/O operations 7139cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * require the {@link android.Manifest.permission#NFC} permission. 726be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 736be655c768a82716612c00fdd156254d8dc00f42Jeff Hamiltonpublic final class MifareClassic extends BasicTagTechnology { 74112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen private static final String TAG = "NFC"; 75112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen 766be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton /** 7774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * The default factory key. 786be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 7965c3f9806edd694c4db00fa2884139ea97c80962Jan Brands public static final byte[] KEY_DEFAULT = 806be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF}; 816be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton /** 8274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * The well-known key for tags formatted according to the 8374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * MIFARE Application Directory (MAD) specification. 846be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 8565c3f9806edd694c4db00fa2884139ea97c80962Jan Brands public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY = 866be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton {(byte)0xA0,(byte)0xA1,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5}; 876be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton /** 8874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * The well-known key for tags formatted according to the 89734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton * NDEF on MIFARE Classic specification. 906be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 9165c3f9806edd694c4db00fa2884139ea97c80962Jan Brands public static final byte[] KEY_NFC_FORUM = 926be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton {(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7}; 936be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 94734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton /** A MIFARE Classic compatible card of unknown type */ 954a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly public static final int TYPE_UNKNOWN = -1; 96ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton /** A MIFARE Classic tag */ 976be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int TYPE_CLASSIC = 0; 98ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton /** A MIFARE Plus tag */ 996be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int TYPE_PLUS = 1; 100ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton /** A MIFARE Pro tag */ 1016be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int TYPE_PRO = 2; 1026be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 10374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** Tag contains 16 sectors, each with 4 blocks. */ 1046be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int SIZE_1K = 1024; 10574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** Tag contains 32 sectors, each with 4 blocks. */ 1066be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int SIZE_2K = 2048; 107ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton /** 10874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Tag contains 40 sectors. The first 32 sectors contain 4 blocks and the last 8 sectors 109ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton * contain 16 blocks. 110ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton */ 1116be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int SIZE_4K = 4096; 11274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** Tag contains 5 sectors, each with 4 blocks. */ 1136be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public static final int SIZE_MINI = 320; 114e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 11574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** Size of a MIFARE Classic block (in bytes) */ 116e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public static final int BLOCK_SIZE = 16; 117e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 118e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly private static final int MAX_BLOCK_COUNT = 256; 119e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly private static final int MAX_SECTOR_COUNT = 40; 1206be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 1216be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton private boolean mIsEmulated; 1226be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton private int mType; 1236be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton private int mSize; 1246be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 1254e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton /** 12674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Get an instance of {@link MifareClassic} for the given tag. 12774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 12874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Returns null if {@link MifareClassic} was not enumerated in {@link Tag#getTechList}. 12974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * This indicates the tag is not MIFARE Classic compatible, or this Android 13074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * device does not support MIFARE Classic. 1314e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton * 13274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param tag an MIFARE Classic compatible tag 13374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return MIFARE Classic object 1344e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton */ 1354e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton public static MifareClassic get(Tag tag) { 1364e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton if (!tag.hasTech(TagTechnology.MIFARE_CLASSIC)) return null; 1374e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton try { 1384e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton return new MifareClassic(tag); 1394e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton } catch (RemoteException e) { 1404e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton return null; 1414e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton } 1424e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton } 1434e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton 144ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton /** @hide */ 1454e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton public MifareClassic(Tag tag) throws RemoteException { 1464e21e1d21a877cce4db5ec8c5786604cc10f2d7eJeff Hamilton super(tag, TagTechnology.MIFARE_CLASSIC); 1476be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 148734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton NfcA a = NfcA.get(tag); // MIFARE Classic is always based on NFC a 1496be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 1506be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton mIsEmulated = false; 1516be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 1526be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton switch (a.getSak()) { 153e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x08: 154e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_CLASSIC; 155e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_1K; 156e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 157e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x09: 158e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_CLASSIC; 159e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_MINI; 160e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 161e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x10: 162e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_PLUS; 163e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_2K; 164e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // SecLevel = SL2 165e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 166e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x11: 167e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_PLUS; 168e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_4K; 169e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // Seclevel = SL2 170e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 171e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x18: 172e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_CLASSIC; 173e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_4K; 174e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 175e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x28: 176e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_CLASSIC; 177e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_1K; 178e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mIsEmulated = true; 179e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 180e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x38: 181e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_CLASSIC; 182e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_4K; 183e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mIsEmulated = true; 184e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 185e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x88: 186e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_CLASSIC; 187e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_1K; 188e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // NXP-tag: false 189e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 190e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0x98: 191e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case 0xB8: 192e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mType = TYPE_PRO; 193e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly mSize = SIZE_4K; 194e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly break; 195e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly default: 196e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // Stack incorrectly reported a MifareClassic. We cannot handle this 197e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // gracefully - we have no idea of the memory layout. Bail. 198e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly throw new RuntimeException( 199734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton "Tag incorrectly enumerated as MIFARE Classic, SAK = " + a.getSak()); 2006be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2016be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2026be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 20374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 20474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the type of this MIFARE Classic compatible tag. 20574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>One of {@link #TYPE_UNKNOWN}, {@link #TYPE_CLASSIC}, {@link #TYPE_PLUS} or 20674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link #TYPE_PRO}. 20774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 20874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 20974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return type 21074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */ 2116be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public int getType() { 2126be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton return mType; 2136be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2146be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 21574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 21674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the size of the tag in bytes 21774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>One of {@link #SIZE_MINI}, {@link #SIZE_1K}, {@link #SIZE_2K}, {@link #SIZE_4K}. 21874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * These constants are equal to their respective size in bytes. 21974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 22074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return size in bytes 22174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */ 222e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public int getSize() { 223e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return mSize; 224e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 225e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 22674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 22774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return true if the tag is emulated, determined at discovery time. 228734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton * These are actually smart-cards that emulate a MIFARE Classic interface. 229734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton * They can be treated identically to a MIFARE Classic tag. 230e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly * @hide 231e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly */ 2326be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public boolean isEmulated() { 2336be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton return mIsEmulated; 2346be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2356be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 23674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 23774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the number of MIFARE Classic sectors. 23874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 23974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return number of sectors 24074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */ 2416be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton public int getSectorCount() { 2426be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton switch (mSize) { 243e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case SIZE_1K: 244e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 16; 245e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case SIZE_2K: 246e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 32; 247e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case SIZE_4K: 248e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 40; 249e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly case SIZE_MINI: 250e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 5; 251e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly default: 252e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 0; 2536be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2546be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2556be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 25674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 25774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the total number of MIFARE Classic blocks. 25874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 25974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return total number of blocks 26046797ac098e90cbef5c266b75fb37fc06e9acc80Nick Pelly */ 261e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public int getBlockCount() { 262e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return mSize / BLOCK_SIZE; 263a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen } 264a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen 26574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 26674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the number of blocks in the given sector. 26774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 26874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 26974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param sectorIndex index of sector, starting from 0 27074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return number of blocks in the sector 27174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */ 272e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public int getBlockCountInSector(int sectorIndex) { 273e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateSector(sectorIndex); 2746be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 275e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (sectorIndex < 32) { 2766be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton return 4; 2776be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } else { 2786be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton return 16; 2796be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2806be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 2816be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 28274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 28374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the sector that contains a given block. 28474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 28574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 28674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to lookup, starting from 0 28774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return sector index that contains the block 28874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */ 289e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public int blockToSector(int blockIndex) { 290e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 291e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 292e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (blockIndex < 32 * 4) { 293e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return blockIndex / 4; 294e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } else { 295e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 32 + (blockIndex - 32 * 4) / 16; 296e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 297e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 298e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 29974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly /** 30074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Return the first block of a given sector. 30174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Does not cause any RF activity and does not block. 30274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 30374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param sectorIndex index of sector to lookup, starting from 0 30474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return block index of first block in sector 30574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly */ 306e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public int sectorToBlock(int sectorIndex) { 307e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (sectorIndex < 32) { 308e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return sectorIndex * 4; 3096be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } else { 310e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return 32 * 4 + (sectorIndex - 32) * 16; 3116be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 3126be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 3136be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 3146be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton /** 31574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Authenticate a sector with key A. 31674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 31774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Successful authentication of a sector with key A enables other 31874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * I/O operations on that sector. The set of operations granted by key A 31974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * key depends on the ACL bits set in that sector. For more information 32074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * see the MIFARE Classic specification on {@see http://www.nxp.com}. 32174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 32274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>A failed authentication attempt causes an implicit reconnection to the 32374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * tag, so authentication to other sectors will be lost. 32474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 32574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 32674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 32774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 32874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 32939cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 33039cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 33174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param sectorIndex index of sector to authenticate, starting from 0 33274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param key 6-byte authentication key 33374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return true on success, false on authentication failure 33474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 33574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 336e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly */ 337e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public boolean authenticateSectorWithKeyA(int sectorIndex, byte[] key) throws IOException { 338e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return authenticate(sectorIndex, key, true); 339e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 340e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 341e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly /** 34274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Authenticate a sector with key B. 34374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 34474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>Successful authentication of a sector with key B enables other 34574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * I/O operations on that sector. The set of operations granted by key B 34674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * depends on the ACL bits set in that sector. For more information 34774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * see the MIFARE Classic specification on {@see http://www.nxp.com}. 34874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 34974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>A failed authentication attempt causes an implicit reconnection to the 35074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * tag, so authentication to other sectors will be lost. 35174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 35274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 35374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 35474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 35574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 35639cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 35739cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 35874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param sectorIndex index of sector to authenticate, starting from 0 35974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param key 6-byte authentication key 36074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return true on success, false on authentication failure 36174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 36274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 3636be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 364e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public boolean authenticateSectorWithKeyB(int sectorIndex, byte[] key) throws IOException { 365e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return authenticate(sectorIndex, key, false); 366e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 367e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 368e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly private boolean authenticate(int sector, byte[] key, boolean keyA) throws IOException { 369e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateSector(sector); 3704049f9d00a86f848d42d2429068496b31a6795adMartijn Coenen checkConnected(); 3714049f9d00a86f848d42d2429068496b31a6795adMartijn Coenen 3726be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton byte[] cmd = new byte[12]; 3736be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 3746be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton // First byte is the command 3756be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton if (keyA) { 3766be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton cmd[0] = 0x60; // phHal_eMifareAuthentA 3776be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } else { 3786be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton cmd[0] = 0x61; // phHal_eMifareAuthentB 3796be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 3806be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 3816be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton // Second byte is block address 382e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // Authenticate command takes a block address. Authenticating a block 383e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // of a sector will authenticate the entire sector. 384e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly cmd[1] = (byte) sectorToBlock(sector); 3856be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 3866be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton // Next 4 bytes are last 4 bytes of UID 3876be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton byte[] uid = getTag().getId(); 3886be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton System.arraycopy(uid, uid.length - 4, cmd, 2, 4); 3896be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 3906be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton // Next 6 bytes are key 3916be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton System.arraycopy(key, 0, cmd, 6, 6); 3926be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 3936be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton try { 394e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (transceive(cmd, false) != null) { 3956be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton return true; 3966be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 397bf34061bb4af12aa9efaab653ae413f2bce4a240Martijn Coenen } catch (TagLostException e) { 398bf34061bb4af12aa9efaab653ae413f2bce4a240Martijn Coenen throw e; 3996be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } catch (IOException e) { 4006be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton // No need to deal with, will return false anyway 4016be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 4026be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton return false; 4036be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 4046be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 4056be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton /** 406e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly * Read 16-byte block. 40774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 40874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 40974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 41074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 41174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 41239cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 41339cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 41474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to read, starting from 0 41574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @return 16 byte block 41674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 41774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 418ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen */ 419e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public byte[] readBlock(int blockIndex) throws IOException { 420e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 421ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen checkConnected(); 422ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen 423e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly byte[] cmd = { 0x30, (byte) blockIndex }; 424e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly return transceive(cmd, false); 425ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen } 426ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen 427ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen /** 428e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly * Write 16-byte block. 42974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 43074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 43174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 43274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 43374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 43439cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 43539cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 43674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to write, starting from 0 43774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param data 16 bytes of data to write 43874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 43974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 4406be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 441e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public void writeBlock(int blockIndex, byte[] data) throws IOException { 442e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 4434049f9d00a86f848d42d2429068496b31a6795adMartijn Coenen checkConnected(); 444e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (data.length != 16) { 445e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly throw new IllegalArgumentException("must write 16-bytes"); 446e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 4474049f9d00a86f848d42d2429068496b31a6795adMartijn Coenen 448e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly byte[] cmd = new byte[data.length + 2]; 449e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly cmd[0] = (byte) 0xA0; // MF write command 450e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly cmd[1] = (byte) blockIndex; 451e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly System.arraycopy(data, 0, cmd, 2, data.length); 452e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 453e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly transceive(cmd, false); 454ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen } 455ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen 456ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen /** 45774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Increment a value block, storing the result in the temporary block on the tag. 45874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 45974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 46074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 46174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 46274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 46339cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 46439cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 46574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to increment, starting from 0 46674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param value non-negative to increment by 46774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 46874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 469ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen */ 4701e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly public void increment(int blockIndex, int value) throws IOException { 471e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 472b134223f91c8801d577cb72e92a37cb65fec717aNick Pelly validateValueOperand(value); 473ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen checkConnected(); 474ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen 4751e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly ByteBuffer cmd = ByteBuffer.allocate(6); 476b134223f91c8801d577cb72e92a37cb65fec717aNick Pelly cmd.order(ByteOrder.LITTLE_ENDIAN); 4771e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly cmd.put( (byte) 0xC1 ); 4781e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly cmd.put( (byte) blockIndex ); 479b134223f91c8801d577cb72e92a37cb65fec717aNick Pelly cmd.putInt(value); 4806be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 4811e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly transceive(cmd.array(), false); 4826be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton } 4836be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 484ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen /** 48574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Decrement a value block, storing the result in the temporary block on the tag. 48674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 48774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 48874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 48974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 49074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 49139cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 49239cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 49374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to decrement, starting from 0 49474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param value non-negative to decrement by 49574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 49674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 497ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen */ 4981e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly public void decrement(int blockIndex, int value) throws IOException { 499e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 500b134223f91c8801d577cb72e92a37cb65fec717aNick Pelly validateValueOperand(value); 501ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen checkConnected(); 502ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen 5031e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly ByteBuffer cmd = ByteBuffer.allocate(6); 504b134223f91c8801d577cb72e92a37cb65fec717aNick Pelly cmd.order(ByteOrder.LITTLE_ENDIAN); 5051e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly cmd.put( (byte) 0xC0 ); 5061e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly cmd.put( (byte) blockIndex ); 507b134223f91c8801d577cb72e92a37cb65fec717aNick Pelly cmd.putInt(value); 508ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen 5091e233af3a783d44843a6f2b895d00a5d3b0c29f0Nick Pelly transceive(cmd.array(), false); 510ab82a5b9a841cf052310e8500224932b9f5e3cadMartijn Coenen } 5116be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton 5126be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton /** 51374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Copy from the temporary block to a value block. 51474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 51574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 51674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 51774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 51874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 51939cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 52039cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 52174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to copy to 52274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 52374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 5246be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton */ 525e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public void transfer(int blockIndex) throws IOException { 526e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 527a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen checkConnected(); 528a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen 529e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly byte[] cmd = { (byte) 0xB0, (byte) blockIndex }; 530a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen 531e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly transceive(cmd, false); 532a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen } 533a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen 534e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly /** 53574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * Copy from a value block to the temporary block. 53674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 53774fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 53874fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 53974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 54074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 54139cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 54239cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 54374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @param blockIndex index of block to copy from 54474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws TagLostException if the tag leaves the field 54574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @throws IOException if there is an I/O failure, or the operation is canceled 546e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly */ 547e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly public void restore(int blockIndex) throws IOException { 548e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly validateBlock(blockIndex); 549a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen checkConnected(); 550a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen 551e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly byte[] cmd = { (byte) 0xC2, (byte) blockIndex }; 552a42b352594edf959302f8fc98041e76adeb6a20dMartijn Coenen 553e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly transceive(cmd, false); 554fc5a3b6cfb85679e82a39730c7154b55b0711a0cMartijn Coenen } 555ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton 556ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton /** 557ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton * Send raw NfcA data to a tag and receive the response. 558ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton * 55974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is equivalent to connecting to this tag via {@link NfcA} 56074fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * and calling {@link NfcA#transceive}. Note that all MIFARE Classic 56174fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * tags are based on {@link NfcA} technology. 56274fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 56374fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * <p>This is an I/O operation and will block until complete. It must 56474fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * not be called from the main application thread. A blocked call will be canceled with 56574fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * {@link IOException} if {@link #close} is called from another thread. 56674fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * 56739cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 56839cf3a445e507f219ecc8a476f6038f095d9d520Nick Pelly * 56974fe6c6b245ebe7d3b3d96962c32980d88dca4f5Nick Pelly * @see NfcA#transceive 570ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton */ 571ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton public byte[] transceive(byte[] data) throws IOException { 572ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton return transceive(data, true); 573ce3224cda51f946871daa1e11e3976e25c59e6faJeff Hamilton } 574e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 575112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen /** 576112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * Set the timeout of {@link #transceive} in milliseconds. 577112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * <p>The timeout only applies to MifareUltralight {@link #transceive}, 578112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * and is reset to a default value when {@link #close} is called. 579112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * <p>Setting a longer timeout may be useful when performing 580112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * transactions that require a long processing time on the tag 581112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * such as key generation. 582112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * 583112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 584112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * 585112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * @param timeout timeout value in milliseconds 586112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen * @hide 587112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen */ 588112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen // TODO Unhide for ICS 589112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen public void setTimeout(int timeout) { 590112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen try { 591112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen int err = mTag.getTagService().setTimeout(TagTechnology.MIFARE_CLASSIC, timeout); 592112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen if (err != ErrorCodes.SUCCESS) { 593112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen throw new IllegalArgumentException("The supplied timeout is not valid"); 594112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen } 595112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen } catch (RemoteException e) { 596112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen Log.e(TAG, "NFC service dead", e); 597112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen } 598112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen } 599112fdf612db71a552fce063136bf2796df3b71ecMartijn Coenen 60020e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen /** 60120e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen * Gets the currently set timeout of {@link #transceive} in milliseconds. 60220e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen * 60320e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. 60420e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen * 60520e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen * @return timeout value in milliseconds 60620e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen * @hide 60720e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen */ 60820e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen // TODO Unhide for ICS 60920e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen public int getTimeout() { 61020e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen try { 61120e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen return mTag.getTagService().getTimeout(TagTechnology.MIFARE_CLASSIC); 61220e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen } catch (RemoteException e) { 61320e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen Log.e(TAG, "NFC service dead", e); 61420e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen return 0; 61520e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen } 61620e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen } 61720e62c9f1466ace5771e244f03a995dc0939b11bMartijn Coenen 6184a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly private static void validateSector(int sector) { 619e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // Do not be too strict on upper bounds checking, since some cards 620e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // have more addressable memory than they report. For example, 621734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton // MIFARE Plus 2k cards will appear as MIFARE Classic 1k cards when in 622734e9b0c73483fdaa582c21dedc24107b1fe8838Jeff Hamilton // MIFARE Classic compatibility mode. 623e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // Note that issuing a command to an out-of-bounds block is safe - the 624e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // tag should report error causing IOException. This validation is a 625e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // helper to guard against obvious programming mistakes. 626e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (sector < 0 || sector >= MAX_SECTOR_COUNT) { 627e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly throw new IndexOutOfBoundsException("sector out of bounds: " + sector); 628e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 629e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 630e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly 6314a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly private static void validateBlock(int block) { 632e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly // Just looking for obvious out of bounds... 633e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly if (block < 0 || block >= MAX_BLOCK_COUNT) { 634e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly throw new IndexOutOfBoundsException("block out of bounds: " + block); 635e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 636e45083b11bef915f713379fb4106dd2ebd897d03Nick Pelly } 6374a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly 6384a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly private static void validateValueOperand(int value) { 6394a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly if (value < 0) { 6404a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly throw new IllegalArgumentException("value operand negative"); 6414a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly } 6424a5e2532205252e0b8616ebc07ca089fd3721681Nick Pelly } 6436be655c768a82716612c00fdd156254d8dc00f42Jeff Hamilton} 644