Initial Implementation
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,3 +12,5 @@ logs
|
|||||||
results
|
results
|
||||||
|
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
node_modules
|
||||||
|
build/
|
||||||
|
|||||||
13
LICENSE
Normal file
13
LICENSE
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Copyright (c) 2013, Santiago Gimeno <santiago.gimeno@gmail>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
51
README.md
51
README.md
@@ -2,3 +2,54 @@ node-pcsclite
|
|||||||
=============
|
=============
|
||||||
|
|
||||||
Bindings over pcsclite to access Smart Cards
|
Bindings over pcsclite to access Smart Cards
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
var pcsc = require('pcsclite');
|
||||||
|
|
||||||
|
var pcsc = pcsc();
|
||||||
|
/* Check for new card reader detection */
|
||||||
|
pcsc.on('reader', function(reader) {
|
||||||
|
console.log('New reader detected', reader.name);
|
||||||
|
|
||||||
|
/* Check for reader status changes such a new card insertion */
|
||||||
|
reader.on('status', function(status) {
|
||||||
|
console.log('Status(', this.name, '):', status);
|
||||||
|
/* check what has changed */
|
||||||
|
var changes = this.status ^ status;
|
||||||
|
if (changes) {
|
||||||
|
if ((changes & this.SCARD_STATE_EMPTY) && (status & this.SCARD_STATE_EMPTY)) {
|
||||||
|
console.log("card removed");/* card removed */
|
||||||
|
} else if ((changes & this.SCARD_STATE_PRESENT) && (status & this.SCARD_STATE_PRESENT)) {
|
||||||
|
console.log("card inserted");/* card inserted */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* You can connect to a smart card */
|
||||||
|
reader.connect(function(err, protocol) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
console.log('Protocol(', this.name, '):', protocol);
|
||||||
|
/* And transmit data */
|
||||||
|
reader.transmit(new Buffer([0x00, 0xB0, 0x00, 0x00, 0x20]), 40, 1, function(err, data) {
|
||||||
|
if (err) console.log(err);
|
||||||
|
else console.log('Data received', data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('end', function() {
|
||||||
|
console.log('Reader', this.name, 'removed');
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('error', function(err) {
|
||||||
|
console.log('Error(', this.name, '):', err.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pcsc.on('error', function(err) {
|
||||||
|
console.log('PCSC error', err.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
19
binding.gyp
Normal file
19
binding.gyp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'pcsclite',
|
||||||
|
'sources': [ 'src/addon.cpp', 'src/pcsclite.cpp', 'src/cardreader.cpp' ],
|
||||||
|
'include_dirs': [
|
||||||
|
'/usr/include/PCSC'
|
||||||
|
],
|
||||||
|
'link_settings': {
|
||||||
|
'libraries': [
|
||||||
|
'-lpcsclite'
|
||||||
|
],
|
||||||
|
'library_dirs': [
|
||||||
|
'/usr/lib'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
44
examples/example.js
Normal file
44
examples/example.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
var pcsc = require('../index');
|
||||||
|
|
||||||
|
var pcsc = pcsc();
|
||||||
|
pcsc.on('reader', function(reader) {
|
||||||
|
|
||||||
|
console.log('New reader detected', reader.name);
|
||||||
|
|
||||||
|
reader.on('error', function(err) {
|
||||||
|
console.log('Error(', this.name, '):', err.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('status', function(status) {
|
||||||
|
console.log('Status(', this.name, '):', status);
|
||||||
|
/* check what has changed */
|
||||||
|
var changes = this.status ^ status;
|
||||||
|
if (changes) {
|
||||||
|
if ((changes & this.SCARD_STATE_EMPTY) && (status & this.SCARD_STATE_EMPTY)) {
|
||||||
|
console.log("card removed");/* card removed */
|
||||||
|
} else if ((changes & this.SCARD_STATE_PRESENT) && (status & this.SCARD_STATE_PRESENT)) {
|
||||||
|
console.log("card inserted");/* card inserted */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('end', function() {
|
||||||
|
console.log('Reader', this.name, 'removed');
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.connect(function(err, protocol) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
} else {
|
||||||
|
console.log('Protocol(', this.name, '):', protocol);
|
||||||
|
reader.transmit(new Buffer([0x00, 0xB0, 0x00, 0x00, 0x20]), 40, 1, function(err, data) {
|
||||||
|
if (err) console.log(err);
|
||||||
|
else console.log('Data received', data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pcsc.on('error', function(err) {
|
||||||
|
console.log('PCSC error', err.message);
|
||||||
|
});
|
||||||
104
lib/pcsclite.js
Normal file
104
lib/pcsclite.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
var events = require('events');
|
||||||
|
require('buffertools');
|
||||||
|
|
||||||
|
/* Make sure we choose the correct build directory */
|
||||||
|
var bindings = require('bindings')('pcsclite');
|
||||||
|
var PCSCLite = bindings.PCSCLite;
|
||||||
|
var CardReader = bindings.CardReader;
|
||||||
|
inherits(PCSCLite, events.EventEmitter);
|
||||||
|
inherits(CardReader, events.EventEmitter);
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
|
||||||
|
var readers = [];
|
||||||
|
var p = new PCSCLite();
|
||||||
|
process.nextTick(function() {
|
||||||
|
p.start(function(e, data) {
|
||||||
|
if (e) {
|
||||||
|
p.emit('error', e);
|
||||||
|
} else {
|
||||||
|
/* parse data buffer to get the card reader name, and get the reader */
|
||||||
|
var readers_aux = [];
|
||||||
|
var ini = 0;
|
||||||
|
var pos = 0;
|
||||||
|
while((pos = data.slice(ini).indexOf('\0')) > 0) {
|
||||||
|
var name = data.slice(ini, ini + pos).toString();
|
||||||
|
var is_old = false;
|
||||||
|
for (var i = 0; i < readers.length; ++i) {
|
||||||
|
if (readers[i].name === name) {
|
||||||
|
readers_aux.push(readers[i]);
|
||||||
|
is_old = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_old) {
|
||||||
|
var r = new CardReader(name);
|
||||||
|
readers_aux.push(r);
|
||||||
|
p.emit('reader', r);
|
||||||
|
r.get_status(function(e, status) {
|
||||||
|
if (e) {
|
||||||
|
r.emit('error', e);
|
||||||
|
} else {
|
||||||
|
r.emit('status', status);
|
||||||
|
r.status = status;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ini += pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
readers = readers_aux;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
CardReader.prototype.connect = function(cb) {
|
||||||
|
|
||||||
|
if (!this.connected) {
|
||||||
|
this._connect(cb);
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CardReader.prototype.disconnect = function(cb) {
|
||||||
|
|
||||||
|
if (this.connected) {
|
||||||
|
this._disconnect(cb);
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CardReader.prototype.transmit = function(data, res_len, protocol, cb) {
|
||||||
|
|
||||||
|
if (!this.connected) {
|
||||||
|
return cb(new Error("Card Reader not connected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
this._transmit(data, res_len, protocol, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
CardReader.prototype.SCARD_STATE_UNAWARE = 0x0000;
|
||||||
|
CardReader.prototype.SCARD_STATE_IGNORE = 0x0001;
|
||||||
|
CardReader.prototype.SCARD_STATE_CHANGED = 0x0002;
|
||||||
|
CardReader.prototype.SCARD_STATE_UNKNOWN = 0x0004;
|
||||||
|
CardReader.prototype.SCARD_STATE_UNAVAILABLE = 0x0008;
|
||||||
|
CardReader.prototype.SCARD_STATE_EMPTY = 0x0010;
|
||||||
|
CardReader.prototype.SCARD_STATE_PRESENT = 0x0020;
|
||||||
|
CardReader.prototype.SCARD_STATE_ATRMATCH = 0x0040;
|
||||||
|
CardReader.prototype.SCARD_STATE_EXCLUSIVE = 0x0080;
|
||||||
|
CardReader.prototype.CARD_STATE_INUSE = 0x0100;
|
||||||
|
CardReader.prototype.SCARD_STATE_MUTE = 0x0200;
|
||||||
|
|
||||||
|
// extend prototype
|
||||||
|
function inherits(target, source) {
|
||||||
|
for (var k in source.prototype) {
|
||||||
|
target.prototype[k] = source.prototype[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "pcsclite",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"engines": {
|
||||||
|
"node": "~0.8.0"
|
||||||
|
},
|
||||||
|
"description": "Bindings over pcsclite to access Smart Cards",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"buffertools": "~1.1.1",
|
||||||
|
"bindings": "~1.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"mocha": "~1.11.0",
|
||||||
|
"sinon": "~1.3.4",
|
||||||
|
"should": "~1.2.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha",
|
||||||
|
"install": "node-gyp rebuild"
|
||||||
|
},
|
||||||
|
"repository": "https://github.com/santigimeno/node-pcsclite.git",
|
||||||
|
"keywords": [
|
||||||
|
"pcsc",
|
||||||
|
"pcsclite",
|
||||||
|
"smartcards"
|
||||||
|
],
|
||||||
|
"author": "Santiago Gimeno <santiago.gimeno@gmail.com>",
|
||||||
|
"license": {
|
||||||
|
"type": "ISC",
|
||||||
|
"url": "https://github.com/santigimeno/node-pcsclite/blob/master/LICENSE"
|
||||||
|
},
|
||||||
|
"gypfile": true
|
||||||
|
}
|
||||||
11
src/addon.cpp
Normal file
11
src/addon.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include <v8.h>
|
||||||
|
|
||||||
|
#include "pcsclite.h"
|
||||||
|
#include "cardreader.h"
|
||||||
|
|
||||||
|
void init_all(v8::Handle<v8::Object> target) {
|
||||||
|
PCSCLite::init(target);
|
||||||
|
CardReader::init(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_MODULE(pcsclite, init_all);
|
||||||
470
src/cardreader.cpp
Normal file
470
src/cardreader.cpp
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
#include "cardreader.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <v8.h>
|
||||||
|
#include <node_buffer.h>
|
||||||
|
#include <pcsclite.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
using namespace v8;
|
||||||
|
using namespace node;
|
||||||
|
|
||||||
|
Persistent<Function> CardReader::constructor;
|
||||||
|
|
||||||
|
void CardReader::init(Handle<Object> target) {
|
||||||
|
|
||||||
|
// Prepare constructor template
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(String::NewSymbol("CardReader"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
|
||||||
|
// Symbol
|
||||||
|
name_symbol = NODE_PSYMBOL("name");
|
||||||
|
connected_symbol = NODE_PSYMBOL("connected");
|
||||||
|
|
||||||
|
// Prototype
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "get_status", GetStatus);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "_connect", Connect);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "_disconnect", Disconnect);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "_transmit", Transmit);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "close", Close);
|
||||||
|
|
||||||
|
constructor = Persistent<Function>::New(tpl->GetFunction());
|
||||||
|
target->Set(String::NewSymbol("CardReader"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
CardReader::CardReader(const std::string &reader_name): m_card_context(0),
|
||||||
|
m_card_handle(0),
|
||||||
|
m_name(reader_name) {
|
||||||
|
pthread_mutex_init(&m_mutex, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
CardReader::~CardReader() {
|
||||||
|
|
||||||
|
fprintf(stderr, "CARDREADER destructor\n");
|
||||||
|
|
||||||
|
if (m_card_context) {
|
||||||
|
SCardReleaseContext(m_card_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_status_card_context) {
|
||||||
|
LONG result = SCardCancel(m_status_card_context);
|
||||||
|
fprintf(stderr, "RESULT: 0x%.8lx", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&m_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CardReader::New(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
v8::String::Utf8Value reader_name(args[0]->ToString());
|
||||||
|
CardReader* obj = new CardReader(*reader_name);
|
||||||
|
obj->Wrap(args.Holder());
|
||||||
|
obj->handle_->Set(name_symbol, args[0]->ToString());
|
||||||
|
obj->handle_->Set(connected_symbol, Boolean::New(false));
|
||||||
|
|
||||||
|
return scope.Close(args.Holder());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CardReader::GetStatus(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
CardReader* obj = ObjectWrap::Unwrap<CardReader>(args.This());
|
||||||
|
Local<Function> cb = Local<Function>::Cast(args[0]);
|
||||||
|
|
||||||
|
AsyncBaton *async_baton = new AsyncBaton();
|
||||||
|
async_baton->async.data = async_baton;
|
||||||
|
async_baton->callback = Persistent<Function>::New(cb);
|
||||||
|
async_baton->reader = obj;
|
||||||
|
|
||||||
|
uv_async_init(uv_default_loop(), &async_baton->async, HandleReaderStatusChange);
|
||||||
|
pthread_create(&obj->m_status_thread, NULL, HandlerFunction, async_baton);
|
||||||
|
pthread_detach(obj->m_status_thread);
|
||||||
|
|
||||||
|
return scope.Close(Undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CardReader::Connect(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
return ThrowException(Exception::TypeError(
|
||||||
|
String::New("First argument must be a callback function")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Function> cb = Local<Function>::Cast(args[0]);
|
||||||
|
|
||||||
|
// This creates our work request, including the libuv struct.
|
||||||
|
Baton* baton = new Baton();
|
||||||
|
baton->request.data = baton;
|
||||||
|
baton->callback = Persistent<Function>::New(cb);
|
||||||
|
baton->reader = ObjectWrap::Unwrap<CardReader>(args.This());
|
||||||
|
|
||||||
|
// Schedule our work request with libuv. Here you can specify the functions
|
||||||
|
// that should be executed in the threadpool and back in the main thread
|
||||||
|
// after the threadpool function completed.
|
||||||
|
int status = uv_queue_work(uv_default_loop(), &baton->request, DoConnect, AfterConnect);
|
||||||
|
assert(status == 0);
|
||||||
|
|
||||||
|
return scope.Close(Undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CardReader::Disconnect(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
if (!args[0]->IsFunction()) {
|
||||||
|
return ThrowException(Exception::TypeError(
|
||||||
|
String::New("First argument must be a callback function")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Function> cb = Local<Function>::Cast(args[0]);
|
||||||
|
|
||||||
|
// This creates our work request, including the libuv struct.
|
||||||
|
Baton* baton = new Baton();
|
||||||
|
baton->request.data = baton;
|
||||||
|
baton->callback = Persistent<Function>::New(cb);
|
||||||
|
baton->reader = ObjectWrap::Unwrap<CardReader>(args.This());
|
||||||
|
|
||||||
|
// Schedule our work request with libuv. Here you can specify the functions
|
||||||
|
// that should be executed in the threadpool and back in the main thread
|
||||||
|
// after the threadpool function completed.
|
||||||
|
int status = uv_queue_work(uv_default_loop(), &baton->request, DoDisconnect, AfterDisconnect);
|
||||||
|
assert(status == 0);
|
||||||
|
|
||||||
|
return scope.Close(Undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CardReader::Transmit(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
// The first argument is the buffer to be transmitted.
|
||||||
|
if (!Buffer::HasInstance(args[0])) {
|
||||||
|
return ThrowException(Exception::TypeError(
|
||||||
|
String::New("First argument must be a Buffer")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The second argument is the length of the data to be received
|
||||||
|
if (!args[1]->IsUint32()) {
|
||||||
|
return ThrowException(Exception::TypeError(
|
||||||
|
String::New("Second argument must be an integer")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The third argument is the protocol to be used
|
||||||
|
if (!args[2]->IsUint32()) {
|
||||||
|
return ThrowException(Exception::TypeError(
|
||||||
|
String::New("Third argument must be an integer")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The fourth argument is the callback function
|
||||||
|
if (!args[3]->IsFunction()) {
|
||||||
|
return ThrowException(Exception::TypeError(
|
||||||
|
String::New("Fourth argument must be a callback function")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Local<Object> buffer_data = args[0]->ToObject();
|
||||||
|
uint32_t out_len = args[1]->Uint32Value();
|
||||||
|
uint32_t protocol = args[2]->Uint32Value();
|
||||||
|
Local<Function> cb = Local<Function>::Cast(args[3]);
|
||||||
|
|
||||||
|
// This creates our work request, including the libuv struct.
|
||||||
|
Baton* baton = new Baton();
|
||||||
|
baton->request.data = baton;
|
||||||
|
baton->callback = Persistent<Function>::New(cb);
|
||||||
|
baton->reader = ObjectWrap::Unwrap<CardReader>(args.This());
|
||||||
|
TransmitInput *ti = new TransmitInput();
|
||||||
|
ti->card_protocol = protocol;
|
||||||
|
ti->in_data = new unsigned char[Buffer::Length(buffer_data)];
|
||||||
|
ti->in_len = Buffer::Length(buffer_data);
|
||||||
|
memcpy(ti->in_data, Buffer::Data(buffer_data), ti->in_len);
|
||||||
|
|
||||||
|
ti->out_len = out_len;
|
||||||
|
baton->input = ti;
|
||||||
|
|
||||||
|
// Schedule our work request with libuv. Here you can specify the functions
|
||||||
|
// that should be executed in the threadpool and back in the main thread
|
||||||
|
// after the threadpool function completed.
|
||||||
|
int status = uv_queue_work(uv_default_loop(), &baton->request, DoTransmit, AfterTransmit);
|
||||||
|
assert(status == 0);
|
||||||
|
|
||||||
|
return scope.Close(Undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> CardReader::Close(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
CardReader* obj = ObjectWrap::Unwrap<CardReader>(args.This());
|
||||||
|
|
||||||
|
LONG result = SCardCancel(obj->m_status_card_context);
|
||||||
|
obj->m_status_card_context = 0;
|
||||||
|
|
||||||
|
return scope.Close(Integer::New(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::HandleReaderStatusChange(uv_async_t *handle, int status) {
|
||||||
|
|
||||||
|
AsyncBaton* async_baton = static_cast<AsyncBaton*>(handle->data);
|
||||||
|
AsyncResult* ar = async_baton->async_result;
|
||||||
|
|
||||||
|
if (ar->do_exit) {
|
||||||
|
uv_close(reinterpret_cast<uv_handle_t*>(&async_baton->async), CloseCallback); // necessary otherwise UV will block
|
||||||
|
|
||||||
|
/* Emit end event */
|
||||||
|
Handle<Value> argv[1] = {
|
||||||
|
String::New("end"), // event name
|
||||||
|
};
|
||||||
|
|
||||||
|
MakeCallback(async_baton->reader->handle_, "emit", 1, argv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ar->result == SCARD_S_SUCCESS) {
|
||||||
|
const unsigned argc = 2;
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
Undefined(), // argument
|
||||||
|
Integer::New(ar->status)
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformCallback(async_baton->reader->handle_, async_baton->callback, argc, argv);
|
||||||
|
} else {
|
||||||
|
Local<Value> err = Exception::Error(String::New(pcsc_stringify_error(ar->result)));
|
||||||
|
// Prepare the parameters for the callback function.
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Handle<Value> argv[argc] = { err };
|
||||||
|
PerformCallback(async_baton->reader->handle_, async_baton->callback, argc, argv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* CardReader::HandlerFunction(void* arg) {
|
||||||
|
|
||||||
|
AsyncBaton* async_baton = static_cast<AsyncBaton*>(arg);
|
||||||
|
CardReader* reader = async_baton->reader;
|
||||||
|
async_baton->async_result = new AsyncResult();
|
||||||
|
async_baton->async_result->do_exit = false;
|
||||||
|
|
||||||
|
/* Lock mutex */
|
||||||
|
pthread_mutex_lock(&reader->m_mutex);
|
||||||
|
LONG result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &reader->m_status_card_context);
|
||||||
|
/* Unlock the mutex */
|
||||||
|
pthread_mutex_unlock(&reader->m_mutex);
|
||||||
|
|
||||||
|
SCARD_READERSTATE card_reader_state;
|
||||||
|
card_reader_state.szReader = reader->m_name.c_str();
|
||||||
|
card_reader_state.dwCurrentState = SCARD_STATE_UNAWARE;
|
||||||
|
|
||||||
|
while(result == SCARD_S_SUCCESS &&
|
||||||
|
!((card_reader_state.dwCurrentState & SCARD_STATE_UNKNOWN) ||
|
||||||
|
(card_reader_state.dwCurrentState & SCARD_STATE_UNAVAILABLE))) {
|
||||||
|
result = SCardGetStatusChange(reader->m_status_card_context, INFINITE, &card_reader_state, 1);
|
||||||
|
async_baton->async_result->result = result;
|
||||||
|
async_baton->async_result->status = card_reader_state.dwEventState;
|
||||||
|
uv_async_send(&async_baton->async);
|
||||||
|
card_reader_state.dwCurrentState = card_reader_state.dwEventState;
|
||||||
|
}
|
||||||
|
|
||||||
|
async_baton->async_result->do_exit = true;
|
||||||
|
uv_async_send(&async_baton->async);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::DoConnect(uv_work_t* req) {
|
||||||
|
|
||||||
|
Baton* baton = static_cast<Baton*>(req->data);
|
||||||
|
|
||||||
|
unsigned long card_protocol;
|
||||||
|
LONG result = SCARD_S_SUCCESS;
|
||||||
|
CardReader* obj = baton->reader;
|
||||||
|
|
||||||
|
/* Lock mutex */
|
||||||
|
pthread_mutex_lock(&obj->m_mutex);
|
||||||
|
/* Is context established */
|
||||||
|
if (!obj->m_card_context) {
|
||||||
|
result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &obj->m_card_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect */
|
||||||
|
if (result == SCARD_S_SUCCESS) {
|
||||||
|
result = SCardConnect(obj->m_card_context, obj->m_name.c_str(),
|
||||||
|
SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
|
||||||
|
&obj->m_card_handle, &card_protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unlock the mutex */
|
||||||
|
pthread_mutex_unlock(&obj->m_mutex);
|
||||||
|
|
||||||
|
ConnectResult *cr = new ConnectResult();
|
||||||
|
cr->result = result;
|
||||||
|
if (!result) {
|
||||||
|
cr->card_protocol = card_protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
baton->result = cr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::AfterConnect(uv_work_t* req) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
Baton* baton = static_cast<Baton*>(req->data);
|
||||||
|
ConnectResult *cr = static_cast<ConnectResult*>(baton->result);
|
||||||
|
|
||||||
|
if (cr->result) {
|
||||||
|
Local<Value> err = Exception::Error(String::New(pcsc_stringify_error(cr->result)));
|
||||||
|
// Prepare the parameters for the callback function.
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Handle<Value> argv[argc] = { err };
|
||||||
|
PerformCallback(baton->reader->handle_, baton->callback, argc, argv);
|
||||||
|
} else {
|
||||||
|
baton->reader->handle_->Set(connected_symbol, Boolean::New(true));
|
||||||
|
const unsigned argc = 2;
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
Local<Value>::New(Null()),
|
||||||
|
Integer::New(cr->card_protocol)
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformCallback(baton->reader->handle_, baton->callback, argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The callback is a permanent handle, so we have to dispose of it manually.
|
||||||
|
baton->callback.Dispose();
|
||||||
|
delete cr;
|
||||||
|
delete baton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::DoDisconnect(uv_work_t* req) {
|
||||||
|
|
||||||
|
Baton* baton = static_cast<Baton*>(req->data);
|
||||||
|
|
||||||
|
LONG result = SCARD_S_SUCCESS;
|
||||||
|
CardReader* obj = baton->reader;
|
||||||
|
|
||||||
|
/* Lock mutex */
|
||||||
|
pthread_mutex_lock(&obj->m_mutex);
|
||||||
|
/* Connect */
|
||||||
|
if (obj->m_card_handle) {
|
||||||
|
result = SCardDisconnect(obj->m_card_handle, SCARD_UNPOWER_CARD);
|
||||||
|
if (result == SCARD_S_SUCCESS) {
|
||||||
|
obj->m_card_handle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unlock the mutex */
|
||||||
|
pthread_mutex_unlock(&obj->m_mutex);
|
||||||
|
|
||||||
|
baton->result = reinterpret_cast<void*>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::AfterDisconnect(uv_work_t* req) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
Baton* baton = static_cast<Baton*>(req->data);
|
||||||
|
LONG result = reinterpret_cast<LONG>(baton->result);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
Local<Value> err = Exception::Error(String::New(pcsc_stringify_error(result)));
|
||||||
|
|
||||||
|
// Prepare the parameters for the callback function.
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Handle<Value> argv[argc] = { err };
|
||||||
|
PerformCallback(baton->reader->handle_, baton->callback, argc, argv);
|
||||||
|
} else {
|
||||||
|
baton->reader->handle_->Set(connected_symbol, Boolean::New(false));
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
Local<Value>::New(Null())
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformCallback(baton->reader->handle_, baton->callback, argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The callback is a permanent handle, so we have to dispose of it manually.
|
||||||
|
baton->callback.Dispose();
|
||||||
|
|
||||||
|
delete baton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::DoTransmit(uv_work_t* req) {
|
||||||
|
|
||||||
|
Baton* baton = static_cast<Baton*>(req->data);
|
||||||
|
TransmitInput *ti = static_cast<TransmitInput*>(baton->input);
|
||||||
|
CardReader* obj = baton->reader;
|
||||||
|
|
||||||
|
SCARD_IO_REQUEST io_request;
|
||||||
|
TransmitResult *tr = new TransmitResult();
|
||||||
|
tr->data = new unsigned char[ti->out_len];
|
||||||
|
tr->len = ti->out_len;
|
||||||
|
LONG result = SCARD_E_INVALID_HANDLE;
|
||||||
|
|
||||||
|
/* Lock mutex */
|
||||||
|
pthread_mutex_lock(&obj->m_mutex);
|
||||||
|
/* Connected? */
|
||||||
|
if (obj->m_card_handle) {
|
||||||
|
result = SCardTransmit(obj->m_card_handle, SCARD_PCI_T0, ti->in_data, ti->in_len,
|
||||||
|
&io_request, tr->data, &tr->len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unlock the mutex */
|
||||||
|
pthread_mutex_unlock(&obj->m_mutex);
|
||||||
|
|
||||||
|
tr->result = result;
|
||||||
|
|
||||||
|
baton->result = tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::AfterTransmit(uv_work_t* req) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
Baton* baton = static_cast<Baton*>(req->data);
|
||||||
|
TransmitInput *ti = static_cast<TransmitInput*>(baton->input);
|
||||||
|
TransmitResult *tr = static_cast<TransmitResult*>(baton->result);
|
||||||
|
|
||||||
|
if (tr->result) {
|
||||||
|
Local<Value> err = Exception::Error(String::New(pcsc_stringify_error(tr->result)));
|
||||||
|
|
||||||
|
// Prepare the parameters for the callback function.
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Handle<Value> argv[argc] = { err };
|
||||||
|
PerformCallback(baton->reader->handle_, baton->callback, argc, argv);
|
||||||
|
} else {
|
||||||
|
const unsigned argc = 2;
|
||||||
|
// get Buffer from global scope.
|
||||||
|
Local<Object> global = v8::Context::GetCurrent()->Global();
|
||||||
|
Local<Value> bv = global->Get(String::NewSymbol("Buffer"));
|
||||||
|
assert(bv->IsFunction());
|
||||||
|
Local<Function> b = Local<Function>::Cast(bv);
|
||||||
|
Handle<Value> argv1[3] = { Buffer::New(reinterpret_cast<char*>(tr->data), tr->len)->handle_, Integer::New(tr->len) , Integer::New(0) };
|
||||||
|
Handle<Object> instance = b->NewInstance(3, argv1);
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
Local<Value>::New(Null()),
|
||||||
|
instance
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformCallback(baton->reader->handle_, baton->callback, argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The callback is a permanent handle, so we have to dispose of it manually.
|
||||||
|
baton->callback.Dispose();
|
||||||
|
delete [] ti->in_data;
|
||||||
|
delete ti;
|
||||||
|
delete [] tr->data;
|
||||||
|
delete tr;
|
||||||
|
delete baton;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardReader::CloseCallback(uv_handle_t *handle) {
|
||||||
|
|
||||||
|
/* cleanup process */
|
||||||
|
AsyncBaton* async_baton = static_cast<AsyncBaton*>(handle->data);
|
||||||
|
AsyncResult* ar = async_baton->async_result;
|
||||||
|
delete ar;
|
||||||
|
async_baton->callback.Dispose();
|
||||||
|
SCardReleaseContext(async_baton->reader->m_status_card_context);
|
||||||
|
delete async_baton;
|
||||||
|
}
|
||||||
93
src/cardreader.h
Normal file
93
src/cardreader.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef CARDREADER_H
|
||||||
|
#define CARDREADER_H
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <winscard.h>
|
||||||
|
#include <string>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
static v8::Persistent<v8::String> name_symbol;
|
||||||
|
static v8::Persistent<v8::String> connected_symbol;
|
||||||
|
|
||||||
|
class CardReader: public node::ObjectWrap {
|
||||||
|
|
||||||
|
// We use a struct to store information about the asynchronous "work request".
|
||||||
|
struct Baton {
|
||||||
|
uv_work_t request;
|
||||||
|
v8::Persistent<v8::Function> callback;
|
||||||
|
CardReader *reader;
|
||||||
|
void *input;
|
||||||
|
void *result;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectResult {
|
||||||
|
LONG result;
|
||||||
|
unsigned long card_protocol;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TransmitInput {
|
||||||
|
uint32_t card_protocol;
|
||||||
|
unsigned char *in_data;
|
||||||
|
unsigned long in_len;
|
||||||
|
unsigned long out_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TransmitResult {
|
||||||
|
LONG result;
|
||||||
|
unsigned char *data;
|
||||||
|
unsigned long len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AsyncResult {
|
||||||
|
LONG result;
|
||||||
|
unsigned long status;
|
||||||
|
bool do_exit;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AsyncBaton {
|
||||||
|
uv_async_t async;
|
||||||
|
v8::Persistent<v8::Function> callback;
|
||||||
|
CardReader *reader;
|
||||||
|
AsyncResult *async_result;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static void init(v8::Handle<v8::Object> target);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
CardReader(const std::string &reader_name);
|
||||||
|
|
||||||
|
~CardReader();
|
||||||
|
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
static v8::Handle<v8::Value> New(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> GetStatus(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> Connect(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> Disconnect(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> Transmit(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> Close(const v8::Arguments& args);
|
||||||
|
|
||||||
|
static void HandleReaderStatusChange(uv_async_t *handle, int status);
|
||||||
|
static void* HandlerFunction(void* arg);
|
||||||
|
static void DoConnect(uv_work_t* req);
|
||||||
|
static void AfterConnect(uv_work_t* req);
|
||||||
|
static void DoDisconnect(uv_work_t* req);
|
||||||
|
static void AfterDisconnect(uv_work_t* req);
|
||||||
|
static void DoTransmit(uv_work_t* req);
|
||||||
|
static void AfterTransmit(uv_work_t* req);
|
||||||
|
static void CloseCallback(uv_handle_t *handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SCARDCONTEXT m_card_context;
|
||||||
|
SCARDCONTEXT m_status_card_context;
|
||||||
|
SCARDHANDLE m_card_handle;
|
||||||
|
std::string m_name;
|
||||||
|
pthread_t m_status_thread;
|
||||||
|
pthread_mutex_t m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CARDREADER_H */
|
||||||
21
src/common.h
Normal file
21
src/common.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef COMMON_H
|
||||||
|
#define COMMON_H
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
|
||||||
|
static void PerformCallback(v8::Handle<v8::Object> object,
|
||||||
|
v8::Persistent<v8::Function> &callback,
|
||||||
|
const unsigned argc, v8::Handle<v8::Value> *argv) {
|
||||||
|
|
||||||
|
// Wrap the callback function call in a TryCatch so that we can call
|
||||||
|
// node's FatalException afterwards. This makes it possible to catch
|
||||||
|
// the exception from JavaScript land using the
|
||||||
|
// process.on('uncaughtException') event.
|
||||||
|
v8::TryCatch try_catch;
|
||||||
|
callback->Call(object, argc, argv);
|
||||||
|
if (try_catch.HasCaught()) {
|
||||||
|
node::FatalException(try_catch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* COMMON_H */
|
||||||
197
src/pcsclite.cpp
Normal file
197
src/pcsclite.cpp
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
#include "pcsclite.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <v8.h>
|
||||||
|
#include <pcsclite.h>
|
||||||
|
#include <node_buffer.h>
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
using namespace v8;
|
||||||
|
using namespace node;
|
||||||
|
|
||||||
|
Persistent<Function> PCSCLite::constructor;
|
||||||
|
|
||||||
|
void PCSCLite::init(Handle<Object> target) {
|
||||||
|
|
||||||
|
// Prepare constructor template
|
||||||
|
Local<FunctionTemplate> tpl = FunctionTemplate::New(New);
|
||||||
|
tpl->SetClassName(String::NewSymbol("PCSCLite"));
|
||||||
|
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||||
|
// Prototype
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "start", Start);
|
||||||
|
NODE_SET_PROTOTYPE_METHOD(tpl, "close", Close);
|
||||||
|
|
||||||
|
constructor = Persistent<Function>::New(tpl->GetFunction());
|
||||||
|
target->Set(String::NewSymbol("PCSCLite"), constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
PCSCLite::PCSCLite(): m_card_context(0) {
|
||||||
|
pthread_mutex_init(&m_mutex, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
PCSCLite::~PCSCLite() {
|
||||||
|
|
||||||
|
fprintf(stderr, "PCSCLite destructor\n");
|
||||||
|
|
||||||
|
if (m_card_context) {
|
||||||
|
SCardReleaseContext(m_card_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&m_mutex);
|
||||||
|
pthread_cancel(m_status_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> PCSCLite::New(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
PCSCLite* obj = new PCSCLite();
|
||||||
|
obj->Wrap(args.Holder());
|
||||||
|
return scope.Close(args.Holder());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> PCSCLite::Start(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
PCSCLite* obj = ObjectWrap::Unwrap<PCSCLite>(args.This());
|
||||||
|
Local<Function> cb = Local<Function>::Cast(args[0]);
|
||||||
|
|
||||||
|
AsyncBaton *async_baton = new AsyncBaton();
|
||||||
|
async_baton->async.data = async_baton;
|
||||||
|
async_baton->callback = Persistent<Function>::New(cb);
|
||||||
|
async_baton->pcsclite = obj;
|
||||||
|
|
||||||
|
uv_async_init(uv_default_loop(), &async_baton->async, HandleReaderStatusChange);
|
||||||
|
pthread_create(&obj->m_status_thread, NULL, HandlerFunction, async_baton);
|
||||||
|
pthread_detach(obj->m_status_thread);
|
||||||
|
|
||||||
|
return scope.Close(Undefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Value> PCSCLite::Close(const Arguments& args) {
|
||||||
|
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
PCSCLite* obj = ObjectWrap::Unwrap<PCSCLite>(args.This());
|
||||||
|
|
||||||
|
LONG result = SCardCancel(obj->m_card_context);
|
||||||
|
|
||||||
|
return scope.Close(Integer::New(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCSCLite::HandleReaderStatusChange(uv_async_t *handle, int status) {
|
||||||
|
|
||||||
|
AsyncBaton* async_baton = static_cast<AsyncBaton*>(handle->data);
|
||||||
|
PCSCLite* pcsclite = async_baton->pcsclite;
|
||||||
|
AsyncResult* ar = async_baton->async_result;
|
||||||
|
|
||||||
|
if (ar->do_exit) {
|
||||||
|
uv_close(reinterpret_cast<uv_handle_t*>(&async_baton->async), CloseCallback); // necessary otherwise UV will block
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ar->result == SCARD_S_SUCCESS) || (ar->result == (LONG)SCARD_E_NO_READERS_AVAILABLE)) {
|
||||||
|
const unsigned argc = 2;
|
||||||
|
Handle<Value> argv[argc] = {
|
||||||
|
Undefined(), // argument
|
||||||
|
Buffer::New(ar->readers_name, ar->readers_name_length)->handle_
|
||||||
|
};
|
||||||
|
|
||||||
|
PerformCallback(async_baton->pcsclite->handle_, async_baton->callback, argc, argv);
|
||||||
|
} else {
|
||||||
|
Local<Value> err = Exception::Error(String::New(pcsc_stringify_error(ar->result)));
|
||||||
|
// Prepare the parameters for the callback function.
|
||||||
|
const unsigned argc = 1;
|
||||||
|
Handle<Value> argv[argc] = { err };
|
||||||
|
PerformCallback(async_baton->pcsclite->handle_, async_baton->callback, argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset AsyncResult */
|
||||||
|
delete [] ar->readers_name;
|
||||||
|
ar->readers_name = NULL;
|
||||||
|
ar->readers_name_length = 0;
|
||||||
|
ar->result = SCARD_S_SUCCESS;
|
||||||
|
/* Unlock the mutex */
|
||||||
|
pthread_mutex_unlock(&pcsclite->m_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* PCSCLite::HandlerFunction(void* arg) {
|
||||||
|
|
||||||
|
LONG result = SCARD_S_SUCCESS;
|
||||||
|
AsyncBaton* async_baton = static_cast<AsyncBaton*>(arg);
|
||||||
|
PCSCLite* pcsclite = async_baton->pcsclite;
|
||||||
|
async_baton->async_result = new AsyncResult();
|
||||||
|
|
||||||
|
SCARD_READERSTATE card_reader_state;
|
||||||
|
card_reader_state.szReader = "\\\\?PnP?\\Notification";
|
||||||
|
card_reader_state.dwCurrentState = SCARD_STATE_UNAWARE;
|
||||||
|
|
||||||
|
while(result == SCARD_S_SUCCESS) {
|
||||||
|
/* Lock mutex. It'll be unlocked after the callback has been sent */
|
||||||
|
pthread_mutex_lock(&pcsclite->m_mutex);
|
||||||
|
/* Get card readers */
|
||||||
|
result = pcsclite->get_card_readers(pcsclite, async_baton->async_result);
|
||||||
|
/* Store the result in the baton */
|
||||||
|
async_baton->async_result->result = result;
|
||||||
|
/* Notify the nodejs thread */
|
||||||
|
uv_async_send(&async_baton->async);
|
||||||
|
/* Start checking for status change */
|
||||||
|
result = SCardGetStatusChange(pcsclite->m_card_context, INFINITE, &card_reader_state, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async_baton->async_result->do_exit = true;
|
||||||
|
uv_async_send(&async_baton->async);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCSCLite::CloseCallback(uv_handle_t *handle) {
|
||||||
|
|
||||||
|
/* cleanup process */
|
||||||
|
AsyncBaton* async_baton = static_cast<AsyncBaton*>(handle->data);
|
||||||
|
AsyncResult* ar = async_baton->async_result;
|
||||||
|
delete [] ar->readers_name;
|
||||||
|
delete ar;
|
||||||
|
async_baton->callback.Dispose();
|
||||||
|
delete async_baton;
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG PCSCLite::get_card_readers(PCSCLite* pcsclite, AsyncResult* async_result) {
|
||||||
|
|
||||||
|
LONG result = SCARD_S_SUCCESS;
|
||||||
|
|
||||||
|
/* Reset the readers_name in the baton */
|
||||||
|
async_result->readers_name = NULL;
|
||||||
|
async_result->readers_name_length = 0;
|
||||||
|
|
||||||
|
if (!pcsclite->m_card_context) {
|
||||||
|
result = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &pcsclite->m_card_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != SCARD_S_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find out ReaderNameLength */
|
||||||
|
unsigned long readers_name_length;
|
||||||
|
result = SCardListReaders(pcsclite->m_card_context, NULL, NULL, &readers_name_length);
|
||||||
|
if (result != SCARD_S_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate Memory for ReaderName and retrieve all readers in the terminal */
|
||||||
|
char* readers_name = new char[readers_name_length];
|
||||||
|
result = SCardListReaders(pcsclite->m_card_context, NULL, readers_name, &readers_name_length);
|
||||||
|
if (result != SCARD_S_SUCCESS) {
|
||||||
|
delete [] readers_name;
|
||||||
|
readers_name = NULL;
|
||||||
|
readers_name_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store the readers_name in the baton */
|
||||||
|
async_result->readers_name = readers_name;
|
||||||
|
async_result->readers_name_length = readers_name_length;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
51
src/pcsclite.h
Normal file
51
src/pcsclite.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef PCSCLITE_H
|
||||||
|
#define PCSCLITE_H
|
||||||
|
|
||||||
|
#include <node.h>
|
||||||
|
#include <winscard.h>
|
||||||
|
|
||||||
|
class PCSCLite: public node::ObjectWrap {
|
||||||
|
|
||||||
|
struct AsyncResult {
|
||||||
|
LONG result;
|
||||||
|
char *readers_name;
|
||||||
|
unsigned long readers_name_length;
|
||||||
|
bool do_exit;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AsyncBaton {
|
||||||
|
uv_async_t async;
|
||||||
|
v8::Persistent<v8::Function> callback;
|
||||||
|
PCSCLite *pcsclite;
|
||||||
|
AsyncResult *async_result;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static void init(v8::Handle<v8::Object> target);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
PCSCLite();
|
||||||
|
|
||||||
|
~PCSCLite();
|
||||||
|
|
||||||
|
static v8::Persistent<v8::Function> constructor;
|
||||||
|
static v8::Handle<v8::Value> New(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> Start(const v8::Arguments& args);
|
||||||
|
static v8::Handle<v8::Value> Close(const v8::Arguments& args);
|
||||||
|
|
||||||
|
static void HandleReaderStatusChange(uv_async_t *handle, int status);
|
||||||
|
static void* HandlerFunction(void* arg);
|
||||||
|
static void CloseCallback(uv_handle_t *handle);
|
||||||
|
|
||||||
|
LONG get_card_readers(PCSCLite* pcsclite, AsyncResult* async_result);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
SCARDCONTEXT m_card_context;
|
||||||
|
pthread_t m_status_thread;
|
||||||
|
pthread_mutex_t m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* PCSCLITE_H */
|
||||||
156
test/test.js
Normal file
156
test/test.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
var should = require('should');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var pcsc = require('../lib/pcsclite');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
describe('Testing PCSCLite private', function() {
|
||||||
|
|
||||||
|
describe('#start()', function() {
|
||||||
|
it('#start() stub', function(done) {
|
||||||
|
var p = pcsc();
|
||||||
|
var stub = sinon.stub(p, 'start', function(my_cb) {
|
||||||
|
/* "MyReader\0" */
|
||||||
|
my_cb(undefined, new Buffer("MyReader\0"));
|
||||||
|
});
|
||||||
|
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
reader.name.should.equal("MyReader");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#start() stub', function() {
|
||||||
|
var cb = sinon.spy();
|
||||||
|
var p = pcsc();
|
||||||
|
var stub = sinon.stub(p, 'start', function(my_cb) {
|
||||||
|
/* "MyReader" */
|
||||||
|
my_cb(undefined, new Buffer("MyReader"));
|
||||||
|
});
|
||||||
|
|
||||||
|
p.on('reader', cb);
|
||||||
|
process.nextTick(function () {
|
||||||
|
sinon.assert.notCalled(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#start() stub', function() {
|
||||||
|
var cb = sinon.spy();
|
||||||
|
var p = pcsc();
|
||||||
|
var stub = sinon.stub(p, 'start', function(my_cb) {
|
||||||
|
/* "MyReader1\0MyReader2\0" */
|
||||||
|
my_cb(undefined, new Buffer("MyReader1\0MyReader2\0"));
|
||||||
|
});
|
||||||
|
|
||||||
|
p.on('reader', cb);
|
||||||
|
process.nextTick(function () {
|
||||||
|
sinon.assert.calledTwice(cb);
|
||||||
|
assert(cb.args[0][0]['name'], "MyReader1");
|
||||||
|
assert(cb.args[1][0]['name'], "MyReader2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Testing CardReader private', function() {
|
||||||
|
|
||||||
|
var get_reader = function() {
|
||||||
|
var p = pcsc();
|
||||||
|
var stub = sinon.stub(p, 'start', function(my_cb) {
|
||||||
|
/* "MyReader\0" */
|
||||||
|
my_cb(undefined, new Buffer("MyReader\0"));
|
||||||
|
});
|
||||||
|
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('#_connect()', function() {
|
||||||
|
|
||||||
|
it('#_connect() success', function(done) {
|
||||||
|
var p = get_reader();
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
var connect_stub = sinon.stub(reader, '_connect', function(connect_cb) {
|
||||||
|
connect_cb(undefined, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.connect(function(err, protocol) {
|
||||||
|
should.not.exist(err);
|
||||||
|
protocol.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#_connect() error', function() {
|
||||||
|
var p = get_reader();
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
var cb = sinon.spy();
|
||||||
|
var connect_stub = sinon.stub(reader, '_connect', function(connect_cb) {
|
||||||
|
connect_cb("");
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.connect(cb);
|
||||||
|
sinon.assert.calledOnce(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#_connect() already connected', function() {
|
||||||
|
var p = get_reader();
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
var cb = sinon.spy();
|
||||||
|
reader.connected = true;
|
||||||
|
|
||||||
|
reader.connect(cb);
|
||||||
|
process.nextTick(function () {
|
||||||
|
sinon.assert.calledOnce(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_disconnect()', function() {
|
||||||
|
|
||||||
|
it('#_disconnect() success', function() {
|
||||||
|
var p = get_reader();
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
reader.connected = true;
|
||||||
|
var cb = sinon.spy();
|
||||||
|
var connect_stub = sinon.stub(reader, '_disconnect', function(disconnect_cb) {
|
||||||
|
disconnect_cb(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.disconnect(cb);
|
||||||
|
sinon.assert.calledOnce(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#_disconnect() error', function() {
|
||||||
|
var p = get_reader();
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
reader.connected = true;
|
||||||
|
var cb = sinon.spy();
|
||||||
|
var connect_stub = sinon.stub(reader, '_disconnect', function(disconnect_cb) {
|
||||||
|
disconnect_cb("");
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.disconnect(cb);
|
||||||
|
sinon.assert.calledOnce(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#_disconnect() already disconnected', function() {
|
||||||
|
var p = get_reader();
|
||||||
|
p.on('reader', function(reader) {
|
||||||
|
var cb = sinon.spy();
|
||||||
|
var connect_stub = sinon.stub(reader, '_disconnect', function(disconnect_cb) {
|
||||||
|
disconnect_cb(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.disconnect(cb);
|
||||||
|
process.nextTick(function () {
|
||||||
|
sinon.assert.calledOnce(cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user