diff options
author | Olivier Guiter <olivier.guiter@linux.intel.com> | 2011-10-14 11:39:46 +0200 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2011-10-20 23:54:06 -0700 |
commit | d1760f7ab051e39bde0a9ca734c8dfa3570ffcd0 (patch) | |
tree | 55c034ebf13ad4936e0cd39b1b13d8dfe3144d36 | |
parent | 0936d62301bb81afc1a31ce56b234c71ef699ff0 (diff) | |
download | neard-d1760f7ab051e39bde0a9ca734c8dfa3570ffcd0.tar.gz neard-d1760f7ab051e39bde0a9ca734c8dfa3570ffcd0.tar.bz2 neard-d1760f7ab051e39bde0a9ca734c8dfa3570ffcd0.zip |
nfctype4: NFC type 4 (V1 and V2) support
-rw-r--r-- | Makefile.plugins | 14 | ||||
-rwxr-xr-x | bootstrap-configure | 1 | ||||
-rw-r--r-- | configure.ac | 6 | ||||
-rw-r--r-- | plugins/nfctype4.c | 583 |
4 files changed, 603 insertions, 1 deletions
diff --git a/Makefile.plugins b/Makefile.plugins index 32af4c3..92ceff6 100644 --- a/Makefile.plugins +++ b/Makefile.plugins @@ -37,4 +37,16 @@ plugin_objects += $(plugins_nfctype3_la_OBJECTS) plugins_nfctype3_la_CFLAGS = $(plugin_cflags) plugins_nfctype3_la_LDFLAGS = $(plugin_ldflags) endif -endif
\ No newline at end of file +endif + +if NFCTYPE4 +if NFCTYPE4_BUILTIN +builtin_modules += nfctype4 +builtin_sources += plugins/nfctype4.c +else +plugin_LTLIBRARIES += plugins/nfctype4.la +plugin_objects += $(plugins_nfctype4_la_OBJECTS) +plugins_nfctype4_la_CFLAGS = $(plugin_cflags) +plugins_nfctype4_la_LDFLAGS = $(plugin_ldflags) +endif +endif diff --git a/bootstrap-configure b/bootstrap-configure index 3316891..509e3f7 100755 --- a/bootstrap-configure +++ b/bootstrap-configure @@ -10,4 +10,5 @@ fi --enable-nfctype1=builtin \ --enable-nfctype2=builtin \ --enable-nfctype3=builtin \ + --enable-nfctype4=builtin \ --prefix=/usr $* diff --git a/configure.ac b/configure.ac index 38b0549..ea97bf8 100644 --- a/configure.ac +++ b/configure.ac @@ -100,4 +100,10 @@ AC_ARG_ENABLE(nfctype3, AM_CONDITIONAL(NFCTYPE3, test "${enable_nfctype3}" != "no") AM_CONDITIONAL(NFCTYPE3_BUILTIN, test "${enable_nfctype3}" = "builtin") +AC_ARG_ENABLE(nfctype4, + AC_HELP_STRING([--enable-nfctype4], [enable NFC forum type 4 tags support]), + [enable_nfctype4=${enableval}], [enable_nfctype4="no"]) +AM_CONDITIONAL(NFCTYPE4, test "${enable_nfctype4}" != "no") +AM_CONDITIONAL(NFCTYPE4_BUILTIN, test "${enable_nfctype4}" = "builtin") + AC_OUTPUT(Makefile include/version.h) diff --git a/plugins/nfctype4.c b/plugins/nfctype4.c new file mode 100644 index 0000000..7d4a29e --- /dev/null +++ b/plugins/nfctype4.c @@ -0,0 +1,583 @@ +/* + * + * neard - Near Field Communication manager + * + * Copyright (C) 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> + +#include <linux/socket.h> +#include <linux/nfc.h> + +#include <near/plugin.h> +#include <near/log.h> +#include <near/types.h> +#include <near/adapter.h> +#include <near/target.h> +#include <near/tag.h> +#include <near/ndef.h> +#include <near/tlv.h> + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +#define NFC_STATUS 0 +#define NFC_STATUS_BYTE_LEN 1 + +#define STATUS_WORD_1 1 +#define STATUS_WORD_2 2 +#define APDU_HEADER_LEN 5 +#define APDU_OK 0x9000 +#define APDU_NOT_FOUND 0x6A82 + +#define APDU_STATUS(a) (g_ntohs(*((uint16_t *)(a)))) + +/* Tag Type 4 version ID */ +static uint8_t iso_appname_v1[] = { 0xd2, 0x76, 0x0, 0x0, 0x85, 0x01, 0x0 }; +static uint8_t iso_appname_v2[] = { 0xd2, 0x76, 0x0, 0x0, 0x85, 0x01, 0x1 }; + +/* Tag T4 File ID */ +static uint8_t iso_cc_fileid[] = { 0xe1, 0x03 }; +#define LEN_ISO_CC_FILEID 2 + +#define LEN_ISO_CC_READ_SIZE 0x0F + +#define CMD_HEADER_SIZE 5 +struct type4_cmd { /* iso 7816 */ + uint8_t class; + uint8_t instruction; + uint8_t param1; + uint8_t param2; + uint8_t data_length; + uint8_t data[]; +} __attribute__((packed)); + +struct type4_NDEF_file_control_tlv { + uint8_t tag ; /* should be 4 */ + uint8_t len ; /* should be 6 */ + uint16_t file_id ; + uint16_t max_ndef_size ; + uint8_t read_access ; + uint8_t write_access ; +} __attribute__((packed)); + +struct type4_cc { /* Capability Container */ + uint16_t CCLEN; + uint8_t mapping_version; + uint16_t max_R_apdu_data_size; + uint16_t mac_C_apdu_data_size; + struct type4_NDEF_file_control_tlv tlv_fc ; + uint8_t tlv_blocks[]; +} __attribute__((packed)); + +struct recv_cookie { + uint32_t adapter_idx; + uint32_t target_idx; + near_tag_read_cb cb; + struct near_tag *tag; + uint16_t read_data; + uint16_t r_apdu_max_size; +}; + +/* ISO functions: This code prepares APDU */ +static int ISO_send_cmd(uint8_t class, + uint8_t instruction, + uint8_t param1, + uint8_t param2, + uint8_t *cmd_data, + uint8_t cmd_data_length, + near_recv cb, + void *in_data) +{ + struct type4_cmd *cmd; + struct recv_cookie *in_rcv = in_data; + int err; + + DBG("CLA:x%02x INS:x%02x P1:%02x P2:%02x", + class, instruction, param1, param2); + + cmd = g_try_malloc0(APDU_HEADER_LEN + cmd_data_length); + if (cmd == NULL) { + DBG("Mem alloc failed"); + err = -ENOMEM; + goto out_err; + } + + cmd->class = class; + cmd->instruction = instruction ; + cmd->param1 = param1 ; + cmd->param2 = param2 ; + cmd->data_length = cmd_data_length; + + if (cmd_data) + memcpy(cmd->data, cmd_data, cmd_data_length); + else + cmd_data_length = 0 ; + + err = near_adapter_send(in_rcv->adapter_idx, (uint8_t *)cmd, + APDU_HEADER_LEN + cmd_data_length , cb, in_rcv); + if (err < 0) + g_free(in_rcv); + +out_err: + /* On exit, clean memory */ + g_free(cmd); + + return err; +} + +/* ISO 7816 command: Select applications or files + * p1=0 select by "file id" + * P1=4 select by "DF name" + * */ +static int ISO_Select(uint8_t *filename, uint8_t fnamelen, uint8_t P1, + near_recv cb, void *cookie) +{ + DBG(""); + + return ISO_send_cmd( + 0x00, /* CLA */ + 0xA4, /* INS: Select file */ + P1, /* P1: select by name */ + 0x00, /* P2: First or only occurence */ + filename, /* cmd_data */ + fnamelen, /* uint8_t cmd_data_length*/ + cb, + cookie); +} + +/* ISO 7816 command: Read binary data from files */ +static int ISO_ReadBinary(uint16_t offset, uint8_t readsize, + near_recv cb, void *cookie) +{ + DBG(""); + return ISO_send_cmd( + 0x00, /* CLA */ + 0xB0, /* INS: Select file */ + (uint8_t)((offset & 0xFF00)>>8), + (uint8_t)(offset & 0xFF), + 0, /* no data send */ + readsize, /* bytes to read */ + cb, + cookie); +} + +static int t4_cookie_release(int err, struct recv_cookie *cookie) +{ + if (cookie == NULL) + return err; + + if (err < 0 && cookie->cb) { + cookie->cb(cookie->adapter_idx, err); + near_adapter_disconnect(cookie->adapter_idx); + } + + g_free(cookie->tag); + g_free(cookie); + + return err; +} + + +static int data_read_cb(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data ; + uint8_t *nfc_data; + uint16_t data_length, length_read, current_length; + uint16_t remain_bytes; + int err = 0; + + DBG("%d", length); + + if (length < 0) + return t4_cookie_release(err, cookie); + + if (APDU_STATUS(resp + length - 2) != APDU_OK) { + DBG("Fail read_cb SW:x%04x", APDU_STATUS(resp + length - 2)); + err = -EIO; + return t4_cookie_release(err, cookie); + } + + nfc_data = near_tag_get_data(cookie->tag, (size_t *)&data_length); + + /* Remove SW1 / SW2 and NFC header */ + length_read = length - NFC_HEADER_SIZE - 2 ; + length = length_read; + + current_length = cookie->read_data; + + if (current_length + (length_read) > data_length) + length_read = data_length - current_length; + + memcpy(nfc_data + current_length, resp + NFC_HEADER_SIZE, length_read); + if (current_length + length_read == data_length) { + DBG("Done reading"); + + near_ndef_parse(cookie->tag, nfc_data, data_length); + /* Notify the change */ + cookie->cb(cookie->adapter_idx, err); + near_adapter_disconnect(cookie->adapter_idx); + + err = 0; + goto out_err; + } + + cookie->read_data += length ; + remain_bytes = (data_length - cookie->read_data); + + if (remain_bytes >= cookie->r_apdu_max_size) + err = ISO_ReadBinary(cookie->read_data + 2, + cookie->r_apdu_max_size, data_read_cb, cookie); + else + err = ISO_ReadBinary(cookie->read_data + 2, + (uint8_t)remain_bytes, data_read_cb, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + + +static int t4_readbin_NDEF_ID(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data ; + struct near_tag *tag; + int err = 0; + + DBG("%d", length); + + if (length < 0) { + err = length; + goto out_err; + } + + if (APDU_STATUS(resp + length - 2) != APDU_OK) { + DBG("Fail SW:x%04x", APDU_STATUS(resp + length - 2)); + err = -EIO; + goto out_err; + } + + tag = near_target_add_tag(cookie->adapter_idx, cookie->target_idx, + g_ntohs(*((uint16_t *)(resp + NFC_STATUS_BYTE_LEN)))); + + if (tag == NULL) { + DBG("near_target_add_tag is null") ; + err = -ENOMEM; + goto out_err; + } + + /* save the tag */ + cookie->tag = tag; + + /* TODO: see how we can get the UID value: + * near_tag_set_uid(tag, resp + NFC_HEADER_SIZE, 8); + * */ + + /* Read 1st block */ + err = ISO_ReadBinary(2, cookie->r_apdu_max_size - 2, + data_read_cb, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + +static int t4_select_NDEF_ID(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data; + int err = 0; + + DBG("%d", length); + + if (length < 0) { + err = length; + goto out_err; + } + + /* Check for APDU error */ + if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { + DBG("Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); + err = -EIO; + goto out_err; + } + + /* Read 0x0f bytes, to grab the NDEF msg length */ + err = ISO_ReadBinary(0, LEN_ISO_CC_READ_SIZE, + t4_readbin_NDEF_ID, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + + +static int t4_readbin_cc(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data ; + struct type4_cc *read_cc ; + int err = 0; + + DBG("%d", length); + + if (length < 0) { + err = length; + goto out_err; + } + + /* Check APDU error ( the two last bytes of the resp) */ + if (APDU_STATUS(resp + length - 2) != APDU_OK) { + DBG("Fail SW:x%04x", APDU_STATUS(resp + length - 2)); + err = -EIO; + goto out_err; + } + + /* -2 for status word and -1 is for NFC first byte... */ + read_cc = g_try_malloc0(length - 2 - NFC_STATUS_BYTE_LEN); + if (read_cc == NULL) { + DBG("Mem alloc failed"); + err = -ENOMEM; + goto out_err; + } + + memcpy(read_cc, &resp[1], length - 2 - NFC_STATUS_BYTE_LEN) ; + + cookie->r_apdu_max_size = g_ntohs(read_cc->max_R_apdu_data_size) - + APDU_HEADER_LEN ; + + /* TODO 5.1.1 :TLV blocks can be zero, one or more... */ + /* TODO 5.1.2 :Must ignore proprietary blocks (x05)... */ + if (read_cc->tlv_fc.tag != 0x4) { + DBG("NDEF File Control tag not found !") ; + err = -EINVAL ; + goto out_err ; + } + + err = ISO_Select((uint8_t *)&read_cc->tlv_fc.file_id, + LEN_ISO_CC_FILEID, 0, t4_select_NDEF_ID, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + +static int t4_select_cc(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data; + int err = 0; + + DBG("%d", length); + + if (length < 0) { + err = length; + goto out_err; + } + + /* Check for APDU error */ + if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { + DBG("Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); + err = -EIO; + goto out_err; + } + + err = ISO_ReadBinary(0, LEN_ISO_CC_READ_SIZE, t4_readbin_cc, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + + +static int t4_select_file_by_name_v1(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data; + int err = 0 ; + + DBG("%d", length); + + if (length < 0) { + err = length; + goto out_err; + } + + /* Check for APDU error */ + if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { + DBG("V1 Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); + err = -EIO; + goto out_err; + } + + if (resp[NFC_STATUS] != 0) { + err = -EIO; + goto out_err; + } + + /* Jump to select phase */ + err = ISO_Select(iso_cc_fileid, LEN_ISO_CC_FILEID, 0, + t4_select_cc, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + + +static int t4_select_file_by_name_v2(uint8_t *resp, int length, void *data) +{ + struct recv_cookie *cookie = data; + int err = 0 ; + + DBG("%d", length); + + if (length < 0) { + err = length; + goto out_err; + } + + /* Check for APDU error - Not found */ + if (APDU_STATUS(resp + STATUS_WORD_1) == APDU_NOT_FOUND) { + DBG("Fallback to V1"); + err = ISO_Select(iso_appname_v1, ARRAY_SIZE(iso_appname_v1), + 0x4, t4_select_file_by_name_v1, cookie); + if (err < 0) + goto out_err; + + return err; + } + + if (APDU_STATUS(resp + STATUS_WORD_1) != APDU_OK) { + DBG("V2 Fail SW:x%04x", APDU_STATUS(resp + STATUS_WORD_1)); + err = -EIO; + goto out_err; + } + + if (resp[NFC_STATUS] != 0) { + err = -EIO; + goto out_err; + } + + /* Jump to select phase */ + err = ISO_Select(iso_cc_fileid, LEN_ISO_CC_FILEID, 0, + t4_select_cc, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + +static int nfctype4_read_meta_v2(uint32_t adapter_idx, uint32_t target_idx, + near_tag_read_cb cb) +{ + struct recv_cookie *cookie; + int err = 0; + + DBG(""); + + cookie = g_try_malloc0(sizeof(struct recv_cookie)); + if (cookie == NULL) { + err = -ENOMEM; + goto out_err; + } + + cookie->adapter_idx = adapter_idx; + cookie->target_idx = target_idx; + cookie->cb = cb; + cookie->tag = NULL; + cookie->read_data = 0;; + + /* Check for V2 type 4 tag */ + err = ISO_Select(iso_appname_v2, ARRAY_SIZE(iso_appname_v2), + 0x4, t4_select_file_by_name_v2, cookie); + if (err < 0) + goto out_err; + + return err; + +out_err: + return t4_cookie_release(err, cookie); +} + +static int nfctype4_read_tag(uint32_t adapter_idx, + uint32_t target_idx, near_tag_read_cb cb) +{ + int err; + + DBG(""); + + err = near_adapter_connect(adapter_idx, target_idx, NFC_PROTO_ISO14443); + if (err < 0) { + near_error("Could not connect %d", err); + + return err; + } + + err = nfctype4_read_meta_v2(adapter_idx, target_idx, cb); + if (err < 0) + near_adapter_disconnect(adapter_idx); + + return err; +} + +static struct near_tag_driver type4_driver = { + .type = NEAR_TAG_NFC_TYPE4, + .read_tag = nfctype4_read_tag, +}; + +static int nfctype4_init(void) +{ + DBG(""); + + return near_tag_driver_register(&type4_driver); +} + +static void nfctype4_exit(void) +{ + DBG(""); + + near_tag_driver_unregister(&type4_driver); +} + +NEAR_PLUGIN_DEFINE(nfctype4, "NFC Forum Type 4 tags support", VERSION, + NEAR_PLUGIN_PRIORITY_HIGH, nfctype4_init, nfctype4_exit) |