| /* |
| * |
| * 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> |
| |
| #include <stdio.h> |
| |
| #define CMD_READ_ALL 0x00 /* Read seg 0 (incl: HR) */ |
| #define CMD_READ_SEGS 0x10 /* Read 16 blocks (128 bytes) */ |
| #define CMD_RID 0x78 /* Read tag UID */ |
| |
| #define CMD_WRITE_E 0x53 /* Write with erase */ |
| #define CMD_WRITE_NE 0x1A /* Write no erase */ |
| |
| #define CMD_WRITE_E8 0x54 /* Write 8 bytes with erase */ |
| #define CMD_WRITE_NE8 0x1B /* Write 8 bytes no erase */ |
| |
| #define OFFSET_STATUS_CMD 0x00 |
| #define OFFSET_HEADER_ROM 0x01 |
| |
| #define HR0_TYPE1_STATIC_96B 0x11 |
| #define HR0_TYPE1_DYNAMIC_512B 0x12 |
| |
| #define HR0_TYPE2_HIGH 0x10 |
| #define HR0_TYPE2_LOW 0x0F |
| |
| #define BLOCK_SIZE 8 |
| #define LEN_STATUS_BYTE 0x01 /* Status byte */ |
| #define LEN_SPEC_BYTES (LEN_STATUS_BYTE + 0x02) /* HRx */ |
| #define LEN_UID_BYTES (LEN_STATUS_BYTE + 0x07) /* UID bytes */ |
| #define LEN_CC_BYTES 0x04 /* Capab. container */ |
| #define LEN_DYN_BYTES 0x0A /* Bytes CtrlIT and TLV - Dyn. only */ |
| |
| #define TYPE1_MAGIC 0xE1 |
| |
| #define TAG_T1_DATA_UID(data) ((data) + LEN_SPEC_BYTES) |
| #define TAG_T1_DATA_CC(data) ((data) + LEN_SPEC_BYTES + LEN_UID_BYTES) |
| #define TAG_T1_DATA_LENGTH(cc) ((cc[2] + 1) * 8 - LEN_CC_BYTES) |
| |
| #define TAG_T1_DATA_NFC(cc) ((cc)[0] & TYPE1_MAGIC) |
| |
| #define TYPE1_NOWRITE_ACCESS 0x0F |
| #define TAG_T1_WRITE_FLAG(cc) ((cc)[3] & TYPE1_NOWRITE_ACCESS) |
| #define TAG_T1_SEGMENT_SIZE 128 |
| |
| /* Max writable data size */ |
| #define TYPE1_STATIC_MAX_DATA_SIZE_96B 95 |
| #define TYPE1_STATIC_MAX_DATA_SIZE_512B 469 |
| |
| #define UID_LENGTH 4 |
| |
| #define TYPE1_TAG_VER_1_1 0x11 |
| #define TYPE1_TAG_STATIC_SIZE_120 0x0E |
| #define TYPE1_READ_WRITE_ACCESS 0x00 |
| #define TYPE1_STATIC_TAG_DATA_LENGTH_96B 116 |
| #define TYPE1_STATIC_TAG_DATA_LENGTH_512B 508 |
| |
| #define RESERVED_BLOCK 0x0D |
| #define FIRST_DYNAMIC_BLOCK 0x02 |
| |
| struct type1_cmd { |
| uint8_t cmd; |
| uint8_t addr; |
| uint8_t data[1]; |
| uint8_t uid[UID_LENGTH]; |
| } __attribute__((packed)); |
| |
| struct type1_cmd8 { |
| uint8_t cmd; |
| uint8_t addr; |
| uint8_t data[8]; |
| uint8_t uid[UID_LENGTH]; |
| } __attribute__((packed)); |
| |
| struct type1_cmd_rseg { |
| uint8_t cmd; |
| uint8_t addr; |
| uint8_t data[8]; |
| uint8_t uid[UID_LENGTH]; |
| } __attribute__((packed)); |
| |
| struct type1_tag { |
| uint32_t adapter_idx; |
| uint16_t current_block; |
| uint16_t current_seg; |
| uint16_t last_seg; |
| uint16_t data_read; |
| uint8_t uid[UID_LENGTH]; |
| |
| near_tag_io_cb cb; |
| struct near_tag *tag; |
| }; |
| |
| struct t1_cookie { |
| uint32_t adapter_idx; |
| uint32_t target_idx; |
| uint8_t uid[UID_LENGTH]; |
| uint32_t current_block; /* Static tag */ |
| uint32_t current_byte; /* Static tag */ |
| struct near_ndef_message *ndef; |
| near_tag_io_cb cb; |
| uint8_t cc[LEN_CC_BYTES]; |
| }; |
| |
| enum type1_tag_version { |
| TYPE1_TAG_UNKNOWN, |
| TYPE1_TAG_96B, |
| TYPE1_TAG_512B |
| } t1_tag_version; |
| |
| static void t1_init_cmd(struct type1_tag *tag, struct type1_cmd *cmd) |
| { |
| if (!tag || !cmd) |
| return; |
| memcpy(cmd->uid, tag->uid, UID_LENGTH); |
| } |
| |
| static int t1_cookie_release(int err, void *data) |
| { |
| struct t1_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); |
| cookie = NULL; |
| |
| return err; |
| } |
| |
| /* Read segments (128 bytes) and store them to the tag data block */ |
| static int data_recv(uint8_t *resp, int length, void *data) |
| { |
| struct type1_tag *t1_tag = data; |
| struct type1_cmd t1_cmd; |
| uint8_t *tagdata; |
| size_t data_length; |
| |
| DBG("%d", length); |
| |
| if (length < 0) |
| return length; |
| |
| length = length - LEN_STATUS_BYTE; /* ignore first byte */ |
| |
| /* Add data to tag mem */ |
| tagdata = near_tag_get_data(t1_tag->tag, &data_length); |
| |
| /* Check that we have enough free space */ |
| if (data_length - t1_tag->data_read < (uint)length) |
| return -EINVAL; |
| |
| memcpy(tagdata + t1_tag->data_read, resp + 2, length - LEN_STATUS_BYTE); |
| |
| /* Next segment */ |
| t1_tag->data_read = t1_tag->data_read + length - LEN_STATUS_BYTE; |
| t1_tag->current_seg = t1_tag->current_seg + 1; |
| |
| if (t1_tag->current_seg <= t1_tag->last_seg) { |
| struct type1_cmd_rseg t1_cmd_rseg; |
| |
| /* RSEG cmd */ |
| t1_init_cmd(t1_tag, &t1_cmd); |
| t1_cmd_rseg.cmd = CMD_READ_SEGS; |
| memset(t1_cmd_rseg.data, 0x00, 8); |
| memcpy(t1_cmd_rseg.uid, t1_cmd.uid, sizeof(t1_cmd.uid)); |
| t1_cmd_rseg.addr = (t1_tag->current_seg << 4) & 0xFF; |
| |
| return near_adapter_send(t1_tag->adapter_idx, |
| (uint8_t *) &t1_cmd_rseg, sizeof(t1_cmd_rseg), |
| data_recv, t1_tag, NULL); |
| } else { /* This is the end */ |
| GList *records; |
| |
| DBG("READ complete"); |
| |
| records = near_tlv_parse(tagdata, data_length); |
| near_tag_add_records(t1_tag->tag, records, t1_tag->cb, 0); |
| |
| /* free memory */ |
| g_free(t1_tag); |
| |
| return 0; |
| } |
| } |
| |
| /* |
| * The dynamic read function: |
| * Bytes [0..3] : CC |
| * [4..8]: TLV Lock ControlIT (0x01, 0x03, v1, V2, V3) |
| * [9..13]: TLV Reserved Memory Control (0x02, 0x03, V1, V2, V3) |
| * [14..]: TLV NDEF (0x03, L0, L1, L2, V1,V2 ...) |
| */ |
| static int read_dynamic_tag(uint8_t *cc, int length, void *data, |
| uint8_t tag_mem_size) |
| { |
| struct type1_tag *t1_tag = data; |
| struct type1_cmd t1_cmd; |
| struct type1_cmd_rseg t1_cmd_rseg; |
| uint8_t *tagdata; |
| uint8_t *pndef; |
| size_t data_length; |
| |
| DBG("Dynamic Mode"); |
| tagdata = near_tag_get_data(t1_tag->tag, &data_length); |
| |
| /* Skip capability container bytes */ |
| pndef = cc + 14; /* right after CC bytes */ |
| |
| /* |
| * Save first NFC bytes to tag memory |
| * 10 blocks[0x3..0xC] of 8 bytes + 2 bytes from block 2 |
| */ |
| memcpy(tagdata, pndef, 10 * BLOCK_SIZE + 2); |
| t1_tag->data_read = 10 * BLOCK_SIZE + 2; |
| |
| /* Read the next one, up to the end of the data area */ |
| t1_tag->current_seg = 1; |
| if (tag_mem_size == HR0_TYPE1_DYNAMIC_512B) |
| t1_tag->last_seg = 3; |
| else |
| return -ENOTSUP; |
| |
| |
| /* RSEG cmd */ |
| t1_init_cmd(t1_tag, &t1_cmd); |
| /* T1 read segment */ |
| t1_cmd_rseg.cmd = CMD_READ_SEGS; |
| memset(t1_cmd_rseg.data, 0x00, 8); |
| memcpy(t1_cmd_rseg.uid, t1_cmd.uid, sizeof(t1_cmd.uid)); |
| /* 5.3.3 ADDS operand is [b8..b5] */ |
| t1_cmd_rseg.addr = (t1_tag->current_seg << 4) & 0xFF; |
| |
| return near_adapter_send(t1_tag->adapter_idx, |
| (uint8_t *)&t1_cmd_rseg, sizeof(t1_cmd_rseg), |
| data_recv, t1_tag, NULL); |
| } |
| |
| static int meta_recv(uint8_t *resp, int length, void *data) |
| { |
| struct t1_cookie *cookie = data; |
| struct near_tag *tag; |
| struct type1_tag *t1_tag; |
| uint8_t *cc; |
| int err = -EOPNOTSUPP; |
| |
| DBG("%d", length); |
| |
| if (length < 0) { |
| err = length; |
| goto out_err; |
| } |
| |
| /* First byte is cmd status */ |
| if (resp[OFFSET_STATUS_CMD] != 0) { |
| DBG("Command failed: 0x%x", resp[OFFSET_STATUS_CMD]); |
| err = -EIO; |
| goto out_err; |
| } |
| |
| /* Set tag type */ |
| if (resp[OFFSET_HEADER_ROM] == HR0_TYPE1_STATIC_96B) |
| t1_tag_version = TYPE1_TAG_96B; |
| else if (resp[OFFSET_HEADER_ROM] == HR0_TYPE1_DYNAMIC_512B) |
| t1_tag_version = TYPE1_TAG_512B; |
| else |
| t1_tag_version = TYPE1_TAG_UNKNOWN; |
| |
| /* Check Magic NFC tag */ |
| cc = TAG_T1_DATA_CC(resp); |
| if (TAG_T1_DATA_NFC(cc) == 0) { |
| if (resp[OFFSET_HEADER_ROM] == HR0_TYPE1_STATIC_96B) { |
| err = near_tag_add_data(cookie->adapter_idx, |
| cookie->target_idx, |
| NULL, |
| TYPE1_STATIC_TAG_DATA_LENGTH_96B); |
| } else if (resp[OFFSET_HEADER_ROM] == HR0_TYPE1_DYNAMIC_512B) { |
| err = near_tag_add_data(cookie->adapter_idx, |
| cookie->target_idx, |
| NULL, |
| TYPE1_STATIC_TAG_DATA_LENGTH_512B); |
| } else { |
| near_error("Not a valid NFC magic tag 0x%x", cc[0]); |
| err = -EINVAL; |
| goto out_err; |
| } |
| } else { |
| /* Add data to the tag */ |
| err = near_tag_add_data(cookie->adapter_idx, cookie->target_idx, |
| NULL, TAG_T1_DATA_LENGTH(cc)); |
| } |
| |
| if (err < 0) |
| goto out_err; |
| |
| tag = near_tag_get_tag(cookie->adapter_idx, cookie->target_idx); |
| if (!tag) { |
| err = -ENOMEM; |
| goto out_err; |
| } |
| |
| t1_tag = g_try_malloc0(sizeof(struct type1_tag)); |
| if (!t1_tag) { |
| err = -ENOMEM; |
| goto out_err; |
| } |
| |
| t1_tag->adapter_idx = cookie->adapter_idx; |
| t1_tag->cb = cookie->cb; |
| t1_tag->tag = tag; |
| memcpy(t1_tag->uid, cookie->uid, UID_LENGTH); |
| |
| /* Set the ReadWrite flag */ |
| if (TAG_T1_WRITE_FLAG(cc) == TYPE1_NOWRITE_ACCESS) |
| near_tag_set_ro(tag, TRUE); |
| else |
| near_tag_set_ro(tag, FALSE); |
| |
| /* Check Static or Dynamic memory model */ |
| if (resp[OFFSET_HEADER_ROM] == HR0_TYPE1_STATIC_96B) { |
| uint8_t *tagdata; |
| size_t data_length; |
| GList *records; |
| |
| if (TAG_T1_DATA_NFC(cc) == 0) |
| near_tag_set_blank(tag, TRUE); |
| else |
| near_tag_set_blank(tag, FALSE); |
| |
| DBG("READ Static complete"); |
| |
| tagdata = near_tag_get_data(t1_tag->tag, &data_length); |
| |
| /* Check that we have enough free space */ |
| if (data_length < (size_t)TAG_T1_DATA_LENGTH(cc)) |
| return -EINVAL; |
| |
| memcpy(tagdata, cc + LEN_CC_BYTES, TAG_T1_DATA_LENGTH(cc)); |
| |
| near_tag_set_memory_layout(tag, NEAR_TAG_MEMORY_STATIC); |
| |
| records = near_tlv_parse(tagdata, data_length); |
| near_tag_add_records(t1_tag->tag, records, t1_tag->cb, 0); |
| |
| g_free(t1_tag); |
| |
| return 0; |
| } else if ((resp[OFFSET_HEADER_ROM] & 0xF0) == HR0_TYPE2_HIGH) { |
| near_tag_set_memory_layout(tag, NEAR_TAG_MEMORY_DYNAMIC); |
| err = read_dynamic_tag(cc, length, t1_tag, |
| resp[OFFSET_HEADER_ROM]); |
| /* |
| * As reading isn't complete, |
| * callback shouldn't be called while freeing the cookie |
| */ |
| cookie->cb = NULL; |
| } else { |
| err = -EOPNOTSUPP; |
| } |
| |
| if (err < 0) |
| g_free(t1_tag); |
| |
| out_err: |
| DBG("err %d", err); |
| |
| return t1_cookie_release(err, cookie); |
| } |
| |
| /* |
| * READALL to read a maximum of 124 bytes. |
| * This cmd is common to static and dynamic targets |
| * This should allow to get the HR0 byte. |
| */ |
| static int rid_resp(uint8_t *resp, int length, void *data) |
| { |
| struct t1_cookie *cookie = data; |
| struct type1_cmd t1_cmd; |
| uint8_t *uid; |
| int err; |
| |
| DBG(""); |
| |
| /* First byte is cmd status */ |
| if (resp[OFFSET_STATUS_CMD] != 0) { |
| DBG("Command failed: 0x%x", resp[OFFSET_STATUS_CMD]); |
| err = -EIO; |
| goto out_err; |
| } |
| |
| uid = TAG_T1_DATA_UID(resp); |
| |
| DBG("UID 0x%x 0x%x 0x%x 0x%x", uid[0], uid[1], uid[2], uid[3]); |
| |
| near_tag_set_nfcid(cookie->adapter_idx, cookie->target_idx, |
| uid, UID_LENGTH); |
| |
| t1_cmd.cmd = CMD_READ_ALL; /* Read ALL cmd give 124 bytes */ |
| t1_cmd.addr = 0; /* NA */ |
| t1_cmd.data[0] = 0; |
| memcpy(t1_cmd.uid, uid, UID_LENGTH); |
| |
| memcpy(cookie->uid, uid, UID_LENGTH); |
| |
| return near_adapter_send(cookie->adapter_idx, |
| (uint8_t *)&t1_cmd, sizeof(t1_cmd), |
| meta_recv, cookie, t1_cookie_release); |
| |
| out_err: |
| DBG("err %d", err); |
| |
| return t1_cookie_release(err, cookie); |
| } |
| |
| static int nfctype1_read_meta(uint32_t adapter_idx, uint32_t target_idx, |
| near_tag_io_cb cb, uint8_t *uid) |
| { |
| struct type1_cmd cmd; |
| struct t1_cookie *cookie; |
| |
| DBG(""); |
| |
| memset(&cmd, 0, sizeof(cmd)); |
| |
| cookie = g_try_malloc0(sizeof(struct t1_cookie)); |
| if (!cookie) |
| return -ENOMEM; |
| |
| cookie->adapter_idx = adapter_idx; |
| cookie->target_idx = target_idx; |
| cookie->cb = cb; |
| |
| if (uid) { |
| cmd.cmd = CMD_READ_ALL; /* Read ALL cmd give 124 bytes */ |
| memcpy(cmd.uid, uid, UID_LENGTH); |
| memcpy(cookie->uid, uid, UID_LENGTH); |
| |
| return near_adapter_send(adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), meta_recv, cookie, |
| t1_cookie_release); |
| } else { |
| cmd.cmd = CMD_RID; |
| |
| return near_adapter_send(adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), rid_resp, cookie, |
| t1_cookie_release); |
| } |
| } |
| |
| /* First step: RID to get the tag UID */ |
| static int nfctype1_read(uint32_t adapter_idx, |
| uint32_t target_idx, near_tag_io_cb cb) |
| { |
| uint8_t *uid; |
| uint8_t uid_length; |
| int err; |
| |
| DBG(""); |
| |
| t1_tag_version = TYPE1_TAG_UNKNOWN; |
| uid = near_tag_get_nfcid(adapter_idx, target_idx, &uid_length); |
| if (!uid || uid_length != UID_LENGTH) { |
| if (uid) { |
| near_error("Invalid UID"); |
| |
| g_free(uid); |
| return -EINVAL; |
| } |
| |
| return nfctype1_read_meta(adapter_idx, target_idx, cb, NULL); |
| } |
| |
| err = nfctype1_read_meta(adapter_idx, target_idx, cb, uid); |
| |
| g_free(uid); |
| |
| return err; |
| } |
| |
| static int write_nmn_e1_resp(uint8_t *resp, int length, void *data) |
| { |
| int err = 0; |
| struct t1_cookie *cookie = data; |
| |
| DBG(""); |
| |
| if (length < 0) |
| err = length; |
| |
| if (resp[OFFSET_STATUS_CMD] != 0) |
| err = -EIO; |
| |
| DBG("Done writing"); |
| |
| return t1_cookie_release(err, cookie); |
| } |
| |
| static int write_nmn_e1(struct t1_cookie *cookie) |
| { |
| struct type1_cmd cmd; |
| |
| DBG(""); |
| |
| cmd.cmd = CMD_WRITE_E; |
| cmd.addr = 0x08; |
| cmd.data[0] = TYPE1_MAGIC; |
| memcpy(cmd.uid, cookie->uid, UID_LENGTH); |
| |
| return near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), write_nmn_e1_resp, cookie, |
| t1_cookie_release); |
| } |
| |
| static int data_write_resp(uint8_t *resp, int length, void *data) |
| { |
| struct t1_cookie *cookie = data; |
| struct type1_cmd cmd; |
| struct type1_cmd8 cmd8; |
| uint8_t data_block[BLOCK_SIZE]; |
| uint8_t addr = 0; |
| int err = -EIO; |
| |
| DBG(""); |
| |
| if (length < 0) { |
| err = length; |
| goto out_err; |
| } |
| |
| if (resp[OFFSET_STATUS_CMD] != 0) { |
| err = -EIO; |
| goto out_err; |
| } |
| |
| if (cookie->ndef->offset >= cookie->ndef->length) |
| return write_nmn_e1(cookie); |
| |
| if (t1_tag_version == TYPE1_TAG_96B) { |
| /* Static */ |
| if (cookie->current_byte >= BLOCK_SIZE) { |
| cookie->current_byte = 0; |
| cookie->current_block++; |
| } |
| |
| cmd.cmd = CMD_WRITE_E; |
| addr = cookie->current_block << 3; |
| cmd.addr = addr | (cookie->current_byte & 0x7); |
| cmd.data[0] = cookie->ndef->data[cookie->ndef->offset]; |
| memcpy(cmd.uid, cookie->uid, UID_LENGTH); |
| cookie->ndef->offset++; |
| cookie->current_byte++; |
| |
| err = near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), data_write_resp, cookie, |
| NULL); |
| if (err < 0) |
| goto out_err; |
| |
| return err; |
| } else { |
| /* Dynamic */ |
| /* Skip reserved and lock/OTP blocks */ |
| if (cookie->current_block == RESERVED_BLOCK) |
| cookie->current_block += 3; |
| |
| if ((cookie->ndef->length |
| - cookie->ndef->offset) >= BLOCK_SIZE) { |
| if (cookie->current_block == FIRST_DYNAMIC_BLOCK) { |
| cmd.cmd = CMD_WRITE_E; |
| cmd.addr = (FIRST_DYNAMIC_BLOCK << 3) |
| | (cookie->current_byte & 0x7); |
| cmd.data[0] = |
| cookie->ndef->data[cookie->ndef->offset]; |
| memcpy(cmd.uid, cookie->uid, |
| UID_LENGTH); |
| cookie->ndef->offset++; |
| cookie->current_byte++; |
| if (cookie->current_byte >= BLOCK_SIZE) |
| cookie->current_block++; |
| goto send_byte; /* Write 2 first bytes */ |
| } else { |
| cmd8.cmd = CMD_WRITE_E8; |
| cmd8.addr = cookie->current_block; |
| memcpy(cmd8.data, |
| &cookie->ndef->data[cookie->ndef->offset], |
| BLOCK_SIZE); |
| cookie->ndef->offset += BLOCK_SIZE; |
| } |
| } else { |
| uint8_t remaining_bytes = 0; |
| |
| memset(data_block, 0x00, BLOCK_SIZE); |
| cmd8.cmd = CMD_WRITE_E8; |
| cmd8.addr = cookie->current_block; |
| remaining_bytes = cookie->ndef->length |
| - cookie->ndef->offset; |
| memcpy(data_block, |
| &cookie->ndef->data[cookie->ndef->offset], |
| remaining_bytes); |
| memcpy(cmd8.data, data_block, BLOCK_SIZE); |
| cookie->ndef->offset += remaining_bytes; |
| } |
| memcpy(cmd8.uid, cookie->uid, UID_LENGTH); |
| cookie->current_block++; |
| return near_adapter_send(cookie->adapter_idx, |
| (uint8_t *)&cmd8, |
| sizeof(cmd8), data_write_resp, cookie, |
| t1_cookie_release); |
| |
| send_byte: |
| return near_adapter_send(cookie->adapter_idx, |
| (uint8_t *)&cmd, |
| sizeof(cmd), data_write_resp, |
| cookie, |
| t1_cookie_release); |
| } |
| |
| out_err: |
| return t1_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) |
| { |
| int err; |
| struct type1_cmd cmd; |
| struct t1_cookie *cookie; |
| uint8_t *uid, uid_length; |
| |
| DBG(""); |
| |
| uid = near_tag_get_nfcid(adapter_idx, target_idx, &uid_length); |
| if (!uid || uid_length != UID_LENGTH) { |
| near_error("Invalid type 1 UID"); |
| err = -EINVAL; |
| goto out_err; |
| } |
| |
| cmd.cmd = CMD_WRITE_E; |
| cmd.data[0] = 0x00; |
| memcpy(cmd.uid, uid, UID_LENGTH); |
| |
| cookie = g_try_malloc0(sizeof(struct t1_cookie)); |
| if (!cookie) { |
| g_free(uid); |
| err = -ENOMEM; |
| goto out_err; |
| } |
| |
| cookie->adapter_idx = adapter_idx; |
| cookie->target_idx = target_idx; |
| memcpy(cookie->uid, uid, UID_LENGTH); |
| cookie->ndef = ndef; |
| cookie->cb = cb; |
| cmd.addr = 0x08; |
| |
| if (t1_tag_version == TYPE1_TAG_96B) { |
| /* Static */ |
| cookie->current_block = 1; |
| cookie->current_byte = 4; |
| } else { |
| /* Dynamic */ |
| cookie->current_block = 2; |
| cookie->current_byte = 6; |
| } |
| |
| g_free(uid); |
| |
| return near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), data_write_resp, cookie, |
| t1_cookie_release); |
| out_err: |
| if (cb) |
| cb(adapter_idx, target_idx, err); |
| |
| return err; |
| } |
| |
| /* |
| * The writing of a new NDEF message SHALL occur as follows: |
| * Write NMN = 00h to indicate that no valid NDEF message is present |
| * during writing to allow error detection in the event that the tag |
| * is removed from the field prior to completion of operation. |
| * Write VNo and RWA if required |
| * Write NDEF Message TLV |
| * Write NDEF Message data |
| * Write NMN = E1h as the last byte to be written |
| */ |
| static int nfctype1_write(uint32_t adapter_idx, uint32_t target_idx, |
| struct near_ndef_message *ndef, |
| near_tag_io_cb cb) |
| { |
| struct near_tag *tag; |
| int err; |
| size_t t1_size = 0; |
| |
| 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; |
| } |
| |
| /* This check is valid for only static tags. |
| * Max data length on Type 1 Tag including TLV's |
| * is TYPE1_STATIC_MAX_DATA_SIZE */ |
| if (near_tag_get_memory_layout(tag) == NEAR_TAG_MEMORY_STATIC) { |
| if (t1_tag_version == TYPE1_TAG_96B) |
| t1_size = TYPE1_STATIC_MAX_DATA_SIZE_96B; |
| else |
| t1_size = 0; |
| |
| if ((ndef->length + 3) > t1_size) { |
| near_error("not enough space on tag"); |
| err = -ENOSPC; |
| goto out_err; |
| } |
| } else if (near_tag_get_memory_layout(tag) == |
| NEAR_TAG_MEMORY_DYNAMIC) { |
| if (t1_tag_version == TYPE1_TAG_512B) |
| t1_size = TYPE1_STATIC_MAX_DATA_SIZE_512B; |
| else |
| t1_size = 0; |
| |
| if ((ndef->length + 3) > t1_size) { |
| near_error("not enough space on tag"); |
| err = -ENOSPC; |
| goto out_err; |
| } |
| } |
| |
| return data_write(adapter_idx, target_idx, ndef, cb); |
| |
| out_err: |
| if (cb) |
| cb(adapter_idx, target_idx, err); |
| |
| return err; |
| } |
| |
| static int check_presence(uint8_t *resp, int length, void *data) |
| { |
| struct t1_cookie *cookie = data; |
| int err = 0; |
| |
| DBG("%d", length); |
| |
| if (length < 0) |
| err = -EIO; |
| |
| return t1_cookie_release(err, cookie); |
| } |
| |
| static int nfctype1_check_presence(uint32_t adapter_idx, |
| uint32_t target_idx, near_tag_io_cb cb) |
| { |
| struct type1_cmd t1_cmd; |
| struct t1_cookie *cookie; |
| uint8_t *uid, uid_length; |
| |
| DBG(""); |
| |
| uid = near_tag_get_nfcid(adapter_idx, target_idx, &uid_length); |
| if (!uid || uid_length != UID_LENGTH) { |
| near_error("Invalid type 1 UID"); |
| return -EINVAL; |
| } |
| |
| t1_cmd.cmd = CMD_READ_ALL; /* Read ALL cmd give 124 bytes */ |
| t1_cmd.addr = 0; /* NA */ |
| t1_cmd.data[0] = 0; |
| memcpy(t1_cmd.uid, uid, UID_LENGTH); |
| |
| g_free(uid); |
| |
| cookie = g_try_malloc0(sizeof(struct t1_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 *) &t1_cmd, |
| sizeof(t1_cmd), check_presence, cookie, |
| t1_cookie_release); |
| } |
| |
| static int format_resp(uint8_t *resp, int length, void *data) |
| { |
| int err = 0; |
| struct t1_cookie *cookie = data; |
| struct near_tag *tag; |
| struct type1_cmd cmd; |
| uint8_t addr; |
| |
| DBG(""); |
| |
| if (length < 0 || resp[0] != 0) { |
| err = -EIO; |
| goto out_err; |
| } |
| |
| if (cookie->current_byte < LEN_CC_BYTES) { |
| cmd.cmd = CMD_WRITE_E; |
| addr = cookie->current_block << 3; |
| cmd.addr = addr | (cookie->current_byte & 0x7); |
| cmd.data[0] = cookie->cc[cookie->current_byte]; |
| cookie->current_byte++; |
| memcpy(cmd.uid, cookie->uid, UID_LENGTH); |
| |
| return near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), format_resp, cookie, |
| t1_cookie_release); |
| } else { |
| 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 t1_cookie_release(err, cookie); |
| } |
| |
| static int nfctype1_format(uint32_t adapter_idx, uint32_t target_idx, |
| near_tag_io_cb cb) |
| { |
| int err; |
| struct near_tag *tag; |
| struct type1_cmd cmd; |
| struct t1_cookie *cookie; |
| uint8_t *uid, uid_length, addr; |
| |
| DBG(""); |
| |
| tag = near_tag_get_tag(adapter_idx, target_idx); |
| if (!tag) |
| return -EINVAL; |
| |
| /* TODO: Dynamic tag format */ |
| if (near_tag_get_memory_layout(tag) != NEAR_TAG_MEMORY_STATIC) |
| return -EOPNOTSUPP; |
| |
| uid = near_tag_get_nfcid(adapter_idx, target_idx, &uid_length); |
| if (!uid || uid_length != UID_LENGTH) { |
| near_error("Invalid type 1 UID"); |
| return -EINVAL; |
| } |
| |
| cookie = g_try_malloc0(sizeof(struct t1_cookie)); |
| if (!cookie) { |
| err = -EINVAL; |
| goto out_err; |
| } |
| |
| cookie->adapter_idx = adapter_idx; |
| cookie->target_idx = target_idx; |
| cookie->cb = cb; |
| memcpy(cookie->uid, uid, UID_LENGTH); |
| cookie->cc[0] = TYPE1_MAGIC; |
| cookie->cc[1] = TYPE1_TAG_VER_1_1; |
| cookie->cc[2] = TYPE1_TAG_STATIC_SIZE_120; |
| cookie->cc[3] = TYPE1_READ_WRITE_ACCESS; |
| cookie->current_block = 1; |
| cookie->current_byte = 0; |
| |
| cmd.cmd = CMD_WRITE_E; |
| addr = cookie->current_block << 3; |
| cmd.addr = addr | (cookie->current_byte & 0x7); |
| cmd.data[0] = cookie->cc[cookie->current_byte]; |
| cookie->current_byte++; |
| memcpy(cmd.uid, cookie->uid, UID_LENGTH); |
| g_free(uid); |
| |
| err = near_adapter_send(cookie->adapter_idx, (uint8_t *) &cmd, |
| sizeof(cmd), format_resp, cookie, |
| t1_cookie_release); |
| if (err < 0) |
| goto out_err; |
| |
| return 0; |
| |
| out_err: |
| g_free(cookie); |
| g_free(uid); |
| |
| return err; |
| } |
| |
| static struct near_tag_driver type1_driver = { |
| .type = NFC_PROTO_JEWEL, |
| .priority = NEAR_TAG_PRIORITY_DEFAULT, |
| .read = nfctype1_read, |
| .write = nfctype1_write, |
| .check_presence = nfctype1_check_presence, |
| .format = nfctype1_format, |
| }; |
| |
| static int nfctype1_init(void) |
| { |
| DBG(""); |
| |
| return near_tag_driver_register(&type1_driver); |
| } |
| |
| static void nfctype1_exit(void) |
| { |
| DBG(""); |
| |
| near_tag_driver_unregister(&type1_driver); |
| } |
| |
| NEAR_PLUGIN_DEFINE(nfctype1, "NFC Forum Type 1 tags support", VERSION, |
| NEAR_PLUGIN_PRIORITY_HIGH, nfctype1_init, nfctype1_exit) |