mirror of
https://github.com/RfidResearchGroup/ChameleonBLEAPI.git
synced 2026-05-12 11:20:47 -07:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
package com.proxgrind.chameleon.utils.mifare;
|
||||
|
||||
import com.proxgrind.chameleon.packets.DataPackets;
|
||||
import com.proxgrind.chameleon.utils.system.LogUtils;
|
||||
import com.proxgrind.chameleon.utils.tools.HexUtil;
|
||||
import com.proxgrind.devices.BleSerialControl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ChameleonBatchAdapterImpl implements BatchAdapter {
|
||||
// 持有一个原生通讯的句柄!
|
||||
private BleSerialControl control = BleSerialControl.get();
|
||||
// 一个空的数组!
|
||||
public static final byte[] EMPTY_DATA_ONE = new byte[]{0x00};
|
||||
|
||||
/**
|
||||
* 读取函数,变色龙的读取实现,依赖起始块
|
||||
*
|
||||
* @param startBlock 起始块,如果当前的模式是读取单块模式,则此参数是需要读取的块
|
||||
* @param isKeyA 当前是否是用keyA来验证并且读取的
|
||||
* @param key 当前被用来读取的秘钥
|
||||
* @param isReadSector 当前是否是读取全部扇区模式,注意,如果是读取扇区模式,
|
||||
* 遇到4K的大扇区的情况下,需要进行每次4个块的读取,也就是
|
||||
* 从startBlock开始表示读取第一个大块
|
||||
* 从startBlock + 4 开始表示读取第二个大块
|
||||
* 从startBlock + 8 开始表示读取第三个大块
|
||||
* 从startBlock + 12 开始表示读取第四个大块
|
||||
*/
|
||||
@Override
|
||||
public byte[][] read(int startBlock, boolean isKeyA, byte[] key, boolean isReadSector) throws IOException {
|
||||
// 组包!
|
||||
byte[] dataOfParam = HexUtil.bytesMerge(
|
||||
getType(isReadSector ? 2 : 1), // 置入类型,如果当前是读取扇区,则设置操作标志为2,否则设置为1
|
||||
EMPTY_DATA_ONE, // 置入状态码!
|
||||
new byte[]{(byte) startBlock}, // 置入起始块!
|
||||
isKeyA(isKeyA), // 置入当前验证的秘钥类型!
|
||||
key, // 置入秘钥!
|
||||
isReadSector ? new byte[16 * 4] : new byte[16] // 置入空字节
|
||||
);
|
||||
byte[] dataOfFinal = new DataPackets(0x72, dataOfParam).getData();
|
||||
LogUtils.d("发送的数据长度: " + dataOfParam.length);
|
||||
// 发送数据!
|
||||
byte[] respDatas = sendAndReadResponse(dataOfFinal, dataOfFinal.length, dataOfParam.length);
|
||||
if (respDatas != null && respDatas.length == dataOfParam.length) {
|
||||
// 得到最终的秘钥索引,并且进行下标 -1 的内容返回!
|
||||
byte status = respDatas[1];
|
||||
checkTagStatus(status);
|
||||
if (status == 0) {
|
||||
LogUtils.d("读取失败");
|
||||
return null;
|
||||
}
|
||||
if (status == 1) {
|
||||
return HexUtil.splitBytes(
|
||||
Arrays.copyOfRange(respDatas, 10, respDatas.length),
|
||||
16
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(int sector, boolean isKeyA, byte[] key, byte[] dataGroup) throws IOException {
|
||||
// 组包!
|
||||
byte[] dataOfParam = HexUtil.bytesMerge(
|
||||
getType(3), // 置入类型!
|
||||
EMPTY_DATA_ONE, // 置入状态码!
|
||||
new byte[]{(byte) sector}, // 置入扇区!
|
||||
isKeyA(isKeyA), // 置入当前验证的秘钥类型!
|
||||
key, // 置入秘钥!
|
||||
dataGroup // 置入数据!
|
||||
);
|
||||
byte[] dataOfFinal = new DataPackets(0x72, dataOfParam).getData();
|
||||
// 发送数据!
|
||||
byte[] respDatas = sendAndReadResponse(dataOfFinal, dataOfFinal.length, dataOfParam.length);
|
||||
if (respDatas != null && respDatas.length == dataOfParam.length) {
|
||||
// 得到最终的秘钥索引,并且进行下标 -1 的内容返回!
|
||||
byte status = respDatas[1];
|
||||
checkTagStatus(status);
|
||||
if (status == 0) {
|
||||
LogUtils.d("写入失败");
|
||||
return false;
|
||||
}
|
||||
return status == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 经过测试,以35个秘钥为一组进行验证比较好!
|
||||
*/
|
||||
@Override
|
||||
public byte[] verity(int sector, byte[][] keysGroup, boolean isKeyA) throws IOException {
|
||||
// 组包!
|
||||
byte[] dataOfParam = HexUtil.bytesMerge(
|
||||
getType(0x04), // 置入类型! 04
|
||||
EMPTY_DATA_ONE, // 置入状态码! 00
|
||||
getBlock(sector), // 置入扇区! ~3F
|
||||
isKeyA(isKeyA), // 置入当前验证的秘钥类型! 00
|
||||
getKeyCount(keysGroup), // 获得当前的秘钥总数!
|
||||
HexUtil.bytesMerge(keysGroup) // 合并秘钥!
|
||||
);
|
||||
byte[] dataOfFinal = new DataPackets(0x72, dataOfParam).getData();
|
||||
byte[] respDatas = sendAndReadResponse(dataOfFinal, dataOfFinal.length, 11);
|
||||
if (respDatas != null && respDatas.length == 11) {
|
||||
// 得到最终的秘钥索引,并且进行下标 -1 的内容返回!
|
||||
byte index = respDatas[1];
|
||||
checkTagStatus(index);
|
||||
// 只有当索引大于1的时候,才是真正的有应答,当应答FF的时候,则是卡片失联了
|
||||
if (index > 0 && index <= keysGroup.length) {
|
||||
return keysGroup[index - 1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void checkTagStatus(int statusCode) throws IOException {
|
||||
if (statusCode == 0xFF || statusCode == -1) {
|
||||
throw new IOException("Tag lost.");
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] sendAndReadResponse(byte[] data, int dataLength, int acceptRespLength) throws IOException {
|
||||
// 发送数据!
|
||||
int timeout = getTimeout();
|
||||
// 刷新缓冲区且发送
|
||||
control.flush();
|
||||
int maxLen = BleSerialControl.MTU;
|
||||
if (control.write(data, 0, dataLength, timeout) == dataLength) {
|
||||
// 读取应答数据
|
||||
byte[] responseBuffer = new byte[acceptRespLength <= 0 ? maxLen : acceptRespLength];
|
||||
// 直接读取
|
||||
control.read(
|
||||
responseBuffer,
|
||||
0,
|
||||
Math.max(acceptRespLength, 0),
|
||||
timeout
|
||||
);
|
||||
return responseBuffer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取byte数组型的类型,分别可能是
|
||||
* 0 读取
|
||||
* 1 写入
|
||||
* 2 验证
|
||||
*/
|
||||
public byte[] getType(int type) {
|
||||
return new byte[]{(byte) type};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个字节的sector转为数组!
|
||||
*/
|
||||
public byte[] getBlock(int sector) {
|
||||
return new byte[]{(byte) MfDataUtils.get_trailer_block(MfDataUtils.sectorToBlock(sector))};
|
||||
}
|
||||
|
||||
public byte[] isKeyA(boolean isKeyA) {
|
||||
return new byte[]{(byte) (isKeyA ? 0 : 1)};
|
||||
}
|
||||
|
||||
public byte[] getKeyCount(byte[][] keys) {
|
||||
return new byte[]{(byte) keys.length};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimeout() {
|
||||
return 2333;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(int timeout) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package com.proxgrind.chameleon.utils.mifare;
|
||||
|
||||
import android.nfc.tech.MifareClassic;
|
||||
|
||||
import com.proxgrind.chameleon.packets.DataPackets;
|
||||
import com.proxgrind.chameleon.utils.system.LogUtils;
|
||||
import com.proxgrind.chameleon.utils.tools.HexUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ChameleonMifareAdapter implements MifareAdapter {
|
||||
private ChameleonBatchAdapterImpl batchAdapter = new ChameleonBatchAdapterImpl();
|
||||
private byte[] uid;
|
||||
private byte[] atqa;
|
||||
private int sak = -1;
|
||||
private byte[] ats;
|
||||
|
||||
@Override
|
||||
public boolean rescantag() throws IOException {
|
||||
// 组包!
|
||||
byte[] dataOfParam = HexUtil.bytesMerge(
|
||||
batchAdapter.getType(0x00), // 置入类型! 04
|
||||
ChameleonBatchAdapterImpl.EMPTY_DATA_ONE, // 置入状态码! 00
|
||||
new byte[10], // 卡号
|
||||
new byte[1], // 卡号长度
|
||||
new byte[2], // ATQA
|
||||
new byte[1], // SAK
|
||||
new byte[1] // ATS长度
|
||||
);
|
||||
byte[] dataOfFinal = new DataPackets(0x72, dataOfParam).getData();
|
||||
byte[] respDatas = batchAdapter.sendAndReadResponse(dataOfFinal, dataOfFinal.length, 0);
|
||||
// byte[] respDatas = HexUtil.hexStringToByteArray("0001505C8E04000000000000040400080B12b12b12b12b12b121b2121b12b12b1212b1bb32513b513513b5131b4141b1");
|
||||
if (respDatas != null && respDatas.length > 2) {
|
||||
// 得到最终的秘钥索引,并且进行下标 -1 的内容返回!
|
||||
byte status = respDatas[1];
|
||||
// 只有当索引大于1的时候,才是真正的有应答,当应答FF或者0的时候,则是卡片失联了
|
||||
batchAdapter.checkTagStatus(status);
|
||||
if (status > 0) {
|
||||
// 我们需要进行信息截取!
|
||||
if (respDatas.length >= 13) { // 可以截取有效的UID!
|
||||
uid = Arrays.copyOfRange(respDatas, 2, 12);
|
||||
uid = Arrays.copyOf(uid, respDatas[12]);
|
||||
LogUtils.d("UID: " + HexUtil.toHexString(uid));
|
||||
}
|
||||
if (respDatas.length >= 15) { // 可以截取有效的ATQA
|
||||
atqa = Arrays.copyOfRange(respDatas, 13, 15);
|
||||
LogUtils.d("ATQA: " + HexUtil.toHexString(atqa));
|
||||
}
|
||||
if (respDatas.length >= 16) { // 可以截取有效的SAK
|
||||
sak = Arrays.copyOfRange(respDatas, 15, 16)[0];
|
||||
LogUtils.d("SAK: " + sak);
|
||||
}
|
||||
int atsLen = 0;
|
||||
if (respDatas.length >= 17) {
|
||||
atsLen = Arrays.copyOfRange(respDatas, 16, 17)[0];
|
||||
}
|
||||
LogUtils.d("ATS长度: " + atsLen);
|
||||
if (atsLen > 0 && respDatas.length - 17 >= atsLen) {
|
||||
ats = Arrays.copyOfRange(respDatas, 17, 17 + atsLen);
|
||||
LogUtils.d("ATS: " + HexUtil.toHexString(ats));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() throws IOException {
|
||||
return rescantag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// 不需要做任何操作!
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(int block) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(int blockIndex, byte[] data) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authA(int sectorIndex, byte[] key) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authB(int sectorIndex, byte[] key) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(int blockIndex, int value) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrement(int blockIndex, int value) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore(int blockIndex) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transfer(int blockIndex) throws IOException {
|
||||
throw new IOException("Unsupported operation.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getUid() {
|
||||
try {
|
||||
if (uid == null)
|
||||
rescantag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAts() {
|
||||
if (ats == null) {
|
||||
try {
|
||||
rescantag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return ats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAtqa() {
|
||||
if (atqa == null) {
|
||||
try {
|
||||
rescantag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return atqa;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSak() {
|
||||
if (sak == -1) {
|
||||
try {
|
||||
rescantag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return new byte[]{(byte) sak};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
try {
|
||||
if (sak == -1)
|
||||
rescantag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
}
|
||||
switch (sak) {
|
||||
case 0x01:
|
||||
case 0x08:
|
||||
case 0x09:
|
||||
// Seclevel = SL2
|
||||
case 0x18:
|
||||
case 0x19:
|
||||
case 0x28:
|
||||
//mIsEmulated = true;
|
||||
case 0x38:
|
||||
case 0x88:
|
||||
return MifareClassic.TYPE_CLASSIC;
|
||||
case 0x10:
|
||||
// SecLevel = SL2
|
||||
case 0x11:
|
||||
return MifareClassic.TYPE_PLUS;
|
||||
// NXP-tag: false
|
||||
case 0x98:
|
||||
case 0xB8:
|
||||
return MifareClassic.TYPE_PRO;
|
||||
default:
|
||||
// Stack incorrectly reported a MifareClassic. We cannot handle this
|
||||
// gracefully - we have no idea of the memory layout. Bail.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectorCount() {
|
||||
switch (getType()) {
|
||||
case 0:
|
||||
return 16;
|
||||
case 1:
|
||||
return 32;
|
||||
case 2:
|
||||
return 40;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockCount() {
|
||||
switch (getType()) {
|
||||
case 0:
|
||||
return MifareClassic.SIZE_1K / MifareClassic.BLOCK_SIZE;
|
||||
case 1:
|
||||
return MifareClassic.SIZE_2K / MifareClassic.BLOCK_SIZE;
|
||||
case 2:
|
||||
return MifareClassic.SIZE_4K / MifareClassic.BLOCK_SIZE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(int ms) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimeout() {
|
||||
return 2333;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchAdapter getBatchImpl() {
|
||||
return batchAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
try {
|
||||
return rescantag();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmulated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpecialTag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTestSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
package com.proxgrind.chameleon.utils.mifare;
|
||||
|
||||
/*
|
||||
* MifareClassic标签用得到的工具!
|
||||
* */
|
||||
public class MfDataUtils {
|
||||
|
||||
public static boolean validateSector(int sector) {
|
||||
// Do not be too strict on upper bounds checking, since some cards
|
||||
// have more addressable memory than they report. For example,
|
||||
// MIFARE Plus 2k cards will appear as MIFARE Classic 1k cards when in
|
||||
// MIFARE Classic compatibility mode.
|
||||
// Note that issuing a command to an out-of-bounds block is safe - the
|
||||
// tag should report error causing IOException. This validation is a
|
||||
// helper to guard against obvious programming mistakes.
|
||||
|
||||
int NR_TRAILERS_4k = 40;
|
||||
if (sector < 0 || sector >= NR_TRAILERS_4k) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean validateBlock(int block) {
|
||||
// Just looking for obvious out of bounds...
|
||||
int NR_BLOCKS_4k = 0xFF;
|
||||
if (block < 0 || block >= NR_BLOCKS_4k) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean validateValueOperand(int value) {
|
||||
if (value < 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int blockToSector(int blockIndex) {
|
||||
if (!validateBlock(blockIndex)) return 0;
|
||||
if (blockIndex < 32 * 4) {
|
||||
return (blockIndex / 4);
|
||||
} else {
|
||||
return (32 + (blockIndex - 32 * 4) / 16);
|
||||
}
|
||||
}
|
||||
|
||||
public static int sectorToBlock(int sectorIndex) {
|
||||
if (!validateSector(sectorIndex)) {
|
||||
return -1;
|
||||
}
|
||||
if (sectorIndex < 32) {
|
||||
return (sectorIndex * 4);
|
||||
} else {
|
||||
return (32 * 4 + (sectorIndex - 32) * 16);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFirstBlock(int uiBlock) {
|
||||
// 测试我们是否处于小扇区或者大扇区?
|
||||
if (uiBlock < 128)
|
||||
return ((uiBlock) % 4 == 0);
|
||||
else
|
||||
return ((uiBlock) % 16 == 0);
|
||||
}
|
||||
|
||||
public static boolean isTrailerBlock(int uiBlock) {
|
||||
// 测试我们处于小区块还是大扇区
|
||||
if (uiBlock < 128)
|
||||
return ((uiBlock + 1) % 4 == 0);
|
||||
else
|
||||
return ((uiBlock + 1) % 16 == 0);
|
||||
}
|
||||
|
||||
public static int getBlockCountInSector(int sectorIndex) {
|
||||
if (!validateSector(sectorIndex)) return -1;
|
||||
if (sectorIndex < 32) {
|
||||
return 4;
|
||||
} else {
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
|
||||
public static int get_trailer_block(int uiFirstBlock) {
|
||||
// Test if we are in the small or big sectors
|
||||
int trailer_block;
|
||||
if (uiFirstBlock < 128) {
|
||||
trailer_block = uiFirstBlock + (3 - (uiFirstBlock % 4));
|
||||
} else {
|
||||
trailer_block = uiFirstBlock + (15 - (uiFirstBlock % 16));
|
||||
}
|
||||
return trailer_block;
|
||||
}
|
||||
|
||||
public static int getIndexOnSector(int block, int sector) {
|
||||
int index = 0;
|
||||
//得到当前的块在扇区中的具体索引!
|
||||
for (int i = 0; i < getBlockCountInSector(sector); i++) { //得到当前扇区的块总数!
|
||||
if (block == (sectorToBlock(block) + i)) break;
|
||||
++index;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.proxgrind.chameleon.utils.mifare;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* MifareClassic定义
|
||||
*/
|
||||
public interface MifareAdapter extends Serializable {
|
||||
|
||||
/*
|
||||
* 一个标准的MifareClassic
|
||||
* 是可以被链接,被验证,被读取,被写入,被增值,被减值,被重置的!
|
||||
* */
|
||||
|
||||
/**
|
||||
* 重新查找获取标签!
|
||||
*
|
||||
* @return 查找结果!
|
||||
*/
|
||||
boolean rescantag() throws IOException;
|
||||
|
||||
/**
|
||||
* 链接标签
|
||||
*
|
||||
* @return 链接结果!
|
||||
*/
|
||||
boolean connect() throws IOException;
|
||||
|
||||
/**
|
||||
* 断开标签
|
||||
*/
|
||||
void close() throws IOException;
|
||||
|
||||
/**
|
||||
* 读取标签
|
||||
*
|
||||
* @param block 读取的块
|
||||
* @return 读取结果, 可能为null
|
||||
*/
|
||||
byte[] read(int block) throws IOException;
|
||||
|
||||
/**
|
||||
* 写入标签
|
||||
*
|
||||
* @param blockIndex 写入的块,
|
||||
* @param data 将被写入的数据,必须是16字节长度的Hex字符串!
|
||||
* @return 写入结果!
|
||||
*/
|
||||
boolean write(int blockIndex, byte[] data) throws IOException;
|
||||
|
||||
/**
|
||||
* 验证密钥A
|
||||
*
|
||||
* @param sectorIndex 被验证的块
|
||||
* @param key 用来验证的密钥
|
||||
* @return 验证结果!
|
||||
*/
|
||||
boolean authA(int sectorIndex, byte[] key) throws IOException;
|
||||
|
||||
/**
|
||||
* 验证密钥B
|
||||
*
|
||||
* @param sectorIndex 被验证的块
|
||||
* @param key 用来验证的密钥
|
||||
* @return 验证结果!
|
||||
*/
|
||||
boolean authB(int sectorIndex, byte[] key) throws IOException;
|
||||
|
||||
/**
|
||||
* 增值
|
||||
*
|
||||
* @param blockIndex 被增值的块
|
||||
* @param value 非负递增的值
|
||||
*/
|
||||
void increment(int blockIndex, int value) throws IOException;
|
||||
|
||||
/**
|
||||
* 增值
|
||||
*
|
||||
* @param blockIndex 被增值的块
|
||||
* @param value 非负递减的值
|
||||
*/
|
||||
void decrement(int blockIndex, int value) throws IOException;
|
||||
|
||||
/**
|
||||
* 恢复增值减值操作
|
||||
*
|
||||
* @param blockIndex 被恢复的块
|
||||
*/
|
||||
void restore(int blockIndex) throws IOException;
|
||||
|
||||
/**
|
||||
* 转移值数据到块
|
||||
*
|
||||
* @param blockIndex 被转移的块
|
||||
*/
|
||||
void transfer(int blockIndex) throws IOException;
|
||||
|
||||
/**
|
||||
* 获得UID
|
||||
*
|
||||
* @return UID字节数组
|
||||
*/
|
||||
byte[] getUid();
|
||||
|
||||
byte[] getAts();
|
||||
|
||||
byte[] getAtqa();
|
||||
|
||||
byte[] getSak();
|
||||
|
||||
/**
|
||||
* 获得类型
|
||||
*
|
||||
* @return 1024 or 2048 or 4096
|
||||
*/
|
||||
int getType();
|
||||
|
||||
/**
|
||||
* 获得扇区数量
|
||||
*
|
||||
* @return 卡片支持的扇区容量
|
||||
*/
|
||||
int getSectorCount();
|
||||
|
||||
/**
|
||||
* 获得块数量
|
||||
*
|
||||
* @return 卡片支持的块容量
|
||||
*/
|
||||
int getBlockCount();
|
||||
|
||||
/**
|
||||
* 设置操作超时
|
||||
*
|
||||
* @param ms 被设置的超时
|
||||
*/
|
||||
void setTimeout(int ms);
|
||||
|
||||
/**
|
||||
* 获取操作超时
|
||||
*
|
||||
* @return 超时参数
|
||||
*/
|
||||
int getTimeout();
|
||||
|
||||
/**
|
||||
* 获取批量操作的实现!
|
||||
* 如果返回null,则不支持批量!
|
||||
*/
|
||||
BatchAdapter getBatchImpl();
|
||||
|
||||
/**
|
||||
* 是否是链接状态!
|
||||
*
|
||||
* @return 如果卡片已经链接(在部分实现机制上)
|
||||
* 否则你可以获取结果为true的返回值,否则为false
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
/**
|
||||
* 是否是仿真卡
|
||||
*
|
||||
* @return true为仿真
|
||||
*/
|
||||
boolean isEmulated();
|
||||
|
||||
/**
|
||||
* 是否是后门卡!
|
||||
*
|
||||
* @return 是否是后门卡
|
||||
*/
|
||||
boolean isSpecialTag();
|
||||
|
||||
/**
|
||||
* 是否是支持测试!
|
||||
*/
|
||||
boolean isTestSupported();
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package com.proxgrind.chameleon.utils.mifare;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.NfcManager;
|
||||
import android.nfc.Tag;
|
||||
import android.nfc.tech.MifareClassic;
|
||||
import android.os.Build;
|
||||
|
||||
import com.proxgrind.chameleon.utils.stream.IOUtils;
|
||||
import com.proxgrind.chameleon.utils.system.LogUtils;
|
||||
import com.proxgrind.chameleon.utils.system.SystemUtils;
|
||||
import com.proxgrind.chameleon.utils.tools.GlobalTag;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class StdMifareImpl implements MifareAdapter, GlobalTag.OnNewTagListener {
|
||||
private static MifareClassic mMfTag = null;
|
||||
private final static StdMifareImpl stdMifareImpl = new StdMifareImpl();
|
||||
|
||||
/*
|
||||
* 此方法需要传入一个TAG,这个TAG将被用来初始化一个MifareClassic标签!
|
||||
* */
|
||||
private StdMifareImpl() {
|
||||
mMfTag = getMfOfGlobalTag();
|
||||
GlobalTag.addListener(this);
|
||||
}
|
||||
|
||||
public static StdMifareImpl getInstance() {
|
||||
synchronized (stdMifareImpl) {
|
||||
mMfTag = getMfOfGlobalTag();
|
||||
}
|
||||
return stdMifareImpl;
|
||||
}
|
||||
|
||||
private static MifareClassic getMfOfGlobalTag() {
|
||||
if (mMfTag != null) IOUtils.close(mMfTag);
|
||||
Tag tag = GlobalTag.getTag();
|
||||
if (tag != null) {
|
||||
return MifareClassic.get(tag);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device supports the MIFARE Classic technology.
|
||||
* In order to do so, there is a first check ensure the device actually has a NFC hardware
|
||||
* After this, this function will check if there are files
|
||||
* like "/dev/bcm2079x-i2c" or "/system/lib/libnfc-bcrm*". Files like
|
||||
* these are indicators for a NFC controller manufactured by Broadcom.
|
||||
* Broadcom chips don't support MIFARE Classic.
|
||||
*
|
||||
* @return True if the device supports MIFARE Classic. False otherwise.
|
||||
*/
|
||||
public static boolean hasMifareClassicSupport(Context context) {
|
||||
|
||||
// Check for the MifareClassic class.
|
||||
// It is most likely there on all NFC enabled phones.
|
||||
// Therefore this check is not needed.
|
||||
/*
|
||||
try {
|
||||
Class.forName("android.nfc.tech.MifareClassic");
|
||||
} catch( ClassNotFoundException e ) {
|
||||
// Class not found. Devices does not support MIFARE Classic.
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
// Check if ther is any NFC hardware at all.
|
||||
if (NfcAdapter.getDefaultAdapter(context) == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there is the NFC device "bcm2079x-i2c".
|
||||
// Chips by Broadcom don't support MIFARE Classic.
|
||||
// This could fail because on a lot of devices apps don't have
|
||||
// the sufficient permissions.
|
||||
// Another exception:
|
||||
// The Lenovo P2 has a device at "/dev/bcm2079x-i2c" but is still
|
||||
// able of reading/writing MIFARE Classic tags. I don't know why...
|
||||
// https://github.com/ikarus23/MifareClassicTool/issues/152
|
||||
boolean isLenovoP2 = Build.MANUFACTURER.equals("LENOVO")
|
||||
&& Build.MODEL.equals("Lenovo P2a42");
|
||||
File device = new File("/dev/bcm2079x-i2c");
|
||||
if (!isLenovoP2 && device.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there is the NFC device "pn544".
|
||||
// The PN544 NFC chip is manufactured by NXP.
|
||||
// Chips by NXP support MIFARE Classic.
|
||||
device = new File("/dev/pn544");
|
||||
if (device.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if there are NFC libs with "brcm" in their names.
|
||||
// "brcm" libs are for devices with Broadcom chips. Broadcom chips
|
||||
// don't support MIFARE Classic.
|
||||
File libsFolder = new File("/system/lib");
|
||||
File[] libs = libsFolder.listFiles();
|
||||
if (libs != null) {
|
||||
for (File lib : libs) {
|
||||
if (lib.isFile()
|
||||
&& lib.getName().startsWith("libnfc")
|
||||
&& lib.getName().contains("brcm")
|
||||
// Add here other non NXP NFC libraries.
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isNfcOpened(Context context) {
|
||||
if (hasMifareClassicSupport(context)) {
|
||||
//判断设备是否支持NFC!
|
||||
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
|
||||
// 可能出现adapter为空的情况!
|
||||
if (adapter != null) {
|
||||
return adapter.isEnabled();
|
||||
} else {
|
||||
LogUtils.d("isNfcOpened:adapter is null. ");
|
||||
}
|
||||
}
|
||||
LogUtils.d("isNfcOpened:MifareClassic unsupported. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
public MifareClassic getMf() {
|
||||
return mMfTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rescantag() throws IOException {
|
||||
Tag tag = GlobalTag.getTag();
|
||||
if (mMfTag != null) mMfTag.close();
|
||||
if (tag != null) {
|
||||
mMfTag = MifareClassic.get(tag);
|
||||
}
|
||||
return mMfTag != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() throws IOException {
|
||||
if (mMfTag.isConnected()) mMfTag.close();
|
||||
if (mMfTag != null && !isConnected()) {
|
||||
mMfTag.connect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (mMfTag != null)
|
||||
if (isConnected())
|
||||
mMfTag.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(int block) throws IOException {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.readBlock(block);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(int blockIndex, byte[] data) throws IOException {
|
||||
if (mMfTag != null) {
|
||||
mMfTag.writeBlock(blockIndex, data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authA(int sectorIndex, byte[] key) throws IOException {
|
||||
if (mMfTag != null) {
|
||||
return mMfTag.authenticateSectorWithKeyA(sectorIndex, key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authB(int sectorIndex, byte[] key) throws IOException {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.authenticateSectorWithKeyB(sectorIndex, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void increment(int blockIndex, int value) throws IOException {
|
||||
if (mMfTag != null)
|
||||
mMfTag.increment(blockIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decrement(int blockIndex, int value) throws IOException {
|
||||
if (mMfTag != null)
|
||||
mMfTag.decrement(blockIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore(int blockIndex) throws IOException {
|
||||
if (mMfTag != null)
|
||||
mMfTag.restore(blockIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transfer(int blockIndex) throws IOException {
|
||||
if (mMfTag != null)
|
||||
mMfTag.transfer(blockIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getUid() {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.getTag().getId();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAts() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAtqa() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSak() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.getType();
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectorCount() {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.getSectorCount();
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockCount() {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.getBlockCount();
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimeout(int ms) {
|
||||
if (mMfTag != null)
|
||||
mMfTag.setTimeout(ms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimeout() {
|
||||
if (mMfTag != null)
|
||||
return mMfTag.getTimeout();
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchAdapter getBatchImpl() {
|
||||
// 标准NFC不支持批量验证,所以需要返回null
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
synchronized (stdMifareImpl) {
|
||||
if (mMfTag == null) {
|
||||
LogUtils.d("MfTag对象为空,将会直接返回NULL");
|
||||
return false;
|
||||
}
|
||||
LogUtils.d("MfTag对象不为空,将会直接返回链接状态: " + mMfTag.isConnected());
|
||||
return mMfTag.isConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmulated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpecialTag() {
|
||||
//自带的NFC不支持特殊的后门标签直接读写,因此直接返回false即可!
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTestSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewTag(Tag tag) {
|
||||
synchronized (stdMifareImpl) {
|
||||
if (mMfTag != null) {
|
||||
if (mMfTag.isConnected()) IOUtils.close(mMfTag);
|
||||
mMfTag = getMfOfGlobalTag();
|
||||
LogUtils.d("StdMifareImpl收到了卡片通知,将会进行卡片更新处理!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.proxgrind.chameleon.utils.mifare;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.NfcManager;
|
||||
import android.nfc.tech.MifareClassic;
|
||||
|
||||
public class StdMifareIntent {
|
||||
|
||||
private NfcAdapter mAdapter = null;
|
||||
|
||||
public StdMifareIntent(Context context) {
|
||||
NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
|
||||
//判断设备是否支持NFC!
|
||||
if (manager == null) return;
|
||||
mAdapter = manager.getDefaultAdapter();
|
||||
}
|
||||
|
||||
/*
|
||||
* 获取当前操作的适配器!
|
||||
* */
|
||||
public NfcAdapter getAdapter() {
|
||||
return mAdapter;
|
||||
}
|
||||
|
||||
/*
|
||||
* 注册前台
|
||||
* */
|
||||
public void enableForegroundDispatch(Activity targetAct) {
|
||||
if (mAdapter == null) return;
|
||||
try {
|
||||
//进行前台广播拦截!
|
||||
Intent intent = new Intent(targetAct,
|
||||
targetAct.getClass()).addFlags(
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(
|
||||
targetAct, 0, intent, 0);
|
||||
mAdapter.enableForegroundDispatch(targetAct, pendingIntent, null, new String[][]{
|
||||
new String[]{MifareClassic.class.getName()}});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 解注册前台
|
||||
* */
|
||||
public void disableForegroundDispatch(Activity activity) {
|
||||
if (mAdapter == null) return;
|
||||
try {
|
||||
//解注册前台拦截
|
||||
mAdapter.disableForegroundDispatch(activity);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user