Initial Implementation

This commit is contained in:
Santiago Gimeno
2012-05-02 12:46:42 +02:00
parent 0484b764bf
commit b40f9a2f88
15 changed files with 1270 additions and 0 deletions

2
.gitignore vendored
View File

@@ -12,3 +12,5 @@ logs
results
npm-debug.log
node_modules
build/

13
LICENSE Normal file
View 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.

View File

@@ -2,3 +2,54 @@ node-pcsclite
=============
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
View 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
View 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);
});

1
index.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./lib/pcsclite');

104
lib/pcsclite.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
});
});
});
});
});