/* NFCard is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. NFCard is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wget. If not, see <http://www.gnu.org/licenses/>. Additional permission under GNU GPL version 3 section 7 */ package com.zzx.factorytest.nfc.tech; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import android.nfc.tech.NfcF; import com.zzx.factorytest.nfc.Util; public class FeliCa { public static final byte[] EMPTY = {}; protected byte[] data; protected FeliCa() { } protected FeliCa(byte[] bytes) { data = (bytes == null) ? FeliCa.EMPTY : bytes; } public int size() { return data.length; } public byte[] getBytes() { return data; } @Override public String toString() { return Util.toHexString(data, 0, data.length); } public final static class IDm extends FeliCa { public static final byte[] EMPTY = { 0, 0, 0, 0, 0, 0, 0, 0, }; public IDm(byte[] bytes) { super((bytes == null || bytes.length < 8) ? IDm.EMPTY : bytes); } public final String getManufactureCode() { return Util.toHexString(data, 0, 2); } public final String getCardIdentification() { return Util.toHexString(data, 2, 6); } public boolean isEmpty() { final byte[] d = data; for (final byte v : d) { if (v != 0) return false; } return true; } } public final static class PMm extends FeliCa { public static final byte[] EMPTY = { 0, 0, 0, 0, 0, 0, 0, 0, }; public PMm(byte[] bytes) { super((bytes == null || bytes.length < 8) ? PMm.EMPTY : bytes); } public final String getIcCode() { return Util.toHexString(data, 0, 2); } public final String getMaximumResponseTime() { return Util.toHexString(data, 2, 6); } } public final static class SystemCode extends FeliCa { public static final byte[] EMPTY = { 0, 0, }; public SystemCode(byte[] sc) { super((sc == null || sc.length < 2) ? SystemCode.EMPTY : sc); } public int toInt() { return toInt(data); } public static int toInt(byte[] data) { return 0x0000FFFF & ((data[0] << 8) | (0x000000FF & data[1])); } } public final static class ServiceCode extends FeliCa { public static final byte[] EMPTY = { 0, 0, }; public static final int T_UNKNOWN = 0; public static final int T_RANDOM = 1; public static final int T_CYCLIC = 2; public static final int T_PURSE = 3; public ServiceCode(byte[] sc) { super((sc == null || sc.length < 2) ? ServiceCode.EMPTY : sc); } public ServiceCode(int code) { this(new byte[] { (byte) (code & 0xFF), (byte) (code >> 8) }); } public boolean isEncrypt() { return (data[0] & 0x1) == 0; } public boolean isWritable() { final int f = data[0] & 0x3F; return (f & 0x2) == 0 || f == 0x13 || f == 0x12; } public int getAccessAttr() { return data[0] & 0x3F; } public int getDataType() { final int f = data[0] & 0x3F; if ((f & 0x10) == 0) return T_PURSE; return ((f & 0x04) == 0) ? T_RANDOM : T_CYCLIC; } } public final static class Block extends FeliCa { public Block() { data = new byte[16]; } public Block(byte[] bytes) { super((bytes == null || bytes.length < 16) ? new byte[16] : bytes); } } public final static class BlockListElement extends FeliCa { private static final byte LENGTH_2_BYTE = (byte) 0x80; private static final byte LENGTH_3_BYTE = (byte) 0x00; // private static final byte ACCESSMODE_DECREMENT = (byte) 0x00; // private static final byte ACCESSMODE_CACHEBACK = (byte) 0x01; private final byte lengthAndaccessMode; private final byte serviceCodeListOrder; public BlockListElement(byte mode, byte order, byte... blockNumber) { if (blockNumber.length > 1) { lengthAndaccessMode = (byte) (mode | LENGTH_2_BYTE & 0xFF); } else { lengthAndaccessMode = (byte) (mode | LENGTH_3_BYTE & 0xFF); } serviceCodeListOrder = (byte) (order & 0x0F); data = (blockNumber == null) ? FeliCa.EMPTY : blockNumber; } @Override public byte[] getBytes() { if ((this.lengthAndaccessMode & LENGTH_2_BYTE) == 1) { ByteBuffer buff = ByteBuffer.allocate(2); buff.put( (byte) ((this.lengthAndaccessMode | this.serviceCodeListOrder) & 0xFF)) .put(data[0]); return buff.array(); } else { ByteBuffer buff = ByteBuffer.allocate(3); buff.put( (byte) ((this.lengthAndaccessMode | this.serviceCodeListOrder) & 0xFF)) .put(data[1]).put(data[0]); return buff.array(); } } } public final static class MemoryConfigurationBlock extends FeliCa { public MemoryConfigurationBlock(byte[] bytes) { super((bytes == null || bytes.length < 4) ? new byte[4] : bytes); } public boolean isNdefSupport() { return (data == null) ? false : (data[3] & (byte) 0xff) == 1; } public void setNdefSupport(boolean ndefSupport) { data[3] = (byte) (ndefSupport ? 1 : 0); } public boolean isWritable(int... addrs) { if (data == null) return false; boolean result = true; for (int a : addrs) { byte b = (byte) ((a & 0xff) + 1); if (a < 8) { result &= (data[0] & b) == b; continue; } else if (a < 16) { result &= (data[1] & b) == b; continue; } else result &= (data[2] & b) == b; } return result; } } public final static class Service extends FeliCa { private final ServiceCode[] serviceCodes; private final BlockListElement[] blockListElements; public Service(ServiceCode[] codes, BlockListElement... blocks) { serviceCodes = (codes == null) ? new ServiceCode[0] : codes; blockListElements = (blocks == null) ? new BlockListElement[0] : blocks; } @Override public byte[] getBytes() { int length = 0; for (ServiceCode s : this.serviceCodes) { length += s.getBytes().length; } for (BlockListElement b : blockListElements) { length += b.getBytes().length; } ByteBuffer buff = ByteBuffer.allocate(length); for (ServiceCode s : this.serviceCodes) { buff.put(s.getBytes()); } for (BlockListElement b : blockListElements) { buff.put(b.getBytes()); } return buff.array(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (ServiceCode s : serviceCodes) { sb.append(s.toString()); } for (BlockListElement b : blockListElements) { sb.append(b.toString()); } return sb.toString(); } } public final static class Command extends FeliCa { private final int length; private final byte code; private final IDm idm; public Command(final byte[] bytes) { this(bytes[0], Arrays.copyOfRange(bytes, 1, bytes.length)); } public Command(byte code, final byte... bytes) { this.code = code; if (bytes.length >= 8) { idm = new IDm(Arrays.copyOfRange(bytes, 0, 8)); data = Arrays.copyOfRange(bytes, 8, bytes.length); } else { idm = null; data = bytes; } length = bytes.length + 2; } public Command(byte code, IDm idm, final byte... bytes) { this.code = code; this.idm = idm; this.data = bytes; this.length = idm.getBytes().length + data.length + 2; } public Command(byte code, byte[] idm, final byte... bytes) { this.code = code; this.idm = new IDm(idm); this.data = bytes; this.length = idm.length + data.length + 2; } @Override public byte[] getBytes() { ByteBuffer buff = ByteBuffer.allocate(length); byte length = (byte) this.length; if (idm != null) { buff.put(length).put(code).put(idm.getBytes()).put(data); } else { buff.put(length).put(code).put(data); } return buff.array(); } } public static class Response extends FeliCa { protected final int length; protected final byte code; protected final IDm idm; public Response(byte[] bytes) { if (bytes != null && bytes.length >= 10) { length = bytes[0] & 0xff; code = bytes[1]; idm = new IDm(Arrays.copyOfRange(bytes, 2, 10)); data = bytes; } else { length = 0; code = 0; idm = new IDm(null); data = FeliCa.EMPTY; } } public IDm getIDm() { return idm; } } public final static class PollingResponse extends Response { private final PMm pmm; public PollingResponse(byte[] bytes) { super(bytes); if (size() >= 18) { pmm = new PMm(Arrays.copyOfRange(data, 10, 18)); } else { pmm = new PMm(null); } } public PMm getPMm() { return pmm; } } public final static class ReadResponse extends Response { public static final byte[] EMPTY = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xFF, (byte) 0xFF }; private final byte[] blockData; public ReadResponse(byte[] rsp) { super((rsp == null || rsp.length < 12) ? ReadResponse.EMPTY : rsp); if (getStatusFlag1() == STA1_NORMAL && getBlockCount() > 0) { blockData = Arrays.copyOfRange(data, 13, data.length); } else { blockData = FeliCa.EMPTY; } } public int getStatusFlag1() { return data[10]; } public int getStatusFlag2() { return data[11]; } public int getBlockCount() { return (data.length > 12) ? (0xFF & data[12]) : 0; } public byte[] getBlockData() { return blockData; } public boolean isOkey() { return getStatusFlag1() == STA1_NORMAL; } } public final static class WriteResponse extends Response { public WriteResponse(byte[] rsp) { super((rsp == null || rsp.length < 12) ? ReadResponse.EMPTY : rsp); } public int getStatusFlag1() { return data[0]; } public int getStatusFlag2() { return data[1]; } public boolean isOkey() { return getStatusFlag1() == STA1_NORMAL; } } public final static class Tag { private final NfcF nfcTag; private boolean isFeliCaLite; private int sys; private IDm idm; private PMm pmm; public Tag(NfcF tag) { nfcTag = tag; sys = SystemCode.toInt(tag.getSystemCode()); idm = new IDm(tag.getTag().getId()); pmm = new PMm(tag.getManufacturer()); } public int getSystemCode() { return sys; } public IDm getIDm() { return idm; } public PMm getPMm() { return pmm; } public boolean checkFeliCaLite() { isFeliCaLite = !polling(SYS_FELICA_LITE).getIDm().isEmpty(); return isFeliCaLite; } public boolean isFeliCaLite() { return isFeliCaLite; } public PollingResponse polling(int systemCode) { Command cmd = new Command(CMD_POLLING, new byte[] { (byte) (systemCode >> 8), (byte) (systemCode & 0xff), (byte) 0x01, (byte) 0x00 }); PollingResponse r = new PollingResponse(transceive(cmd)); idm = r.getIDm(); pmm = r.getPMm(); return r; } public PollingResponse polling() { Command cmd = new Command(CMD_POLLING, new byte[] { (byte) (SYS_FELICA_LITE >> 8), (byte) (SYS_FELICA_LITE & 0xff), (byte) 0x01, (byte) 0x00 }); PollingResponse r = new PollingResponse(transceive(cmd)); idm = r.getIDm(); pmm = r.getPMm(); return r; } public final SystemCode[] getSystemCodeList() { final Command cmd = new Command(CMD_REQUEST_SYSTEMCODE, idm); final byte[] bytes = transceive(cmd); final int num = (int) bytes[10]; final SystemCode ret[] = new SystemCode[num]; for (int i = 0; i < num; ++i) { ret[i] = new SystemCode(Arrays.copyOfRange(bytes, 11 + i * 2, 13 + i * 2)); } return ret; } public ServiceCode[] getServiceCodeList() { ArrayList<ServiceCode> ret = new ArrayList<ServiceCode>(); int index = 1; while (true) { byte[] bytes = searchServiceCode(index); if (bytes.length != 2 && bytes.length != 4) break; if (bytes.length == 2) { if (bytes[0] == (byte) 0xff && bytes[1] == (byte) 0xff) break; ret.add(new ServiceCode(bytes)); } ++index; } return ret.toArray(new ServiceCode[ret.size()]); } private byte[] searchServiceCode(int index) { Command cmd = new Command(CMD_SEARCH_SERVICECODE, idm, new byte[] { (byte) (index & 0xff), (byte) (index >> 8) }); byte[] bytes = transceive(cmd); if (bytes == null || bytes.length < 12 || bytes[1] != (byte) 0x0b) { return FeliCa.EMPTY; } return Arrays.copyOfRange(bytes, 10, bytes.length); } public ReadResponse readWithoutEncryption(ServiceCode code, byte addr) { byte[] bytes = code.getBytes(); Command cmd = new Command(CMD_READ_WO_ENCRYPTION, idm, new byte[] { (byte) 0x01, (byte) bytes[0], (byte) bytes[1], (byte) 0x01, (byte) 0x80, addr }); return new ReadResponse(transceive(cmd)); } public ReadResponse readWithoutEncryption(byte addr) { Command cmd = new Command(CMD_READ_WO_ENCRYPTION, idm, new byte[] { (byte) 0x01, (byte) (SRV_FELICA_LITE_READONLY >> 8), (byte) (SRV_FELICA_LITE_READONLY & 0xff), (byte) 0x01, (byte) 0x80, addr }); return new ReadResponse(transceive(cmd)); } public WriteResponse writeWithoutEncryption(ServiceCode code, byte addr, byte[] buff) { byte[] bytes = code.getBytes(); ByteBuffer b = ByteBuffer.allocate(22); b.put(new byte[] { (byte) 0x01, (byte) bytes[0], (byte) bytes[1], (byte) 0x01, (byte) 0x80, (byte) addr }); b.put(buff, 0, buff.length > 16 ? 16 : buff.length); Command cmd = new Command(CMD_WRITE_WO_ENCRYPTION, idm, b.array()); return new WriteResponse(transceive(cmd)); } public WriteResponse writeWithoutEncryption(byte addr, byte[] buff) { ByteBuffer b = ByteBuffer.allocate(22); b.put(new byte[] { (byte) 0x01, (byte) (SRV_FELICA_LITE_READWRITE >> 8), (byte) (SRV_FELICA_LITE_READWRITE & 0xff), (byte) 0x01, (byte) 0x80, addr }); b.put(buff, 0, buff.length > 16 ? 16 : buff.length); Command cmd = new Command(CMD_WRITE_WO_ENCRYPTION, idm, b.array()); return new WriteResponse(transceive(cmd)); } public MemoryConfigurationBlock getMemoryConfigBlock() { ReadResponse r = readWithoutEncryption((byte) 0x88); return (r != null) ? new MemoryConfigurationBlock(r.getBlockData()) : null; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (idm != null) { sb.append(idm.toString()); if (pmm != null) sb.append(pmm.toString()); } return sb.toString(); } public void connect() { try { nfcTag.connect(); } catch (Exception e) { } } public void close() { try { nfcTag.close(); } catch (Exception e) { } } public byte[] transceive(Command cmd) { try { return nfcTag.transceive(cmd.getBytes()); } catch (Exception e) { return Response.EMPTY; } } } // polling public static final byte CMD_POLLING = 0x00; public static final byte RSP_POLLING = 0x01; // request service public static final byte CMD_REQUEST_SERVICE = 0x02; public static final byte RSP_REQUEST_SERVICE = 0x03; // request RESPONSE public static final byte CMD_REQUEST_RESPONSE = 0x04; public static final byte RSP_REQUEST_RESPONSE = 0x05; // read without encryption public static final byte CMD_READ_WO_ENCRYPTION = 0x06; public static final byte RSP_READ_WO_ENCRYPTION = 0x07; // write without encryption public static final byte CMD_WRITE_WO_ENCRYPTION = 0x08; public static final byte RSP_WRITE_WO_ENCRYPTION = 0x09; // search service code public static final byte CMD_SEARCH_SERVICECODE = 0x0a; public static final byte RSP_SEARCH_SERVICECODE = 0x0b; // request system code public static final byte CMD_REQUEST_SYSTEMCODE = 0x0c; public static final byte RSP_REQUEST_SYSTEMCODE = 0x0d; // authentication 1 public static final byte CMD_AUTHENTICATION1 = 0x10; public static final byte RSP_AUTHENTICATION1 = 0x11; // authentication 2 public static final byte CMD_AUTHENTICATION2 = 0x12; public static final byte RSP_AUTHENTICATION2 = 0x13; // read public static final byte CMD_READ = 0x14; public static final byte RSP_READ = 0x15; // write public static final byte CMD_WRITE = 0x16; public static final byte RSP_WRITE = 0x17; public static final int SYS_ANY = 0xffff; public static final int SYS_FELICA_LITE = 0x88b4; public static final int SYS_COMMON = 0xfe00; public static final int SRV_FELICA_LITE_READONLY = 0x0b00; public static final int SRV_FELICA_LITE_READWRITE = 0x0900; public static final int STA1_NORMAL = 0x00; public static final int STA1_ERROR = 0xff; public static final int STA2_NORMAL = 0x00; public static final int STA2_ERROR_LENGTH = 0x01; public static final int STA2_ERROR_FLOWN = 0x02; public static final int STA2_ERROR_MEMORY = 0x70; public static final int STA2_ERROR_WRITELIMIT = 0x71; }