#include "cardreader.h" #include "common.h" #include #include #include #include using namespace v8; using namespace node; Persistent CardReader::constructor; void CardReader::init(Handle target) { // Prepare constructor template Local 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::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 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 CardReader::GetStatus(const Arguments& args) { HandleScope scope; CardReader* obj = ObjectWrap::Unwrap(args.This()); Local cb = Local::Cast(args[0]); AsyncBaton *async_baton = new AsyncBaton(); async_baton->async.data = async_baton; async_baton->callback = Persistent::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 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 cb = Local::Cast(args[0]); // This creates our work request, including the libuv struct. Baton* baton = new Baton(); baton->request.data = baton; baton->callback = Persistent::New(cb); baton->reader = ObjectWrap::Unwrap(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 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 cb = Local::Cast(args[0]); // This creates our work request, including the libuv struct. Baton* baton = new Baton(); baton->request.data = baton; baton->callback = Persistent::New(cb); baton->reader = ObjectWrap::Unwrap(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 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 buffer_data = args[0]->ToObject(); uint32_t out_len = args[1]->Uint32Value(); uint32_t protocol = args[2]->Uint32Value(); Local cb = Local::Cast(args[3]); // This creates our work request, including the libuv struct. Baton* baton = new Baton(); baton->request.data = baton; baton->callback = Persistent::New(cb); baton->reader = ObjectWrap::Unwrap(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 CardReader::Close(const Arguments& args) { HandleScope scope; CardReader* obj = ObjectWrap::Unwrap(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(handle->data); AsyncResult* ar = async_baton->async_result; if (ar->do_exit) { uv_close(reinterpret_cast(&async_baton->async), CloseCallback); // necessary otherwise UV will block /* Emit end event */ Handle 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 argv[argc] = { Undefined(), // argument Integer::New(ar->status) }; PerformCallback(async_baton->reader->handle_, async_baton->callback, argc, argv); } else { Local err = Exception::Error(String::New(pcsc_stringify_error(ar->result))); // Prepare the parameters for the callback function. const unsigned argc = 1; Handle argv[argc] = { err }; PerformCallback(async_baton->reader->handle_, async_baton->callback, argc, argv); } } void* CardReader::HandlerFunction(void* arg) { AsyncBaton* async_baton = static_cast(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(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(req->data); ConnectResult *cr = static_cast(baton->result); if (cr->result) { Local err = Exception::Error(String::New(pcsc_stringify_error(cr->result))); // Prepare the parameters for the callback function. const unsigned argc = 1; Handle 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 argv[argc] = { Local::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(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(result); } void CardReader::AfterDisconnect(uv_work_t* req) { HandleScope scope; Baton* baton = static_cast(req->data); LONG result = reinterpret_cast(baton->result); if (result) { Local err = Exception::Error(String::New(pcsc_stringify_error(result))); // Prepare the parameters for the callback function. const unsigned argc = 1; Handle 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 argv[argc] = { Local::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(req->data); TransmitInput *ti = static_cast(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(req->data); TransmitInput *ti = static_cast(baton->input); TransmitResult *tr = static_cast(baton->result); if (tr->result) { Local err = Exception::Error(String::New(pcsc_stringify_error(tr->result))); // Prepare the parameters for the callback function. const unsigned argc = 1; Handle argv[argc] = { err }; PerformCallback(baton->reader->handle_, baton->callback, argc, argv); } else { const unsigned argc = 2; // get Buffer from global scope. Local global = v8::Context::GetCurrent()->Global(); Local bv = global->Get(String::NewSymbol("Buffer")); assert(bv->IsFunction()); Local b = Local::Cast(bv); Handle argv1[3] = { Buffer::New(reinterpret_cast(tr->data), tr->len)->handle_, Integer::New(tr->len) , Integer::New(0) }; Handle instance = b->NewInstance(3, argv1); Handle argv[argc] = { Local::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(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; }