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
其它的就沒有差異了