diff --git a/libfreefare/Makefile.am b/libfreefare/Makefile.am index 0eefabc..95d8d5c 100644 --- a/libfreefare/Makefile.am +++ b/libfreefare/Makefile.am @@ -4,7 +4,10 @@ AM_LDFLAGS = @LIBNFC_LIBS@ lib_LTLIBRARIES = libfreefare.la -libfreefare_la_SOURCES = mifare_classic.c mad.c mifare_application.c +libfreefare_la_SOURCES = mifare_classic.c \ + mifare_ultralight.c \ + mad.c \ + mifare_application.c libfreefare_la_HEADERS = freefare.h libfreefare_ladir = $(includedir) diff --git a/libfreefare/freefare.h b/libfreefare/freefare.h index e1fb5d3..27b853d 100644 --- a/libfreefare/freefare.h +++ b/libfreefare/freefare.h @@ -30,6 +30,21 @@ extern "C" { #endif // __cplusplus +struct mifare_ultralight_tag; +typedef struct mifare_ultralight_tag *MifareUltralightTag; +typedef uint8_t MifareUltralightPageNumber; +typedef unsigned char MifareUltralightPage[4]; + +MifareUltralightTag *mifare_ultralight_get_tags (nfc_device_t *device); +void mifare_ultralight_free_tags (MifareUltralightTag *tags); +int mifare_ultralight_connect (MifareUltralightTag tag); +int mifare_ultralight_disconnect (MifareUltralightTag tag); + +int mifare_ultralight_read (MifareUltralightTag tag, const MifareUltralightPageNumber page, MifareUltralightPage *data); +int mifare_ultralight_write (MifareUltralightTag tag, const MifareUltralightPageNumber page, const MifareUltralightPage data); + +char *mifare_ultralight_get_uid (MifareUltralightTag tag); + struct mifare_classic_tag; typedef struct mifare_classic_tag *MifareClassicTag; diff --git a/libfreefare/freefare_internal.h b/libfreefare/freefare_internal.h index 3fa401c..0b87cd8 100644 --- a/libfreefare/freefare_internal.h +++ b/libfreefare/freefare_internal.h @@ -27,4 +27,19 @@ void crc8 (uint8_t *crc, const uint8_t value); uint8_t sector_0x00_crc8 (Mad mad); uint8_t sector_0x10_crc8 (Mad mad); +#define MIFARE_ULTRALIGHT_PAGE_COUNT 16 + +struct mifare_ultralight_tag { + nfc_device_t *device; + nfc_iso14443a_info_t info; + int active; + + /* mifare_ultralight_read() reads 4 pages at a time (wrapping) */ + MifareUltralightPage cache[MIFARE_ULTRALIGHT_PAGE_COUNT + 3]; + uint8_t cached_pages[MIFARE_ULTRALIGHT_PAGE_COUNT]; +}; + +#define ASSERT_ACTIVE(tag) do { if (!tag->active) return errno = ENXIO, -1; } while (0) +#define ASSERT_INACTIVE(tag) do { if (tag->active) return errno = ENXIO, -1; } while (0) + #endif /* !__FREEFARE_INTERNAL_H__ */ diff --git a/libfreefare/mifare_ultralight.c b/libfreefare/mifare_ultralight.c new file mode 100644 index 0000000..6ee9f7b --- /dev/null +++ b/libfreefare/mifare_ultralight.c @@ -0,0 +1,260 @@ +/*- + * Copyright (C) 2010, Romain Tartiere. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * 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 Lesser General Public License + * along with this program. If not, see + * + * $Id$ + */ + +/* + * This implementation was written based on information provided by the + * following documents: + * + * Contactless Single-trip Ticket IC + * MF0 IC U1 + * Functional Specification + * Revision 3.0 + * March 2003 + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include +#include "freefare_internal.h" + +#define ASSERT_VALID_PAGE(page) do { if (page >= MIFARE_ULTRALIGHT_PAGE_COUNT) return errno = EINVAL, -1; } while (0) + + +/* + * MIFARE card communication preparation functions + * + * The following functions send NFC commands to the initiator to prepare + * communication with a MIFARE card, and perform required cleannups after using + * the target. + */ + +/* + * Get a list of the MIFARE card near to the provided NFC initiator. + * + * The list can be freed using the mifare_ultralight_free_tags() function. + */ +MifareUltralightTag * +mifare_ultralight_get_tags (nfc_device_t *device) +{ + MifareUltralightTag *tags = NULL; + int tag_count = 0; + + nfc_initiator_init(device); + + // Drop the field for a while + nfc_configure(device,NDO_ACTIVATE_FIELD,false); + + // Let the reader only try once to find a tag + nfc_configure(device,NDO_INFINITE_SELECT,false); + + // Configure the CRC and Parity settings + nfc_configure(device,NDO_HANDLE_CRC,true); + nfc_configure(device,NDO_HANDLE_PARITY,true); + + // Enable field so more power consuming cards can power themselves up + nfc_configure(device,NDO_ACTIVATE_FIELD,true); + + // Poll for a ISO14443A (MIFARE) tag + nfc_target_info_t target_info; + + tags = malloc(sizeof (void *)); + if(!tags) return NULL; + tags[0] = NULL; + + while (nfc_initiator_select_tag(device,NM_ISO14443A_106,NULL,0,&target_info)) { + + // Ensure the target is a MIFARE UltraLight tag. + if (!((target_info.nai.abtAtqa[0] == 0x00) && + (target_info.nai.abtAtqa[1] == 0x44) && + (target_info.nai.btSak == 0x00))) /* NXP MIFARE UltraLight */ + continue; + + tag_count++; + + /* (Re)Allocate memory for the found MIFARE UltraLight array */ + MifareUltralightTag *p = realloc (tags, (tag_count) * sizeof (MifareUltralightTag) + sizeof (void *)); + if (p) + tags = p; + else + return tags; // FAIL! Return what has been found so far. + + /* Allocate memory for the found MIFARE UltraLight tag */ + if (!(tags[tag_count-1] = malloc (sizeof (struct mifare_ultralight_tag)))) { + return tags; // FAIL! Return what has been found before. + } + (tags[tag_count-1])->device = device; + (tags[tag_count-1])->info = target_info.nai; + (tags[tag_count-1])->active = 0; + for (int i = 0; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) { + tags[tag_count-1]->cached_pages[i] = 0; + } + tags[tag_count] = NULL; + + nfc_initiator_deselect_tag (device); + } + + return tags; +} + +/* + * Free the provided tag list. + */ +void +mifare_ultralight_free_tags (MifareUltralightTag *tags) +{ + if (tags) { + for (int i=0; tags[i]; i++) { + free (tags[i]); + } + free (tags); + } +} + +/* + * Establish connection to the provided tag. + */ +int +mifare_ultralight_connect (MifareUltralightTag tag) +{ + ASSERT_INACTIVE (tag); + + nfc_target_info_t pnti; + if (nfc_initiator_select_tag (tag->device, NM_ISO14443A_106, tag->info.abtUid, 7, &pnti)) { + tag->active = 1; + } else { + errno = EIO; + return -1; + } + return 0; +} + +/* + * Terminate connection with the provided tag. + */ +int +mifare_ultralight_disconnect (MifareUltralightTag tag) +{ + ASSERT_ACTIVE (tag); + + if (nfc_initiator_deselect_tag (tag->device)) { + tag->active = 0; + } else { + errno = EIO; + return -1; + } + return 0; +} + + +/* + * Card manipulation functions + * + * The following functions perform direct communication with the connected + * MIFARE UltraLight tag. + */ + +/* + * Read data from the provided MIFARE tag. + */ +int +mifare_ultralight_read (MifareUltralightTag tag, MifareUltralightPageNumber page, MifareUltralightPage *data) +{ + ASSERT_ACTIVE (tag); + ASSERT_VALID_PAGE (page); + + if (!tag->cached_pages[page]) { + uint8_t cmd[2]; + cmd[0] = 0x30; + cmd[1] = page; + + size_t n; + if (!(nfc_initiator_transceive_dep_bytes (tag->device, cmd, sizeof (cmd), tag->cache[page], &n))) { + errno = EIO; + return -1; + } + + /* Handle wrapped pages */ + for (int i = MIFARE_ULTRALIGHT_PAGE_COUNT; i <= page + 3; i++) { + memcpy (tag->cache[i % MIFARE_ULTRALIGHT_PAGE_COUNT], tag->cache[i], sizeof (MifareUltralightPage)); + } + + /* Mark pages as cached */ + for (int i = page; i <= page + 3; i++) { + tag->cached_pages[i % MIFARE_ULTRALIGHT_PAGE_COUNT] = 1; + } + } + + memcpy (data, tag->cache[page], sizeof (*data)); + return 0; +} + +/* + * Read data to the provided MIFARE tag. + */ +int +mifare_ultralight_write (MifareUltralightTag tag, const MifareUltralightPageNumber page, const MifareUltralightPage data) +{ + ASSERT_ACTIVE (tag); + ASSERT_VALID_PAGE (page); + + uint8_t cmd[6]; + cmd[0] = 0xA2; + cmd[1] = page; + memcpy (cmd + 2, data, sizeof (MifareUltralightPage)); + + size_t n; + if (!(nfc_initiator_transceive_dep_bytes (tag->device, cmd, sizeof (cmd), NULL, &n))) { + errno = EIO; + return -1; + } + + /* Invalidate page in cache */ + tag->cached_pages[page] = 0; + + return 0; +} + + + +/* + * Miscellaneous functions + */ +char * +mifare_ultralight_get_uid (MifareUltralightTag tag) +{ + char *uid = malloc (2 * 7 + 1); + MifareUltralightPage p0, p1; + mifare_ultralight_read (tag, 0, &p0); + mifare_ultralight_read (tag, 1, &p1); + sprintf (uid, "%02x%02x%02x%02x%02x%02x%02x", + p0[0], + p0[1], + p0[2], + p1[0], + p1[1], + p1[2], + p1[3]); + return uid; +} diff --git a/test/Makefile.am b/test/Makefile.am index 503fbed..a266390 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -15,24 +15,33 @@ noinst_LTLIBRARIES = \ test_mad.la \ test_mifare_classic.la \ test_mifare_classic_create_trailer_block.la \ - test_mifare_classic_application.la + test_mifare_classic_application.la \ + test_mifare_ultralight.la AM_LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined test_mad_la_SOURCES = test_mad.c -test_mad_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la +test_mad_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la test_mifare_classic_la_SOURCES = test_mifare_classic.c \ - mifare_classic_fixture.c -test_mifare_classic_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la + mifare_classic_fixture.c \ + mifare_classic_fixture.h +test_mifare_classic_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la test_mifare_classic_create_trailer_block_la_SOURCES = test_mifare_classic_create_trailer_block.c -test_mifare_classic_create_trailer_block_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la +test_mifare_classic_create_trailer_block_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la test_mifare_classic_application_la_SOURCES = test_mifare_classic_application.c -test_mifare_classic_application_la_LIBADD = $(top_srcdir)/libfreefare/libfreefare.la +test_mifare_classic_application_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la + +test_mifare_ultralight_la_SOURCES = test_mifare_ultralight.c \ + mifare_ultralight_fixture.c \ + mifare_ultralight_fixture.h +test_mifare_ultralight_la_LIBADD = $(top_builddir)/libfreefare/libfreefare.la echo-cutter: @echo $(CUTTER) +EXTRA_DIST = run-test.sh + endif diff --git a/test/mifare_ultralight_fixture.c b/test/mifare_ultralight_fixture.c new file mode 100644 index 0000000..f4e984b --- /dev/null +++ b/test/mifare_ultralight_fixture.c @@ -0,0 +1,58 @@ +/*- + * Copyright (C) 2010, Romain Tartiere. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * 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 Lesser General Public License + * along with this program. If not, see + * + * $Id$ + */ + +#include +#include + +static nfc_device_t *device = NULL; +static MifareUltralightTag *tags = NULL; +MifareUltralightTag tag = NULL; + +void +setup () +{ + int res; + + device = nfc_connect (NULL); + cut_assert_not_null (device, "No device found"); + + tags = mifare_ultralight_get_tags (device); + cut_assert_not_null (tags ,"Error enumerating NFC tags"); + + cut_assert_not_null (tags[0], "No MIFARE CLassic tag on NFC device"); + + tag = tags[0]; + + res = mifare_ultralight_connect (tag); + cut_assert_equal_int (0, res); +} + +void +teardown () +{ + if (tag) + mifare_ultralight_disconnect (tag); + + if (tags) + mifare_ultralight_free_tags (tags); + + if (device) + nfc_disconnect (device); +} + diff --git a/test/mifare_ultralight_fixture.h b/test/mifare_ultralight_fixture.h new file mode 100644 index 0000000..7abd272 --- /dev/null +++ b/test/mifare_ultralight_fixture.h @@ -0,0 +1,20 @@ +/*- + * Copyright (C) 2010, Romain Tartiere. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * 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 Lesser General Public License + * along with this program. If not, see + * + * $Id$ + */ + +extern MifareUltralightTag tag; diff --git a/test/test_mifare_ultralight.c b/test/test_mifare_ultralight.c new file mode 100644 index 0000000..e86182a --- /dev/null +++ b/test/test_mifare_ultralight.c @@ -0,0 +1,157 @@ +/*- + * Copyright (C) 2010, Romain Tartiere. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * 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 Lesser General Public License + * along with this program. If not, see + * + * $Id$ + */ + +#include +#include +#include + +#include + +#include +#include "freefare_internal.h" + +#include "mifare_ultralight_fixture.h" + +void +test_mifare_ultralight_write (void) +{ + int res; + + MifareUltralightPage initial; + MifareUltralightPage page; + MifareUltralightPage payload1 = { 0x12, 0x34, 0x56, 0x78 }; + MifareUltralightPage payload2 = { 0xaa, 0x55, 0x00, 0xff }; + + MifareUltralightPageNumber n = 7; + + /* Read and save current value (should be { 0x00 0x00 0x00 0x00 }) */ + res = mifare_ultralight_read (tag, n, &initial); + cut_assert_equal_int (0, res, "mifare_ultralight_read failed"); + + /* Write payload1 */ + res = mifare_ultralight_write (tag, n, payload1); + cut_assert_equal_int (0, res, "mifare_ultralight_write failed"); + + /* Check it */ + res = mifare_ultralight_read (tag, n, &page); + cut_assert_equal_int (0, res, "mifare_ultralight_read failed"); + cut_assert_equal_memory (payload1, sizeof (payload1), page, sizeof (page)); + + /* Write payload2 */ + res = mifare_ultralight_write (tag, n, payload2); + cut_assert_equal_int (0, res, "mifare_ultralight_write failed"); + + /* Check it */ + res = mifare_ultralight_read (tag, n, &page); + cut_assert_equal_int (0, res, "mifare_ultralight_read failed"); + cut_assert_equal_memory (payload2, sizeof (payload2), page, sizeof (page)); + + /* Write initial data */ + res = mifare_ultralight_write (tag, n, initial); + cut_assert_equal_int (0, res, "mifare_ultralight_write failed"); + + /* While here check it (no reason to fail since the rest of the test passed) */ + res = mifare_ultralight_read (tag, n, &page); + cut_assert_equal_int (0, res, "mifare_ultralight_read failed"); + cut_assert_equal_memory (initial, sizeof (initial), page, sizeof (page)); +} + +void +test_mifare_ultralight_invalid_page (void) +{ + int res; + MifareUltralightPage page = { 0x00, 0x00, 0x00, 0x00 }; + + res = mifare_ultralight_read (tag, 16, &page); + cut_assert_equal_int (-1, res); + cut_assert_equal_int (EINVAL, errno); + + res = mifare_ultralight_write (tag, 16, page); + cut_assert_equal_int (-1, res); + cut_assert_equal_int (EINVAL, errno); +} + +void +test_mifare_ultralight_cache (void) +{ + int res; + MifareUltralightPage page; + + res = mifare_ultralight_read (tag, 0, &page); + cut_assert_equal_int (0, res, "mifare_ultralight_read() failed"); + + /* Check cached pages consistency */ + for (int i = 0; i <= 3; i++) { + cut_assert_equal_int (1, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); + } + for (int i = 4; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) { + cut_assert_equal_int (0, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); + } +} + +void +test_mifare_ultralight_cache_hit (void) +{ + int res; + + MifareUltralightPage page1; + MifareUltralightPage page2; + + res = mifare_ultralight_read (tag, 0, &page1); + cut_assert_equal_int (0, res, "mifare_ultralight_read() failed"); + + res = mifare_ultralight_read (tag, 0, &page2); + cut_assert_equal_int (0, res, "mifare_ultralight_read() failed"); + cut_assert_equal_memory (page1, sizeof (page1), page2, sizeof (page2)); +} + + +void +test_mifare_ultralight_cache_wrap (void) +{ + int res; + MifareUltralightPage page; + + res = mifare_ultralight_read (tag, 15, &page); + cut_assert_equal_int (0, res, "mifare_ultralight_read() failed"); + + /* Check cached pages consistency */ + for (int i = 0; i <= 2; i++) { + cut_assert_equal_int (1, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); + } + for (int i = 3; i <= 14; i++) { + cut_assert_equal_int (0, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); + } + for (int i = 15; i < MIFARE_ULTRALIGHT_PAGE_COUNT; i++) { + cut_assert_equal_int (1, tag->cached_pages[i], cut_message ("Wrong page cache value for tag->cached_pages[%d]", i)); + } +} + +void +test_mifare_ultralight_get_uid (void) +{ + char *uid; + + uid = mifare_ultralight_get_uid (tag); + + cut_assert_not_null (uid); + cut_assert_equal_int (14, strlen (uid)); + + free (uid); +}