顯示具有 NFC 標籤的文章。 顯示所有文章
顯示具有 NFC 標籤的文章。 顯示所有文章

2014/12/08

NFC NXP 203 Tag content example

1. Plain text example


底下是用一般的 nfc tag writter 寫進ndef plain text "123456789"的tag content

page 0     0469b055 c22d3580 5a480000 e1101200
page 4     0103a010 440310d1 010c5402 656e3132
page 8     33343536 373839fe 00000000 00000000
page 12    00000000 00000000 00000000 00000000
page 16    00000000 00000000 00000000 00000000
page 20    00000000 00000000 00000000 00000000
page 24    00000000 00000000 00000000 00000000
page 28    00000000 00000000 00000000 00000000
page 32    00000000 00000000 00000000 00000000
page 36    00000000 00000000 00000000 00000000
page 40    00000000 00000000

可以看到它總共有42個page, 根據 NFX 203F 的 format, user data 存在 page 4~39
而page 0~3, 40~41 用於紀錄 ID, checksum, read only, counter

1.1 TLV Blocks


Tag 內容格式為 TLV (Tag, Length, Value)
Tag 的值為底下幾種

  • NULL TLV (0x00) : 用於對齊 memory
  • Lock Control TLV (0x01) : 對 lock bit 做進一步描述
  • Memory Control TLV (0x02) : 定義剩下的memory
  • NDEF Message TLV (0x03)
  • Proprietary TLV (0xFD)
  • Terminator TLV (0xFE) : 最後一個TLV
上面的例子裡, 可以看到3個TLV
TLV 1: (Lock Control)
01 03 a01044

TLV 2: (NDEF Message)
03 10 d1010c5402656e313233343536373839

TLV 3: (Terminator)
fe


1.2 NDEF Message


在 NDEF TLV 的 VALUE 為 NDEF message
d1010c5402656e313233343536373839

第一個byte 0xd1為 NDEF Flag + TNF

  • MB = 1
  • ME = 1
  • CF = 0
  • SR = 1
  • IL = 0
  • TNF = 1

接下來的 1 個byte是 Type Length
Type Length = 0x01

因為 SR (Short Record) 為 1, 所以接下來的Payload length 長度為1個byte
Payload Length = 0x0c

因為 IL (ID Length is present) 為0, 所以沒有 ID 相關的欄位

接著是1個 byte 的 Payload Type
Payload Type = 0x54 (WELL-KNOWN: urn:nfc:wkt:T (Text))

最後是NDEF Text content:
02 65 6e 31 32 33 34 35 36 37 38 39
   E  N  1  2  3  4  5  6  7  8  9

第一個byte 0x02為Status

  • bit 7 : 0表示UTF-8 encoded, 1表示UTF16 encoded
  • bit 6 : RFU (must be set to zero)
  • bit 5..0 : IANA language code的長度
可以看到它用UTF-8, IANA language code的長度為2
所以接著2個byte為 0x65, 0x6e 為 "EN" 是它的 IANA language code
接著是我們的message "123456789"













2014/10/07

libllcp in nfc-tools

1. nfc-tools

nfc-tools 開發 nfc 相關的 tool, 最有名的應該是libnfc
底下是 nfc-tools 的網頁
https://code.google.com/p/nfc-tools/

但是libnfc還是太底層, 為了實作 NDEF 或 NPP, 可能還要裝 libfreefare 或 libllcp

libfreefare 是專門用在 Mifare
https://code.google.com/p/libfreefare/

libllcp 則是一般性的NDEF communication
https://code.google.com/p/libllcp/
關於怎麼install:
http://nfc-tools.org/index.php?title=Libllcp

LLCP (Logical Link Control Protocol), 有點像是 IP (Internet Protocol)
它可以經由建立連線之後, 由兩端傳data
而 LLCP 又實作了兩種 protocol
一個是 NPP (NDEF Push Protocol), 就像是 UDP 一樣, 丟封包出去就不用等
另個是 SNEP (Simple NDEF Exchange Protocol), 就像是 TCP, 丟封包出去要等ACK, (但沒有三方交握)

2. NPP sample code

在 libllcp/examples/npp-server/ 有個npp server的範例
它的用途是建立連線之後, 接收從tag來的資料

但是裡面似乎有些bug, 所以我將它重寫並測試過, sample code如下:
(錯的部份是, header 檔像 llcp.h 應該在 nfc/llcp.h底下, 然後stdout的部份會強制導到file)

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <nfc/llcp.h>
#include <nfc/llc_service.h>
#include <nfc/llc_link.h>
#include <nfc/llc_connection.h>
#include <nfc/mac.h>

#define errx(code, ...) do { \
    fprintf (stderr, __VA_ARGS__); \
    fprintf (stderr, "\n"); \
    exit (code); \
  } while (0)

struct mac_link *mac_link;
nfc_device *device;

static void stop_mac_link(int sig) {
 if (mac_link && mac_link->device) {
  nfc_abort_command(mac_link->device);
 }
}

static void bye(VOID) {
 if (device) {
  nfc_close(device);
 }
}

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void* com_android_npp_thread(void *arg) {
 struct llc_connection *connection = (struct llc_connection *)arg;
 uint8_t buffer[1024];

 int len;
 size_t n = 0;

 len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL);
 if (len < 0) return NULL;
 if (len < 10) return NULL; // NPP's header (5 bytes) + NDEF entry header (5 bytes)

 // byte 0 : protocol version
 printf("NDEF Push Protocol version: %02x\n", buffer[n]);
 if (buffer[n] != 0x01) return NULL; // version not supported
 n++;

 // byte 1~4 : number of NDEF entries
 uint32_t ndef_entries_count = be32toh(*((uint32_t *)(buffer+n)));
 printf("NDEF entries count: %u\n", ndef_entries_count);
 if (ndef_entries_count != 1) return NULL; // Version 0x01 has value 0x00 0x00 0x00 0x01
 n += 4;

 // NDEF Entry
 printf("Action code: %02x\n", buffer[n]);
 if (buffer[n] != 0x01) return NULL; // Action code not supported
 n++;

 uint32_t ndef_length = be32toh(*((uint32_t *)(buffer+n)));
 n += 4;

 if ((len-n) < ndef_length) return NULL; // less received bytes than expected?

 printf("NDEF entry received (%u bytes): ", ndef_length);
 print_hex(buffer + n, ndef_length);

 llc_connection_stop(connection);

 return NULL;
}

int main(int argc, char *argv[]) {

 int ret;

 if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()");

 signal(SIGINT, stop_mac_link);
 atexit(bye);

 nfc_context *context;
 nfc_init(&context);
 if (context == NULL) errx(EXIT_FAILURE, "nfc_init()");

 device = nfc_open(context, NULL);
 if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device");

 struct llc_link *llc_link = llc_link_new();
 if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures");

 struct llc_service *com_android_npp 
  = llc_service_new_with_uri(NULL, com_android_npp_thread, "com.android.npp", NULL);
 if (com_android_npp == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service");

 llc_service_set_miu(com_android_npp, 512);
 llc_service_set_rw(com_android_npp, 2);

 ret = llc_link_service_bind(llc_link, com_android_npp, -1);
 if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service");

 mac_link = mac_link_new(device, llc_link);
 if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link");

 ret = mac_link_activate_as_target(mac_link);
 if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link");

 void *err;
 mac_link_wait(mac_link, &err);

 mac_link_free(mac_link);
 llc_link_free(llc_link);

 nfc_close(device);
 device = NULL;

 llcp_fini();
 nfc_exit(context);
 exit(EXIT_SUCCESS);
}

然後將它compile, 其中 llcp 的部份官網寫 -lnfc-llcp是錯的
 $ gcc -o npp_server_example npp_server_example.c -lnfc -lllcp

有些手機仍舊可以接受NPP
我手上有台 HTC ONE, 將它的瀏覽器打開, 把手機放在 reader 上面
然後執行剛剛的程式, 手機會感應到, 手機螢幕顯示點選以傳送資料, 點它, 程式就會收到data, 最後將手機移開reader, 程式就結束

$ ./npp_server_example
NDEF Push Protocol version: 01
NDEF entries count: 1
Action code: 01
NDEF entry received (23 bytes): d1 01 13 55 04 74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f

其中
74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f = tw.mobi.yahoo.com/
(TODO: 前面header應該可以parse)

3. npp server code analysis


llcp_init() 是使用llcp要用的初始化, 裡面設定signal handler以及log function,
llcp_fini() 則是結束時釋放資源
int llcp_init(void)
int llcp_fini(void)

nfc context 以及 nfc device 的用法和 libnfc 相同

llc_link 用於紀錄 llc link 相關資訊
struct llc_link {
  uint8_t role;
  enum {
    LL_ACTIVATED,
    LL_DEACTIVATED,
  } status;
  struct llcp_version version;
  uint16_t local_miu;
  uint16_t remote_miu;
  uint16_t remote_wks;
  struct timeval local_lto;
  struct timeval remote_lto;
  uint8_t local_lsc;
  uint8_t remote_lsc;
  uint8_t opt;

  pthread_t thread;
  char *mq_up_name;
  char *mq_down_name;
  mqd_t llc_up;
  mqd_t llc_down;

  struct llc_service *available_services[MAX_LLC_LINK_SERVICE + 1];
  struct llc_connection *datagram_handlers[MAX_LOGICAL_DATA_LINK];
  struct llc_connection *transmission_handlers[MAX_LLC_LINK_SERVICE + 1];

  /* Unit tests metadata */
  void *cut_test_context;
  struct mac_link *mac_link;
};

用 llc_link_new() 來設定 llc link 的 default 值
struct llc_link* llc_link_new(void)


llc_service 用來建 LLC(logical link control) 會用到的相關設定
struct llc_service {
  char *uri;
  void *(*accept_routine)(void *);
  void *(*thread_routine)(void *);
  int8_t sap;
  uint8_t rw;
  uint16_t miu;
  void *user_data;
};

llc_service_new_with_uri() 則是設定 llc_service 成default
struct llc_service* llc_service_new_with_uri(void * (*accept_routine)(void *),
                                             void * (*thread_routine)(void *),
                                             const char *uri,
                                             void *user_data)
accept_routine 是 thread function, 在建立連線時(收到PDU_CONNECT) 呼叫
thread_routine 也是 thread function, 在收到 PDU_UI 時呼叫
uri 應該只是 llc service identify
user_data 可以送 data, 這個例子裡沒用到

有了相關設定之後, 可以bind service
int llc_link_service_bind(struct llc_link *link, struct llc_service *service, int8_t sap)


接著是 mac layer 相關設定
struct mac_link {
  enum { MAC_LINK_UNSET, MAC_LINK_INITIATOR, MAC_LINK_TARGET } mode;
  nfc_device *device;
  struct llc_link *llc_link;
  uint8_t nfcid[10];
  uint8_t buffer[BUFSIZ];
  size_t buffer_size;
  pthread_t *__restrict__ exchange_pdus_thread;
};

一樣有設定default值的function
struct mac_link* mac_link_new(nfc_device *device, struct llc_link *llc_link)

然後enable mac layer setting
int mac_link_activate_as_target(struct mac_link *mac_link)
裡面做的事是用 libnfc 選 target tag
其中 mac_link_run() 開了一條thread 用於傳送接收apdu
預設是
void* mac_link_exchange_pdus(void *arg)

最後用這個 function 等 mac_link_exchange_pdus 這條 thread 結束
int mac_link_wait(struct mac_link *link, void **value_ptr)

4. snep sample code

我一樣改寫了 libllcp 的 libllcp/examples/snep-server/

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <nfc/llcp.h>
#include <nfc/llc_service.h>
#include <nfc/llc_link.h>
#include <nfc/llc_connection.h>
#include <nfc/mac.h>

#define LOG_DGB 1

#define errx(code, ...) do { \
    fprintf (stderr, __VA_ARGS__); \
    fprintf (stderr, "\n"); \
    exit (code); \
  } while (0)

struct mac_link *mac_link;
nfc_device *device;

static void stop_mac_link(int sig) {
 if (mac_link && mac_link->device) {
  nfc_abort_command(mac_link->device);
 }
}

static void bye(VOID) {
 if (device) {
  nfc_close(device);
 }
}

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void* com_android_snep_thread(void *arg) {
 struct llc_connection *connection = (struct llc_connection *) arg;
 uint8_t buffer[1024];
 uint8_t frame_snep_response_success[] = {
  0x10, 0x81, 0x00, 0x00, 0x00, 0x00
 };

 int len;
 len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL);
 if (len < 0 || len < 7) {
  // SNEP's header (2 bytes) + NDEF entry header (5 bytes)
printf("ERR: invalid length\n"); return NULL; } size_t n = 0; printf("SNEP version: %d.%d\n", buffer[n]>>4, buffer[n] & 0x0f ); if (buffer[n] != 0x10) { printf("WRN: snep version other than version 1.0 may not support.\n"); } n++; uint8_t action_code = buffer[n]; n++; switch(action_code) { case 0x00: // Continue break; case 0x01: // GET break; case 0x02: // PUT { uint32_t ndef_length = be32toh(*((uint32_t *)(buffer + n))); if ((len - 6) < ndef_length) { printf("ERR: less received bytes than expected?\n"); return NULL; } n+=4; /** return snep success response package */ llc_connection_send(connection, frame_snep_response_success, sizeof(frame_snep_response_success)); print_hex(buffer + n, ndef_length); break; } default: printf("unknown action code\n"); break; } llc_connection_stop(connection); return NULL; } int main(int argc, char *argv[]) { int ret; if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()"); signal(SIGINT, stop_mac_link); atexit(bye); nfc_context *context; nfc_init(&context); if (context == NULL) errx(EXIT_FAILURE, "nfc_init()"); device = nfc_open(context, NULL); if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device"); struct llc_link *llc_link = llc_link_new(); if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures"); struct llc_service *com_android_snep = llc_service_new_with_uri(NULL, com_android_snep_thread, "urn:nfc:sn:snep", NULL); if (com_android_snep == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service"); llc_service_set_miu(com_android_snep, 512); llc_service_set_rw(com_android_snep, 2); ret = llc_link_service_bind(llc_link, com_android_snep, LLCP_SNEP_SAP); if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service"); mac_link = mac_link_new(device, llc_link); if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link"); ret = mac_link_activate_as_target(mac_link); if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link"); void *err; mac_link_wait(mac_link, &err); mac_link_free(mac_link); llc_link_free(llc_link); nfc_close(device); device = NULL; llcp_fini(); nfc_exit(context); exit(EXIT_SUCCESS); }


compile 之後, 實驗方式跟 npp server 一樣

5. code analysis


整份 code 跟 npp server 很像, 但有些小差異
在 llc_link_service_bind(), 後面帶的 sap 是 LLCP_SNEP_SAP

另個差異是 header 不一樣

然後在收到data之後, 要回傳OK packet

其它的就沒有差異了




2014/10/06

NFC ISO14443A

1. ISO14443

ISO14443 定義描寫了近距離接觸(<10cm)的卡片, 運作在13.56 MHz, 以及card & reader之間的溝通方式, 分成 ISO14443A 及 ISO14443B, 其中 ISO14443A 最為普及

2. ISO14443A

在 NFC forum 定義的 4 種NFC tag type裡, ISO14443A佔了 type1, type2, type4, 還有Mifare classic tag雖然不在NFC forum定義裡, 但也實作了 ISO14443A

在辨識 tag 的過程中, anti-collision 可以得到辨識 tag 所需的資訊, 包括 ATQA, UID, SAK, ATS

  • ATQA : Answer To Request acc. to ISO/IEC 14443-4
  • UID : Unique Identifier, Type A
  • SAK : Select Acknowledge, Type A
  • ATS : Answer To Select acc. to ISO/IEC 14443-4
相關文件可以參考identify Mifare tags
http://www.nxp.com/documents/application_note/AN10833.pdf

3. anti-collision

anti-collision 可以實作在 firmware, 也可以實作在 userspace, 但是中間涉及到傳送7 bit data, 大多數的 driver 不允許傳送非8-bit單位的data, 所以如果沒有對應的SDK就無法實作在userspace

下圖是anti-collision流程圖

(1) 第一步由 reader 往外送 REQA(0x26) 或 WUPA(0x52), 這兩個訊號都是 7-bit, 這是為了讓這兩個 command 可以與其它 command 做區隔, 也因為這兩個 command 都是 7-bit, 所以要將 CRC 的位元檢查關掉

REQA 作用是邀請在整個區域裡新的 tag 來回應這個 command, 整個區域裡也有舊的 tag, 指的是曾經回應過或已經被關掉的tag

WUPA 則是邀請並喚醒區域裡所有tag, 包括回應過或關掉的tag

之所以有新舊的差別, 是因為只要是卡片未離開 reader 的區域, 就會被持續供電

如果沒有任何 tag 回應, 那麼 reader 會持續發出 REQA/WUPA, 中間的間隔雖然有被定義, 但各家廠商作法不同, 有的等上1s, 有的則是等幾個milliseconds,

(2) 接著 tag 回應 ATQA, 此時 reader 開始 anti-collision 的流程, 區分每張卡片的方式是使用 tag的 UID, UID的長度可能是4 bytes, 7 bytes, 或 10 bytes

(3) reader 接著發出 SELECT

(4) tag 回應 UID

(5) reader 發出 SELECT 並帶 UID, 成功的話, tag 會回應 SAK

底下是 libnfc 的 sample flow
http://nfc-tools.org/index.php?title=Libnfc:nfc-anticol

R: 26                                    => Welcome (REQA) (or use WUPA = 0x52)
T: 44  03                                => Respond (ATQA)
R: 93  20                                => Select cascade 1 (SEL)
T: 88  04  34  74  cc                    => CT, UID(byte 1,2,3), BCC
R: 93  70  88  04  34  74  cc  0e  05    => Select available tag (SEL)
T: 24  d8  36                            => Select Acknowledge (SAK) with cascade bit set
R: 95  20                                => Select cascade 2 (SEL)    
T: e1  e3  1c  80  9e                    => UID(byte 4,5,6,7), BCC
R: 95  70  e1  e3  1c  80  9e  b9  e1    => Finish select (SEL)
T: 20  fc  70                            => SAK without cascade bit set
R: e0  50  bc  a5                        => Request Answer to Select (RATS)
T: 06  75  77  81  02  80                => ATS (DESFire EV1)
R: 50  00  57  cd                        => Disable (HALT)

Found tag with UID: 043474e1e31c80

CT  => [Cascade Tag] byte (88), signals that the UID is not complete yet
BCC => [Bit Count Check] byte, calculated as exclusive-or over the four previous bytes

整個流成跑完之後, 其中 ATQA, SAK, ATS 包含廠商資訊
http://nfc-tools.org/index.php?title=ISO14443A







libnfc example

1. Brief Introduction


在使用libnfc相關的code, 第一步一定要先呼叫 nfc_init(), 它會做初始設定和配置相關資源
void nfc_init(nfc_context **context)


接著呼叫 nfc_open(), 它會找第一個可用的 nfc device
nfc_device* nfc_open(nfc_context *context, const nfc_connstring connstring)


參數 context 就是在 nfc_init() 用到的那個
參數 connstring 表示你想找有特定名字的device, 像"usb"之類的, 如果沒有特定就帶NULL

它會依照下列順序找:
(1) 環境變數裡預設值
(2) libnfc.conf 設定的值
(3) /etc/nfc/devices.d 裡找到
(4) auto-detected 第一個找到的值

找到之後, 它會宣告目前它在使用這個device, 並回傳device pointer


然後記得對這個 device 做初始化
int nfc_initiator_init(nfc_device *pnd)


struct nfc_modulation 表示不同ISO規格的資料
typedef struct {
  nfc_modulation_type nmt;
  nfc_baud_rate nbr;
} nfc_modulation;
其中 nfc_modulation_type為ISO標準
typedef enum {
  NMT_ISO14443A = 1,
  NMT_JEWEL,
  NMT_ISO14443B,
  NMT_ISO14443BI, // pre-ISO14443B aka ISO/IEC 14443 B' or Type B'
  NMT_ISO14443B2SR, // ISO14443-2B ST SRx
  NMT_ISO14443B2CT, // ISO14443-2B ASK CTx
  NMT_FELICA,
  NMT_DEP,
} nfc_modulation_type;
然後 nfc_baud_rate 為 tag 的 baud rate, (每種規格有不同的baud rate)
typedef enum {
  NBR_UNDEFINED = 0,
  NBR_106,
  NBR_212,
  NBR_424,
  NBR_847,
} nfc_baud_rate;
nfc_modulation 主要用在描述現在想嘗試找什麼規格的 tag


struct nfc_target 則是用來儲存找到的 tag
typedef struct {
  nfc_target_info nti;
  nfc_modulation nm;
} nfc_target;
其中 nfc_modulation 只是紀錄它的ISO規格
union nfc_target_info 則是儲存找到的這張 tag 的內容
typedef union {
  nfc_iso14443a_info nai;
  nfc_felica_info nfi;
  nfc_iso14443b_info nbi;
  nfc_iso14443bi_info nii;
  nfc_iso14443b2sr_info nsi;
  nfc_iso14443b2ct_info nci;
  nfc_jewel_info nji;
  nfc_dep_info ndi;
} nfc_target_info;
不同的 ISO 規格, 它能提供的資訊也不同


我們有nfc device, nfc modulation, 就可以找 nfc target, 用底下這個function
int nfc_initiator_select_passive_target(nfc_device *pnd,
                                        const nfc_modulation nm,
                                        const uint8_t *pbtInitData,
                                        const size_t szInitData,
                                        nfc_target *pnt)
其中 pnd 是 nfc device
nm 是  nfc modulation
pbtInitData 可以帶 NULL, 有些 ISO 規格裡可以帶data給tag, 比如說ISO14443A裡, 可以帶 UID, 而 ISO14443B 可以帶 Application Family Identifier (AFI)
szInitData 則是前一項 pbtInitData的size
pnt 則是存找到的nfc target


最後記得要關device
void nfc_close(nfc_device *pnd)

以及要記得釋放nfc context資源
void nfc_exit(nfc_context *context)

2. Sample code


這份sample code從libnfc官方網頁上可以找到, 這是原始網頁
http://nfc-tools.org/index.php?title=Libnfc:quick_start_example

它可以找 ISO14443-A tag, (可能是最常見的tag)

#include <stdlib.h>
#include <nfc/nfc.h>

static void
print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;

 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

int
main(int argc, const char *argv[]) {
 nfc_device *pnd;
 nfc_target nt;
 nfc_context *context;

 nfc_init(&context);
 if (context == NULL) {
  printf("Unable to init libnfc (malloc)\n");
  exit(EXIT_FAILURE);
 }

 pnd = nfc_open(context, NULL);
 if (pnd == NULL) {
  printf("ERROR: %s\n", "Unable to open NFC device.");
  exit(EXIT_FAILURE);
 }

 if (nfc_initiator_init(pnd) < 0) {
  nfc_perror(pnd, "nfc_initiator_init");
  exit(EXIT_FAILURE);
 }

 printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));

 const nfc_modulation nmMifare = {
  .nmt = NMT_ISO14443A,
  .nbr = NBR_106,
 };
 if (nfc_initiator_select_passive_target(pnd, nmMifare, NULL, 0, &nt) > 0) {
  printf("The following (NFC) ISO14443A tag was found:\n");
  printf("    ATQA (SENS_RES): ");
  print_hex(nt.nti.nai.abtAtqa, 2);
  printf("       UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1'));
  print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
  printf("      SAK (SEL_RES): ");
  print_hex(&nt.nti.nai.btSak, 1);
  if (nt.nti.nai.szAtsLen) {
   printf("          ATS (ATR): ");
   print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);
  }
 }

 nfc_close(pnd);

 nfc_exit(context);
 exit(EXIT_SUCCESS);
}

然後 compile它
$ gcc -o quick_start_example1 quick_start_example1.c -lnfc

執行結果
$ ./quick_start_example1
NFC reader: pn532_spi:/dev/spidev0.0 opened
The following (NFC) ISO14443A tag was found:
    ATQA (SENS_RES): 00 04
       UID (NFCID1): 45 d1 a9 51
      SAK (SEL_RES): 08

3. Polling


前個例子是一執行馬上就掃當下讀的到的卡
也可以改成用 poll 的方式, 在一定的時間內讀到卡即可

int nfc_initiator_poll_target(nfc_device *pnd,
                              const nfc_modulation *pnmModulations,
                              const size_t szModulations,
                              const uint8_t uiPollNr,
                              const uint8_t uiPeriod,
                              nfc_target *pnt)
其中, pnd 是 nfc device, pnt 是 nfc target
pnmModulations 是 nfc_modulation array, 裡面放想掃的 modulation type
szModulations 是 pnmModulations 的 array 長度
uiPollNr 指要 poll 幾次, 可以 1~254 次, 如果是 0xFF 表示要永遠 poll
uiPeriod 表是每次 poll 間隔幾個單位, 1個單位內定是150ms, 2個單位就是300ms, 以此類推
回傳值是 poll 掃到的 nfc target 數目, <0 表示error, =0表示沒掃到

可以 poll 卡是不是接近之後, 也可以檢查卡是不是已經拿開
int nfc_initiator_target_is_present(nfc_device *pnd, const nfc_target *pnt)
回傳 0 表示還在, 其它的值表示 error (即離開或讀不到了)

3.1 Polling sample code


相關的 sample code 可以參考 libnfc 裡的 nfc-poll.c
底下這個例子稍微簡化 nfc-poll.c,

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include <nfc/nfc.h>
#include <nfc/nfc-types.h>

static nfc_device *pnd = NULL;
static nfc_context *context;

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void stop_polling(int sig) {
 if (pnd != NULL) {
  nfc_abort_command(pnd);
 } else {
  nfc_exit(context);
  exit(EXIT_FAILURE);
 }
}

int main(int argc, const char *argv[]) {
 signal(SIGINT, stop_polling);

 const uint8_t uiPollNr = 20;
 const uint8_t uiPeriod = 2;
 const nfc_modulation nmModulations[1] = {
  { .nmt = NMT_ISO14443A, .nbr = NBR_106 },
 };
 const size_t szModulations = 1;

 nfc_target nt;
 int res = 0;

 nfc_init(&context);
 if (context == NULL) {
  printf("Unable to init libnfc (malloc)\n");
  exit(EXIT_FAILURE);
 }

 pnd = nfc_open(context, NULL);
 if (pnd == NULL) {
  printf("Unable to open NFC device\n");
  exit(EXIT_FAILURE);
 }

 if (nfc_initiator_init(pnd) < 0) {
  nfc_perror(pnd, "nfc_initiator_init");
  nfc_close(pnd);
  nfc_exit(context);
  exit(EXIT_FAILURE);
 }

 printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));
 printf("NFC device will poll %d ms\n", uiPollNr * szModulations * uiPeriod * 150);
 res = nfc_initiator_poll_target(pnd, nmModulations, szModulations, uiPollNr, uiPeriod, &nt);
 if (res < 0) {
  nfc_perror(pnd, "nfc_initiator_poll_target");
  nfc_close(pnd);
  nfc_exit(context);
  exit(EXIT_FAILURE);
 } else if (res == 0) {
  printf("No target found\n");
 } else {
  printf("The following (NFC) ISO14443A tag was found:\n");
  printf("    ATQA (SENS_RES): ");
  print_hex(nt.nti.nai.abtAtqa, 2);
  printf("       UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1'));
  print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
  printf("      SAK (SEL_RES): ");
  print_hex(&nt.nti.nai.btSak, 1);
  if (nt.nti.nai.szAtsLen) {
   printf("          ATS (ATR): ");
   print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);
  }
 }

 printf("Waiting for card removing...\n");
 while (0 == nfc_initiator_target_is_present(pnd, NULL)) {};
 nfc_perror(pnd, "nfc_initiator_target_is_present");
 printf("done\n");

 nfc_close(pnd);
 nfc_exit(context);
 exit(EXIT_SUCCESS);
}






2014/09/29

NFC NDEF message format

1. NDEF message format


下圖是 NDEF mssage format 的示意圖, 1份 NDEF message 包含 >= 1 NDEF Record
1份 NDEF Record 包含 Header & Payload
Header包含好幾個欄位, 包括 TNF (Type Name Format)



1.1 Record Layout


每個 Record 前5個 bit 定義了record 在message中的位置

  • MB (Message Begin) : True when this is the first record in a message.
  • ME (Message End) : True when this is the last record in a message.
  • CF (Chunk Flag) : True when this record is chunked. (只用在NDEF message大到無法一次傳完)
  • SR (Short Record) : True if the short record format is used for payload length.
  • IL (ID Length is present) : True if the ID Length field is present.

如果 message 有多個 record, 那麼第一個 record 的 MB=1, ME = 0, 最後一個 record 的 MB = 0, ME = 1
如果 message 只有1個 record, 那個這個 record 的 MB=1, ME = 1

1.2 Type Name Format


TNF (Type Name Format) 佔 3 bits, 值為 0~7

  • 0 Empty : Empty record that has no type or payload
  • 1 Well-Known : NFC Forum RTD (Record Type Definition) specification 定義好的其中1種
  • 2 MIME media-type : Internet media type (defined in RFC 2046 )
  • 3 Absolute URI : URI (defined in RFC 3986 )
  • 4 External : User-defined value, based on rules in NFC Forum RTD specification
  • 5 Unknown : Type is unknown. Type length must be 0.
  • 6 Unchanged : Only for middle and terminating records of chunked payloads. Type length must be 0.
  • 7 Reserved : Reserved by the NFC Forum for future use.

常見的應用程式裡, TNF 01 (Well-Known) 以及 TNF 02 (MIME media-type) 最為常用
另外, TNF 04 (External) 也很常用, 包括了 Android App

1.3 Record Type Length


在 TNF & Layout Flag 之後是 1 byte 的 Payload Type Length
它定義了 Record Type 的長度
Record Type的用途是在TNF之外提供更細的分類, 而這個欄位的長度是可變動的
而 Record Type Length 定義這個欄位的長度

1.4 Payload Length


接著是 Payload Length, 它可能佔用1個 byte 或佔用 4個 byte
如果前面的 SR (Short Record) = 1, 那麼它佔用1個 byte
如果 SR = 0, 那麼它佔用4個byte

1.5 Record Type


Record Type,  RTD (Record Type Definition)進一步描述 Payload的內容, 這個

比如說, 當 TNF = 01 (Well-Known), 那麼 Record Type = "T" 表示 text, "U" 表示 URI, "Sp" 表示 Smart poster,
當 TNF = 02 (MIME media-type), 那麼 Record Type 可以描述 "text/html," "text/json," 以及 "image/gif."
當 TNF = 04 (External), Record Type 可以描述它是 Android APP

1.6 ID length &  ID (optional)


如果前面的 flag IL(ID Length is present)=1, 那麼就會有 ID length 以及 ID 欄位

Payload ID 是 optional field, 通常是 URL, 但也可以隨便放, 它的用途是讓應用程式提供相關資訊, 或是讓不同的 NDEF record reference 其它 record
因為是 optional, 所以也可以不放

1.7 Payload


Payload 沒有規定它的 format, 通常 NDEF library 在包NDEF record時, 是直接塞data進去而不做檢查, 所以可以加密, 也可以帶二進位資料












NFC Introduction

1. Introduction


RFID (Radio Frequency IDentification)

  • 通常用於 identification, 而不是 communication
  • RFID tags 可以帶少量的 data, 通常小於1000 bytes


NFC (Near Field Communication)

  • 交換更複雜的資料
  • NFC reader 可以讀 passive RFID tags, 也可以寫入部份的memory, 或是寫入標準格式的 RFID tags
  • 有效距離 <= 10cm, 低功耗, 其它裝置的干擾較小
  • 傳輸速率比wifi/bt低, 不適合需要高速傳輸的用途



2. RFID operating


Acting rules in RFID exchange:

  • initiator: 可能是 tag reader, 或是 tag reader/writer device
  • target: 也就是 tag, 當 initiator 嘗試連線時回應 UID (Unique IDentifier number)

Communication modes:

  • Passive RFID exchange: 用於 reader/writer 和 tag, 板上沒有power, 利用 radio power 回應 data
  • Active RFID exchanges: 用於接電的 reader/writer, 有效距離較長


RFID tag 在板上有少量的 memory <= 1KB,
initiator device 可以讀上面的 data
如果是 reader/writer device, 除了讀 data, 也可以寫 data 到 memory 上
(通常用於紀錄剩下多少錢之類的, 但現在都把database建在網路上, 然後用UID在網路找這筆資料)

ISO 制定了 很多 RFID 標準
不同的標準裡定義了 radio frequency, data transfer rate, data formats,...


3. NFC operating


NFC 和 RFID 一樣有 initiator & target
通常 target 是 programmable device, 可以做複雜的操作之後再回傳 data

Communication modes in NFC

  • passive communication mode: initiator 持續供電, 使 target 持續從 initiator 那邊吃電
  • active communication mode: initiator & target 都有電源

Operating modes in NFC

  • reader/writer, 可對 targer 讀寫
  • card emulator, 當它位於其它 NFC/RFID device field 裡
  • peer-to-peer mode, 可彼此交換 data


4. NFC data format


NFC devices 交換 data 的格式是 NDEF (NFC Data Exchange Format)
一份 NDEF message 包含 >=1 NDEF record
每個 NDEF record 包含 record type, a unique ID, length, payload
有幾個知名的 record type 是每個 NFC device 應該都會實作的:

  • Simple text records: 送text, 包括metadata像是language, encoding scheme, 通常不包含要求 target 要做的事
  • URIs: 送 network address, 通常預期 target 會送到對應的app, 像是browser
  • Smart Posters: 可以帶更多的 data, 像是 URIs, text, 通常 target 收到 Smart Poster record 會開啟 browser, SMS, email ap之類的
  • Signatures: 提供 trustworthy information

NDEF 是 NFC 與 RFID 最大的不同, 雖然 NFC 和許多 RFID protocol 都操作在 13.56 MHz
但 RFID protocol 沒有 common data format


5. NFC architecture




6. NFC Tag Types


NFC forum 定義了4種 NFC Tag Types:

Type 1

  • Based on ISO-14443A specification.
  • Can be read-only, or read/write capable.
  • 96 B ~ 2 KB memory.
  • Communication speed 106Kb.
  • No data collision protection.
  • Examples: Innovision Topaz, Broadcom BCM20203.


Type 2

  • Based on NXP/Philips Mifare Ultralight tag (ISO-14443A) specification.
  • Can be read-only, or read/write capable.
  • 96 B ~ 2 KB memory.
  • Communication speed 106Kb.
  • Anti-collision support.
  • Example: NXP Mifare Ultralight.


Type 3

  • Based on the Sony FeliCa tags (ISO-18092 and JIS-X-6319-4), without the encryption and authentication support that FeliCa affords.
  • Configured by factory to be read-only, or read/write capable.
  • Variable memory, up to 1MB per exchange.
  • Two communication speeds, 212 or 424Kbps.
  • Anti-collision support.
  • Example: Sony FeliCa.


Type 4

  • Based on NXP DESFire tag (ISO-14443A) specification.
  • Configured by factory to be read-only, or read/write capable.
  • 2, 4, or 8 KB of memory.
  • Variable memory, up to 32KB per exchange.
  • Three communication speeds: 106, 212, or 424Kbps.
  • Anti-collision support.
  • Example: NXP DESFire, SmartMX-JCOP.

另外一種雖然不在 NFC 定義裡, 但已經是目前最常見的 NFC tag type

Mifare Classic tag (ISO–14443A)

  • Memory options: 192, 768, or 3,584 bytes.
  • Communication speed 106 Kbps
  • Anti-collision support.
  • Examples: NXP Mifare Classic 1K, Mifare Classic 4K, Mifare Classic Mini.