blob: 70d6d82ec74675fcfff7bdf25270ca9da0e55377 [file] [log] [blame]
/*
*
* 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 <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/socket.h>
#include <near/nfc_copy.h>
#include <near/plugin.h>
#include <near/log.h>
#include <near/types.h>
#include <near/adapter.h>
#include <near/tag.h>
#include <near/ndef.h>
#include <near/tlv.h>
extern int mifare_read(uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb, enum near_tag_sub_type tgt_subtype);
extern int mifare_check_presence(uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb, enum near_tag_sub_type tgt_subtype);
extern int mifare_write(uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_tag_io_cb cb, enum near_tag_sub_type tgt_subtype);
#define CMD_READ 0x30
#define CMD_READ_SIZE 0x02
#define CMD_WRITE 0xA2
#define READ_SIZE 16
#define BLOCK_SIZE 4
#define META_BLOCK_START 0
#define DATA_BLOCK_START 4
#define TYPE2_MAGIC 0xe1
#define TAG_DATA_CC(data) ((data) + 12)
#define TAG_DATA_LENGTH(cc) ((cc)[2] * 8)
#define TAG_DATA_NFC(cc) ((cc)[0] & TYPE2_MAGIC)
#define TYPE2_NOWRITE_ACCESS 0x0F
#define TYPE2_READWRITE_ACCESS 0x00
#define TAG_T2_WRITE_FLAG(cc) ((cc)[3] & TYPE2_NOWRITE_ACCESS)
#define NDEF_MAX_SIZE 0x30
#define CC_BLOCK_START 3
#define TYPE2_TAG_VER_1_0 0x10
#define TYPE2_DATA_SIZE_48 0x6
struct type2_cmd {
uint8_t cmd;
uint8_t block;
uint8_t data[BLOCK_SIZE];
} __attribute__((packed));
struct type2_tag {
uint32_t adapter_idx;
uint16_t current_block;
near_tag_io_cb cb;
struct near_tag *tag;
};
struct t2_cookie {
uint32_t adapter_idx;
uint32_t target_idx;
uint8_t current_block;
struct near_ndef_message *ndef;
near_tag_io_cb cb;
};
struct type2_cc {
uint8_t magic;
uint8_t version;
uint8_t mem_size;
uint8_t read_write;
};
static int t2_cookie_release(int err, void *data)
{
struct t2_cookie *cookie = data;
DBG("%p", cookie);
if (!cookie)
return err;
if (cookie->cb)
cookie->cb(cookie->adapter_idx, cookie->target_idx, err);
if (cookie->ndef)
g_free(cookie->ndef->data);
g_free(cookie->ndef);
g_free(cookie);
return err;
}
static int data_recv(uint8_t *resp, int length, void *data)
{
struct type2_tag *tag = data;
struct type2_cmd cmd;
uint8_t *nfc_data;
size_t current_length, length_read, data_length;
uint32_t adapter_idx;
int read_blocks;
DBG("%d", length);
if (length < 0) {
g_free(tag);
return length;
}
nfc_data = near_tag_get_data(tag->tag, &data_length);
adapter_idx = near_tag_get_adapter_idx(tag->tag);
length_read = length - NFC_HEADER_SIZE;
current_length = tag->current_block * BLOCK_SIZE;
if (current_length + length - NFC_HEADER_SIZE > 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) {
GList *records;
/* TODO parse tag->data for NDEFS, and notify target.c */
tag->current_block = 0;
DBG("Done reading");
records = near_tlv_parse(nfc_data, data_length);
near_tag_add_records(tag->tag, records, tag->cb, 0);
g_free(tag);
return 0;
}
read_blocks = length / BLOCK_SIZE;
tag->current_block += read_blocks;
cmd.cmd = CMD_READ;
cmd.block = DATA_BLOCK_START + tag->current_block;
DBG("adapter %d", adapter_idx);
return near_adapter_send(adapter_idx,
(uint8_t *) &cmd, CMD_READ_SIZE,
data_recv, tag, NULL);
}
static int data_read(struct type2_tag *tag)
{
struct type2_cmd cmd;
uint32_t adapter_idx;
DBG("");
tag->current_block = 0;
cmd.cmd = CMD_READ;
cmd.block = DATA_BLOCK_START;
adapter_idx = near_tag_get_adapter_idx(tag->tag);
return near_adapter_send(adapter_idx,
(uint8_t *) &cmd, CMD_READ_SIZE,
data_recv, tag, NULL);
}
static int meta_recv(uint8_t *resp, int length, void *data)
{
struct t2_cookie *cookie = data;
struct near_tag *tag;
struct type2_tag *t2_tag;
uint8_t *cc;
int err;
DBG("%d", length);
if (length < 0) {
err = length;
goto out_err;
}
if (resp[0] != 0) {
err = -EIO;
goto out_err;
}
cc = TAG_DATA_CC(resp + NFC_HEADER_SIZE);
/* Default to 48 bytes data size in case of blank tag */
err = near_tag_add_data(cookie->adapter_idx, cookie->target_idx,
NULL, (TAG_DATA_LENGTH(cc) ? TAG_DATA_LENGTH(cc) :
TYPE2_DATA_SIZE_48 << 3));
if (err < 0)
goto out_err;
tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx);
if (!tag) {
err = -ENOMEM;
goto out_err;
}
t2_tag = g_try_malloc0(sizeof(struct type2_tag));
if (!t2_tag) {
err = -ENOMEM;
goto out_err;
}
t2_tag->adapter_idx = cookie->adapter_idx;
t2_tag->cb = cookie->cb;
t2_tag->tag = tag;
/* Set the ReadWrite flag */
if (TAG_T2_WRITE_FLAG(cc) == TYPE2_NOWRITE_ACCESS)
near_tag_set_ro(tag, TRUE);
else
near_tag_set_ro(tag, FALSE);
near_tag_set_memory_layout(tag, NEAR_TAG_MEMORY_STATIC);
if (TAG_DATA_NFC(cc) == 0) {
DBG("Mark as blank tag");
near_tag_set_blank(tag, TRUE);
} else {
near_tag_set_blank(tag, FALSE);
}
err = data_read(t2_tag);
if (err < 0)
goto out_tag;
/*
* As reading isn't complete,
* callback shouldn't be called while freeing the cookie
*/
cookie->cb = NULL;
return t2_cookie_release(err, cookie);
out_tag:
g_free(t2_tag);
out_err:
return t2_cookie_release(err, cookie);
}
static int nfctype2_read_meta(uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb)
{
struct type2_cmd cmd;
struct t2_cookie *cookie;
DBG("");
cmd.cmd = CMD_READ;
cmd.block = META_BLOCK_START;
cookie = g_try_malloc0(sizeof(struct t2_cookie));
if (!cookie)
return -ENOMEM;
cookie->adapter_idx = adapter_idx;
cookie->target_idx = target_idx;
cookie->cb = cb;
return near_adapter_send(adapter_idx, (uint8_t *) &cmd, CMD_READ_SIZE,
meta_recv, cookie, t2_cookie_release);
}
static int nfctype2_read(uint32_t adapter_idx,
uint32_t target_idx, near_tag_io_cb cb)
{
enum near_tag_sub_type tgt_subtype;
DBG("");
tgt_subtype = near_tag_get_subtype(adapter_idx, target_idx);
switch (tgt_subtype) {
case NEAR_TAG_NFC_T2_MIFARE_ULTRALIGHT:
return nfctype2_read_meta(adapter_idx, target_idx, cb);
/* Specific Mifare read access */
case NEAR_TAG_NFC_T2_MIFARE_CLASSIC_1K:
case NEAR_TAG_NFC_T2_MIFARE_CLASSIC_4K:
return mifare_read(adapter_idx, target_idx,
cb, tgt_subtype);
default:
DBG("Unknown Tag Type 2 subtype %d", tgt_subtype);
return -1;
}
}
static int data_write_resp(uint8_t *resp, int length, void *data)
{
int err;
struct t2_cookie *cookie = data;
struct type2_cmd cmd;
DBG("");
if (length < 0 || resp[0] != 0) {
err = -EIO;
goto out_err;
}
if (cookie->ndef->offset > cookie->ndef->length) {
DBG("Done writing");
return t2_cookie_release(0, cookie);
}
cmd.cmd = CMD_WRITE;
cmd.block = cookie->current_block;
cookie->current_block++;
memset(cmd.data,0,BLOCK_SIZE);
if ((cookie->ndef->offset + BLOCK_SIZE) <
cookie->ndef->length) {
memcpy(cmd.data, cookie->ndef->data +
cookie->ndef->offset, BLOCK_SIZE);
cookie->ndef->offset += BLOCK_SIZE;
} else {
memcpy(cmd.data, cookie->ndef->data + cookie->ndef->offset,
cookie->ndef->length - cookie->ndef->offset);
cookie->ndef->offset = cookie->ndef->length + 1;
}
return near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd,
sizeof(cmd), data_write_resp, cookie,
t2_cookie_release);
out_err:
return t2_cookie_release(err, cookie);
}
static int data_write(uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_tag_io_cb cb)
{
struct type2_cmd cmd;
struct t2_cookie *cookie;
int err;
DBG("");
cookie = g_try_malloc0(sizeof(struct t2_cookie));
if (!cookie) {
err = -ENOMEM;
if (cb)
cb(adapter_idx, target_idx, err);
return err;
}
cookie->adapter_idx = adapter_idx;
cookie->target_idx = target_idx;
cookie->current_block = DATA_BLOCK_START;
cookie->ndef = ndef;
cookie->cb = cb;
cmd.cmd = CMD_WRITE;
cmd.block = cookie->current_block;
memcpy(cmd.data, cookie->ndef->data, BLOCK_SIZE);
cookie->ndef->offset += BLOCK_SIZE;
cookie->current_block++;
return near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd,
sizeof(cmd), data_write_resp, cookie,
t2_cookie_release);
}
static int nfctype2_write(uint32_t adapter_idx, uint32_t target_idx,
struct near_ndef_message *ndef,
near_tag_io_cb cb)
{
struct near_tag *tag;
enum near_tag_sub_type tgt_subtype;
int err;
DBG("");
if (!ndef || !cb) {
err = -EINVAL;
goto out_err;
}
tag = near_tag_get_tag(adapter_idx, target_idx);
if (!tag) {
err = -EINVAL;
goto out_err;
}
tgt_subtype = near_tag_get_subtype(adapter_idx, target_idx);
switch (tgt_subtype) {
case NEAR_TAG_NFC_T2_MIFARE_ULTRALIGHT:
/*
* This check is valid for only static tags.
* Max data length on Type 2 Tag
* including TLV's is NDEF_MAX_SIZE
*/
if (near_tag_get_memory_layout(tag) == NEAR_TAG_MEMORY_STATIC) {
if ((ndef->length) > near_tag_get_data_length(tag)) {
near_error("Not enough space on tag %zd %zd",
ndef->length,
near_tag_get_data_length(tag));
err = -ENOSPC;
goto out_err;
}
}
return data_write(adapter_idx, target_idx, ndef, cb);
/* Specific Mifare write access */
case NEAR_TAG_NFC_T2_MIFARE_CLASSIC_1K:
case NEAR_TAG_NFC_T2_MIFARE_CLASSIC_4K:
return mifare_write(adapter_idx, target_idx, ndef,
cb, tgt_subtype);
default:
DBG("Unknown TAG Type 2 subtype %d", tgt_subtype);
err = -EINVAL;
goto out_err;
}
return 0;
out_err:
if (cb)
cb(adapter_idx, target_idx, err);
return err;
}
static int check_presence(uint8_t *resp, int length, void *data)
{
struct t2_cookie *cookie = data;
int err = 0;
DBG("%d", length);
if (length < 0)
err = -EIO;
return t2_cookie_release(err, cookie);
}
static int nfctype2_check_presence(uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb)
{
struct type2_cmd cmd;
struct t2_cookie *cookie;
enum near_tag_sub_type tgt_subtype;
DBG("");
tgt_subtype = near_tag_get_subtype(adapter_idx, target_idx);
switch (tgt_subtype) {
case NEAR_TAG_NFC_T2_MIFARE_ULTRALIGHT:
cmd.cmd = CMD_READ;
cmd.block = META_BLOCK_START;
cookie = g_try_malloc0(sizeof(struct t2_cookie));
if (!cookie)
return -ENOMEM;
cookie->adapter_idx = adapter_idx;
cookie->target_idx = target_idx;
cookie->cb = cb;
return near_adapter_send(adapter_idx, (uint8_t *) &cmd,
CMD_READ_SIZE, check_presence, cookie,
t2_cookie_release);
/* Specific Mifare check presence */
case NEAR_TAG_NFC_T2_MIFARE_CLASSIC_1K:
case NEAR_TAG_NFC_T2_MIFARE_CLASSIC_4K:
return mifare_check_presence(adapter_idx, target_idx,
cb, tgt_subtype);
default:
DBG("Unknown TAG Type 2 subtype %d", tgt_subtype);
return -1;
}
}
static int format_resp(uint8_t *resp, int length, void *data)
{
int err = 0;
struct t2_cookie *cookie = data;
struct near_tag *tag;
DBG("");
if (length < 0 || resp[0] != 0) {
err = -EIO;
goto out_err;
}
tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx);
if (!tag) {
err = -EINVAL;
goto out_err;
}
DBG("Done formatting");
near_tag_set_blank(tag, FALSE);
out_err:
return t2_cookie_release(err, cookie);
}
static int nfctype2_format(uint32_t adapter_idx, uint32_t target_idx,
near_tag_io_cb cb)
{
struct type2_cmd cmd;
struct t2_cookie *cookie;
struct near_ndef_message *cc_ndef;
struct type2_cc *t2_cc;
struct near_tag *tag;
enum near_tag_sub_type tgt_subtype;
int err;
DBG("");
tag = near_tag_get_tag(adapter_idx, target_idx);
if (!tag)
return -EINVAL;
tgt_subtype = near_tag_get_subtype(adapter_idx, target_idx);
if (tgt_subtype != NEAR_TAG_NFC_T2_MIFARE_ULTRALIGHT) {
DBG("Unknown Tag Type 2 subtype %d", tgt_subtype);
return -1;
}
t2_cc = g_try_malloc0(sizeof(struct type2_cc));
cc_ndef = g_try_malloc0(sizeof(struct near_ndef_message));
cookie = g_try_malloc0(sizeof(struct t2_cookie));
if (!t2_cc || !cc_ndef || !cookie) {
err = -ENOMEM;
goto out_err;
}
t2_cc->magic = TYPE2_MAGIC;
t2_cc->version = TYPE2_TAG_VER_1_0;
t2_cc->mem_size = TYPE2_DATA_SIZE_48;
t2_cc->read_write = TYPE2_READWRITE_ACCESS;
cookie->adapter_idx = adapter_idx;
cookie->target_idx = target_idx;
cookie->current_block = CC_BLOCK_START;
cookie->ndef = cc_ndef;
cookie->ndef->data = (uint8_t *) t2_cc;
cookie->cb = cb;
cmd.cmd = CMD_WRITE;
cmd.block = CC_BLOCK_START;
memcpy(cmd.data, (uint8_t *) t2_cc, BLOCK_SIZE);
err = near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd,
sizeof(cmd), format_resp, cookie, NULL);
out_err:
if (err < 0) {
g_free(t2_cc);
g_free(cc_ndef);
g_free(cookie);
}
return err;
}
static struct near_tag_driver type2_driver = {
.type = NFC_PROTO_MIFARE,
.priority = NEAR_TAG_PRIORITY_DEFAULT,
.read = nfctype2_read,
.write = nfctype2_write,
.check_presence = nfctype2_check_presence,
.format = nfctype2_format,
};
static int nfctype2_init(void)
{
DBG("");
return near_tag_driver_register(&type2_driver);
}
static void nfctype2_exit(void)
{
DBG("");
near_tag_driver_unregister(&type2_driver);
}
NEAR_PLUGIN_DEFINE(nfctype2, "NFC Forum Type 2 tags support", VERSION,
NEAR_PLUGIN_PRIORITY_HIGH, nfctype2_init, nfctype2_exit)