From cdc6c7596f4986a1060ba08bacac00f85fc16a34 Mon Sep 17 00:00:00 2001 From: Tim Stableford Date: Tue, 27 Feb 2018 23:07:31 +0000 Subject: [PATCH 1/6] Added partial NTAG support --- binding.gyp | 2 +- examples/ntag.js | 100 ++++++++++++++++++++++ lib/freefare.js | 219 +++++++++++++++++++++++++++++++++++++++++------ src/common.h | 43 ++++++++++ src/device.cpp | 60 +++++++++++++ src/device.h | 1 + src/tag.cpp | 22 ++++- src/tag.h | 16 +++- src/tag_ntag.cpp | 209 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 639 insertions(+), 33 deletions(-) mode change 100644 => 100755 binding.gyp create mode 100755 examples/ntag.js mode change 100644 => 100755 lib/freefare.js mode change 100644 => 100755 src/device.cpp mode change 100644 => 100755 src/tag.cpp mode change 100644 => 100755 src/tag.h create mode 100755 src/tag_ntag.cpp diff --git a/binding.gyp b/binding.gyp old mode 100644 new mode 100755 index e6d3b96..9c0aa09 --- a/binding.gyp +++ b/binding.gyp @@ -3,7 +3,7 @@ { "target_name": "freefare", "defines": [ 'V8_DEPRECATION_WARNINGS=1', '_FILE_OFFSET_BITS=32' ], - "sources": [ "src/addon.cpp", "src/freefare.cpp", "src/device.cpp", "src/tag.cpp", "src/tag_mifareultralight.cpp", "src/tag_mifareclassic.cpp", "src/tag_mifaredesfire.cpp" ], + "sources": [ "src/addon.cpp", "src/freefare.cpp", "src/device.cpp", "src/tag.cpp", "src/tag_mifareultralight.cpp", "src/tag_mifareclassic.cpp", "src/tag_mifaredesfire.cpp", "src/tag_ntag.cpp" ], "include_dirs" : [ " kCardTimeout || lastTag.uid != tag.getUID()) { + lastTag = { + time: Date.now(), + uid: tag.getUID() + }; + try { + console.time('tagAdded'); + await tagAdded(device, tag); + console.timeEnd('tagAdded'); + } catch (err) { + console.log('tagAdded call failed', err); + startPoll(device); + } + } else { + startPoll(device); + } + } + } catch (err) { + console.log('Failed to list tags', err); + } +} + +function startPoll(device) { + setTimeout(function() { + listTags(device); + }, 0); +} + +(async function() { + const devices = await freefare.listDevices(); + await devices[0].open(); + startPoll(devices[0]); +})(); diff --git a/lib/freefare.js b/lib/freefare.js old mode 100644 new mode 100755 index 1c45cc3..f414387 --- a/lib/freefare.js +++ b/lib/freefare.js @@ -103,39 +103,66 @@ class Device { }); } + parseTag(cppTag) { + if (!cppTag) { + return null; + } + switch (cppTag.getTagType()) { + case 'MIFARE_CLASSIC_1K': + case 'MIFARE_CLASSIC_4K': + return new MifareClassicTag(cppTag); + case 'MIFARE_DESFIRE': + return new MifareDesfireTag(cppTag); + case 'MIFARE_ULTRALIGHT': + return new MifareUltralightTag(cppTag); + case 'NTAG_21x': + return new NtagTag(cppTag); + case 'MIFARE_ULTRALIGHT_C': + default: + return new Tag(cppTag); + } + } + + parseList(list) { + let res = []; + for (let cppTag of list) { + const tag = this.parseTag(cppTag); + if (tag) { + res.push(tag); + } + } + return res; + } + /** * List of detected tags * @return {Promise>} A promise to the list of `Tag` */ listTags() { + const $this = this; return new Promise((resolve, reject) => { this[cppObj].listTags((error, list) => { - if(error) { - switch (error) { - default: - reject(new Error('Unknown error (' + error + ')')); - } + if (error) { + return reject(new Error(error)); } - let res = []; - for (let cppTag of list) { - switch(cppTag.getTagType()) { - case 'MIFARE_CLASSIC_1K': - case 'MIFARE_CLASSIC_4K': - res.push(new MifareClassicTag(cppTag)); - break; - case 'MIFARE_DESFIRE': - res.push(new MifareDesfireTag(cppTag)); - break; - case 'MIFARE_ULTRALIGHT': - res.push(new MifareUltralightTag(cppTag)); - break; - case 'MIFARE_ULTRALIGHT_C': - default: - res.push(new Tag(cppTag)); - } + resolve(this.parseList(list)); + }); + }); + } + + /** + * Wait infinitely for a tag to be added. + * @return {Promise} A promise to of `Tag` + */ + poll() { + const $this = this; + return new Promise((resolve, reject) => { + this[cppObj].poll((error, tag) => { + if (error) { + return reject(new Error(error)); } - resolve(res); + resolve(this.parseTag(tag)); }); }); } @@ -512,7 +539,6 @@ class MifareClassicTag extends Tag { } } - /** * A MIFARE DESFire tag * @@ -699,7 +725,152 @@ class MifareDesfireTag extends Tag { }); }); } +} + +/** +* A NTAG_21x tag +* +* @class NTAG_21x Tag +* @extends Tag +*/ +class NtagTag extends Tag { + constructor(cppTag) { + super(cppTag); + } + + wrap(fn) { + const $arguments = Array.from(arguments).slice(1); + const $this = this; + return new Promise(function(resolve, reject) { + $arguments.push(function(error, data) { + if (error) { + reject(error); + } else { + resolve(data); + } + }); + fn.apply($this[cppObj], $arguments); + }); + } + /** + * Open tag for further communication + * @return {Promise} A promise to the end of the action. + */ + open() { + return this.wrap(this[cppObj].ntag_connect); + } + + /** + * Close a tag + * @return {Promise} A promise to the end of the action. + */ + close() { + return this.wrap(this[cppObj].ntag_disconnect); + } + + /** + * Retrieve info such as the sub-type. + * @return {Promise} A promise to the end of the action. + */ + getInfo() { + return this.wrap(this[cppObj].ntag_getInfo); + } + + /** + * Gets the tag sub-type as a string. getInfo must be called first. + * @return {Promise} A promise to the end of the action which returns the sub-type. + */ + getSubType() { + return this.wrap(this[cppObj].ntag_getType); + } + + /** + * Writes a 4 byte buffer to the given page. + * @param page The given page to write to. Must be between 0x00 and 0xff inclusive. + * @param buffer A 4 byte buffer to write to the given page. + * @return {Promise} A promise to the end of the action. + */ + write(page, buffer) { + if (page < 0 || page > 0xff) { + throw new Error('Invalid page'); + } + if (!(buffer instanceof Buffer)) { + throw new Error('Data is not a buffer'); + } + if (buffer.length != 4) { + throw new Error('Data is not 4 bytes'); + } + return this.wrap(this[cppObj].ntag_write, page, buffer); + } + + /** + * Reads a 4 byte buffer from the given page. + * @param page The given page to write to. Must be between 0x00 and 0xff inclusive. + * @return {Promise} A promise to the end of the action which returns a 4 byte buffer of the page. + */ + read(page) { + if (page < 0 || page > 0xff) { + throw new Error('Invalid page'); + } + return this.wrap(this[cppObj].ntag_read, page); + } + + /** + * Set a password for writing or reading and writing to tag. + * @param password A 4 byte buffer representing the password. + * @param pack A 2 byte buffer representing the password acknowledgement bytes. + * These could be considered as an extra 2 password bytes. + * @param startPage The page to start the protection from 0x00-0xff. + * @param prot Set to true to enable password protection for reads. False to only password protect writes. + * @return {Promise} A promise to the end of the action. + */ + setAuth(password, pack, startPage, prot) { + if (!(password instanceof Buffer)) { + throw new Error('Password is not a buffer'); + } + if (password.length != 4) { + throw new Error('Password is not 4 bytes'); + } + if (!(pack instanceof Buffer)) { + throw new Error('Pack is not a buffer'); + } + if (pack.length != 2) { + throw new Error('Pack is not 2 bytes'); + } + prot = prot ? true : false; + if (startPage < 0 || startPage > 0xff) { + throw new Error('Invalid start page'); + } + return this.wrap(this[cppObj].ntag_set_auth, password, pack, startPage, prot); + } + + async removeAuth() { + await this.setAuth(Buffer.from([0x0, 0x0, 0x0, 0x0]), Buffer.from([0x00, 0x00]), 0xff, false); + } + + /** + * Authenticates a tag with a given password. + * @param password A 4 byte buffer representing the password. + * @param pack A 2 byte buffer representing the password acknowledgement bytes. + * These could be considered as an extra 2 password bytes. + * @return {Promise} A promise to the end of the action. + */ + authenticate(password, pack) { + if (!(password instanceof Buffer)) { + throw new Error('Password is not a buffer'); + } + if (password.length != 4) { + throw new Error('Password is not 4 bytes'); + } + if (!(pack instanceof Buffer)) { + throw new Error('Pack is not a buffer'); + } + if (pack.length != 2) { + throw new Error('Pack is not 2 bytes'); + } + return this.wrap(this[cppObj].ntag_authenticate, password, pack); + } } module.exports = Freefare; diff --git a/src/common.h b/src/common.h index c9b92f2..f639b64 100644 --- a/src/common.h +++ b/src/common.h @@ -60,6 +60,49 @@ NFF_ERROR_LIBNFC_UNKNOWN // Global vars extern nfc_context* libnfc_context; +class AsyncWrapper : public Nan::AsyncWorker +{ +public: + typedef std::function ResultFunction; + typedef std::function ExecuteFunction; + AsyncWrapper(Nan::Callback *callback, + ExecuteFunction execute) + : AsyncWorker(callback), m_execute(execute) {} + ~AsyncWrapper() + { + if (this->m_args != nullptr) + { + delete[] m_args; + } + } + void Execute() + { + m_result = this->m_execute(); + } + void HandleOKCallback() + { + Nan::HandleScope scope; + m_result(*this); + callback->Call(this->m_argc, this->m_args, this->async_resource); + } + + void SetArgs(int argc, v8::Local *argv) + { + if (this->m_args != nullptr) + { + delete[] m_args; + } + this->m_args = new v8::Local[argc]; + memcpy(this->m_args, argv, sizeof(v8::Local) * argc); + this->m_argc = argc; + } + +private: + ExecuteFunction m_execute; + ResultFunction m_result; + v8::Local *m_args = nullptr; + int m_argc = 0; +}; #endif /* NFF_COMMON_H */ diff --git a/src/device.cpp b/src/device.cpp old mode 100644 new mode 100755 index a677c77..e111b7e --- a/src/device.cpp +++ b/src/device.cpp @@ -1,5 +1,11 @@ #include "device.h" +extern "C" { +#include +} + +#define MAX_CANDIDATES 16 + using namespace Nan; Device::Device(std::string connstring) : connstring(connstring) {} @@ -13,6 +19,7 @@ NAN_MODULE_INIT(Device::Init) { Nan::SetPrototypeMethod(tpl, "open", Device::Open); Nan::SetPrototypeMethod(tpl, "listTags", Device::ListTags); + Nan::SetPrototypeMethod(tpl, "poll", Device::Poll); Nan::SetPrototypeMethod(tpl, "getConnstring", Device::GetConnstring); Nan::SetPrototypeMethod(tpl, "abort", Device::Abort); @@ -229,3 +236,56 @@ NAN_METHOD(Device::Abort) { Callback *callback = new Callback(info[0].As()); AsyncQueueWorker(new AbortWorker(callback, obj->device)); } + +int FreefareList(nfc_device *device, FreefareTag &tag) { + nfc_initiator_init(device); + // Disabling NP_AUTO_ISO14443_4 saves a massive amount of time. ~400ms. + nfc_device_set_property_bool(device, NP_AUTO_ISO14443_4, false); + + // Poll infinitely + const uint8_t uiPollNr = 0xff; + // Period in increments of 150ms. So poll every 150ms. + const uint8_t uiPeriod = 1; + const nfc_modulation nmModulations[1] = { + { .nmt = NMT_ISO14443A, .nbr = NBR_106 } + }; + const size_t szModulations = sizeof(nmModulations) / sizeof(nfc_modulation); + nfc_target nt; + + int res = 0; + if ((res = nfc_initiator_poll_target(device, nmModulations, szModulations, uiPollNr, uiPeriod, &nt)) < 0) { + nfc_perror(device, "nfc_initiator_poll_target"); + return res; + } + + if (res > 0) { + tag = freefare_tag_new(device, nt); + return res; + } + + return 0; +} + +NAN_METHOD(Device::Poll){ + Device *obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[0].As()); + + AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { + FreefareTag tag; + int res = FreefareList(obj->device, tag); + return [res, tag](AsyncWrapper &wrapper) { + v8::Local err = Nan::Null(); + v8::Local result = Nan::Null(); + if (res < 0) { + err = Nan::New(res); + } else if (res > 0) { + result = Tag::Instantiate(tag); + } + v8::Local argv[] = { + err, + result + }; + wrapper.SetArgs(2, argv); + }; + })); +} diff --git a/src/device.h b/src/device.h index 6a355ff..f48981c 100644 --- a/src/device.h +++ b/src/device.h @@ -32,6 +32,7 @@ class Device: public Nan::ObjectWrap { static NAN_METHOD(Close); static NAN_METHOD(GetConnstring); static NAN_METHOD(ListTags); + static NAN_METHOD(Poll); static NAN_METHOD(Abort); diff --git a/src/tag.cpp b/src/tag.cpp old mode 100644 new mode 100755 index c18b9ec..8c958fa --- a/src/tag.cpp +++ b/src/tag.cpp @@ -2,10 +2,12 @@ using namespace Nan; -Tag::Tag(MifareTag tag) : tag(tag) {} -Tag::~Tag() {} +Tag::Tag(FreefareTag tag) : tag(tag) {} +Tag::~Tag() { + freefare_free_tag(tag); +} -MifareTag Tag::constructorTag = NULL; +FreefareTag Tag::constructorTag = NULL; // TODO free tag on delete @@ -46,6 +48,15 @@ NAN_MODULE_INIT(Tag::Init) { Nan::SetPrototypeMethod(tpl, "mifareDesfire_getFileIds", Tag::mifareDesfire_getFileIds); Nan::SetPrototypeMethod(tpl, "mifareDesfire_write", Tag::mifareDesfire_write); Nan::SetPrototypeMethod(tpl, "mifareDesfire_read", Tag::mifareDesfire_read); + + Nan::SetPrototypeMethod(tpl, "ntag_connect", Tag::ntag_connect); + Nan::SetPrototypeMethod(tpl, "ntag_disconnect", Tag::ntag_disconnect); + Nan::SetPrototypeMethod(tpl, "ntag_getInfo", Tag::ntag_getInfo); + Nan::SetPrototypeMethod(tpl, "ntag_getType", Tag::ntag_getType); + Nan::SetPrototypeMethod(tpl, "ntag_read", Tag::ntag_read); + Nan::SetPrototypeMethod(tpl, "ntag_write", Tag::ntag_write); + Nan::SetPrototypeMethod(tpl, "ntag_set_auth", Tag::ntag_set_auth); + Nan::SetPrototypeMethod(tpl, "ntag_authenticate", Tag::ntag_authenticate); constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked()); @@ -72,7 +83,7 @@ NAN_METHOD(Tag::New) { } } -v8::Handle Tag::Instantiate(MifareTag constructorTag) { +v8::Handle Tag::Instantiate(FreefareTag constructorTag) { Nan::EscapableHandleScope scope; Tag::constructorTag = constructorTag; @@ -93,6 +104,9 @@ NAN_METHOD(Tag::GetTagType) { case DESFIRE: typeStr = "MIFARE_DESFIRE"; break; case ULTRALIGHT: typeStr = "MIFARE_ULTRALIGHT"; break; case ULTRALIGHT_C: typeStr = "MIFARE_ULTRALIGHT_C"; break; + case NTAG_21x: typeStr = "NTAG_21x"; break; + case FELICA: typeStr = "FELICA"; break; + case MIFARE_MINI: typeStr = "MIFARE_MINI"; break; } info.GetReturnValue().Set(Nan::New(typeStr).ToLocalChecked()); diff --git a/src/tag.h b/src/tag.h old mode 100644 new mode 100755 index da4f041..1abc09c --- a/src/tag.h +++ b/src/tag.h @@ -20,10 +20,10 @@ class Tag: public Nan::ObjectWrap { public: static NAN_MODULE_INIT(Init); - static v8::Handle Instantiate(MifareTag tag); + static v8::Handle Instantiate(FreefareTag tag); private: - explicit Tag(MifareTag tag); + explicit Tag(FreefareTag tag); ~Tag(); static inline Nan::Persistent & constructor(); @@ -60,15 +60,23 @@ class Tag: public Nan::ObjectWrap { static NAN_METHOD(mifareDesfire_write); static NAN_METHOD(mifareDesfire_read); + static NAN_METHOD(ntag_connect); + static NAN_METHOD(ntag_disconnect); + static NAN_METHOD(ntag_getInfo); + static NAN_METHOD(ntag_getType); + static NAN_METHOD(ntag_read); + static NAN_METHOD(ntag_write); + static NAN_METHOD(ntag_set_auth); + static NAN_METHOD(ntag_authenticate); private: nfc_device* device; std::string connstring; - MifareTag tag; + FreefareTag tag; - static MifareTag constructorTag; + static FreefareTag constructorTag; }; diff --git a/src/tag_ntag.cpp b/src/tag_ntag.cpp new file mode 100755 index 0000000..601db8a --- /dev/null +++ b/src/tag_ntag.cpp @@ -0,0 +1,209 @@ +#include "tag.h" + +#include + +using namespace Nan; + +#define READ_WRITE_BYTES (4) // Number of bytes to read/write at a time. +#define PASSWORD_LENGTH (4) // Length of password in bytes. +#define PACK_LENGTH (2) // Password acknowledgement length in bytes. + +NAN_METHOD(Tag::ntag_connect) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[0].As()); + AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { + int error = ntag21x_connect(obj->tag); + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_disconnect) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[0].As()); + AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { + int error = ntag21x_disconnect(obj->tag); + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_getInfo) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[0].As()); + AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { + int error = ntag21x_get_info(obj->tag); + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_getType) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[0].As()); + AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { + ntag_tag_subtype type = ntag21x_get_subtype(obj->tag); + std::string typeStr = "Unknown"; + switch (type) { + case NTAG_213: + typeStr = "NTAG213"; + break; + case NTAG_215: + typeStr = "NTAG215"; + break; + case NTAG_216: + typeStr = "NTAG216"; + break; + default: + break; + } + return [typeStr](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(0), + Nan::New(typeStr).ToLocalChecked() + }; + wrapper.SetArgs(2, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_read) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[1].As()); + uint32_t page = info[0]->Uint32Value(); + AsyncQueueWorker(new AsyncWrapper(callback, [page, obj]() { + uint8_t *data = new uint8_t[READ_WRITE_BYTES]; + int error = ntag21x_fast_read4(obj->tag, page, data); + return [data, error](AsyncWrapper &wrapper) { + Nan::MaybeLocal buf = Nan::CopyBuffer(reinterpret_cast(data), 4); + delete[] data; + v8::Local argv[] = { + Nan::New(error), + buf.ToLocalChecked() + }; + wrapper.SetArgs(2, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_write) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + uint32_t page = info[0]->Uint32Value(); + uint8_t *origBuffer = reinterpret_cast(node::Buffer::Data(info[1])); + uint8_t *buffer = new uint8_t[READ_WRITE_BYTES]; + memcpy(buffer, origBuffer, READ_WRITE_BYTES); + Callback *callback = new Callback(info[2].As()); + AsyncQueueWorker(new AsyncWrapper(callback, [obj, page, buffer]() { + int error = ntag21x_write(obj->tag, page, buffer); + delete[] buffer; + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_set_auth) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + + uint8_t *origBuffer = reinterpret_cast(node::Buffer::Data(info[0])); + uint8_t *password = new uint8_t[PASSWORD_LENGTH]; + memcpy(password, origBuffer, PASSWORD_LENGTH); + + uint8_t *origPack = reinterpret_cast(node::Buffer::Data(info[1])); + uint8_t *pack = new uint8_t[PACK_LENGTH]; + memcpy(pack, origPack, PACK_LENGTH); + + // Verified range in freefare.js + uint8_t startPage = static_cast(info[2]->Uint32Value()); + bool prot = info[3]->BooleanValue(); + + Callback *callback = new Callback(info[4].As()); + + AsyncQueueWorker(new AsyncWrapper(callback, [obj, password, pack, startPage, prot]() { + NTAG21xKey key = ntag21x_key_new(password, pack); + + int error = ntag21x_set_key(obj->tag, key); + if (error < 0) { + error = -1; + goto end; + } + + // Authenticate to ensure password and pack are set correctly. + error = ntag21x_authenticate(obj->tag, key); + if (error < 0) { + error = -2; + goto end; + } + + ntag21x_key_free(key); + + error = ntag21x_set_auth(obj->tag, startPage); + if (error < 0) { + error = -3; + goto end; + } + + if (prot) { + error = ntag21x_access_enable(obj->tag, NTAG_PROT); + } else { + error = ntag21x_access_disable(obj->tag, NTAG_PROT); + } + if (error < 0) { + error = -4; + } + + end: + delete[] password; + delete[] pack; + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + +NAN_METHOD(Tag::ntag_authenticate) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + + uint8_t *origBuffer = reinterpret_cast(node::Buffer::Data(info[0])); + uint8_t *password = new uint8_t[PASSWORD_LENGTH]; + memcpy(password, origBuffer, PASSWORD_LENGTH); + + uint8_t *origPack = reinterpret_cast(node::Buffer::Data(info[1])); + uint8_t *pack = new uint8_t[PACK_LENGTH]; + memcpy(pack, origPack, PACK_LENGTH); + + Callback *callback = new Callback(info[2].As()); + + AsyncQueueWorker(new AsyncWrapper(callback, [obj, password, pack]() { + NTAG21xKey key = ntag21x_key_new(password, pack); + int error = ntag21x_authenticate(obj->tag, key); + ntag21x_key_free(key); + + delete[] password; + delete[] pack; + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} From 042e4dee4181cb59ad3cab61fc6f76d4a7cbb529 Mon Sep 17 00:00:00 2001 From: Tim Stableford Date: Tue, 27 Feb 2018 23:07:57 +0000 Subject: [PATCH 2/6] Added initial README.md changes --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) mode change 100644 => 100755 README.md diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 4c310db..eb5deac --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Due to lack of time and testing material, the binding is not complete : * Mifare Ultralight : fully Supported * Mifare Classic 1K/4K : Partially supported (enough to authenticate, write and read a data block) * Mifare DESFire : Partially supported (enough to select an application, authenticate in DES/3DES and read/write on a file) +* NTAG21x : Partially supported (read/write, authnetication) If you need Freefare function which are currently not bound, submit an issue, a pull request or contact me by email. @@ -66,6 +67,12 @@ List of detected tags **Returns**: `Promise.>`, A promise to the list of `Tag` +#### Device.poll() + +Polls for NFC devices infinitely. When a device is found it returns the tag. + +**Returns**: `Promise.>`, A promise of a `Tag` + #### Device.abort() Abort command blocking the device like open(). From 73d27287dbf92fc14e32120ed714021a048ab85b Mon Sep 17 00:00:00 2001 From: Tim Stableford Date: Tue, 27 Feb 2018 23:12:24 +0000 Subject: [PATCH 3/6] Formatting fixes --- lib/freefare.js | 43 ++++++++++++++++++++++--------------------- src/device.cpp | 4 ++-- src/tag.cpp | 10 +++++----- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/freefare.js b/lib/freefare.js index f414387..4d0a455 100755 --- a/lib/freefare.js +++ b/lib/freefare.js @@ -539,6 +539,7 @@ class MifareClassicTag extends Tag { } } + /** * A MIFARE DESFire tag * @@ -737,53 +738,53 @@ class NtagTag extends Tag { constructor(cppTag) { super(cppTag); } - - wrap(fn) { + + wrap(fn) { const $arguments = Array.from(arguments).slice(1); const $this = this; - return new Promise(function(resolve, reject) { + return new Promise(function(resolve, reject) { $arguments.push(function(error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); + if (error) { + reject(error); + } else { + resolve(data); + } + }); fn.apply($this[cppObj], $arguments); - }); - } + }); + } /** * Open tag for further communication * @return {Promise} A promise to the end of the action. */ open() { - return this.wrap(this[cppObj].ntag_connect); + return this.wrap(this[cppObj].ntag_connect); } /** * Close a tag * @return {Promise} A promise to the end of the action. */ - close() { - return this.wrap(this[cppObj].ntag_disconnect); - } + close() { + return this.wrap(this[cppObj].ntag_disconnect); + } /** * Retrieve info such as the sub-type. * @return {Promise} A promise to the end of the action. */ - getInfo() { - return this.wrap(this[cppObj].ntag_getInfo); - } + getInfo() { + return this.wrap(this[cppObj].ntag_getInfo); + } /** * Gets the tag sub-type as a string. getInfo must be called first. * @return {Promise} A promise to the end of the action which returns the sub-type. */ - getSubType() { - return this.wrap(this[cppObj].ntag_getType); - } + getSubType() { + return this.wrap(this[cppObj].ntag_getType); + } /** * Writes a 4 byte buffer to the given page. diff --git a/src/device.cpp b/src/device.cpp index e111b7e..2e118ea 100755 --- a/src/device.cpp +++ b/src/device.cpp @@ -237,7 +237,7 @@ NAN_METHOD(Device::Abort) { AsyncQueueWorker(new AbortWorker(callback, obj->device)); } -int FreefareList(nfc_device *device, FreefareTag &tag) { +int FreefarePoll(nfc_device *device, FreefareTag &tag) { nfc_initiator_init(device); // Disabling NP_AUTO_ISO14443_4 saves a massive amount of time. ~400ms. nfc_device_set_property_bool(device, NP_AUTO_ISO14443_4, false); @@ -272,7 +272,7 @@ NAN_METHOD(Device::Poll){ AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { FreefareTag tag; - int res = FreefareList(obj->device, tag); + int res = FreefarePoll(obj->device, tag); return [res, tag](AsyncWrapper &wrapper) { v8::Local err = Nan::Null(); v8::Local result = Nan::Null(); diff --git a/src/tag.cpp b/src/tag.cpp index 8c958fa..559a756 100755 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -48,11 +48,11 @@ NAN_MODULE_INIT(Tag::Init) { Nan::SetPrototypeMethod(tpl, "mifareDesfire_getFileIds", Tag::mifareDesfire_getFileIds); Nan::SetPrototypeMethod(tpl, "mifareDesfire_write", Tag::mifareDesfire_write); Nan::SetPrototypeMethod(tpl, "mifareDesfire_read", Tag::mifareDesfire_read); - - Nan::SetPrototypeMethod(tpl, "ntag_connect", Tag::ntag_connect); + + Nan::SetPrototypeMethod(tpl, "ntag_connect", Tag::ntag_connect); Nan::SetPrototypeMethod(tpl, "ntag_disconnect", Tag::ntag_disconnect); - Nan::SetPrototypeMethod(tpl, "ntag_getInfo", Tag::ntag_getInfo); - Nan::SetPrototypeMethod(tpl, "ntag_getType", Tag::ntag_getType); + Nan::SetPrototypeMethod(tpl, "ntag_getInfo", Tag::ntag_getInfo); + Nan::SetPrototypeMethod(tpl, "ntag_getType", Tag::ntag_getType); Nan::SetPrototypeMethod(tpl, "ntag_read", Tag::ntag_read); Nan::SetPrototypeMethod(tpl, "ntag_write", Tag::ntag_write); Nan::SetPrototypeMethod(tpl, "ntag_set_auth", Tag::ntag_set_auth); @@ -104,7 +104,7 @@ NAN_METHOD(Tag::GetTagType) { case DESFIRE: typeStr = "MIFARE_DESFIRE"; break; case ULTRALIGHT: typeStr = "MIFARE_ULTRALIGHT"; break; case ULTRALIGHT_C: typeStr = "MIFARE_ULTRALIGHT_C"; break; - case NTAG_21x: typeStr = "NTAG_21x"; break; + case NTAG_21x: typeStr = "NTAG_21x"; break; case FELICA: typeStr = "FELICA"; break; case MIFARE_MINI: typeStr = "MIFARE_MINI"; break; } From 1f9cb4cdf972268e9807d3ea0716dae71afdf9bc Mon Sep 17 00:00:00 2001 From: Tim Stableford Date: Tue, 27 Feb 2018 23:14:25 +0000 Subject: [PATCH 4/6] Removed redundant variables --- lib/freefare.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/freefare.js b/lib/freefare.js index 4d0a455..e83e4a0 100755 --- a/lib/freefare.js +++ b/lib/freefare.js @@ -139,7 +139,6 @@ class Device { * @return {Promise>} A promise to the list of `Tag` */ listTags() { - const $this = this; return new Promise((resolve, reject) => { this[cppObj].listTags((error, list) => { if (error) { @@ -156,7 +155,6 @@ class Device { * @return {Promise} A promise to of `Tag` */ poll() { - const $this = this; return new Promise((resolve, reject) => { this[cppObj].poll((error, tag) => { if (error) { From dea3f40bd020cfcb8aa9babbd1c5083fd67c044b Mon Sep 17 00:00:00 2001 From: Tim Stableford Date: Tue, 27 Feb 2018 23:30:42 +0000 Subject: [PATCH 5/6] Fixed errors with setAuth causing lockout setAuth and removeAuth could cause lockouts if the password were set correctly but the pack not. --- examples/ntag.js | 6 +++++- lib/freefare.js | 4 ++-- src/tag.cpp | 1 + src/tag.h | 9 +++++---- src/tag_ntag.cpp | 42 ++++++++++++++++++++++++++++++++++++++---- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/examples/ntag.js b/examples/ntag.js index eeaed39..1084d89 100755 --- a/examples/ntag.js +++ b/examples/ntag.js @@ -46,7 +46,11 @@ async function tagAdded(device, tag) { console.log('Read data from 0x27', await tag.read(0x27)); console.log('Disabling auth'); - await tag.removeAuth(); + try { + await tag.disableAuth(); + } catch (err) { + console.log('Failed to remove authentication'); + } } catch (err) { console.log('Tag authenticate failed', err); } diff --git a/lib/freefare.js b/lib/freefare.js index e83e4a0..1d0e4d1 100755 --- a/lib/freefare.js +++ b/lib/freefare.js @@ -844,8 +844,8 @@ class NtagTag extends Tag { return this.wrap(this[cppObj].ntag_set_auth, password, pack, startPage, prot); } - async removeAuth() { - await this.setAuth(Buffer.from([0x0, 0x0, 0x0, 0x0]), Buffer.from([0x00, 0x00]), 0xff, false); + disableAuth() { + return this.wrap(this[cppObj].ntag_disable_auth); } /** diff --git a/src/tag.cpp b/src/tag.cpp index 559a756..6d2f1b7 100755 --- a/src/tag.cpp +++ b/src/tag.cpp @@ -56,6 +56,7 @@ NAN_MODULE_INIT(Tag::Init) { Nan::SetPrototypeMethod(tpl, "ntag_read", Tag::ntag_read); Nan::SetPrototypeMethod(tpl, "ntag_write", Tag::ntag_write); Nan::SetPrototypeMethod(tpl, "ntag_set_auth", Tag::ntag_set_auth); + Nan::SetPrototypeMethod(tpl, "ntag_disable_auth", Tag::ntag_disable_auth); Nan::SetPrototypeMethod(tpl, "ntag_authenticate", Tag::ntag_authenticate); diff --git a/src/tag.h b/src/tag.h index 1abc09c..08b91e0 100755 --- a/src/tag.h +++ b/src/tag.h @@ -60,13 +60,14 @@ class Tag: public Nan::ObjectWrap { static NAN_METHOD(mifareDesfire_write); static NAN_METHOD(mifareDesfire_read); - static NAN_METHOD(ntag_connect); - static NAN_METHOD(ntag_disconnect); - static NAN_METHOD(ntag_getInfo); - static NAN_METHOD(ntag_getType); + static NAN_METHOD(ntag_connect); + static NAN_METHOD(ntag_disconnect); + static NAN_METHOD(ntag_getInfo); + static NAN_METHOD(ntag_getType); static NAN_METHOD(ntag_read); static NAN_METHOD(ntag_write); static NAN_METHOD(ntag_set_auth); + static NAN_METHOD(ntag_disable_auth); static NAN_METHOD(ntag_authenticate); private: diff --git a/src/tag_ntag.cpp b/src/tag_ntag.cpp index 601db8a..3dee4e6 100755 --- a/src/tag_ntag.cpp +++ b/src/tag_ntag.cpp @@ -137,16 +137,24 @@ NAN_METHOD(Tag::ntag_set_auth) { AsyncQueueWorker(new AsyncWrapper(callback, [obj, password, pack, startPage, prot]() { NTAG21xKey key = ntag21x_key_new(password, pack); - int error = ntag21x_set_key(obj->tag, key); + // First disable auth in case set key fails. + // Set key may fail by setting the password but not the pack. + int error = ntag21x_set_auth(obj->tag, startPage); if (error < 0) { error = -1; goto end; } + error = ntag21x_set_key(obj->tag, key); + if (error < 0) { + error = -2; + goto end; + } + // Authenticate to ensure password and pack are set correctly. error = ntag21x_authenticate(obj->tag, key); if (error < 0) { - error = -2; + error = -3; goto end; } @@ -154,7 +162,7 @@ NAN_METHOD(Tag::ntag_set_auth) { error = ntag21x_set_auth(obj->tag, startPage); if (error < 0) { - error = -3; + error = -4; goto end; } @@ -164,7 +172,7 @@ NAN_METHOD(Tag::ntag_set_auth) { error = ntag21x_access_disable(obj->tag, NTAG_PROT); } if (error < 0) { - error = -4; + error = -5; } end: @@ -179,6 +187,32 @@ NAN_METHOD(Tag::ntag_set_auth) { })); } +NAN_METHOD(Tag::ntag_disable_auth) { + Tag* obj = ObjectWrap::Unwrap(info.This()); + Callback *callback = new Callback(info[0].As()); + + AsyncQueueWorker(new AsyncWrapper(callback, [obj]() { + int error = ntag21x_set_auth(obj->tag, 0xff); + if (error < 0) { + error = -1; + goto end; + } + + error = ntag21x_access_disable(obj->tag, NTAG_PROT); + if (error < 0) { + error = -2; + } + + end: + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + NAN_METHOD(Tag::ntag_authenticate) { Tag* obj = ObjectWrap::Unwrap(info.This()); From acc104a5e2ac74650b967a4365f6f3fca65d34e9 Mon Sep 17 00:00:00 2001 From: Tim Stableford Date: Tue, 27 Feb 2018 23:31:58 +0000 Subject: [PATCH 6/6] Spaces to tabs --- examples/ntag.js | 140 +++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/examples/ntag.js b/examples/ntag.js index 1084d89..04eda85 100755 --- a/examples/ntag.js +++ b/examples/ntag.js @@ -10,7 +10,7 @@ const Freefare = require('../index'); const freefare = new Freefare(); process.on('unhandledRejection', function(error, p) { - console.log('Unhandled Rejection at: Promise', p, 'reason:', error); + console.log('Unhandled Rejection at: Promise', p, 'reason:', error); }); // The 4 byte buffer for the tag password. @@ -20,85 +20,85 @@ const kPassword = Buffer.from([0x11, 0x22, 0x33, 0x44]); const kPack = Buffer.from([0xaa, 0xaa]); async function tagAdded(device, tag) { - if (tag.getType() == 'NTAG_21x') { - await tag.open(); - await tag.getInfo(); - const subtype = await tag.getSubType(); - console.log(`${tag.getFriendlyName()} - ${tag.getUID()} - ${subtype}`); - try { - await tag.write(0x27, Buffer.from([0x01, 0x02, 0x03, 0x04])); - console.log('Read data from 0x27', await tag.read(0x27)); - // If the read suceeds then do set auth. - try { - await tag.setAuth(kPassword, kPack, 0x00, true); - console.log('Tag auth set'); - } catch (err) { - console.log('Unable to set tag auth', err); - } - } catch (err) { - console.log('Unable to read from tag, attempting auth', err); - try { - // If other commands are run before this then the tag must be re-opened. - await tag.close(); - await tag.open(); - await tag.authenticate(kPassword, kPack); - console.log('Tag authentication success, attempting read'); - console.log('Read data from 0x27', await tag.read(0x27)); - - console.log('Disabling auth'); - try { - await tag.disableAuth(); - } catch (err) { - console.log('Failed to remove authentication'); - } - } catch (err) { - console.log('Tag authenticate failed', err); - } - } + if (tag.getType() == 'NTAG_21x') { + await tag.open(); + await tag.getInfo(); + const subtype = await tag.getSubType(); + console.log(`${tag.getFriendlyName()} - ${tag.getUID()} - ${subtype}`); + try { + await tag.write(0x27, Buffer.from([0x01, 0x02, 0x03, 0x04])); + console.log('Read data from 0x27', await tag.read(0x27)); + // If the read suceeds then do set auth. + try { + await tag.setAuth(kPassword, kPack, 0x00, true); + console.log('Tag auth set'); + } catch (err) { + console.log('Unable to set tag auth', err); + } + } catch (err) { + console.log('Unable to read from tag, attempting auth', err); + try { + // If other commands are run before this then the tag must be re-opened. + await tag.close(); + await tag.open(); + await tag.authenticate(kPassword, kPack); + console.log('Tag authentication success, attempting read'); + console.log('Read data from 0x27', await tag.read(0x27)); + + console.log('Disabling auth'); + try { + await tag.disableAuth(); + } catch (err) { + console.log('Failed to remove authentication'); + } + } catch (err) { + console.log('Tag authenticate failed', err); + } + } - await tag.close(); - } else { - console.log(`${tag.getFriendlyName()} - ${tag.getUID()}`); - } - startPoll(device); + await tag.close(); + } else { + console.log(`${tag.getFriendlyName()} - ${tag.getUID()}`); + } + startPoll(device); } let lastTag = null; const kCardTimeout = 5 * 1000; async function listTags(device) { - try { - const tag = await device.poll(); - if (tag) { - if (!lastTag || Date.now() - lastTag.time > kCardTimeout || lastTag.uid != tag.getUID()) { - lastTag = { - time: Date.now(), - uid: tag.getUID() - }; - try { - console.time('tagAdded'); - await tagAdded(device, tag); - console.timeEnd('tagAdded'); - } catch (err) { - console.log('tagAdded call failed', err); - startPoll(device); - } - } else { - startPoll(device); - } - } - } catch (err) { - console.log('Failed to list tags', err); - } + try { + const tag = await device.poll(); + if (tag) { + if (!lastTag || Date.now() - lastTag.time > kCardTimeout || lastTag.uid != tag.getUID()) { + lastTag = { + time: Date.now(), + uid: tag.getUID() + }; + try { + console.time('tagAdded'); + await tagAdded(device, tag); + console.timeEnd('tagAdded'); + } catch (err) { + console.log('tagAdded call failed', err); + startPoll(device); + } + } else { + startPoll(device); + } + } + } catch (err) { + console.log('Failed to list tags', err); + } } function startPoll(device) { - setTimeout(function() { - listTags(device); - }, 0); + setTimeout(function() { + listTags(device); + }, 0); } (async function() { - const devices = await freefare.listDevices(); - await devices[0].open(); - startPoll(devices[0]); + const devices = await freefare.listDevices(); + await devices[0].open(); + startPoll(devices[0]); })();