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);
+}