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(). 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..1d0e4d1 --- a/lib/freefare.js +++ b/lib/freefare.js @@ -103,6 +103,37 @@ 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` @@ -110,32 +141,26 @@ class Device { listTags() { 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() { + return new Promise((resolve, reject) => { + this[cppObj].poll((error, tag) => { + if (error) { + return reject(new Error(error)); } - resolve(res); + resolve(this.parseTag(tag)); }); }); } @@ -699,7 +724,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); + } + + disableAuth() { + return this.wrap(this[cppObj].ntag_disable_auth); + } + + /** + * 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..2e118ea --- 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 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); + + // 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 = FreefarePoll(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..6d2f1b7 --- 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,16 @@ 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_disable_auth", Tag::ntag_disable_auth); + Nan::SetPrototypeMethod(tpl, "ntag_authenticate", Tag::ntag_authenticate); constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked()); @@ -72,7 +84,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 +105,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..08b91e0 --- 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,24 @@ 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_disable_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..3dee4e6 --- /dev/null +++ b/src/tag_ntag.cpp @@ -0,0 +1,243 @@ +#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); + + // 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 = -3; + goto end; + } + + ntag21x_key_free(key); + + error = ntag21x_set_auth(obj->tag, startPage); + if (error < 0) { + error = -4; + 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 = -5; + } + + end: + delete[] password; + delete[] pack; + return [error](AsyncWrapper &wrapper) { + v8::Local argv[] = { + Nan::New(error) + }; + wrapper.SetArgs(1, argv); + }; + })); +} + +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()); + + 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); + }; + })); +}