Lấy block data của Felica (NFC) ở Android
Bài viết dịch từ http://qiita.com/pear510/items/38f94d61c020a17314b6 Thẻ sinh viên của tôi ở đại học ở thẻ Felica, tôi muốn lấy dữ liệu ở thẻ đó nên đã quyết định sẽ học coding ở Android. Tuy nhiên, dù đã tìm hiểu rất nhiều nhưng các bài viết hay sách thường chỉ đề cập đến việc lấy IDm nên tôi ...
Bài viết dịch từ http://qiita.com/pear510/items/38f94d61c020a17314b6
Thẻ sinh viên của tôi ở đại học ở thẻ Felica, tôi muốn lấy dữ liệu ở thẻ đó nên đã quyết định sẽ học coding ở Android. Tuy nhiên, dù đã tìm hiểu rất nhiều nhưng các bài viết hay sách thường chỉ đề cập đến việc lấy IDm nên tôi quyết định sẽ viết về việc lấy block data.
Felica được cấu tạo từ System / Area /Service / Block data. Lần này tôi muốn lấy block data trong phần service. Ngoài ra, Felica có thể chia hệ thống ra nhỏ hơn, cũng có thể tạo ra nhiều hệ thống. Từng hệ thống đó tách rời nhau về mặt function, security, không giao thoa với nhau. Felica lần này tôi muốn lấy sẽ có cấu trúc như dưới đây:
- System 0: (System Code -> 0x8CDA) - IDm - Area - Area - Service - Service - ... - System 1: (System Code -> 0xFE00) - IDm - Area - Area - ... - Area - Service - Service: (Service Code -> 0x1A8B) ← Muốn access vào đây - ...
Dù có thay đổi thẻ sinh viên thì vẫn dùng chung 1 System Code, Service Code.
Dưới đây là phương pháp để lấy Felica. (Felica thuộc NFC-TypeF.) Lần này tôi chỉ muốn lấy Felica khi App khởi động sử dụng enableForegroundDispatch.
NFCReaderActivity.java private IntentFilter[] intentFiltersArray; private String[][] techListsArray; private NfcAdapter mAdapter; private PendingIntent pendingIntent; private NfcReader nfcReader = new NfcReader(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_nfcreader); pendingIntent = PendingIntent.getActivity( this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndef.addDataType("text/plain"); } catch (IntentFilter.MalformedMimeTypeException e) { throw new RuntimeException("fail", e); } intentFiltersArray = new IntentFilter[] {ndef}; // Felica là NFC-TypeF nên chỉ cần chỉ định NfcF là OK techListsArray = new String[][] { new String[] { NfcF.class.getName() } }; // Lấy NfcAdapter mAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); } @Override protected void onResume() { super.onResume(); // Kích hoạtt lấy NFC mAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray); } @Override protected void onNewIntent(Intent intent) { // Lấy vì data cơ bản của Tag sẽ đi vào trong Intent. Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); if (tag == null) { return; } // Sử dụng Tag đã lấy ở đây để tiến hành load data } @Override protected void onPause() { super.onPause(); mAdapter.disableForegroundDispatch(this); } Ngoài ra, đừng quên viết sẵn phần dưới đây vào Manifest nhé! AndroidManifest.xml ... <uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc" android:required="true" /> ...
Ở bước này, khi khởi động App và giữ NFC thì data sẽ đi vào onNewIntent. Khi lấy Tag từ Intent như trên thì sẽ có thể access vào Felica đang giữ từ Tag đã lấy.
Đầu tiên, cần sử dụng command Felica để access vào Felica.
Để access vào Felica ở Android, chỉ cần connect bằng NfcF.connect() của package android.nfc.NfcF có trong SDK, gửi command bằng NfcF.tranceive(byte[] data) rồi close lại bằng NfcF.close() là được. Command gửi đi sẽ được reform thành packet data.
Để command, cần có IDm của system region chứa service mình muốn lấy. Trường hợp muốn access vào service thuộc region System 0, có thể lấy IDm của region System 0 từ tag đã lấy ở trên bằng cách sử dụng tag.getId(). Tuy nhiên, lần này tôi muốn access vào Service thuộc region System 1 nên trước tiên, cần tiến hành xử lý polling rồi lấy IDm của System 1. Sau đó, sử dụng IDm đã lấy để lấy block data mong muốn. Ngoài ra, command cần thiết là command Poliing và command Read Without Rncryption. ※ Read Without Rncryption dùng khi acces data không cần xác thực, trường hợp access đến data cần xác thực thì sẽ dùng command khác.
NfcReader.java import android.nfc.Tag; import android.nfc.tech.NfcF; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; public class NfcReader { public byte[][] readTag(Tag tag) { NfcF nfc = NfcF.get(tag); try { nfc.connect(); //System code của System 1-> 0xFE00 byte[] targetSystemCode = new byte[]{(byte) 0xfe,(byte) 0x00}; // Tạo command polling byte[] polling = polling(targetSystemCode); // Gửi command rồi lấy kết quả byte[] pollingRes = nfc.transceive(polling); // Lấy IDm của System 0 (Byte thứ 1 là data size, byte thứ 2 là response code, size của IDm là 8 byte) byte[] targetIDm = Arrays.copyOfRange(pollingRes, 2, 10); // Size của data chứa trong service (Lần này là 4) int size = 4; // Service code của đối tượng là -> 0x1A8B byte[] targetServiceCode = new byte[]{(byte) 0x1A, (byte) 0x8B}; // Tạo command Read Without Encryption byte[] req = readWithoutEncryption(targetIDm, size, targetServiceCode); // Gửi command rồi lấy kết quả byte[] res = nfc.transceive(req); nfc.close(); // Parse kết quả, chỉ lấy data return parse(res); } catch (Exception e) { Log.e(TAG, e.getMessage() , e); } return null; } /** * Lấy command Pollin * System code chỉ định @param systemCode byte[] * Command @return Polling * @throws IOException */ private byte[] polling(byte[] systemCode) { ByteArrayOutputStream bout = new ByteArrayOutputStream(100); bout.write(0x00); // Dummy của byte độ dài data bout.write(0x00); // Command code bout.write(systemCode[0]); // systemCode bout.write(systemCode[1]); // systemCode bout.write(0x01); // Request code bout.write(0x0f); // Timeslot byte[] msg = bout.toByteArray(); msg[0] = (byte) msg.length; // 1 byte ở đầu là chiều dài data return msg; } /** * Lấy command Read Without Encryption * ID của system chỉ định @param IDm * Số data lấy @param size * Command @return Read Without Encryption * @throws IOException */ private byte[] readWithoutEncryption(byte[] idm, int size, byte[] serviceCode) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(100); bout.write(0); // データ長バイトのダミー bout.write(0x06); // コマンドコード bout.write(idm); // IDm 8byte bout.write(1); // サービス数の長さ(以下2バイトがこの数分繰り返す) // Việc chỉ định service code là little endian nên sẽ chỉ định từ low byte. bout.write(serviceCode[1]); // Service code low byte bout.write(serviceCode[0]); // Service code high byte bout.write(size); // Số block // Chỉ định Block number for (int i = 0; i < size; i++) { bout.write(0x80); // Block element high byte bout.write(i); // Block number } byte[] msg = bout.toByteArray(); msg[0] = (byte) msg.length; // 1 byte ở đầu là chiều dài của data return msg; } /** * Phân tích kết quả Read Without Encryption * @param res byte[] * Show string @return * @throws Exception */ private byte[][] parse(byte[] res) throws Exception { // res[10] error code. Trường hợp 0x00 thì không có vấn đề gì if (res[10] != 0x00) throw new RuntimeException("Read Without Encryption Command Error"); // res[12] Số block trả lời // res[13 + n * 16] Lặp lại 16 data thực (byte/block) int size = res[12]; byte[][] data = new byte[size][16]; String str = ""; for (int i = 0; i < size; i++) { byte[] tmp = new byte[16]; int offset = 13 + i * 16; for (int j = 0; j < 16; j++) { tmp[j] = res[offset + j]; } data[i] = tmp; } return data; } }
Chú ý
• PacketData gửi bằng NfcF.tranceive(byte[] data) cần gắn size data vào byte thứ 1. Ngoài ra, size data này sẽ là size chứa size data gắn vào byte thứ 1. • Khi nhận command không phù hợp, Felica không trả về error mà sẽ không có phản ứng. Đại khái, khi NfcF.tranceive(byte[] data) thì sẽ không phản ứng, android.nfc.TagLostException sẽ bị chậm lại.
Command Polling
Là command lấy ID(IDm) cấu trúc và parameter cấu trúc (PMm) của hệ thống chỉ định bằng system command. Bằng việc chỉ định request code hoặc timeslot mà có thể chỉ định được system code của hệ thống hay chỉ định được số timeslot tối đa có thể response.
Ký hiệu của size tất cả là 16 số thập phân.
Command packet data
Tên Parameter | Size | Data | Ghi chú | Ex |
---|---|---|---|---|
Command code | 1 | 0x00 | 0x00 | |
System code | 2 | Chỉ đinh system code | 0xFE00 | |
Request code | 1 | Text | Chỉ định data request 0x00: Không yêu cầu 0x01: Yêu cầu request code 0x02: Yêu cầu tính năng gửi Khác: Đặt trước | 0x01 |
Timeslot | 1 | Chỉ định số slot tối đa có thể response | 0x0F |
Data packet response
Tên Parameter | Size | Data | Ghi chú | Ex |
---|---|---|---|---|
Response code | 1 | 0x00 | 0x00 | |
System code | 2 | Chỉ định system code | 0xFE00 | |
Request code | 1 | Chỉ định request data 0x00: Không yêu cầu 0x01: Yêu cầu system code 0x02: Yêu cầu tính năng gửi Khác: Đặt trước | 0x01 | |
Timeslot | 1 | Chỉ định số timeslot tối đa có thể response | 0x0F |
Read Without Encryption
Là command dùng để load block data từ service không cần xác thực.
Command packet data
Tên Parameter | Size | Data | Ghi chú | Ex |
---|---|---|---|---|
Command code | 1 | 0x06 | 0x06 | |
IDm | 8 | targetIDm | ||
Số Service | 1 | m | 1 <= m <= 16 | 1 |
List service code | 2m | Service code chi định bằng little endian | 0x01 | |
Số block | 1 | n | 4 | |
List block | N | 2n <= N <= 3n |
Response packet data
Tên parameter | Size | Data | Ghi chú | Ex |
---|---|---|---|---|
Response code | 1 | 0x07 | 0x07 | |
IDm | 8 | targetIDm | ||
Status flag 1 | 1 | 0xFE00 | ||
Status flag 2 | 1 | 0x01 | ||
Số block | 1 | n | Chỉ đưa ra trong trường hợp status flag 1 là 0x00 | |
Block data | 16n | Chỉ đưa ra trong trường hợp status flag 1 là 0x00 |