[Pcsclite-muscle] RFC - one old and one new bluetooth device driver.

James pcsclite at madingley.org
Mon Aug 14 12:53:37 UTC 2017


Attached are patches to support the HID Omnikey 2061, and
the ACR3901U-S1 bluetooth card readers. 

---

The HID Omnikey 2061, is end of life but is readily
available on eBay. I reverse engineered the protocol
from observing the windows drivers. It uses CCID over
serial over Bluetooth RFCOMM. As such the pin is not
particularly well protected.

To use the HID driver, first pair the reader with the
computer using your favourite bluetooth stack then create
a file in /etc/reader.conf.d/ containing (edit the path
and set the DEVICENAME to be the MAC address of the reader)

DEVICENAME        00:80:25:33:44:55
FRIENDLYNAME      "My HID 2061"
LIBPATH 	  /usr/lib64/pcsc/drivers/serial/libccidhid.so

---

The ACR3901U-S1 is in current production and communicates 
using a stripped down version of CCID over Bluetooth
Low-Energy GATT, or CCID over USB. The over-the-air
interface is protected by mutual authentication, and 
encrypted using 128 bit AES CBC using a random session
key. The driver implements support for both interfaces.

The device requires a 16 byte secret key to be known by
the connecting computer, at the moment pcscd doesn't
provide a simple way to insert this - (in this patch it's
hard coded to the default value). What would be the
preffered method of getting this into the driver?

To use the ACR driver find the MAC address of the device
(use hcitool lescan) on linux

and create a file in /etc/reader.conf.d/ containing (edit
the path and set the DEVICENAME to be the MAC address of
the reader)

DEVICENAME        11:22:33:44:55:66
FRIENDLYNAME      "My ACR3901U-S1"
LIBPATH 	  /usr/lib64/pcsc/drivers/serial/libccidacr.so

For USB operation the drive is plug and play.

The ACR driver still outputs some debug output to stderr
which should be fixed.

The HID driver patch contains support for multiple serial
devices, which is used by the ACR driver patch.


James.
-------------- next part --------------
diff --git a/.gitignore b/.gitignore
index 220a00b..96262ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,7 +71,7 @@ src/libccidtwin.la
 src/libccidtwin_la-atr.lo
 src/libccidtwin_la-buffer.lo
 src/libccidtwin_la-ccid.lo
-src/libccidtwin_la-ccid_serial.lo
+src/libccidtwin_la-ccid_twin_serial.lo
 src/libccidtwin_la-checksum.lo
 src/libccidtwin_la-commands.lo
 src/libccidtwin_la-ifdhandler.lo
@@ -81,6 +81,20 @@ src/libccidtwin_la-simclist.lo
 src/libccidtwin_la-strlcpy.lo
 src/libccidtwin_la-tokenparser.lo
 src/libccidtwin_la-utils.lo
+src/libccidhid.la
+src/libccidhid_la-atr.lo
+src/libccidhid_la-buffer.lo
+src/libccidhid_la-ccid.lo
+src/libccidhid_la-ccid_hid_serial.lo
+src/libccidhid_la-checksum.lo
+src/libccidhid_la-commands.lo
+src/libccidhid_la-ifdhandler.lo
+src/libccidhid_la-pps.lo
+src/libccidhid_la-proto-t1.lo
+src/libccidhid_la-simclist.lo
+src/libccidhid_la-strlcpy.lo
+src/libccidhid_la-tokenparser.lo
+src/libccidhid_la-utils.lo
 src/openct/.deps/
 src/openct/.dirstamp
 src/openct/.libs/
@@ -106,5 +120,10 @@ src/towitoko/libccid_la-atr.lo
 src/towitoko/libccid_la-pps.lo
 src/towitoko/libccidtwin_la-atr.lo
 src/towitoko/libccidtwin_la-pps.lo
+src/openct/libccidhid_la-buffer.lo
+src/openct/libccidhid_la-checksum.lo
+src/openct/libccidhid_la-proto-t1.lo
+src/towitoko/libccidhid_la-atr.lo
+src/towitoko/libccidhid_la-pps.lo
 stamp-h1
 tags
diff --git a/configure.ac b/configure.ac
index 688c207..41eebfe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -204,6 +204,23 @@ if test "${ccidtwindir}" = false ; then
 	ccidtwindir=$usbdropdir/serial
 fi
 
+
+# --enable-hidserial
+AC_ARG_ENABLE(hidserial,
+	AS_HELP_STRING([--enable-hidserial],[also compile and install the bluetooth serial HID driver]),
+	[hidserial="${enableval}"], [hidserial=no])
+AM_CONDITIONAL(WITH_HID_SERIAL, test "${hidserial}" != "no")
+
+# --enable-ccidhiddir=DIR
+AC_ARG_ENABLE(ccidhiddir,
+	AS_HELP_STRING([--enable-ccidhiddir=DIR],[directory to install the
+	bluetooth serial HID driver (default to pcscd config or $(prefix)/pcsc/drivers/serial)]),
+	[ccidhiddir="${enableval}"], [ccidhiddir=false])
+if test "${ccidhiddir}" = false ; then
+	ccidhiddir=$usbdropdir/serial
+fi
+
+
 # --enable-serialconfdir=DIR
 AC_ARG_ENABLE(serialconfdir,
 	AS_HELP_STRING([--enable-serialconfdir=dir],[directory containing
@@ -270,6 +287,7 @@ AC_SUBST(ac_aux_dir)
 AC_SUBST(bundle)
 AC_SUBST(usbdropdir)
 AC_SUBST(ccidtwindir)
+AC_SUBST(ccidhiddir)
 AC_SUBST(serialconfdir)
 AS_AC_EXPAND(bindir_exp,$bindir)
 AS_AC_EXPAND(sysconfdir_exp,$sysconfdir)
@@ -309,6 +327,8 @@ bundle directory name:   ${bundle}
 USB drop directory:      ${usbdropdir}
 serial Twin support:     ${twinserial}
 serial twin install dir: ${ccidtwindir}
+serial HID support:      ${hidserial}
+serial HID install dir:  ${ccidhiddir}
 serial config directory: ${serialconfdir}
 compiled for pcsc-lite:  ${pcsclite}
 syslog debug:            ${use_syslog}
diff --git a/src/Makefile.am b/src/Makefile.am
index fcb5a61..e97db83 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,7 @@
 CCID_BUNDLE = $(bundle)
 CCID_LIB = libccid.$(DYN_LIB_EXT)
 CCIDTWIN_LIB = libccidtwin.$(DYN_LIB_EXT)
+CCIDHID_LIB = libccidhid.$(DYN_LIB_EXT)
 
 CCID_VERSION=CCID_VERSION=`$(srcdir)/convert_version.pl $(PACKAGE_VERSION)`
 
@@ -18,6 +19,11 @@ lib_LTLIBRARIES += libccidtwin.la
 LIBS_TO_INSTALL += install_ccidtwin
 LIBS_TO_UNINSTALL += uninstall_ccidtwin
 endif
+if WITH_HID_SERIAL
+lib_LTLIBRARIES += libccidhid.la
+LIBS_TO_INSTALL += install_ccidhid
+LIBS_TO_UNINSTALL += uninstall_ccidhid
+endif
 
 COMMON = ccid.c \
 	ccid.h \
@@ -30,7 +36,8 @@ COMMON = ccid.c \
 	utils.c \
 	utils.h
 USB = ccid_usb.c ccid_usb.h
-SERIAL = ccid_serial.c ccid_serial.h
+TWIN_SERIAL = ccid_twin_serial.c ccid_twin_serial.h
+HID_SERIAL =ccid_hid_serial.c ccid_hid_serial.h
 T1 = towitoko/atr.c \
 	towitoko/atr.h \
 	towitoko/defines.h \
@@ -59,13 +66,21 @@ libccid_la_CFLAGS = $(PCSC_CFLAGS) $(LIBUSB_CFLAGS) $(PTHREAD_CFLAGS) \
 	$(SYMBOL_VISIBILITY) -D$(CCID_VERSION) -DSIMCLIST_NO_DUMPRESTORE
 libccid_la_LDFLAGS = -avoid-version
 
-libccidtwin_la_SOURCES = $(COMMON) $(SERIAL) $(TOKEN_PARSER) \
+libccidtwin_la_SOURCES = $(COMMON) $(TWIN_SERIAL) $(TOKEN_PARSER) \
 	$(PROVIDED_BY_PCSC) $(T1)
 libccidtwin_la_CFLAGS = $(PCSC_CFLAGS) $(PTHREAD_CFLAGS) $(SYMBOL_VISIBILITY) \
 	-DTWIN_SERIAL -D$(CCID_VERSION) -DSIMCLIST_NO_DUMPRESTORE
 libccidtwin_la_LIBADD = $(PTHREAD_LIBS)
 libccidtwin_la_LDFLAGS = -avoid-version
 
+libccidhid_la_SOURCES = $(COMMON) $(HID_SERIAL) $(TOKEN_PARSER) \
+	$(PROVIDED_BY_PCSC) $(T1)
+libccidhid_la_CFLAGS = $(PCSC_CFLAGS) $(PTHREAD_CFLAGS) $(SYMBOL_VISIBILITY) \
+	-DHID_SERIAL -D$(CCID_VERSION) -DSIMCLIST_NO_DUMPRESTORE
+libccidhid_la_LIBADD = $(PTHREAD_LIBS)
+libccidhid_la_LDFLAGS = -avoid-version
+
+
 parse_SOURCES = parse.c debug.c ccid_usb.c $(TOKEN_PARSER)
 parse_LDADD = $(LIBUSB_LIBS)
 parse_CFLAGS = $(PCSC_CFLAGS) $(LIBUSB_CFLAGS) -DSIMCLIST_NO_DUMPRESTORE
@@ -97,6 +112,11 @@ install_ccidtwin: libccidtwin.la
 	$(mkinstalldirs) $(DESTDIR)/$(serialconfdir) ; \
 		perl -ne "s|TARGET|$(ccidtwindir)/$(CCIDTWIN_LIB)| ; print" $(srcdir)/reader.conf.in > $(DESTDIR)/$(serialconfdir)/libccidtwin
 
+install_ccidhid: libccidhid.la
+	$(mkinstalldirs) $(DESTDIR)$(ccidhiddir)
+	cp .libs/$(CCIDHID_LIB) $(DESTDIR)$(ccidhiddir)/$(CCIDHID_LIB)
+
+
 # do not uninstall the serial driver by default
 # use explicitely 'make uninstall_ccidtwin'
 uninstall: $(LIBS_TO_UNINSTALL)
@@ -108,3 +128,7 @@ uninstall_ccidtwin:
 	rm -f $(DESTDIR)$(ccidtwindir)/$(CCIDTWIN_LIB)
 	rm -f $(DESTDIR)/$(serialconfdir)/libccidtwin
 
+uninstall_ccidhid:
+	rm -f $(DESTDIR)$(ccidhiddir)/$(CCIDHID_LIB)
+	rm -f $(DESTDIR)/$(serialconfdir)/libccidhid
+
diff --git a/src/ccid.c b/src/ccid.c
index a1c3b21..5682f08 100644
--- a/src/ccid.c
+++ b/src/ccid.c
@@ -86,7 +86,7 @@ int ccid_open_hack_pre(unsigned int reader_index)
 	if ((PROTOCOL_CCID == ccid_descriptor->bInterfaceProtocol)
 		&& (3 == ccid_descriptor -> bNumEndpoints))
 	{
-#ifndef TWIN_SERIAL
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 		/* just wait for 100ms in case a notification is in the pipe */
 		(void)InterruptRead(reader_index, 100);
 #endif
diff --git a/src/ccid.h b/src/ccid.h
index da0fd75..89617ca 100644
--- a/src/ccid.h
+++ b/src/ccid.h
@@ -213,6 +213,7 @@ typedef struct
 #define ElatecTWN4	0x09D80427
 #define SCM_SCL011 0x04E65293
 #define HID_AVIATOR	0x076B3A21
+#define HID_OMNIKEY_2061 0x076B2061
 
 #define VENDOR_GEMALTO 0x08E6
 #define GET_VENDOR(readerID) ((readerID >> 16) & 0xFFFF)
diff --git a/src/ccid_hid_serial.c b/src/ccid_hid_serial.c
new file mode 100644
index 0000000..42acee3
--- /dev/null
+++ b/src/ccid_hid_serial.c
@@ -0,0 +1,737 @@
+/*
+ * ccid_hid_serial.c: communicate with an HID bluetooth serial smart card reader
+ * Copyright (C) 2001-2010 Ludovic Rousseau <ludovic.rousseau at free.fr>
+ *
+ * Thanks to Niki W. Waibel <niki.waibel at gmx.net> for a prototype version
+ *
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public License
+	along with this library; if not, write to the Free Software Foundation,
+	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <ifdhandler.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/rfcomm.h>
+
+
+#include <config.h>
+#include "defs.h"
+#include "ccid_ifdhandler.h"
+#include "debug.h"
+#include "ccid.h"
+#include "utils.h"
+#include "commands.h"
+#include "parser.h"
+#include "strlcpycat.h"
+
+/*
+ * normal command:
+ * a5 <CCID command> <one byte checksum (XOR of a5+CCID bytes)>
+ *
+ * wake up command:
+ * a9 73 73 
+ *
+ * normal replies
+ * a5 <CCID reply> <one byte checksum (XOR...) >
+ *
+ * replys to wakeup
+ *
+ * a9 74 74
+ *
+ * reply sent once after wakeup (voltage in BCD maybe?)
+ *
+ * a3 50 03 53
+ *
+ *
+ */
+
+#define HID_MAXBUF (271 +2 +1) * 2
+
+typedef struct
+{
+	/*
+	 * File handle on the serial port
+	 */
+	int fd;
+
+	/*
+	 * device used ("/dev/ttyS?" under Linux)
+	 */
+	/*@null@*/ char *device;
+
+	/*
+	 * Number of slots using the same device
+	 */
+	int real_nb_opened_slots;
+	int *nb_opened_slots;
+
+
+	/*
+	 * serial communication buffer
+	 */
+	unsigned char buffer[HID_MAXBUF];
+
+	/*
+	 * next available byte
+	 */
+	int buffer_offset;
+
+	/*
+	 * number of available bytes
+	 */
+	int buffer_offset_last;
+
+	int fake_empty;
+	unsigned char fake_empty_seq;
+
+	int empty_slots;
+
+
+	/*
+	 * CCID infos common to USB and serial
+	 */
+	_ccid_descriptor ccid;
+
+} _serialDevice;
+
+/* The _serialDevice structure must be defined before including ccid_serial.h */
+#include "ccid_hid_serial.h"
+
+
+unsigned int OMNIKey2061Rates[] ={  ISO_DATA_RATES , 0 };
+
+
+/* no need to initialize to 0 since it is static */
+static _serialDevice serialDevice[CCID_DRIVER_MAX_READERS];
+
+
+static int my_bachk(const char *str)
+{
+    if (!str)
+        return -1;
+
+    if (strlen(str) != 17)
+        return -1;
+
+    while (*str) {
+        if (!isxdigit(*str++))
+            return -1;
+
+        if (!isxdigit(*str++))
+            return -1;
+
+        if (*str == 0)
+            break;
+
+        if (*str++ != ':')
+            return -1;
+    }
+
+    return 0;
+}
+
+static int my_str2ba(const char *str, bdaddr_t *ba)
+{
+    int i;
+
+    if (my_bachk(str) < 0) {
+        memset(ba, 0, sizeof(*ba));
+        return -1;
+    }
+
+    for (i = 5; i >= 0; i--, str += 3)
+        ba->b[i] = strtol(str, NULL, 16);
+
+    return 0;
+}
+
+static int open_rfcomm(const char *addr)
+{
+  struct sockaddr_rc src_self = { 0 }, src_peer = { 0};
+  int fd;
+
+  src_self.rc_family = AF_BLUETOOTH;
+  bacpy (&src_self.rc_bdaddr, BDADDR_ANY);
+  src_self.rc_channel = 0;
+
+  src_peer.rc_family = AF_BLUETOOTH;
+
+  if (my_str2ba (addr, &src_peer.rc_bdaddr)) {
+  	DEBUG_CRITICAL3("open_rfcomm can't parse %s %d",addr,1);
+	      return -1;
+  }
+
+  src_peer.rc_channel = 1;
+
+  DEBUG_CRITICAL3("open_rfcomm connecting to %s %d",addr,src_peer.rc_channel);
+
+
+  fd = socket (AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+
+    if (fd < 0)
+    {
+      DEBUG_CRITICAL ("socket(AF_BLUETOOTH,SOCK_STREAM,BTPROTO_RFCOMM)");
+      return -1;
+    }
+
+
+  if (bind (fd, (struct sockaddr *) &src_self, sizeof (src_self)))
+    {
+      close (fd);
+      DEBUG_CRITICAL ("bind");
+      return -1;
+    }
+
+    if (connect (fd, (struct sockaddr *) &src_peer, sizeof (src_peer)))
+    {
+      close (fd);
+      DEBUG_CRITICAL ("connect");
+      return -1;
+    }
+
+    return fd;
+}
+
+
+static status_t check_open(unsigned int reader_index)
+{
+
+	if (serialDevice[reader_index].fd!=-1) return STATUS_SUCCESS;
+
+
+	serialDevice[reader_index].fd = open_rfcomm(serialDevice[reader_index].device);
+
+	if (-1 == serialDevice[reader_index].fd)
+	{
+		DEBUG_CRITICAL3("open %s: %s", serialDevice[reader_index].device, "failed");
+		return STATUS_COMM_ERROR;
+	}
+
+	serialDevice[reader_index].buffer_offset = 0;
+	serialDevice[reader_index].buffer_offset_last = 0;
+
+	{
+
+		unsigned char tx_buffer[]={0xa9,0x73,0x73};
+		if (write(serialDevice[reader_index].fd, tx_buffer,sizeof(tx_buffer)) != sizeof(tx_buffer)) {
+		(void)close(serialDevice[reader_index].fd);
+		serialDevice[reader_index].fd = -1;
+		DEBUG_INFO2("send init error: %s", strerror(errno));
+		return STATUS_COMM_ERROR;
+		}
+		sleep(1);
+
+	}
+
+	{
+		unsigned char tx_buffer[] = { 0x02,0x00 };
+		unsigned char rx_buffer[50];
+		unsigned int rx_length = sizeof(rx_buffer);
+
+		if (IFD_SUCCESS != CmdEscape(reader_index, tx_buffer, sizeof(tx_buffer),
+			rx_buffer, &rx_length, 0))
+		{
+			DEBUG_CRITICAL("thing 1 failed.");
+			(void)CloseHidSerial(reader_index);
+			return STATUS_COMM_ERROR;
+		}
+	}
+
+
+	{
+		unsigned char tx_buffer[] = { 0x0c,0x26 };
+		unsigned char rx_buffer[50];
+		unsigned int rx_length = sizeof(rx_buffer);
+
+		if (IFD_SUCCESS != CmdEscape(reader_index, tx_buffer, sizeof(tx_buffer),
+			rx_buffer, &rx_length, 0))
+		{
+			DEBUG_CRITICAL("thing 2 failed.");
+			(void)CloseHidSerial(reader_index);
+			return STATUS_COMM_ERROR;
+		}
+	}
+
+
+	return STATUS_SUCCESS;
+}
+
+
+/* unexported functions */
+static int ReadChunk(unsigned int reader_index, unsigned char *buffer,
+	int buffer_length, int min_length);
+
+static int get_bytes(unsigned int reader_index, /*@out@*/ unsigned char *buffer,
+	int length);
+
+
+/*****************************************************************************
+ *
+ *				WriteHidSerial: Send bytes to the card reader
+ *
+ *****************************************************************************/
+status_t WriteHidSerial(unsigned int reader_index, unsigned int length,
+	unsigned char *buffer)
+{
+	status_t ret;
+	unsigned int i;
+	unsigned char lrc;
+	unsigned char low_level_buffer[HID_MAXBUF];
+
+	char debug_header[] = "-> 123456 ";
+
+	(void)snprintf(debug_header, sizeof(debug_header), "-> %06X ",
+		reader_index);
+
+	if (length > HID_MAXBUF-3)
+	{
+		DEBUG_CRITICAL3("command too long: %d for max %d",
+			length, HID_MAXBUF-3);
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	ret=check_open(reader_index);
+	if (ret!=STATUS_SUCCESS) {
+		if  ((length==10) &&
+		(buffer[0]==0x65) &&
+		(buffer[1]==0x00) &&
+		(buffer[2]==0x00) &&
+		(buffer[3]==0x00) &&
+		(buffer[4]==0x00) &&
+		(buffer[5]==0x00) &&
+		(buffer[7]==0x00) &&
+		(buffer[8]==0x00) &&
+		(buffer[9]==0x00)) {
+
+			serialDevice[reader_index].fake_empty=1;
+			serialDevice[reader_index].fake_empty_seq=buffer[6];
+
+			return STATUS_SUCCESS;
+		}
+
+		return ret;
+	}
+
+
+	/* header */
+	low_level_buffer[0] = 0xa5;
+
+	/* CCID command */
+	memcpy(low_level_buffer+1, buffer, length);
+
+	/* checksum */
+	lrc = 0;
+	for(i=1; i<length+1; i++)
+		lrc ^= low_level_buffer[i];
+	low_level_buffer[length+1] = lrc;
+
+	DEBUG_XXD(debug_header, low_level_buffer, 1);
+	DEBUG_XXD(debug_header, low_level_buffer+1, length);
+	DEBUG_XXD(debug_header, low_level_buffer+length+1, 1);
+
+	if (write(serialDevice[reader_index].fd, low_level_buffer,
+		length+2) != length+2)
+	{
+		DEBUG_CRITICAL2("write error: %s", strerror(errno));
+		close(serialDevice[reader_index].fd);
+		serialDevice[reader_index].fd=-1;
+		return STATUS_COMM_ERROR;
+	}
+
+	return STATUS_SUCCESS;
+} /* WriteHidSerial */
+
+
+/*****************************************************************************
+ *
+ *				ReadHidSerial: Receive bytes from the card reader
+ *
+ *****************************************************************************/
+status_t ReadHidSerial(unsigned int reader_index,
+	unsigned int *length, unsigned char *buffer)
+{
+	unsigned char low_level_buffer[0x10];
+	unsigned char lrc;
+	int rv;
+	unsigned to_read;
+	unsigned  i;
+	char debug_header[] = "<- 123456 ";
+	status_t ret;
+
+
+	if (serialDevice[reader_index].fake_empty) {
+		*length=10;
+		buffer[0]=0x81;
+		buffer[1]=0x00;
+		buffer[2]=0x00;
+		buffer[3]=0x00;
+		buffer[4]=0x00;
+		buffer[5]=0x00;
+		buffer[6]= serialDevice[reader_index].fake_empty_seq;
+		buffer[7]=0x02;
+		buffer[8]=0x00;
+		buffer[9]=0x00;
+		serialDevice[reader_index].fake_empty=0;
+
+		return STATUS_SUCCESS;
+
+	}
+
+	ret=check_open(reader_index);
+	if (ret!=STATUS_SUCCESS)
+		return ret;
+
+
+do {
+	DEBUG_COMM("start");
+	if ((rv = get_bytes(reader_index, &low_level_buffer[0], 1)) != STATUS_SUCCESS)
+		return rv;
+
+	DEBUG_COMM2("Got first byte 0x%02x\n",low_level_buffer[0]);
+
+        switch (low_level_buffer[0]) {
+	case 0x55:
+		if ((rv = get_bytes(reader_index, &low_level_buffer[1], 2)) != STATUS_SUCCESS)
+			return rv;
+		DEBUG_XXD(debug_header, low_level_buffer, 3);
+		return STATUS_UNSUCCESSFUL;
+	case 0xa9:
+		if ((rv = get_bytes(reader_index, &low_level_buffer[1], 2)) != STATUS_SUCCESS)
+			return rv;
+	DEBUG_XXD(debug_header, low_level_buffer, 3);
+		break;
+	case 0xa3:
+		if ((rv = get_bytes(reader_index, &low_level_buffer[1], 3)) != STATUS_SUCCESS)
+			return rv;
+	DEBUG_XXD(debug_header, low_level_buffer, 4);
+		break;
+	case 0xa5: /*ccid reply */
+
+	if ((rv = get_bytes(reader_index, buffer, 10)) != STATUS_SUCCESS)
+		return rv;
+
+	to_read = dw2i(buffer, 1);
+
+	if ((rv = get_bytes(reader_index, &buffer[10], to_read)) != STATUS_SUCCESS)
+		return rv;
+
+        to_read+=10;
+
+	if ((rv = get_bytes(reader_index, &low_level_buffer[1], 1)) != STATUS_SUCCESS)
+		return rv;
+
+	DEBUG_XXD(debug_header, &low_level_buffer[0],1);
+	DEBUG_XXD(debug_header, buffer,to_read);
+	DEBUG_XXD(debug_header, &low_level_buffer[1],1);
+
+	lrc=0;
+	for (i=0;i<to_read;++i) 
+		lrc^=buffer[i];
+
+
+	if (lrc!=low_level_buffer[1]) {
+		DEBUG_CRITICAL3("Wrong LRC: 0x%02X != 0x%02X",lrc,low_level_buffer[1]);
+		return STATUS_COMM_ERROR;
+	}
+
+	*length =to_read;
+
+	return STATUS_SUCCESS;
+}
+} while (1);
+} /* ReadHidSerial */
+
+
+/*****************************************************************************
+ *
+ *				get_bytes: get n bytes
+ *
+ *****************************************************************************/
+int get_bytes(unsigned int reader_index, unsigned char *buffer, int length)
+{
+	int offset = serialDevice[reader_index].buffer_offset;
+	int offset_last = serialDevice[reader_index].buffer_offset_last;
+
+	DEBUG_COMM3("available: %d, needed: %d", offset_last-offset,
+		length);
+	/* enough data are available */
+	if (offset + length <= offset_last)
+	{
+		DEBUG_COMM("data available");
+		memcpy(buffer, serialDevice[reader_index].buffer + offset, length);
+		serialDevice[reader_index].buffer_offset += length;
+	}
+	else
+	{
+		int present, rv;
+
+		/* copy available data */
+		present = offset_last - offset;
+
+		if (present > 0)
+		{
+			DEBUG_COMM2("some data available: %d", present);
+			memcpy(buffer, serialDevice[reader_index].buffer + offset,
+				present);
+		}
+
+		/* get fresh data */
+		DEBUG_COMM2("get more data: %d", length - present);
+		rv = ReadChunk(reader_index, serialDevice[reader_index].buffer,
+			sizeof(serialDevice[reader_index].buffer), length - present);
+		if (rv < 0)
+			return STATUS_COMM_ERROR;
+
+		/* fill the buffer */
+		memcpy(buffer + present, serialDevice[reader_index].buffer,
+			length - present);
+		serialDevice[reader_index].buffer_offset = length - present;
+		serialDevice[reader_index].buffer_offset_last = rv;
+		DEBUG_COMM3("offset: %d, last_offset: %d",
+			serialDevice[reader_index].buffer_offset,
+			serialDevice[reader_index].buffer_offset_last);
+	}
+
+	return STATUS_SUCCESS;
+} /* get_bytes */
+
+
+/*****************************************************************************
+ *
+ *				ReadChunk: read a minimum number of bytes
+ *
+ *****************************************************************************/
+static int ReadChunk(unsigned int reader_index, unsigned char *buffer,
+	int buffer_length, int min_length)
+{
+	int fd = serialDevice[reader_index].fd;
+# ifndef S_SPLINT_S
+	fd_set fdset;
+# endif
+	struct timeval t;
+	int i, rv = 0;
+	int already_read;
+	char debug_header[] = "<- 123456 ";
+
+	(void)snprintf(debug_header, sizeof(debug_header), "<- %06X ",
+		reader_index);
+
+	already_read = 0;
+	while (already_read < min_length)
+	{
+		/* use select() to, eventually, timeout */
+		FD_ZERO(&fdset);
+		FD_SET(fd, &fdset);
+		t.tv_sec = serialDevice[reader_index].ccid.readTimeout / 1000;
+		t.tv_usec = (serialDevice[reader_index].ccid.readTimeout - t.tv_sec*1000)*1000;
+
+		i = select(fd+1, &fdset, NULL, NULL, &t);
+		if (i == -1)
+		{
+			DEBUG_CRITICAL2("select: %s", strerror(errno));
+			return -1;
+		}
+		else
+			if (i == 0)
+			{
+				DEBUG_COMM2("Timeout! (%d ms)", serialDevice[reader_index].ccid.readTimeout);
+				return -1;
+			}
+
+		rv = read(fd, buffer + already_read, buffer_length - already_read);
+		if (rv < 0)
+		{
+			DEBUG_COMM2("read error: %s", strerror(errno));
+			return -1;
+		}
+		if (!rv) {
+			close(fd);
+			serialDevice[reader_index].fd=-1;
+			return -1;
+		}
+		
+
+		DEBUG_XXD(debug_header, buffer + already_read, rv);
+
+		already_read += rv;
+		DEBUG_COMM3("read: %d, to read: %d", already_read,
+			min_length);
+	}
+
+	return already_read;
+} /* ReadChunk */
+
+
+/*****************************************************************************
+ *
+ *				OpenHidSerial: open the port
+ *
+ *****************************************************************************/
+status_t OpenHidSerial(unsigned int reader_index, int channel)
+{
+	return STATUS_UNSUCCESSFUL;
+} /* OpenHidSerial */
+
+/*****************************************************************************
+ *
+ *				set_ccid_descriptor: init ccid descriptor
+ *				depending on reader type specified in device.
+ *
+ *				return: STATUS_UNSUCCESSFUL,
+ *						STATUS_SUCCESS,
+ *						-1 (Reader already used)
+ *
+ *****************************************************************************/
+static status_t set_ccid_descriptor(unsigned int reader_index,
+	const char *reader_name, const char *dev_name)
+{
+	int readerID;
+	int i;
+
+	readerID = HID_OMNIKEY_2061;
+
+	/* check if the same channel is not already used to manage multi-slots readers*/
+	for (i = 0; i < CCID_DRIVER_MAX_READERS; i++)
+	{
+		if (serialDevice[i].device
+			&& strcmp(serialDevice[i].device, dev_name) == 0)
+		{
+			DEBUG_COMM2("%s already used. Multi-slot reader?", dev_name);
+			return  STATUS_UNSUCCESSFUL;
+		}
+	}
+
+	/* Common to all readers */
+	serialDevice[reader_index].ccid.real_bSeq = 0x77;
+	serialDevice[reader_index].ccid.pbSeq = &serialDevice[reader_index].ccid.real_bSeq;
+	serialDevice[reader_index].real_nb_opened_slots = 1;
+	serialDevice[reader_index].nb_opened_slots = &serialDevice[reader_index].real_nb_opened_slots;
+
+	serialDevice[reader_index].buffer_offset = 0;
+	serialDevice[reader_index].buffer_offset_last = 0;
+
+	serialDevice[reader_index].ccid.readerID = readerID;
+
+	serialDevice[reader_index].ccid.bCurrentSlotIndex = 0;
+
+	serialDevice[reader_index].ccid.dwMaxCCIDMessageLength = 0x10f;
+	serialDevice[reader_index].ccid.dwMaxIFSD = 0xfe;
+	serialDevice[reader_index].ccid.dwDefaultClock = 0x12c0;
+	serialDevice[reader_index].ccid.bPINSupport = 0x0;
+	serialDevice[reader_index].ccid.dwMaxDataRate = 0x64ce7;
+	serialDevice[reader_index].ccid.bMaxSlotIndex = 0;
+	serialDevice[reader_index].ccid.arrayOfSupportedDataRates = OMNIKey2061Rates;
+
+
+	serialDevice[reader_index].ccid.dwFeatures = 0x000207B8;
+
+
+	serialDevice[reader_index].ccid.readTimeout = DEFAULT_COM_READ_TIMEOUT;
+	serialDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT;
+	serialDevice[reader_index].ccid.bVoltageSupport = 0x07;	/* 1.8V, 3V and 5V */
+
+	serialDevice[reader_index].ccid.gemalto_firmware_features = NULL;
+
+	return STATUS_SUCCESS;
+} /* set_ccid_descriptor  */
+
+
+/*****************************************************************************
+ *
+ *				OpenHidSerialByName: open the port
+ *
+ *****************************************************************************/
+status_t OpenHidSerialByName(unsigned int reader_index, char *dev_name)
+{
+	unsigned int reader = reader_index;
+	/* 255 is MAX_DEVICENAME in pcscd.h */
+	char reader_name[255] = "HID 2061";
+	status_t ret;
+
+	DEBUG_COMM3("Reader index: %X, Device: %s", reader_index, dev_name);
+
+	ret = set_ccid_descriptor(reader_index, reader_name, dev_name);
+	if (STATUS_UNSUCCESSFUL == ret)
+		return STATUS_UNSUCCESSFUL;
+
+	/* set channel used */
+	serialDevice[reader].device = strdup(dev_name);
+
+	serialDevice[reader].fd = -1;
+
+	serialDevice[reader_index].ccid.sIFD_serial_number = NULL;
+	serialDevice[reader_index].ccid.sIFD_iManufacturer = NULL;
+	serialDevice[reader_index].ccid.IFD_bcdDevice = 0;
+
+	return STATUS_SUCCESS;
+} /* OpenHidSerialByName */
+
+
+/*****************************************************************************
+ *
+ *				CloseHidSerial: close the port
+ *
+ *****************************************************************************/
+status_t CloseHidSerial(unsigned int reader_index)
+{
+	unsigned int reader = reader_index;
+
+	/* device not opened */
+	if (NULL == serialDevice[reader_index].device)
+		return STATUS_UNSUCCESSFUL;
+
+	DEBUG_COMM2("Closing serial device: %s", serialDevice[reader_index].device);
+
+	/* Decrement number of opened slot */
+	(*serialDevice[reader_index].nb_opened_slots)--;
+
+	/* release the allocated ressources for the last slot only */
+	if (0 == *serialDevice[reader_index].nb_opened_slots)
+	{
+		DEBUG_COMM("Last slot closed. Release resources");
+
+		if (serialDevice[reader].fd!=-1) {
+		(void)close(serialDevice[reader].fd);
+		serialDevice[reader].fd = -1;
+		}
+
+		free(serialDevice[reader].device);
+		serialDevice[reader].device = NULL;
+	}
+
+	return STATUS_SUCCESS;
+} /* CloseHidSerial */
+
+
+/*****************************************************************************
+ *
+ *					get_ccid_descriptor
+ *
+ ****************************************************************************/
+_ccid_descriptor *get_ccid_descriptor(unsigned int reader_index)
+{
+	return &serialDevice[reader_index].ccid;
+} /* get_ccid_descriptor */
+
+
diff --git a/src/ccid_hid_serial.h b/src/ccid_hid_serial.h
new file mode 100644
index 0000000..60e1587
--- /dev/null
+++ b/src/ccid_hid_serial.h
@@ -0,0 +1,35 @@
+/*
+    ccid_hid_serial.h:  Serial access routines
+    Copyright (C) 2003-2008   Ludovic Rousseau
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public License
+	along with this library; if not, write to the Free Software Foundation,
+	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef __CCID_HID_SERIAL_H__
+#define __CCID_HID_SERIAL_H__
+
+status_t OpenHidSerial(unsigned int reader_index, int channel);
+
+status_t OpenHidSerialByName(unsigned int reader_index, char *dev_name);
+
+status_t WriteHidSerial(unsigned int reader_index, unsigned int length,
+	unsigned char *Buffer);
+
+status_t ReadHidSerial(unsigned int reader_index, unsigned int *length,
+	unsigned char *Buffer);
+
+status_t CloseHidSerial(unsigned int reader_index);
+
+#endif
diff --git a/src/ccid_serial.c b/src/ccid_serial.c
deleted file mode 100644
index 32f4027..0000000
--- a/src/ccid_serial.c
+++ /dev/null
@@ -1,916 +0,0 @@
-/*
- * ccid_serial.c: communicate with a GemPC Twin smart card reader
- * Copyright (C) 2001-2010 Ludovic Rousseau <ludovic.rousseau at free.fr>
- *
- * Thanks to Niki W. Waibel <niki.waibel at gmx.net> for a prototype version
- *
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-	You should have received a copy of the GNU Lesser General Public License
-	along with this library; if not, write to the Free Software Foundation,
-	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <termios.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <ifdhandler.h>
-
-#include <config.h>
-#include "defs.h"
-#include "ccid_ifdhandler.h"
-#include "debug.h"
-#include "ccid.h"
-#include "utils.h"
-#include "commands.h"
-#include "parser.h"
-#include "strlcpycat.h"
-
-#define SYNC 0x03
-#define CTRL_ACK 0x06
-#define CTRL_NAK 0x15
-#define RDR_to_PC_NotifySlotChange 0x50
-#define CARD_ABSENT 0x02
-#define CARD_PRESENT 0x03
-
-/*
- * normal command:
- * 1 : SYNC
- * 1 : CTRL
- * 10 +data length : CCID command
- * 1 : LRC
- *
- * SYNC : 0x03
- * CTRL : ACK (0x06) or NAK (0x15)
- * CCID command : see USB CCID specs
- * LRC : xor of all the previous byes
- *
- * Error message:
- * 1 : SYNC (0x03)
- * 1 : CTRL (NAK: 0x15)
- * 1 : LRC (0x16)
- *
- * Card insertion/withdrawal
- * 1 : RDR_to_PC_NotifySlotChange (0x50)
- * 1 : bmSlotIccState
- *     0x02 if card absent
- *     0x03 is card present
- *
- * Time request
- * T=1 : normal CCID command
- * T=0 : 1 byte (value between 0x80 and 0xFF)
- *
- */
-
-/*
- * You may get read timeout after a card movement.
- * This is because you will get the echo of the CCID command
- * but not the result of the command.
- *
- * This is not an applicative issue since the card is either removed (and
- * powered off) or just inserted (and not yet powered on).
- */
-
-/* 271 = max size for short APDU
- * 2 bytes for header
- * 1 byte checksum
- * doubled for echo
- */
-#define GEMPCTWIN_MAXBUF (271 +2 +1) * 2
-
-typedef struct
-{
-	/*
-	 * File handle on the serial port
-	 */
-	int fd;
-
-	/*
-	 * device used ("/dev/ttyS?" under Linux)
-	 */
-	/*@null@*/ char *device;
-
-	/*
-	 * Number of slots using the same device
-	 */
-	int real_nb_opened_slots;
-	int *nb_opened_slots;
-
-	/*
-	 * does the reader echoes the serial communication bytes?
-	 */
-	int echo;
-
-	/*
-	 * serial communication buffer
-	 */
-	unsigned char buffer[GEMPCTWIN_MAXBUF];
-
-	/*
-	 * next available byte
-	 */
-	int buffer_offset;
-
-	/*
-	 * number of available bytes
-	 */
-	int buffer_offset_last;
-
-	/*
-	 * CCID infos common to USB and serial
-	 */
-	_ccid_descriptor ccid;
-
-} _serialDevice;
-
-/* The _serialDevice structure must be defined before including ccid_serial.h */
-#include "ccid_serial.h"
-
-/* data rates supported by the GemPC Twin (serial and PCMCIA) */
-unsigned int SerialTwinDataRates[] = { ISO_DATA_RATES, 0 };
-
-/* data rates supported by the GemPC PinPad, GemCore Pos Pro & SIM Pro */
-unsigned int SerialExtendedDataRates[] = { ISO_DATA_RATES, 500000, 0 };
-
-/* data rates supported by the secondary slots on the GemCore Pos Pro & SIM Pro */
-unsigned int SerialCustomDataRates[] = { GEMPLUS_CUSTOM_DATA_RATES, 0 };
-
-/* data rates supported by the GemCore SIM Pro 2 */
-unsigned int SIMPro2DataRates[] = { SIMPRO2_ISO_DATA_RATES, 0  };
-
-/* no need to initialize to 0 since it is static */
-static _serialDevice serialDevice[CCID_DRIVER_MAX_READERS];
-
-/* unexported functions */
-static int ReadChunk(unsigned int reader_index, unsigned char *buffer,
-	int buffer_length, int min_length);
-
-static int get_bytes(unsigned int reader_index, /*@out@*/ unsigned char *buffer,
-	int length);
-
-
-/*****************************************************************************
- *
- *				WriteSerial: Send bytes to the card reader
- *
- *****************************************************************************/
-status_t WriteSerial(unsigned int reader_index, unsigned int length,
-	unsigned char *buffer)
-{
-	unsigned int i;
-	unsigned char lrc;
-	unsigned char low_level_buffer[GEMPCTWIN_MAXBUF];
-
-	char debug_header[] = "-> 123456 ";
-
-	(void)snprintf(debug_header, sizeof(debug_header), "-> %06X ",
-		reader_index);
-
-	if (length > GEMPCTWIN_MAXBUF-3)
-	{
-		DEBUG_CRITICAL3("command too long: %d for max %d",
-			length, GEMPCTWIN_MAXBUF-3);
-		return STATUS_UNSUCCESSFUL;
-	}
-
-	/* header */
-	low_level_buffer[0] = 0x03;	/* SYNC */
-	low_level_buffer[1] = 0x06;	/* ACK */
-
-	/* CCID command */
-	memcpy(low_level_buffer+2, buffer, length);
-
-	/* checksum */
-	lrc = 0;
-	for(i=0; i<length+2; i++)
-		lrc ^= low_level_buffer[i];
-	low_level_buffer[length+2] = lrc;
-
-	DEBUG_XXD(debug_header, low_level_buffer, length+3);
-
-	if (write(serialDevice[reader_index].fd, low_level_buffer,
-		length+3) != length+3)
-	{
-		DEBUG_CRITICAL2("write error: %s", strerror(errno));
-		return STATUS_UNSUCCESSFUL;
-	}
-
-	return STATUS_SUCCESS;
-} /* WriteSerial */
-
-
-/*****************************************************************************
- *
- *				ReadSerial: Receive bytes from the card reader
- *
- *****************************************************************************/
-status_t ReadSerial(unsigned int reader_index,
-	unsigned int *length, unsigned char *buffer)
-{
-	unsigned char c;
-	int rv;
-	int echo;
-	int to_read;
-	int i;
-
-	/* we get the echo first */
-	echo = serialDevice[reader_index].echo;
-
-start:
-	DEBUG_COMM("start");
-	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
-		return rv;
-
-	if (c == RDR_to_PC_NotifySlotChange)
-		goto slot_change;
-
-	if (c == SYNC)
-		goto sync;
-
-	if (c >= 0x80)
-	{
-		DEBUG_COMM2("time request: 0x%02X", c);
-		goto start;
-	}
-
-	DEBUG_CRITICAL2("Got 0x%02X", c);
-	return STATUS_COMM_ERROR;
-
-slot_change:
-	DEBUG_COMM("slot change");
-	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
-		return rv;
-
-	if (c == CARD_ABSENT)
-	{
-		DEBUG_COMM("Card removed");
-	}
-	else
-		if (c == CARD_PRESENT)
-		{
-			DEBUG_COMM("Card inserted");
-		}
-		else
-		{
-			DEBUG_COMM2("Unknown card movement: %d", c);
-		}
-	goto start;
-
-sync:
-	DEBUG_COMM("sync");
-	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
-		return rv;
-
-	if (c == CTRL_ACK)
-		goto ack;
-
-	if (c == CTRL_NAK)
-		goto nak;
-
-	DEBUG_CRITICAL2("Got 0x%02X instead of ACK/NAK", c);
-	return STATUS_COMM_ERROR;
-
-nak:
-	DEBUG_COMM("nak");
-	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
-		return rv;
-
-	if (c != (SYNC ^ CTRL_NAK))
-	{
-		DEBUG_CRITICAL2("Wrong LRC: 0x%02X", c);
-		return STATUS_COMM_ERROR;
-	}
-	else
-	{
-		DEBUG_COMM("NAK requested");
-		return STATUS_COMM_NAK;
-	}
-
-ack:
-	DEBUG_COMM("ack");
-	/* normal CCID frame */
-	if ((rv = get_bytes(reader_index, buffer, 5)) != STATUS_SUCCESS)
-		return rv;
-
-	/* total frame size */
-	to_read = 10+dw2i(buffer, 1);
-
-	if ((to_read < 10) || (to_read > (int)*length))
-	{
-		DEBUG_CRITICAL2("Wrong value for frame size: %d", to_read);
-		return STATUS_COMM_ERROR;
-	}
-
-	DEBUG_COMM2("frame size: %d", to_read);
-	if ((rv = get_bytes(reader_index, buffer+5, to_read-5)) != STATUS_SUCCESS)
-		return rv;
-
-	DEBUG_XXD("frame: ", buffer, to_read);
-
-	/* lrc */
-	DEBUG_COMM("lrc");
-	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
-		return rv;
-
-	DEBUG_COMM2("lrc: 0x%02X", c);
-	for (i=0; i<to_read; i++)
-		c ^= buffer[i];
-
-	if (c != (SYNC ^ CTRL_ACK))
-		DEBUG_CRITICAL2("Wrong LRC: 0x%02X", c);
-
-	if (echo)
-	{
-		echo = FALSE;
-		goto start;
-	}
-
-	/* length of data read */
-	*length = to_read;
-
-	return STATUS_SUCCESS;
-} /* ReadSerial */
-
-
-/*****************************************************************************
- *
- *				get_bytes: get n bytes
- *
- *****************************************************************************/
-int get_bytes(unsigned int reader_index, unsigned char *buffer, int length)
-{
-	int offset = serialDevice[reader_index].buffer_offset;
-	int offset_last = serialDevice[reader_index].buffer_offset_last;
-
-	DEBUG_COMM3("available: %d, needed: %d", offset_last-offset,
-		length);
-	/* enough data are available */
-	if (offset + length <= offset_last)
-	{
-		DEBUG_COMM("data available");
-		memcpy(buffer, serialDevice[reader_index].buffer + offset, length);
-		serialDevice[reader_index].buffer_offset += length;
-	}
-	else
-	{
-		int present, rv;
-
-		/* copy available data */
-		present = offset_last - offset;
-
-		if (present > 0)
-		{
-			DEBUG_COMM2("some data available: %d", present);
-			memcpy(buffer, serialDevice[reader_index].buffer + offset,
-				present);
-		}
-
-		/* get fresh data */
-		DEBUG_COMM2("get more data: %d", length - present);
-		rv = ReadChunk(reader_index, serialDevice[reader_index].buffer,
-			sizeof(serialDevice[reader_index].buffer), length - present);
-		if (rv < 0)
-			return STATUS_COMM_ERROR;
-
-		/* fill the buffer */
-		memcpy(buffer + present, serialDevice[reader_index].buffer,
-			length - present);
-		serialDevice[reader_index].buffer_offset = length - present;
-		serialDevice[reader_index].buffer_offset_last = rv;
-		DEBUG_COMM3("offset: %d, last_offset: %d",
-			serialDevice[reader_index].buffer_offset,
-			serialDevice[reader_index].buffer_offset_last);
-	}
-
-	return STATUS_SUCCESS;
-} /* get_bytes */
-
-
-/*****************************************************************************
- *
- *				ReadChunk: read a minimum number of bytes
- *
- *****************************************************************************/
-static int ReadChunk(unsigned int reader_index, unsigned char *buffer,
-	int buffer_length, int min_length)
-{
-	int fd = serialDevice[reader_index].fd;
-# ifndef S_SPLINT_S
-	fd_set fdset;
-# endif
-	struct timeval t;
-	int i, rv = 0;
-	int already_read;
-	char debug_header[] = "<- 123456 ";
-
-	(void)snprintf(debug_header, sizeof(debug_header), "<- %06X ",
-		reader_index);
-
-	already_read = 0;
-	while (already_read < min_length)
-	{
-		/* use select() to, eventually, timeout */
-		FD_ZERO(&fdset);
-		FD_SET(fd, &fdset);
-		t.tv_sec = serialDevice[reader_index].ccid.readTimeout / 1000;
-		t.tv_usec = (serialDevice[reader_index].ccid.readTimeout - t.tv_sec*1000)*1000;
-
-		i = select(fd+1, &fdset, NULL, NULL, &t);
-		if (i == -1)
-		{
-			DEBUG_CRITICAL2("select: %s", strerror(errno));
-			return -1;
-		}
-		else
-			if (i == 0)
-			{
-				DEBUG_COMM2("Timeout! (%d ms)", serialDevice[reader_index].ccid.readTimeout);
-				return -1;
-			}
-
-		rv = read(fd, buffer + already_read, buffer_length - already_read);
-		if (rv < 0)
-		{
-			DEBUG_COMM2("read error: %s", strerror(errno));
-			return -1;
-		}
-
-		DEBUG_XXD(debug_header, buffer + already_read, rv);
-
-		already_read += rv;
-		DEBUG_COMM3("read: %d, to read: %d", already_read,
-			min_length);
-	}
-
-	return already_read;
-} /* ReadChunk */
-
-
-/*****************************************************************************
- *
- *				OpenSerial: open the port
- *
- *****************************************************************************/
-status_t OpenSerial(unsigned int reader_index, int channel)
-{
-	char dev_name[FILENAME_MAX];
-
-	DEBUG_COMM3("Reader index: %X, Channel: %d", reader_index, channel);
-
-	/*
-	 * Conversion of old-style ifd-hanler 1.0 CHANNELID
-	 */
-	if (channel == 0x0103F8)
-		channel = 1;
-	else
-		if (channel == 0x0102F8)
-			channel = 2;
-		else
-			if (channel == 0x0103E8)
-				channel = 3;
-			else
-				if (channel == 0x0102E8)
-					channel = 4;
-
-	if (channel < 0)
-	{
-		DEBUG_CRITICAL2("wrong port number: %d", channel);
-		return STATUS_UNSUCCESSFUL;
-	}
-
-	(void)snprintf(dev_name, sizeof(dev_name), "/dev/pcsc/%d", channel);
-
-	return OpenSerialByName(reader_index, dev_name);
-} /* OpenSerial */
-
-/*****************************************************************************
- *
- *				set_ccid_descriptor: init ccid descriptor
- *				depending on reader type specified in device.
- *
- *				return: STATUS_UNSUCCESSFUL,
- *						STATUS_SUCCESS,
- *						-1 (Reader already used)
- *
- *****************************************************************************/
-static status_t set_ccid_descriptor(unsigned int reader_index,
-	const char *reader_name, const char *dev_name)
-{
-	int readerID;
-	int i;
-	int already_used = FALSE;
-	static int previous_reader_index = -1;
-
-	readerID = GEMPCTWIN;
-	if (0 == strcasecmp(reader_name,"GemCorePOSPro"))
-		readerID = GEMCOREPOSPRO;
-	else if (0 == strcasecmp(reader_name,"GemCoreSIMPro"))
-		readerID = GEMCORESIMPRO;
-	else if (0 == strcasecmp(reader_name,"GemCoreSIMPro2"))
-		readerID = GEMCORESIMPRO2;
-	else if (0 == strcasecmp(reader_name,"GemPCPinPad"))
-		readerID = GEMPCPINPAD;
-
-	/* check if the same channel is not already used to manage multi-slots readers*/
-	for (i = 0; i < CCID_DRIVER_MAX_READERS; i++)
-	{
-		if (serialDevice[i].device
-			&& strcmp(serialDevice[i].device, dev_name) == 0)
-		{
-			already_used = TRUE;
-
-			DEBUG_COMM2("%s already used. Multi-slot reader?", dev_name);
-			break;
-		}
-	}
-
-	/* this reader is already managed by us */
-	if (already_used)
-	{
-		if ((previous_reader_index != -1)
-			&& serialDevice[previous_reader_index].device
-			&& (strcmp(serialDevice[previous_reader_index].device, dev_name) == 0)
-			&& serialDevice[previous_reader_index].ccid.bCurrentSlotIndex < serialDevice[previous_reader_index].ccid.bMaxSlotIndex)
-		{
-			/* we reuse the same device and the reader is multi-slot */
-			serialDevice[reader_index] = serialDevice[previous_reader_index];
-
-			*serialDevice[reader_index].nb_opened_slots += 1;
-			serialDevice[reader_index].ccid.bCurrentSlotIndex++;
-			serialDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT;
-			DEBUG_INFO2("Opening slot: %d",
-					serialDevice[reader_index].ccid.bCurrentSlotIndex);
-			switch (readerID)
-			{
-				case GEMCOREPOSPRO:
-				case GEMCORESIMPRO:
-					{
-						/* Allocate a memory buffer that will be
-						 * released in CloseUSB() */
-						void *ptr = malloc(sizeof SerialCustomDataRates);
-						if (ptr)
-						{
-							memcpy(ptr, SerialCustomDataRates,
-									sizeof SerialCustomDataRates);
-						}
-
-						serialDevice[reader_index].ccid.arrayOfSupportedDataRates = ptr;
-					}
-					serialDevice[reader_index].ccid.dwMaxDataRate = 125000;
-					break;
-
-				/* GemPC Twin or GemPC Card */
-				default:
-					serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialTwinDataRates;
-					serialDevice[reader_index].ccid.dwMaxDataRate = 344086;
-					break;
-			}
-			goto end;
-		}
-		else
-		{
-			DEBUG_CRITICAL2("Trying to open too many slots on %s", dev_name);
-			return STATUS_UNSUCCESSFUL;
-		}
-
-	}
-
-	/* Common to all readers */
-	serialDevice[reader_index].ccid.real_bSeq = 0;
-	serialDevice[reader_index].ccid.pbSeq = &serialDevice[reader_index].ccid.real_bSeq;
-	serialDevice[reader_index].real_nb_opened_slots = 1;
-	serialDevice[reader_index].nb_opened_slots = &serialDevice[reader_index].real_nb_opened_slots;
-	serialDevice[reader_index].ccid.bCurrentSlotIndex = 0;
-
-	serialDevice[reader_index].ccid.dwMaxCCIDMessageLength = 271;
-	serialDevice[reader_index].ccid.dwMaxIFSD = 254;
-	serialDevice[reader_index].ccid.dwFeatures = 0x00010230;
-	serialDevice[reader_index].ccid.dwDefaultClock = 4000;
-
-	serialDevice[reader_index].buffer_offset = 0;
-	serialDevice[reader_index].buffer_offset_last = 0;
-
-	serialDevice[reader_index].ccid.readerID = readerID;
-	serialDevice[reader_index].ccid.bPINSupport = 0x0;
-	serialDevice[reader_index].ccid.dwMaxDataRate = 344086;
-	serialDevice[reader_index].ccid.bMaxSlotIndex = 0;
-	serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialTwinDataRates;
-	serialDevice[reader_index].ccid.readTimeout = DEFAULT_COM_READ_TIMEOUT;
-	serialDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT;
-	serialDevice[reader_index].ccid.bVoltageSupport = 0x07;	/* 1.8V, 3V and 5V */
-	serialDevice[reader_index].ccid.gemalto_firmware_features = NULL;
-	serialDevice[reader_index].echo = TRUE;
-
-	/* change some values depending on the reader */
-	switch (readerID)
-	{
-		case GEMCOREPOSPRO:
-			serialDevice[reader_index].ccid.bMaxSlotIndex = 4;	/* 5 slots */
-			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
-			serialDevice[reader_index].echo = FALSE;
-			serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
-			break;
-
-		case GEMCORESIMPRO:
-			serialDevice[reader_index].ccid.bMaxSlotIndex = 1; /* 2 slots */
-			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
-			serialDevice[reader_index].echo = FALSE;
-			serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
-			break;
-
-		case GEMCORESIMPRO2:
-			serialDevice[reader_index].ccid.dwDefaultClock = 4800;
-			serialDevice[reader_index].ccid.bMaxSlotIndex = 1; /* 2 slots */
-			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SIMPro2DataRates;
-			serialDevice[reader_index].echo = FALSE;
-			serialDevice[reader_index].ccid.dwMaxDataRate = 825806;
-			break;
-
-		case GEMPCPINPAD:
-			serialDevice[reader_index].ccid.bPINSupport = 0x03;
-			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
-			serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
-			break;
-	}
-
-end:
-	/* memorise the current reader_index so we can detect
-	 * a new OpenSerialByName on a multi slot reader */
-	previous_reader_index = reader_index;
-
-	/* we just created a secondary slot on a multi-slot reader */
-	if (already_used)
-		return STATUS_SECONDARY_SLOT;
-
-	return STATUS_SUCCESS;
-} /* set_ccid_descriptor  */
-
-
-/*****************************************************************************
- *
- *				OpenSerialByName: open the port
- *
- *****************************************************************************/
-status_t OpenSerialByName(unsigned int reader_index, char *dev_name)
-{
-	struct termios current_termios;
-	unsigned int reader = reader_index;
-	/* 255 is MAX_DEVICENAME in pcscd.h */
-	char reader_name[255] = "GemPCTwin";
-	char *p;
-	status_t ret;
-
-	DEBUG_COMM3("Reader index: %X, Device: %s", reader_index, dev_name);
-
-	/* parse dev_name using the pattern "device:name" */
-	p = strchr(dev_name, ':');
-	if (p)
-	{
-		/* copy the second part of the string */
-		strlcpy(reader_name, p+1, sizeof(reader_name));
-
-		/* replace ':' by '\0' so that dev_name only contains the device name */
-		*p = '\0';
-	}
-
-	ret = set_ccid_descriptor(reader_index, reader_name, dev_name);
-	if (STATUS_UNSUCCESSFUL == ret)
-		return STATUS_UNSUCCESSFUL;
-
-	/* secondary slot so do not physically open the device */
-	if (STATUS_SECONDARY_SLOT == ret)
-		return STATUS_SUCCESS;
-
-	serialDevice[reader].fd = open(dev_name, O_RDWR | O_NOCTTY);
-
-	if (-1 == serialDevice[reader].fd)
-	{
-		DEBUG_CRITICAL3("open %s: %s", dev_name, strerror(errno));
-		return STATUS_UNSUCCESSFUL;
-	}
-
-	/* Set RTS signal to low to prevent the smart card reader
-	 * from sending its plug and play string. */
-	{
-		int flags;
-
-		if (ioctl(serialDevice[reader].fd, TIOCMGET, &flags) < 0)
-		{
-			DEBUG_CRITICAL2("Get RS232 signals state failed: %s",
-				strerror(errno));
-		}
-		else
-		{
-			flags &= ~TIOCM_RTS;
-			if (ioctl(serialDevice[reader].fd, TIOCMSET, &flags) < 0)
-			{
-				DEBUG_CRITICAL2("Set RTS to low failed: %s", strerror(errno));
-			}
-			else
-			{
-				DEBUG_COMM("Plug-n-Play inhibition successful");
-			}
-		}
-	}
-
-	/* set channel used */
-	serialDevice[reader].device = strdup(dev_name);
-
-	/* empty in and out serial buffers */
-	if (tcflush(serialDevice[reader].fd, TCIOFLUSH))
-			DEBUG_INFO2("tcflush() function error: %s", strerror(errno));
-
-	/* get config attributes */
-	if (tcgetattr(serialDevice[reader].fd, &current_termios) == -1)
-	{
-		DEBUG_INFO2("tcgetattr() function error: %s", strerror(errno));
-		(void)close(serialDevice[reader].fd);
-		serialDevice[reader].fd = -1;
-
-		return STATUS_UNSUCCESSFUL;
-	}
-
-	/* IGNBRK: ignore BREAK condition on input
-	 * IGNPAR: ignore framing errors and parity errors. */
-	current_termios.c_iflag = IGNBRK | IGNPAR;
-	current_termios.c_oflag = 0;	/* Raw output modes */
-	/* CS8: 8-bits character size
-	 * CSTOPB: set two stop bits
-	 * CREAD: enable receiver
-	 * CLOCAL: ignore modem control lines */
-	current_termios.c_cflag = CS8 | CSTOPB | CREAD | CLOCAL;
-
-	/* Do not echo characters because if you connect to a host it or your modem
-	 * will echo characters for you.  Don't generate signals. */
-	current_termios.c_lflag = 0;
-
-	if (0 == strcasecmp(reader_name,"GemCoreSIMPro2"))
-	{
-		unsigned char pcbuffer[SIZE_GET_SLOT_STATUS];
-		unsigned int old_timeout;
-		RESPONSECODE r;
-
-		/* Unless we resume from a stand-by condition, GemCoreSIMPro2
-		 * starts at 9600 bauds, so let's first try this speed */
-		/* set serial port speed to 9600 bauds */
-		(void)cfsetspeed(&current_termios, B9600);
-		DEBUG_INFO1("Set serial port baudrate to 9600 and correct configuration");
-		if (tcsetattr(serialDevice[reader_index].fd, TCSANOW, &current_termios) == -1)
-		{
-			(void)close(serialDevice[reader_index].fd);
-			serialDevice[reader_index].fd = -1;
-			DEBUG_CRITICAL2("tcsetattr error: %s", strerror(errno));
-
-			return STATUS_UNSUCCESSFUL;
-		}
-
-		/* Test current speed issuing a CmdGetSlotStatus with a very
-		 * short time out of 1 seconds */
-		old_timeout = serialDevice[reader_index].ccid.readTimeout;
-
-		serialDevice[reader_index].ccid.readTimeout = 1*1000;
-		r = CmdGetSlotStatus(reader_index, pcbuffer);
-
-		/* Restore default time out value */
-		serialDevice[reader_index].ccid.readTimeout = old_timeout;
-
-		if (IFD_SUCCESS == r)
-		{
-			/* We are at 9600 bauds, let's move to 115200 */
-			unsigned char tx_buffer[] = { 0x01, 0x10, 0x20 };
-			unsigned char rx_buffer[50];
-			unsigned int rx_length = sizeof(rx_buffer);
-
-			if (IFD_SUCCESS == CmdEscape(reader_index, tx_buffer,
-				sizeof(tx_buffer), rx_buffer, &rx_length, 0))
-			{
-				/* Let the reader setup its new communication speed */
-				(void)usleep(250*1000);
-			}
-			else
-			{
-				DEBUG_INFO1("CmdEscape to configure 115200 bauds failed");
-			}
-		}
-		/* In case of a failure, reader is probably already at 115200
-		 * bauds as code below assumes */
-	}
-
-	/* set serial port speed to 115200 bauds */
-	(void)cfsetspeed(&current_termios, B115200);
-
-	DEBUG_INFO1("Set serial port baudrate to 115200 and correct configuration");
-	if (tcsetattr(serialDevice[reader].fd, TCSANOW, &current_termios) == -1)
-	{
-		(void)close(serialDevice[reader].fd);
-		serialDevice[reader].fd = -1;
-		DEBUG_INFO2("tcsetattr error: %s", strerror(errno));
-
-		return STATUS_UNSUCCESSFUL;
-	}
-
-	/* perform a command to be sure a Gemalto reader is connected
-	 * get the reader firmware */
-	{
-		unsigned char tx_buffer[] = { 0x02 };
-		unsigned char rx_buffer[50];
-		unsigned int rx_length = sizeof(rx_buffer);
-
-		/* 2 seconds timeout to not wait too long if no reader is connected */
-		if (IFD_SUCCESS != CmdEscape(reader_index, tx_buffer, sizeof(tx_buffer),
-			rx_buffer, &rx_length, 2*1000))
-		{
-			DEBUG_CRITICAL("Get firmware failed. Maybe the reader is not connected");
-			(void)CloseSerial(reader_index);
-			return STATUS_UNSUCCESSFUL;
-		}
-
-		rx_buffer[rx_length] = '\0';
-		DEBUG_INFO2("Firmware: %s", rx_buffer);
-	}
-
-	/* perform a command to configure GemPC Twin reader card movement
-	 * notification to synchronous mode: the card movement is notified _after_
-	 * the host command and _before_ the reader anwser */
-	{
-		unsigned char tx_buffer[] = { 0x01, 0x01, 0x01};
-		unsigned char rx_buffer[50];
-		unsigned int rx_length = sizeof(rx_buffer);
-
-		if (IFD_SUCCESS != CmdEscape(reader_index, tx_buffer, sizeof(tx_buffer),
-			rx_buffer, &rx_length, 0))
-		{
-			DEBUG_CRITICAL("Change card movement notification failed.");
-			(void)CloseSerial(reader_index);
-			return STATUS_UNSUCCESSFUL;
-		}
-	}
-
-	serialDevice[reader_index].ccid.sIFD_serial_number = NULL;
-	serialDevice[reader_index].ccid.sIFD_iManufacturer = NULL;
-	serialDevice[reader_index].ccid.IFD_bcdDevice = 0;
-
-	return STATUS_SUCCESS;
-} /* OpenSerialByName */
-
-
-/*****************************************************************************
- *
- *				CloseSerial: close the port
- *
- *****************************************************************************/
-status_t CloseSerial(unsigned int reader_index)
-{
-	unsigned int reader = reader_index;
-
-	/* device not opened */
-	if (NULL == serialDevice[reader_index].device)
-		return STATUS_UNSUCCESSFUL;
-
-	DEBUG_COMM2("Closing serial device: %s", serialDevice[reader_index].device);
-
-	/* Decrement number of opened slot */
-	(*serialDevice[reader_index].nb_opened_slots)--;
-
-	/* release the allocated ressources for the last slot only */
-	if (0 == *serialDevice[reader_index].nb_opened_slots)
-	{
-		DEBUG_COMM("Last slot closed. Release resources");
-
-		(void)close(serialDevice[reader].fd);
-		serialDevice[reader].fd = -1;
-
-		free(serialDevice[reader].device);
-		serialDevice[reader].device = NULL;
-	}
-
-	return STATUS_SUCCESS;
-} /* CloseSerial */
-
-
-/*****************************************************************************
- *
- *					get_ccid_descriptor
- *
- ****************************************************************************/
-_ccid_descriptor *get_ccid_descriptor(unsigned int reader_index)
-{
-	return &serialDevice[reader_index].ccid;
-} /* get_ccid_descriptor */
-
-
diff --git a/src/ccid_serial.h b/src/ccid_serial.h
deleted file mode 100644
index c946dd0..0000000
--- a/src/ccid_serial.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-    ccid_serial.h:  Serial access routines
-    Copyright (C) 2003-2008   Ludovic Rousseau
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-	You should have received a copy of the GNU Lesser General Public License
-	along with this library; if not, write to the Free Software Foundation,
-	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#ifndef __CCID_SERAL_H__
-#define __CCID_SERAL_H__
-
-status_t OpenSerial(unsigned int reader_index, int channel);
-
-status_t OpenSerialByName(unsigned int reader_index, char *dev_name);
-
-status_t WriteSerial(unsigned int reader_index, unsigned int length,
-	unsigned char *Buffer);
-
-status_t ReadSerial(unsigned int reader_index, unsigned int *length,
-	unsigned char *Buffer);
-
-status_t CloseSerial(unsigned int reader_index);
-
-#endif
diff --git a/src/ccid_twin_serial.c b/src/ccid_twin_serial.c
new file mode 100644
index 0000000..217ab7d
--- /dev/null
+++ b/src/ccid_twin_serial.c
@@ -0,0 +1,916 @@
+/*
+ * ccid_twin_serial.c: communicate with a GemPC Twin smart card reader
+ * Copyright (C) 2001-2010 Ludovic Rousseau <ludovic.rousseau at free.fr>
+ *
+ * Thanks to Niki W. Waibel <niki.waibel at gmx.net> for a prototype version
+ *
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public License
+	along with this library; if not, write to the Free Software Foundation,
+	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <ifdhandler.h>
+
+#include <config.h>
+#include "defs.h"
+#include "ccid_ifdhandler.h"
+#include "debug.h"
+#include "ccid.h"
+#include "utils.h"
+#include "commands.h"
+#include "parser.h"
+#include "strlcpycat.h"
+
+#define SYNC 0x03
+#define CTRL_ACK 0x06
+#define CTRL_NAK 0x15
+#define RDR_to_PC_NotifySlotChange 0x50
+#define CARD_ABSENT 0x02
+#define CARD_PRESENT 0x03
+
+/*
+ * normal command:
+ * 1 : SYNC
+ * 1 : CTRL
+ * 10 +data length : CCID command
+ * 1 : LRC
+ *
+ * SYNC : 0x03
+ * CTRL : ACK (0x06) or NAK (0x15)
+ * CCID command : see USB CCID specs
+ * LRC : xor of all the previous byes
+ *
+ * Error message:
+ * 1 : SYNC (0x03)
+ * 1 : CTRL (NAK: 0x15)
+ * 1 : LRC (0x16)
+ *
+ * Card insertion/withdrawal
+ * 1 : RDR_to_PC_NotifySlotChange (0x50)
+ * 1 : bmSlotIccState
+ *     0x02 if card absent
+ *     0x03 is card present
+ *
+ * Time request
+ * T=1 : normal CCID command
+ * T=0 : 1 byte (value between 0x80 and 0xFF)
+ *
+ */
+
+/*
+ * You may get read timeout after a card movement.
+ * This is because you will get the echo of the CCID command
+ * but not the result of the command.
+ *
+ * This is not an applicative issue since the card is either removed (and
+ * powered off) or just inserted (and not yet powered on).
+ */
+
+/* 271 = max size for short APDU
+ * 2 bytes for header
+ * 1 byte checksum
+ * doubled for echo
+ */
+#define GEMPCTWIN_MAXBUF (271 +2 +1) * 2
+
+typedef struct
+{
+	/*
+	 * File handle on the serial port
+	 */
+	int fd;
+
+	/*
+	 * device used ("/dev/ttyS?" under Linux)
+	 */
+	/*@null@*/ char *device;
+
+	/*
+	 * Number of slots using the same device
+	 */
+	int real_nb_opened_slots;
+	int *nb_opened_slots;
+
+	/*
+	 * does the reader echoes the serial communication bytes?
+	 */
+	int echo;
+
+	/*
+	 * serial communication buffer
+	 */
+	unsigned char buffer[GEMPCTWIN_MAXBUF];
+
+	/*
+	 * next available byte
+	 */
+	int buffer_offset;
+
+	/*
+	 * number of available bytes
+	 */
+	int buffer_offset_last;
+
+	/*
+	 * CCID infos common to USB and serial
+	 */
+	_ccid_descriptor ccid;
+
+} _serialDevice;
+
+/* The _serialDevice structure must be defined before including ccid_serial.h */
+#include "ccid_twin_serial.h"
+
+/* data rates supported by the GemPC Twin (serial and PCMCIA) */
+unsigned int SerialTwinDataRates[] = { ISO_DATA_RATES, 0 };
+
+/* data rates supported by the GemPC PinPad, GemCore Pos Pro & SIM Pro */
+unsigned int SerialExtendedDataRates[] = { ISO_DATA_RATES, 500000, 0 };
+
+/* data rates supported by the secondary slots on the GemCore Pos Pro & SIM Pro */
+unsigned int SerialCustomDataRates[] = { GEMPLUS_CUSTOM_DATA_RATES, 0 };
+
+/* data rates supported by the GemCore SIM Pro 2 */
+unsigned int SIMPro2DataRates[] = { SIMPRO2_ISO_DATA_RATES, 0  };
+
+/* no need to initialize to 0 since it is static */
+static _serialDevice serialDevice[CCID_DRIVER_MAX_READERS];
+
+/* unexported functions */
+static int ReadChunk(unsigned int reader_index, unsigned char *buffer,
+	int buffer_length, int min_length);
+
+static int get_bytes(unsigned int reader_index, /*@out@*/ unsigned char *buffer,
+	int length);
+
+
+/*****************************************************************************
+ *
+ *				WriteTwinSerial: Send bytes to the card reader
+ *
+ *****************************************************************************/
+status_t WriteTwinSerial(unsigned int reader_index, unsigned int length,
+	unsigned char *buffer)
+{
+	unsigned int i;
+	unsigned char lrc;
+	unsigned char low_level_buffer[GEMPCTWIN_MAXBUF];
+
+	char debug_header[] = "-> 123456 ";
+
+	(void)snprintf(debug_header, sizeof(debug_header), "-> %06X ",
+		reader_index);
+
+	if (length > GEMPCTWIN_MAXBUF-3)
+	{
+		DEBUG_CRITICAL3("command too long: %d for max %d",
+			length, GEMPCTWIN_MAXBUF-3);
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	/* header */
+	low_level_buffer[0] = 0x03;	/* SYNC */
+	low_level_buffer[1] = 0x06;	/* ACK */
+
+	/* CCID command */
+	memcpy(low_level_buffer+2, buffer, length);
+
+	/* checksum */
+	lrc = 0;
+	for(i=0; i<length+2; i++)
+		lrc ^= low_level_buffer[i];
+	low_level_buffer[length+2] = lrc;
+
+	DEBUG_XXD(debug_header, low_level_buffer, length+3);
+
+	if (write(serialDevice[reader_index].fd, low_level_buffer,
+		length+3) != length+3)
+	{
+		DEBUG_CRITICAL2("write error: %s", strerror(errno));
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	return STATUS_SUCCESS;
+} /* WriteTwinSerial */
+
+
+/*****************************************************************************
+ *
+ *				ReadTwinSerial: Receive bytes from the card reader
+ *
+ *****************************************************************************/
+status_t ReadTwinSerial(unsigned int reader_index,
+	unsigned int *length, unsigned char *buffer)
+{
+	unsigned char c;
+	int rv;
+	int echo;
+	int to_read;
+	int i;
+
+	/* we get the echo first */
+	echo = serialDevice[reader_index].echo;
+
+start:
+	DEBUG_COMM("start");
+	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
+		return rv;
+
+	if (c == RDR_to_PC_NotifySlotChange)
+		goto slot_change;
+
+	if (c == SYNC)
+		goto sync;
+
+	if (c >= 0x80)
+	{
+		DEBUG_COMM2("time request: 0x%02X", c);
+		goto start;
+	}
+
+	DEBUG_CRITICAL2("Got 0x%02X", c);
+	return STATUS_COMM_ERROR;
+
+slot_change:
+	DEBUG_COMM("slot change");
+	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
+		return rv;
+
+	if (c == CARD_ABSENT)
+	{
+		DEBUG_COMM("Card removed");
+	}
+	else
+		if (c == CARD_PRESENT)
+		{
+			DEBUG_COMM("Card inserted");
+		}
+		else
+		{
+			DEBUG_COMM2("Unknown card movement: %d", c);
+		}
+	goto start;
+
+sync:
+	DEBUG_COMM("sync");
+	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
+		return rv;
+
+	if (c == CTRL_ACK)
+		goto ack;
+
+	if (c == CTRL_NAK)
+		goto nak;
+
+	DEBUG_CRITICAL2("Got 0x%02X instead of ACK/NAK", c);
+	return STATUS_COMM_ERROR;
+
+nak:
+	DEBUG_COMM("nak");
+	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
+		return rv;
+
+	if (c != (SYNC ^ CTRL_NAK))
+	{
+		DEBUG_CRITICAL2("Wrong LRC: 0x%02X", c);
+		return STATUS_COMM_ERROR;
+	}
+	else
+	{
+		DEBUG_COMM("NAK requested");
+		return STATUS_COMM_NAK;
+	}
+
+ack:
+	DEBUG_COMM("ack");
+	/* normal CCID frame */
+	if ((rv = get_bytes(reader_index, buffer, 5)) != STATUS_SUCCESS)
+		return rv;
+
+	/* total frame size */
+	to_read = 10+dw2i(buffer, 1);
+
+	if ((to_read < 10) || (to_read > (int)*length))
+	{
+		DEBUG_CRITICAL2("Wrong value for frame size: %d", to_read);
+		return STATUS_COMM_ERROR;
+	}
+
+	DEBUG_COMM2("frame size: %d", to_read);
+	if ((rv = get_bytes(reader_index, buffer+5, to_read-5)) != STATUS_SUCCESS)
+		return rv;
+
+	DEBUG_XXD("frame: ", buffer, to_read);
+
+	/* lrc */
+	DEBUG_COMM("lrc");
+	if ((rv = get_bytes(reader_index, &c, 1)) != STATUS_SUCCESS)
+		return rv;
+
+	DEBUG_COMM2("lrc: 0x%02X", c);
+	for (i=0; i<to_read; i++)
+		c ^= buffer[i];
+
+	if (c != (SYNC ^ CTRL_ACK))
+		DEBUG_CRITICAL2("Wrong LRC: 0x%02X", c);
+
+	if (echo)
+	{
+		echo = FALSE;
+		goto start;
+	}
+
+	/* length of data read */
+	*length = to_read;
+
+	return STATUS_SUCCESS;
+} /* ReadTwinSerial */
+
+
+/*****************************************************************************
+ *
+ *				get_bytes: get n bytes
+ *
+ *****************************************************************************/
+int get_bytes(unsigned int reader_index, unsigned char *buffer, int length)
+{
+	int offset = serialDevice[reader_index].buffer_offset;
+	int offset_last = serialDevice[reader_index].buffer_offset_last;
+
+	DEBUG_COMM3("available: %d, needed: %d", offset_last-offset,
+		length);
+	/* enough data are available */
+	if (offset + length <= offset_last)
+	{
+		DEBUG_COMM("data available");
+		memcpy(buffer, serialDevice[reader_index].buffer + offset, length);
+		serialDevice[reader_index].buffer_offset += length;
+	}
+	else
+	{
+		int present, rv;
+
+		/* copy available data */
+		present = offset_last - offset;
+
+		if (present > 0)
+		{
+			DEBUG_COMM2("some data available: %d", present);
+			memcpy(buffer, serialDevice[reader_index].buffer + offset,
+				present);
+		}
+
+		/* get fresh data */
+		DEBUG_COMM2("get more data: %d", length - present);
+		rv = ReadChunk(reader_index, serialDevice[reader_index].buffer,
+			sizeof(serialDevice[reader_index].buffer), length - present);
+		if (rv < 0)
+			return STATUS_COMM_ERROR;
+
+		/* fill the buffer */
+		memcpy(buffer + present, serialDevice[reader_index].buffer,
+			length - present);
+		serialDevice[reader_index].buffer_offset = length - present;
+		serialDevice[reader_index].buffer_offset_last = rv;
+		DEBUG_COMM3("offset: %d, last_offset: %d",
+			serialDevice[reader_index].buffer_offset,
+			serialDevice[reader_index].buffer_offset_last);
+	}
+
+	return STATUS_SUCCESS;
+} /* get_bytes */
+
+
+/*****************************************************************************
+ *
+ *				ReadChunk: read a minimum number of bytes
+ *
+ *****************************************************************************/
+static int ReadChunk(unsigned int reader_index, unsigned char *buffer,
+	int buffer_length, int min_length)
+{
+	int fd = serialDevice[reader_index].fd;
+# ifndef S_SPLINT_S
+	fd_set fdset;
+# endif
+	struct timeval t;
+	int i, rv = 0;
+	int already_read;
+	char debug_header[] = "<- 123456 ";
+
+	(void)snprintf(debug_header, sizeof(debug_header), "<- %06X ",
+		reader_index);
+
+	already_read = 0;
+	while (already_read < min_length)
+	{
+		/* use select() to, eventually, timeout */
+		FD_ZERO(&fdset);
+		FD_SET(fd, &fdset);
+		t.tv_sec = serialDevice[reader_index].ccid.readTimeout / 1000;
+		t.tv_usec = (serialDevice[reader_index].ccid.readTimeout - t.tv_sec*1000)*1000;
+
+		i = select(fd+1, &fdset, NULL, NULL, &t);
+		if (i == -1)
+		{
+			DEBUG_CRITICAL2("select: %s", strerror(errno));
+			return -1;
+		}
+		else
+			if (i == 0)
+			{
+				DEBUG_COMM2("Timeout! (%d ms)", serialDevice[reader_index].ccid.readTimeout);
+				return -1;
+			}
+
+		rv = read(fd, buffer + already_read, buffer_length - already_read);
+		if (rv < 0)
+		{
+			DEBUG_COMM2("read error: %s", strerror(errno));
+			return -1;
+		}
+
+		DEBUG_XXD(debug_header, buffer + already_read, rv);
+
+		already_read += rv;
+		DEBUG_COMM3("read: %d, to read: %d", already_read,
+			min_length);
+	}
+
+	return already_read;
+} /* ReadChunk */
+
+
+/*****************************************************************************
+ *
+ *				OpenTwinSerial: open the port
+ *
+ *****************************************************************************/
+status_t OpenTwinSerial(unsigned int reader_index, int channel)
+{
+	char dev_name[FILENAME_MAX];
+
+	DEBUG_COMM3("Reader index: %X, Channel: %d", reader_index, channel);
+
+	/*
+	 * Conversion of old-style ifd-hanler 1.0 CHANNELID
+	 */
+	if (channel == 0x0103F8)
+		channel = 1;
+	else
+		if (channel == 0x0102F8)
+			channel = 2;
+		else
+			if (channel == 0x0103E8)
+				channel = 3;
+			else
+				if (channel == 0x0102E8)
+					channel = 4;
+
+	if (channel < 0)
+	{
+		DEBUG_CRITICAL2("wrong port number: %d", channel);
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	(void)snprintf(dev_name, sizeof(dev_name), "/dev/pcsc/%d", channel);
+
+	return OpenTwinSerialByName(reader_index, dev_name);
+} /* OpenTwinSerial */
+
+/*****************************************************************************
+ *
+ *				set_ccid_descriptor: init ccid descriptor
+ *				depending on reader type specified in device.
+ *
+ *				return: STATUS_UNSUCCESSFUL,
+ *						STATUS_SUCCESS,
+ *						-1 (Reader already used)
+ *
+ *****************************************************************************/
+static status_t set_ccid_descriptor(unsigned int reader_index,
+	const char *reader_name, const char *dev_name)
+{
+	int readerID;
+	int i;
+	int already_used = FALSE;
+	static int previous_reader_index = -1;
+
+	readerID = GEMPCTWIN;
+	if (0 == strcasecmp(reader_name,"GemCorePOSPro"))
+		readerID = GEMCOREPOSPRO;
+	else if (0 == strcasecmp(reader_name,"GemCoreSIMPro"))
+		readerID = GEMCORESIMPRO;
+	else if (0 == strcasecmp(reader_name,"GemCoreSIMPro2"))
+		readerID = GEMCORESIMPRO2;
+	else if (0 == strcasecmp(reader_name,"GemPCPinPad"))
+		readerID = GEMPCPINPAD;
+
+	/* check if the same channel is not already used to manage multi-slots readers*/
+	for (i = 0; i < CCID_DRIVER_MAX_READERS; i++)
+	{
+		if (serialDevice[i].device
+			&& strcmp(serialDevice[i].device, dev_name) == 0)
+		{
+			already_used = TRUE;
+
+			DEBUG_COMM2("%s already used. Multi-slot reader?", dev_name);
+			break;
+		}
+	}
+
+	/* this reader is already managed by us */
+	if (already_used)
+	{
+		if ((previous_reader_index != -1)
+			&& serialDevice[previous_reader_index].device
+			&& (strcmp(serialDevice[previous_reader_index].device, dev_name) == 0)
+			&& serialDevice[previous_reader_index].ccid.bCurrentSlotIndex < serialDevice[previous_reader_index].ccid.bMaxSlotIndex)
+		{
+			/* we reuse the same device and the reader is multi-slot */
+			serialDevice[reader_index] = serialDevice[previous_reader_index];
+
+			*serialDevice[reader_index].nb_opened_slots += 1;
+			serialDevice[reader_index].ccid.bCurrentSlotIndex++;
+			serialDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT;
+			DEBUG_INFO2("Opening slot: %d",
+					serialDevice[reader_index].ccid.bCurrentSlotIndex);
+			switch (readerID)
+			{
+				case GEMCOREPOSPRO:
+				case GEMCORESIMPRO:
+					{
+						/* Allocate a memory buffer that will be
+						 * released in CloseUSB() */
+						void *ptr = malloc(sizeof SerialCustomDataRates);
+						if (ptr)
+						{
+							memcpy(ptr, SerialCustomDataRates,
+									sizeof SerialCustomDataRates);
+						}
+
+						serialDevice[reader_index].ccid.arrayOfSupportedDataRates = ptr;
+					}
+					serialDevice[reader_index].ccid.dwMaxDataRate = 125000;
+					break;
+
+				/* GemPC Twin or GemPC Card */
+				default:
+					serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialTwinDataRates;
+					serialDevice[reader_index].ccid.dwMaxDataRate = 344086;
+					break;
+			}
+			goto end;
+		}
+		else
+		{
+			DEBUG_CRITICAL2("Trying to open too many slots on %s", dev_name);
+			return STATUS_UNSUCCESSFUL;
+		}
+
+	}
+
+	/* Common to all readers */
+	serialDevice[reader_index].ccid.real_bSeq = 0;
+	serialDevice[reader_index].ccid.pbSeq = &serialDevice[reader_index].ccid.real_bSeq;
+	serialDevice[reader_index].real_nb_opened_slots = 1;
+	serialDevice[reader_index].nb_opened_slots = &serialDevice[reader_index].real_nb_opened_slots;
+	serialDevice[reader_index].ccid.bCurrentSlotIndex = 0;
+
+	serialDevice[reader_index].ccid.dwMaxCCIDMessageLength = 271;
+	serialDevice[reader_index].ccid.dwMaxIFSD = 254;
+	serialDevice[reader_index].ccid.dwFeatures = 0x00010230;
+	serialDevice[reader_index].ccid.dwDefaultClock = 4000;
+
+	serialDevice[reader_index].buffer_offset = 0;
+	serialDevice[reader_index].buffer_offset_last = 0;
+
+	serialDevice[reader_index].ccid.readerID = readerID;
+	serialDevice[reader_index].ccid.bPINSupport = 0x0;
+	serialDevice[reader_index].ccid.dwMaxDataRate = 344086;
+	serialDevice[reader_index].ccid.bMaxSlotIndex = 0;
+	serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialTwinDataRates;
+	serialDevice[reader_index].ccid.readTimeout = DEFAULT_COM_READ_TIMEOUT;
+	serialDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT;
+	serialDevice[reader_index].ccid.bVoltageSupport = 0x07;	/* 1.8V, 3V and 5V */
+	serialDevice[reader_index].ccid.gemalto_firmware_features = NULL;
+	serialDevice[reader_index].echo = TRUE;
+
+	/* change some values depending on the reader */
+	switch (readerID)
+	{
+		case GEMCOREPOSPRO:
+			serialDevice[reader_index].ccid.bMaxSlotIndex = 4;	/* 5 slots */
+			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
+			serialDevice[reader_index].echo = FALSE;
+			serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
+			break;
+
+		case GEMCORESIMPRO:
+			serialDevice[reader_index].ccid.bMaxSlotIndex = 1; /* 2 slots */
+			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
+			serialDevice[reader_index].echo = FALSE;
+			serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
+			break;
+
+		case GEMCORESIMPRO2:
+			serialDevice[reader_index].ccid.dwDefaultClock = 4800;
+			serialDevice[reader_index].ccid.bMaxSlotIndex = 1; /* 2 slots */
+			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SIMPro2DataRates;
+			serialDevice[reader_index].echo = FALSE;
+			serialDevice[reader_index].ccid.dwMaxDataRate = 825806;
+			break;
+
+		case GEMPCPINPAD:
+			serialDevice[reader_index].ccid.bPINSupport = 0x03;
+			serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
+			serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
+			break;
+	}
+
+end:
+	/* memorise the current reader_index so we can detect
+	 * a new OpenTwinSerialByName on a multi slot reader */
+	previous_reader_index = reader_index;
+
+	/* we just created a secondary slot on a multi-slot reader */
+	if (already_used)
+		return STATUS_SECONDARY_SLOT;
+
+	return STATUS_SUCCESS;
+} /* set_ccid_descriptor  */
+
+
+/*****************************************************************************
+ *
+ *				OpenTwinSerialByName: open the port
+ *
+ *****************************************************************************/
+status_t OpenTwinSerialByName(unsigned int reader_index, char *dev_name)
+{
+	struct termios current_termios;
+	unsigned int reader = reader_index;
+	/* 255 is MAX_DEVICENAME in pcscd.h */
+	char reader_name[255] = "GemPCTwin";
+	char *p;
+	status_t ret;
+
+	DEBUG_COMM3("Reader index: %X, Device: %s", reader_index, dev_name);
+
+	/* parse dev_name using the pattern "device:name" */
+	p = strchr(dev_name, ':');
+	if (p)
+	{
+		/* copy the second part of the string */
+		strlcpy(reader_name, p+1, sizeof(reader_name));
+
+		/* replace ':' by '\0' so that dev_name only contains the device name */
+		*p = '\0';
+	}
+
+	ret = set_ccid_descriptor(reader_index, reader_name, dev_name);
+	if (STATUS_UNSUCCESSFUL == ret)
+		return STATUS_UNSUCCESSFUL;
+
+	/* secondary slot so do not physically open the device */
+	if (STATUS_SECONDARY_SLOT == ret)
+		return STATUS_SUCCESS;
+
+	serialDevice[reader].fd = open(dev_name, O_RDWR | O_NOCTTY);
+
+	if (-1 == serialDevice[reader].fd)
+	{
+		DEBUG_CRITICAL3("open %s: %s", dev_name, strerror(errno));
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	/* Set RTS signal to low to prevent the smart card reader
+	 * from sending its plug and play string. */
+	{
+		int flags;
+
+		if (ioctl(serialDevice[reader].fd, TIOCMGET, &flags) < 0)
+		{
+			DEBUG_CRITICAL2("Get RS232 signals state failed: %s",
+				strerror(errno));
+		}
+		else
+		{
+			flags &= ~TIOCM_RTS;
+			if (ioctl(serialDevice[reader].fd, TIOCMSET, &flags) < 0)
+			{
+				DEBUG_CRITICAL2("Set RTS to low failed: %s", strerror(errno));
+			}
+			else
+			{
+				DEBUG_COMM("Plug-n-Play inhibition successful");
+			}
+		}
+	}
+
+	/* set channel used */
+	serialDevice[reader].device = strdup(dev_name);
+
+	/* empty in and out serial buffers */
+	if (tcflush(serialDevice[reader].fd, TCIOFLUSH))
+			DEBUG_INFO2("tcflush() function error: %s", strerror(errno));
+
+	/* get config attributes */
+	if (tcgetattr(serialDevice[reader].fd, &current_termios) == -1)
+	{
+		DEBUG_INFO2("tcgetattr() function error: %s", strerror(errno));
+		(void)close(serialDevice[reader].fd);
+		serialDevice[reader].fd = -1;
+
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	/* IGNBRK: ignore BREAK condition on input
+	 * IGNPAR: ignore framing errors and parity errors. */
+	current_termios.c_iflag = IGNBRK | IGNPAR;
+	current_termios.c_oflag = 0;	/* Raw output modes */
+	/* CS8: 8-bits character size
+	 * CSTOPB: set two stop bits
+	 * CREAD: enable receiver
+	 * CLOCAL: ignore modem control lines */
+	current_termios.c_cflag = CS8 | CSTOPB | CREAD | CLOCAL;
+
+	/* Do not echo characters because if you connect to a host it or your modem
+	 * will echo characters for you.  Don't generate signals. */
+	current_termios.c_lflag = 0;
+
+	if (0 == strcasecmp(reader_name,"GemCoreSIMPro2"))
+	{
+		unsigned char pcbuffer[SIZE_GET_SLOT_STATUS];
+		unsigned int old_timeout;
+		RESPONSECODE r;
+
+		/* Unless we resume from a stand-by condition, GemCoreSIMPro2
+		 * starts at 9600 bauds, so let's first try this speed */
+		/* set serial port speed to 9600 bauds */
+		(void)cfsetspeed(&current_termios, B9600);
+		DEBUG_INFO1("Set serial port baudrate to 9600 and correct configuration");
+		if (tcsetattr(serialDevice[reader_index].fd, TCSANOW, &current_termios) == -1)
+		{
+			(void)close(serialDevice[reader_index].fd);
+			serialDevice[reader_index].fd = -1;
+			DEBUG_CRITICAL2("tcsetattr error: %s", strerror(errno));
+
+			return STATUS_UNSUCCESSFUL;
+		}
+
+		/* Test current speed issuing a CmdGetSlotStatus with a very
+		 * short time out of 1 seconds */
+		old_timeout = serialDevice[reader_index].ccid.readTimeout;
+
+		serialDevice[reader_index].ccid.readTimeout = 1*1000;
+		r = CmdGetSlotStatus(reader_index, pcbuffer);
+
+		/* Restore default time out value */
+		serialDevice[reader_index].ccid.readTimeout = old_timeout;
+
+		if (IFD_SUCCESS == r)
+		{
+			/* We are at 9600 bauds, let's move to 115200 */
+			unsigned char tx_buffer[] = { 0x01, 0x10, 0x20 };
+			unsigned char rx_buffer[50];
+			unsigned int rx_length = sizeof(rx_buffer);
+
+			if (IFD_SUCCESS == CmdEscape(reader_index, tx_buffer,
+				sizeof(tx_buffer), rx_buffer, &rx_length, 0))
+			{
+				/* Let the reader setup its new communication speed */
+				(void)usleep(250*1000);
+			}
+			else
+			{
+				DEBUG_INFO1("CmdEscape to configure 115200 bauds failed");
+			}
+		}
+		/* In case of a failure, reader is probably already at 115200
+		 * bauds as code below assumes */
+	}
+
+	/* set serial port speed to 115200 bauds */
+	(void)cfsetspeed(&current_termios, B115200);
+
+	DEBUG_INFO1("Set serial port baudrate to 115200 and correct configuration");
+	if (tcsetattr(serialDevice[reader].fd, TCSANOW, &current_termios) == -1)
+	{
+		(void)close(serialDevice[reader].fd);
+		serialDevice[reader].fd = -1;
+		DEBUG_INFO2("tcsetattr error: %s", strerror(errno));
+
+		return STATUS_UNSUCCESSFUL;
+	}
+
+	/* perform a command to be sure a Gemalto reader is connected
+	 * get the reader firmware */
+	{
+		unsigned char tx_buffer[] = { 0x02 };
+		unsigned char rx_buffer[50];
+		unsigned int rx_length = sizeof(rx_buffer);
+
+		/* 2 seconds timeout to not wait too long if no reader is connected */
+		if (IFD_SUCCESS != CmdEscape(reader_index, tx_buffer, sizeof(tx_buffer),
+			rx_buffer, &rx_length, 2*1000))
+		{
+			DEBUG_CRITICAL("Get firmware failed. Maybe the reader is not connected");
+			(void)CloseTwinSerial(reader_index);
+			return STATUS_UNSUCCESSFUL;
+		}
+
+		rx_buffer[rx_length] = '\0';
+		DEBUG_INFO2("Firmware: %s", rx_buffer);
+	}
+
+	/* perform a command to configure GemPC Twin reader card movement
+	 * notification to synchronous mode: the card movement is notified _after_
+	 * the host command and _before_ the reader anwser */
+	{
+		unsigned char tx_buffer[] = { 0x01, 0x01, 0x01};
+		unsigned char rx_buffer[50];
+		unsigned int rx_length = sizeof(rx_buffer);
+
+		if (IFD_SUCCESS != CmdEscape(reader_index, tx_buffer, sizeof(tx_buffer),
+			rx_buffer, &rx_length, 0))
+		{
+			DEBUG_CRITICAL("Change card movement notification failed.");
+			(void)CloseTwinSerial(reader_index);
+			return STATUS_UNSUCCESSFUL;
+		}
+	}
+
+	serialDevice[reader_index].ccid.sIFD_serial_number = NULL;
+	serialDevice[reader_index].ccid.sIFD_iManufacturer = NULL;
+	serialDevice[reader_index].ccid.IFD_bcdDevice = 0;
+
+	return STATUS_SUCCESS;
+} /* OpenTwinSerialByName */
+
+
+/*****************************************************************************
+ *
+ *				CloseTwinSerial: close the port
+ *
+ *****************************************************************************/
+status_t CloseTwinSerial(unsigned int reader_index)
+{
+	unsigned int reader = reader_index;
+
+	/* device not opened */
+	if (NULL == serialDevice[reader_index].device)
+		return STATUS_UNSUCCESSFUL;
+
+	DEBUG_COMM2("Closing serial device: %s", serialDevice[reader_index].device);
+
+	/* Decrement number of opened slot */
+	(*serialDevice[reader_index].nb_opened_slots)--;
+
+	/* release the allocated ressources for the last slot only */
+	if (0 == *serialDevice[reader_index].nb_opened_slots)
+	{
+		DEBUG_COMM("Last slot closed. Release resources");
+
+		(void)close(serialDevice[reader].fd);
+		serialDevice[reader].fd = -1;
+
+		free(serialDevice[reader].device);
+		serialDevice[reader].device = NULL;
+	}
+
+	return STATUS_SUCCESS;
+} /* CloseTwinSerial */
+
+
+/*****************************************************************************
+ *
+ *					get_ccid_descriptor
+ *
+ ****************************************************************************/
+_ccid_descriptor *get_ccid_descriptor(unsigned int reader_index)
+{
+	return &serialDevice[reader_index].ccid;
+} /* get_ccid_descriptor */
+
+
diff --git a/src/ccid_twin_serial.h b/src/ccid_twin_serial.h
new file mode 100644
index 0000000..051b62f
--- /dev/null
+++ b/src/ccid_twin_serial.h
@@ -0,0 +1,35 @@
+/*
+    ccid_twin_serial.h:  Serial access routines
+    Copyright (C) 2003-2008   Ludovic Rousseau
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public License
+	along with this library; if not, write to the Free Software Foundation,
+	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef __CCID_TWIN_SERIAL_H__
+#define __CCID_TWIN_SERIAL_H__
+
+status_t OpenTwinSerial(unsigned int reader_index, int channel);
+
+status_t OpenTwinSerialByName(unsigned int reader_index, char *dev_name);
+
+status_t WriteTwinSerial(unsigned int reader_index, unsigned int length,
+	unsigned char *Buffer);
+
+status_t ReadTwinSerial(unsigned int reader_index, unsigned int *length,
+	unsigned char *Buffer);
+
+status_t CloseTwinSerial(unsigned int reader_index);
+
+#endif
diff --git a/src/commands.c b/src/commands.c
index 0dea038..adec95a 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -108,7 +108,7 @@ RESPONSECODE CmdPowerOn(unsigned int reader_index, unsigned int * nlength,
 	RESPONSECODE return_value = IFD_SUCCESS;
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 
-#ifndef TWIN_SERIAL
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1035,7 +1035,7 @@ RESPONSECODE CmdPowerOff(unsigned int reader_index)
 	RESPONSECODE return_value = IFD_SUCCESS;
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 
-#ifndef TWIN_SERIAL
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1124,7 +1124,7 @@ RESPONSECODE CmdGetSlotStatus(unsigned int reader_index, unsigned char buffer[])
 	RESPONSECODE return_value = IFD_SUCCESS;
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 
-#ifndef TWIN_SERIAL
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1305,7 +1305,7 @@ RESPONSECODE CCID_Transmit(unsigned int reader_index, unsigned int tx_length,
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 	status_t ret;
 
-#ifndef TWIN_SERIAL
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1380,7 +1380,7 @@ RESPONSECODE CCID_Receive(unsigned int reader_index, unsigned int *rx_length,
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 	unsigned int old_timeout;
 
-#ifndef TWIN_SERIAL
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		unsigned char pcbuffer[SIZE_GET_SLOT_STATUS];
diff --git a/src/defs.h b/src/defs.h
index 6523de1..d94a39c 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -91,12 +91,21 @@ typedef enum {
  */
 #ifdef TWIN_SERIAL
 
-#define OpenPortByName OpenSerialByName
-#define OpenPort OpenSerial
-#define ClosePort CloseSerial
-#define ReadPort ReadSerial
-#define WritePort WriteSerial
-#include "ccid_serial.h"
+#define OpenPortByName OpenTwinSerialByName
+#define OpenPort OpenTwinSerial
+#define ClosePort CloseTwinSerial
+#define ReadPort ReadTwinSerial
+#define WritePort WriteTwinSerial
+#include "ccid_twin_serial.h"
+
+#elif HID_SERIAL
+
+#define OpenPortByName OpenHidSerialByName
+#define OpenPort OpenHidSerial
+#define ClosePort CloseHidSerial
+#define ReadPort ReadHidSerial
+#define WritePort WriteHidSerial
+#include "ccid_hid_serial.h"
 
 #else
 
diff --git a/src/ifdhandler.c b/src/ifdhandler.c
index 20465c6..36411d5 100644
--- a/src/ifdhandler.c
+++ b/src/ifdhandler.c
@@ -297,7 +297,7 @@ EXTERNAL RESPONSECODE IFDHCloseChannel(DWORD Lun)
 } /* IFDHCloseChannel */
 
 
-#if !defined(TWIN_SERIAL)
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 static RESPONSECODE IFDHPolling(DWORD Lun, int timeout)
 {
 	int reader_index;
@@ -522,7 +522,7 @@ EXTERNAL RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag,
 				*(uint32_t *)Value = get_ccid_descriptor(reader_index) -> dwMaxCCIDMessageLength -10;
 			break;
 
-#if !defined(TWIN_SERIAL)
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
 		case TAG_IFD_POLLING_THREAD_WITH_TIMEOUT:
 			{
 				_ccid_descriptor *ccid_desc;
-------------- next part --------------
commit 6d9e1882af8c293cb514deb6fdc660c4ea6cbef2
Author: root <root at lamia.panaceas.james.local>
Date:   Mon Aug 14 13:31:22 2017 +0100

    ACR3901U BTLE and USB reader support

diff --git a/.gitignore b/.gitignore
index 96262ca..2db0202 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,5 +125,19 @@ src/openct/libccidhid_la-checksum.lo
 src/openct/libccidhid_la-proto-t1.lo
 src/towitoko/libccidhid_la-atr.lo
 src/towitoko/libccidhid_la-pps.lo
+src/libccidacr.la
+src/libccidacr_la-ccid.lo
+src/libccidacr_la-ccid_acr_btle.lo
+src/libccidacr_la-commands.lo
+src/libccidacr_la-ifdhandler.lo
+src/libccidacr_la-simclist.lo
+src/libccidacr_la-strlcpy.lo
+src/libccidacr_la-tokenparser.lo
+src/libccidacr_la-utils.lo
+src/openct/libccidacr_la-buffer.lo
+src/openct/libccidacr_la-checksum.lo
+src/openct/libccidacr_la-proto-t1.lo
+src/towitoko/libccidacr_la-atr.lo
+src/towitoko/libccidacr_la-pps.lo
 stamp-h1
 tags
diff --git a/configure.ac b/configure.ac
index 41eebfe..583e4d7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -221,6 +221,48 @@ if test "${ccidhiddir}" = false ; then
 fi
 
 
+# --enable-acrbtle
+AC_ARG_ENABLE(acrbtle,
+	AS_HELP_STRING([--enable-acrbtle],[also compile and install the bluetooth LE ACR driver]),
+	[acrbtle="${enableval}"], [acrbtle=no])
+
+if test "x$acrbtle" != xno ; then
+
+	PKG_CHECK_MODULES(LIBSSL, openssl)
+
+	saved_CPPFLAGS="$CPPFLAGS"
+	saved_LIBS="$LIBS"
+
+	CPPFLAGS="$CPPFLAGS $LIBSSL_CFLAGS"
+	LIBS="$LDFLAGS $LIBSSL_LIBS"
+
+	AC_CHECK_HEADERS(openssl/aes.h, [],
+		[ AC_MSG_ERROR([openssl/aes.h not found, install openssl or use ./configure LIBSSL_CFLAGS=...]) ])
+
+	AC_MSG_CHECKING([for libusb_init])
+	AC_TRY_LINK_FUNC(AES_cbc_encrypt, [ AC_MSG_RESULT([yes]) ],
+		[ AC_MSG_ERROR([AES_cbc_encrypt not found, use ./configure LIBSSL_LIBS=...]) ])
+
+	CPPFLAGS="$saved_CPPFLAGS"
+	LIBS="$saved_LIBS"
+
+	use_libusb=yes
+fi
+AC_SUBST(LIBSSL_CFLAGS)
+AC_SUBST(LIBSSL_LIBS)
+
+AM_CONDITIONAL(WITH_ACR_BTLE, test "${acrbtle}" != "no")
+
+# --enable-ccidhiddir=DIR
+AC_ARG_ENABLE(ccidacrdir,
+	AS_HELP_STRING([--enable-ccidacrdir=DIR],[directory to install the
+	bluetooth LE ACR driver (default to pcscd config or $(prefix)/pcsc/drivers/serial)]),
+	[ccidacrdir="${enableval}"], [ccidacrdir=false])
+if test "${ccidacrdir}" = false ; then
+	ccidacrdir=$usbdropdir/serial
+fi
+
+
 # --enable-serialconfdir=DIR
 AC_ARG_ENABLE(serialconfdir,
 	AS_HELP_STRING([--enable-serialconfdir=dir],[directory containing
@@ -288,6 +330,7 @@ AC_SUBST(bundle)
 AC_SUBST(usbdropdir)
 AC_SUBST(ccidtwindir)
 AC_SUBST(ccidhiddir)
+AC_SUBST(ccidacrdir)
 AC_SUBST(serialconfdir)
 AS_AC_EXPAND(bindir_exp,$bindir)
 AS_AC_EXPAND(sysconfdir_exp,$sysconfdir)
@@ -329,6 +372,8 @@ serial Twin support:     ${twinserial}
 serial twin install dir: ${ccidtwindir}
 serial HID support:      ${hidserial}
 serial HID install dir:  ${ccidhiddir}
+BT LE ACR support:       ${acrbtle}
+BT LE ACR install dir:   ${ccidacrdir}
 serial config directory: ${serialconfdir}
 compiled for pcsc-lite:  ${pcsclite}
 syslog debug:            ${use_syslog}
diff --git a/src/Makefile.am b/src/Makefile.am
index e97db83..ed65630 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,11 @@ lib_LTLIBRARIES += libccidhid.la
 LIBS_TO_INSTALL += install_ccidhid
 LIBS_TO_UNINSTALL += uninstall_ccidhid
 endif
+if WITH_ACR_BTLE
+lib_LTLIBRARIES += libccidacr.la
+LIBS_TO_INSTALL += install_ccidacr
+LIBS_TO_UNINSTALL += uninstall_ccidacr
+endif
 
 COMMON = ccid.c \
 	ccid.h \
@@ -38,6 +43,7 @@ COMMON = ccid.c \
 USB = ccid_usb.c ccid_usb.h
 TWIN_SERIAL = ccid_twin_serial.c ccid_twin_serial.h
 HID_SERIAL =ccid_hid_serial.c ccid_hid_serial.h
+ACR_BTLE =ccid_acr_btle.c ccid_acr_btle.h
 T1 = towitoko/atr.c \
 	towitoko/atr.h \
 	towitoko/defines.h \
@@ -80,6 +86,13 @@ libccidhid_la_CFLAGS = $(PCSC_CFLAGS) $(PTHREAD_CFLAGS) $(SYMBOL_VISIBILITY) \
 libccidhid_la_LIBADD = $(PTHREAD_LIBS)
 libccidhid_la_LDFLAGS = -avoid-version
 
+libccidacr_la_SOURCES = $(COMMON) $(ACR_BTLE) $(TOKEN_PARSER) \
+	$(PROVIDED_BY_PCSC) $(T1)
+libccidacr_la_CFLAGS = $(PCSC_CFLAGS) $(PTHREAD_CFLAGS) $(SYMBOL_VISIBILITY) \
+	$(LIBSSL_CFLAGS) \
+	-DACR_BTLE -D$(CCID_VERSION) -DSIMCLIST_NO_DUMPRESTORE
+libccidacr_la_LIBADD = $(PTHREAD_LIBS) ${LIBSSL_LIBS}
+libccidacr_la_LDFLAGS = -avoid-version
 
 parse_SOURCES = parse.c debug.c ccid_usb.c $(TOKEN_PARSER)
 parse_LDADD = $(LIBUSB_LIBS)
diff --git a/src/ccid.c b/src/ccid.c
index 5682f08..cab7ef7 100644
--- a/src/ccid.c
+++ b/src/ccid.c
@@ -86,7 +86,7 @@ int ccid_open_hack_pre(unsigned int reader_index)
 	if ((PROTOCOL_CCID == ccid_descriptor->bInterfaceProtocol)
 		&& (3 == ccid_descriptor -> bNumEndpoints))
 	{
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 		/* just wait for 100ms in case a notification is in the pipe */
 		(void)InterruptRead(reader_index, 100);
 #endif
@@ -533,6 +533,13 @@ int ccid_open_hack_post(unsigned int reader_index)
 			/* restore default timeout (modified in ccid_open_hack_pre()) */
 			ccid_descriptor->readTimeout = DEFAULT_COM_READ_TIMEOUT;
 			break;
+		case ACR3901U_USB:
+			/* My version of the firmware V1.09 silently does PPS automatically */
+			/* for both USB and BT interfaces, infact the BT interface doesn't */
+			/* even implement the Card Set Parameters command - JMM 08/2017 */
+			ccid_descriptor->dwFeatures |= CCID_CLASS_AUTO_PPS_PROP;
+			break;
+
 	}
 
 	/* Gemalto readers may report additional information */
diff --git a/src/ccid.h b/src/ccid.h
index 89617ca..4314cce 100644
--- a/src/ccid.h
+++ b/src/ccid.h
@@ -214,6 +214,8 @@ typedef struct
 #define SCM_SCL011 0x04E65293
 #define HID_AVIATOR	0x076B3A21
 #define HID_OMNIKEY_2061 0x076B2061
+#define ACR3901U_USB	0x072FB000
+#define ACR3901U_BTLE	0xF72FB000 /*BOGUS*/
 
 #define VENDOR_GEMALTO 0x08E6
 #define GET_VENDOR(readerID) ((readerID >> 16) & 0xFFFF)
diff --git a/src/ccid_acr_btle.c b/src/ccid_acr_btle.c
new file mode 100644
index 0000000..1a05b69
--- /dev/null
+++ b/src/ccid_acr_btle.c
@@ -0,0 +1,1788 @@
+/*
+ * ccid_hid_serial.c: communicate with an HID bluetooth serial smart card reader
+ * Copyright (C) 2001-2010 Ludovic Rousseau <ludovic.rousseau at free.fr>
+ *
+ * Thanks to Niki W. Waibel <niki.waibel at gmx.net> for a prototype version
+ *
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public License
+	along with this library; if not, write to the Free Software Foundation,
+	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <ifdhandler.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <openssl/aes.h>
+
+
+
+
+#include <config.h>
+#include "defs.h"
+#include "ccid_ifdhandler.h"
+#include "debug.h"
+#include "ccid.h"
+#include "utils.h"
+#include "commands.h"
+#include "parser.h"
+#include "strlcpycat.h"
+
+#ifndef ATT_CID
+#define ATT_CID 4
+#endif
+
+#ifndef ATT_PSM
+#define ATT_PSM 31
+#endif
+
+
+#define BT_ATT_OP_WRITE_REQ                     0x12
+#define BT_ATT_OP_WRITE_RSP                     0x13
+#define BT_ATT_OP_WRITE_CMD                     0x52
+#define BT_ATT_OP_HANDLE_VAL_NOT                0x1B
+
+
+#define RDR_to_SPH_AuthRsp1 	0x20
+#define RDR_to_SPH_AuthRsp2 	0x21
+#define RDR_to_SPH_EncMsg	0x22
+
+#define SPH_to_RDR_ReqAuth	0x70
+#define SPH_to_RDR_AuthRsp	0x71
+#define SPH_to_RDR_EncMsg	0x72
+
+#define ACR3901U_MTU			20
+
+
+/* FIXME: we should probe these, but ACR have used the same GUID for all of them */
+/* BT LE GATT handles */
+#define DATA_OUT_HANDLE 	0xb
+#define DATA_OUT_CFG_HANDLE 	0xc
+#define DATA_IN_HANDLE 		0xe
+#define CARD_STATUS_HANDLE	0x10
+#define CARD_STATUS_CFG_HANDLE	0x11
+#define BATTERY_HANDLE		0x14
+#define BATTERY_CFG_HANDLE	0x15
+
+
+const static uint8_t shared_key[0x10] =
+  { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff
+};
+
+
+typedef struct
+{
+  char *bd_address;
+  int sock;
+  uint16_t mtu;
+  uint16_t card_status;
+  uint8_t battery_status;
+
+  uint8_t slot_status;
+
+  uint8_t rnd_a[0x10];
+  uint8_t rnd_b[0x10];
+
+  uint8_t session_key[0x10];
+
+  int slot_status_stale;
+
+
+  int write_rsp;
+  int authrsp1;
+  int authrsp2;
+  int authenticated;
+
+  uint8_t rx_buf[1024];
+  size_t rx_len;
+
+  int rx_valid;
+  uint8_t rx_type;
+  uint8_t rx_payload[1024];
+  size_t rx_payload_len;
+
+  int opened;
+  int open;
+
+  int fake_get_slot_status;
+
+  int fake_params;
+  uint8_t fake_param_protocol_num;
+  size_t fake_param_data_len;
+  uint8_t fake_param_data[1024];
+
+  int ignore_atr;
+  int powered_up;
+
+  uint8_t seq;
+
+
+  /*
+   * CCID infos common to USB and serial
+   */
+  _ccid_descriptor ccid;
+
+
+
+} ACR3901U;
+
+/* Generic system functions */
+
+
+static void
+acr3901u_info (char *fmt, ...)
+{
+  va_list ap;
+
+  va_start (ap, fmt);
+  vfprintf (stderr, fmt, ap);
+  va_end (ap);
+
+  fprintf (stderr, "\n");
+
+}
+
+
+static void
+acr3901u_warn (char *fmt, ...)
+{
+  va_list ap;
+
+  va_start (ap, fmt);
+  vfprintf (stderr, fmt, ap);
+  va_end (ap);
+
+  fprintf (stderr, "\n");
+
+}
+
+
+static void
+hexdump (const void *_buf, uint64_t os, uint64_t oe)
+{
+  const uint8_t *d = (const uint8_t *) _buf;
+  uint8_t v;
+
+  uint64_t ls, le;
+  uint64_t i, j, l;
+
+  ls = os & ~15;
+  le = (oe - 1) | 15;
+  le++;
+
+  for (l = ls; l < le; l += 0x10)
+    {
+
+      fprintf (stderr, "%04llx:", (long long unsigned) l);
+
+      for (i = l, j = 0; j < 0x10; ++i, ++j)
+        {
+
+          if ((i < os) || (i >= oe))
+            fprintf (stderr, "   ");
+          else
+            fprintf (stderr, " %02x", d[i]);
+
+          if (j == 7)
+            fprintf (stderr, " ");
+
+        }
+      fprintf (stderr, "  ");
+
+      for (i = l, j = 0; j < 0x10; ++i, ++j)
+        {
+
+          if ((i < os) || (i >= oe))
+            fprintf (stderr, " ");
+          else if ((d[i] >= ' ') && (d[i] <= '~'))
+            fprintf (stderr, "%c", d[i]);
+          else
+            fprintf (stderr, " ");
+
+          if ((i & 15) == 7)
+            fprintf (stderr, " ");
+
+        }
+
+      fprintf (stderr, "\n");
+    }
+
+
+}
+
+static uint16_t
+read_le16 (void *d)
+{
+  return *(uint16_t *) d;
+}
+
+static uint16_t
+read_le32 (void *d)
+{
+  return *(uint32_t *) d;
+}
+
+static void
+write_le16 (void *d, uint16_t v)
+{
+  *(uint16_t *) d = v;
+}
+
+static void
+write_le32 (void *d, uint32_t v)
+{
+  *(uint32_t *) d = v;
+}
+
+
+static void
+nonblocking (int fd)
+{
+  int flags;
+
+  flags = fcntl (fd, F_GETFD, 0);
+  flags |= O_NONBLOCK;
+  fcntl (fd, F_SETFL, flags);
+}
+
+
+static void
+blocking (int fd)
+{
+  int flags;
+
+  flags = fcntl (fd, F_GETFD, 0);
+  flags &= ~O_NONBLOCK;
+  fcntl (fd, F_SETFL, flags);
+}
+
+static int
+blocking_send (ACR3901U * a, void *buf, size_t len, int timeout)
+{
+  fd_set wfds;
+  ssize_t r;
+  struct timeval tv = { 0 };
+
+  FD_ZERO (&wfds);
+  tv.tv_sec = timeout;
+
+  for (;;)
+    {
+
+      r = send (a->sock, buf, len, MSG_NOSIGNAL);
+
+      if (r >= 0)
+        return r;
+
+      if ((errno != EAGAIN) && (errno != EINPROGRESS))
+        return -1;
+
+      FD_SET (a->sock, &wfds);
+
+      r = select (a->sock + 1, NULL, &wfds, NULL, &tv);
+
+      if (r < 0)
+        {
+          acr3901u_warn ("blocking_send: select: %m");
+          return -1;
+        }
+      if (!r)
+        {
+          acr3901u_warn ("blocking_send: timeout");
+          return -1;
+        }
+    }
+
+  return -1;
+
+
+}
+
+static int
+read_random (void *d, size_t l)
+{
+  int fd;
+  ssize_t r;
+
+  fd = open ("/dev/urandom", O_RDONLY);
+  if (fd < 0)
+    {
+      acr3901u_warn ("open /dev/random: %m");
+      return -1;
+    }
+  r = read (fd, d, l);
+  close (fd);
+
+  if (r != l)
+    {
+      acr3901u_warn ("read /dev/random: %m");
+      return -1;
+    }
+  return 0;
+}
+
+
+/* bluetooth utils */
+
+
+static void
+local_baswap (bdaddr_t * dst, const bdaddr_t * src)
+{
+  register unsigned char *d = (unsigned char *) dst;
+  register const unsigned char *s = (const unsigned char *) src;
+  register int i;
+  for (i = 0; i < 6; i++)
+    d[i] = s[5 - i];
+}
+
+
+static int
+local_str2ba (const char *str, bdaddr_t * ba)
+{
+  uint8_t b[6];
+  const char *ptr = str;
+  int i;
+  for (i = 0; i < 6; i++)
+    {
+      b[i] = (uint8_t) strtol (ptr, NULL, 16);
+      if (i != 5 && !(ptr = strchr (ptr, ':')))
+        ptr = ":00:00:00:00:00";
+      ptr++;
+    }
+  local_baswap (ba, (bdaddr_t *) b);
+  return 0;
+}
+
+static int
+bt_socket (void)
+{
+  int sock;
+  struct sockaddr_l2 sal;
+  struct bt_security sec;
+
+
+  sock = socket (PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+
+  if (sock < 0)
+    {
+      acr3901u_warn ("socket failed:%m");
+      return -1;
+    }
+
+  memset (&sal, 0, sizeof (sal));
+
+  sal.l2_family = AF_BLUETOOTH;
+  bacpy (&sal.l2_bdaddr, BDADDR_ANY);
+  sal.l2_cid = htobs (ATT_CID);
+
+  sal.l2_bdaddr_type = BDADDR_LE_PUBLIC;
+
+  if (bind (sock, (struct sockaddr *) &sal, sizeof (sal)) < 0)
+    {
+      acr3901u_warn ("bind failed:%m");
+      close (sock);
+      return -1;
+    }
+
+
+  memset (&sec, 0, sizeof (sec));
+  sec.level = BT_SECURITY_LOW;
+
+  if (setsockopt (sock, SOL_BLUETOOTH, BT_SECURITY, &sec, sizeof (sec)) < 0)
+    {
+      acr3901u_warn ("setsockopt(,SOL_BLUETOOTH, BT_SECURITY,..) failed:%m");
+      int opt = L2CAP_LM_AUTH;
+      if (setsockopt (sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof (opt)) < 0)
+        {
+          acr3901u_warn ("setsockopt(, SOL_L2CAP, L2CAP_LM,..) failed:%m");
+          close (sock);
+          return -1;
+        }
+    }
+
+  nonblocking (sock);
+
+  return sock;
+}
+
+
+
+static int
+bt_connect (int sock, const char *dst)
+{
+  struct sockaddr_l2 sal;
+  int err;
+
+
+  memset (&sal, 0, sizeof (sal));
+  sal.l2_family = AF_BLUETOOTH;
+  local_str2ba (dst, &sal.l2_bdaddr);
+  sal.l2_cid = htobs (ATT_CID);
+  sal.l2_bdaddr_type = BDADDR_LE_RANDOM;
+
+  return connect (sock, (struct sockaddr *) &sal, sizeof (sal));
+}
+
+
+/* BT GATT */
+static int
+gatt_write_req (ACR3901U * a, uint16_t handle, void *data, size_t len)
+{
+  uint8_t *buf;
+  ssize_t ret;
+
+  buf = malloc (1 + sizeof (handle) + len);
+
+  buf[0] = BT_ATT_OP_WRITE_REQ;
+  write_le16 (&buf[1], handle);
+  memcpy (&buf[3], data, len);
+
+
+  ret = blocking_send (a, buf, len + 3, 3);
+
+  free (buf);
+
+  return !(ret == (len + 3));
+}
+
+
+static int
+gatt_write_req_16 (ACR3901U * a, uint16_t handle, uint16_t value)
+{
+  uint8_t buf[5];
+
+
+  buf[0] = BT_ATT_OP_WRITE_REQ;
+  write_le16 (&buf[1], handle);
+  write_le16 (&buf[3], value);
+
+  return !(blocking_send (a, buf, sizeof (buf), 3) == sizeof (buf));
+}
+
+
+static int
+gatt_do_write_cmd (ACR3901U * a, uint16_t handle, void *data, size_t len)
+{
+  uint8_t buf[ACR3901U_MTU + 3];
+  ssize_t r;
+
+  if (len > (ACR3901U_MTU + 3))
+    return -1;
+
+  buf[0] = BT_ATT_OP_WRITE_CMD;
+  write_le16 (&buf[1], handle);
+  memcpy (&buf[3], data, len);
+
+  len += 3;
+
+  r = blocking_send (a, buf, len, 3);
+
+  if (r != len)
+    {
+      if (errno == ENOTCONN)
+        {
+          acr3901u_info ("gatt_do_write_cmd: connection closed");
+          close (a->sock);
+          a->sock = -1;
+        }
+      else
+        {
+          acr3901u_warn ("gatt_do_write_cmd: send: %d (%m)", errno);
+        }
+      return -1;
+    }
+
+  return 0;
+}
+
+
+
+static int
+gatt_write_cmd (ACR3901U * a, uint16_t handle, void *data, size_t len)
+{
+  uint8_t *buf;
+  ssize_t ret;
+
+  while (len > ACR3901U_MTU)
+    {
+      if (gatt_do_write_cmd (a, handle, data, ACR3901U_MTU))
+        return -1;
+      len -= ACR3901U_MTU;
+      data += ACR3901U_MTU;
+    }
+
+  return gatt_do_write_cmd (a, handle, data, len);
+}
+
+/* generic utils */
+
+static uint8_t
+acr3901u_checksum (uint8_t * buf, size_t len)
+{
+  uint8_t ret = 0;
+
+  while (len--)
+    ret ^= *(buf++);
+  return ret;
+}
+
+/* crypto */
+
+static int
+encrypt (uint8_t * dst, uint8_t * src, size_t len, const uint8_t * key_data)
+{
+
+  uint8_t iv[AES_BLOCK_SIZE];
+  AES_KEY key;
+
+  memset (iv, 0, sizeof (iv));
+
+  AES_set_encrypt_key (key_data, 128, &key);
+
+  AES_cbc_encrypt (src, dst, len, &key, iv, AES_ENCRYPT);
+
+  return 0;
+}
+
+static int
+decrypt (uint8_t * dst, uint8_t * src, size_t len, const uint8_t * key_data)
+{
+
+  uint8_t iv[AES_BLOCK_SIZE];
+  AES_KEY key;
+
+  memset (iv, 0, sizeof (iv));
+
+  AES_set_decrypt_key (key_data, 128, &key);
+
+  AES_cbc_encrypt (src, dst, len, &key, iv, AES_DECRYPT);
+
+  return 0;
+}
+
+
+/* socket stuffs */
+
+static int
+acr3901u_connect (ACR3901U * a, int timeout)
+{
+  fd_set wfds;
+  int err;
+  socklen_t len;
+  struct timeval tv = { 0 };
+  int n;
+
+  a->sock = bt_socket ();
+
+  if (a->sock < 0)
+    return -1;
+
+  err = bt_connect (a->sock, a->bd_address);
+
+  if (!err)
+    return 0;
+
+  if ((errno != EAGAIN) && (errno != EINPROGRESS))
+    {
+      acr3901u_warn ("connect: %m");
+      close (a->sock);
+      a->sock = -1;
+      return -1;
+    }
+
+  FD_ZERO (&wfds);
+
+  tv.tv_sec = timeout;
+
+  do
+    {
+      FD_SET (a->sock, &wfds);
+
+      n = select (a->sock + 1, NULL, &wfds, NULL, &tv);
+
+      if (!n)
+        {
+          //acr3901u_warn ("connect timeout");
+          close (a->sock);
+          a->sock = -1;
+          return -1;
+        }
+
+    }
+  while (n != 1);
+
+  err = 0;
+  len = sizeof (err);
+
+  if (getsockopt (a->sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
+    {
+      acr3901u_warn ("getsockopt SO_ERROR failed:%m");
+      close (a->sock);
+      a->sock = -1;
+      return -1;
+    }
+
+  if (err)
+    {
+      //acr3901u_warn ("async connect gave error %d");
+      close (a->sock);
+      a->sock = -1;
+      return -1;
+    }
+
+//  acr3901u_warn ("connected");
+
+  return 0;
+}
+
+
+/* specific inbound message handlers */
+
+
+static void
+rdr_to_sph_authrsp1 (ACR3901U * a, uint8_t * buf, size_t len)
+{
+  if (len != sizeof (a->rnd_a))
+    return;
+
+  decrypt (a->rnd_a, buf, sizeof (a->rnd_a), shared_key);
+  a->authrsp1++;
+
+}
+
+
+static void
+rdr_to_sph_authrsp2 (ACR3901U * a, uint8_t * buf, size_t len)
+{
+  uint8_t dc_buf[0x10];
+
+  if (len != sizeof (dc_buf))
+    return;
+
+  decrypt (dc_buf, buf, sizeof (dc_buf), shared_key);
+
+  if (!memcmp (a->rnd_b, dc_buf, sizeof (a->rnd_b)))
+    {
+// acr3901u_warn("Mutual authentication completed");
+      a->authenticated++;
+
+      memcpy (&a->session_key[0], a->rnd_a, 8);
+      memcpy (&a->session_key[8], a->rnd_b, 8);
+    }
+
+  a->authrsp2++;
+
+}
+
+
+static void
+reader_error (int enc, uint8_t type, uint8_t err)
+{
+
+  switch (err)
+    {
+    case 1:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Invalid checksum",
+         enc, type, err);
+      break;
+    case 2:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Invalid data length",
+         enc, type, err);
+      break;
+    case 3:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Invalid command format",
+         enc, type, err);
+      break;
+    case 4:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Invalid/Unknown command",
+         enc, type, err);
+      break;
+    case 5:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Card operation error",
+         enc, type, err);
+      break;
+    case 6:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Authentication is required/Authentication Error",
+         enc, type, err);
+      break;
+    case 7:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Low battery", enc,
+         type, err);
+      break;
+    case 8:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): Authentication error",
+         enc, type, err);
+      break;
+
+    default:
+      acr3901u_warn
+        ("Reader reports (e=%d, type=0x%02x, err=0x%02x): (unknown error code)",
+         enc, type, err);
+      break;
+
+    }
+}
+
+
+
+static void
+enc_data_in_dispatch (ACR3901U * a, uint8_t * buf, size_t len)
+{
+  uint8_t dbuf[1024];
+  size_t plen;
+  uint8_t *payload;
+
+  if (len & 0xf)
+    {
+      acr3901u_warn ("Encrypted packet incorrectly padded\n");
+      return;
+    }
+
+  if (len > sizeof (dbuf))
+    {
+      acr3901u_warn ("Encrypted packet too long\n");
+      return;
+    }
+
+  decrypt (dbuf, buf, len, a->session_key);
+
+  payload = dbuf + 3;
+
+  plen = read_le16 (&dbuf[1]);
+
+  if ((plen < 1) || (len < (plen + 3)))
+    {
+      acr3901u_warn ("Encrypted packet wrong length");
+      return;
+    }
+
+  if (acr3901u_checksum (dbuf, plen + 2) != dbuf[plen + 2])
+    {
+      acr3901u_warn ("Encrypted bad acr3901u_checksum");
+      return;
+    }
+
+  plen--;
+#if 0
+  acr3901u_warn
+    ("Received encrypted packet of type %02x: %d bytes of payload", dbuf[0],
+     plen);
+  hexdump (payload, 0, plen);
+  hexdump (dbuf, 0, plen + 4);
+#endif
+
+  switch (dbuf[0])
+    {
+
+    case 0x11:
+    case 0x12:
+    case 0x14:
+    case 0x16:
+
+      a->rx_type = dbuf[0];
+      a->rx_payload_len = plen;
+      if (plen)
+        memcpy (a->rx_payload, payload, plen);
+
+      a->rx_valid = 1;
+
+      break;
+
+    case 0x90:
+    case 0x91:
+    case 0x92:
+    case 0x93:
+    case 0x94:
+    case 0x95:
+    case 0x96:
+    case 0x97:
+    case 0x98:
+    case 0x99:
+    case 0x9a:
+    case 0x9b:
+    case 0x9c:
+    case 0x9d:
+    case 0x9e:
+    case 0x9f:
+
+      if (plen == 1)
+        {
+          reader_error (1, dbuf[0], payload[0]);
+          break;
+        }
+
+
+      /* fall through */
+    default:
+
+
+#if 1
+      acr3901u_warn
+        ("Received encrypted packet of type %02x: %d bytes of payload",
+         dbuf[0], plen);
+      hexdump (payload, 0, plen);
+#endif
+      break;
+
+    }
+
+}
+
+
+/* generic data in */
+
+static void
+data_in_dispatch (ACR3901U * a, uint8_t * fragment, size_t fragment_len)
+{
+  size_t plen;
+  uint8_t *payload;
+
+
+  if (fragment_len > sizeof (a->rx_buf))
+    {
+      acr3901u_warn ("fragment too long");
+      return;
+    }
+
+
+  if ((a->rx_len + fragment_len) > sizeof (a->rx_buf))
+    {
+      acr3901u_warn ("buffer overflow, resyncing");
+      a->rx_len = 0;
+    }
+
+  memcpy (&a->rx_buf[a->rx_len], fragment, fragment_len);
+  a->rx_len += fragment_len;
+
+
+  if (a->rx_len < 4)
+    {
+      acr3901u_warn ("too short");
+      return;
+    }
+  payload = a->rx_buf + 3;
+
+  plen = read_le16 (&a->rx_buf[1]);
+
+#if 0
+  acr3901u_warn ("ax->rx_len=%d fragment_len=%d packet_len=%d\n",
+                 (int) a->rx_len, (int) fragment_len, (int) (plen + 3));
+#endif
+
+  if ((plen < 1) || (a->rx_len > (plen + 3)))
+    {
+      acr3901u_warn ("wrong length");
+      a->rx_len = 0;
+      return;
+    }
+  if (a->rx_len < (plen + 3))
+    return;
+
+  if (acr3901u_checksum (a->rx_buf, plen + 2) != a->rx_buf[plen + 2])
+    {
+      acr3901u_warn ("bad acr3901u_checksum - discarding");
+      a->rx_len = 0;
+      return;
+    }
+
+  plen--;
+
+#if 0
+  acr3901u_warn ("Received packet of type %02x: %d bytes of payload",
+                 a->rx_buf[0], plen);
+  hexdump (payload, 0, plen);
+#endif
+
+  switch (a->rx_buf[0])
+    {
+
+    case RDR_to_SPH_AuthRsp1:
+      rdr_to_sph_authrsp1 (a, payload, plen);
+      break;
+
+    case RDR_to_SPH_AuthRsp2:
+      rdr_to_sph_authrsp2 (a, payload, plen);
+      break;
+
+    case RDR_to_SPH_EncMsg:
+      enc_data_in_dispatch (a, payload, plen);
+      break;
+    case 0xa0:
+    case 0xa1:
+    case 0xa2:
+    case 0xa3:
+    case 0xa4:
+    case 0xa5:
+    case 0xa6:
+    case 0xa7:
+    case 0xa8:
+    case 0xa9:
+    case 0xaa:
+    case 0xab:
+    case 0xac:
+    case 0xad:
+    case 0xae:
+    case 0xaf:
+
+
+      if (plen == 1)
+        {
+          reader_error (0, a->rx_buf[0], payload[0]);
+          break;
+        }
+
+
+      /*fall through */
+
+    default:
+#if 1
+      acr3901u_warn ("Received packet of type %02x: %d bytes of payload",
+                     a->rx_buf[0], plen);
+      hexdump (payload, 0, plen);
+#endif
+
+      break;
+
+    }
+
+  a->rx_len = 0;
+}
+
+static void
+dispatch (ACR3901U * a, uint8_t * buf, size_t len)
+{
+  uint16_t h;
+
+  if (!len)
+    return;
+  switch (buf[0])
+    {
+
+    case BT_ATT_OP_WRITE_RSP:
+      if (len != 1)
+        break;
+      a->write_rsp++;
+      return;
+    case BT_ATT_OP_HANDLE_VAL_NOT:
+      if (len < 3)
+        break;
+      h = read_le16 (&buf[1]);
+      if ((h == CARD_STATUS_HANDLE) && (len == 5))
+        {
+          a->card_status = read_le16 (&buf[3]);
+
+          a->slot_status_stale++;
+//          acr3901u_warn ("Card status now: %x", a->card_status);
+        }
+      if ((h == BATTERY_HANDLE) && (len == 4))
+        {
+          a->battery_status = buf[3];
+//          acr3901u_warn ("Battery status now: %x", a->battery_status);
+        }
+      if (h == DATA_OUT_HANDLE)
+        {
+
+// acr3901u_warn("%d bytes of data_in",len-3);
+
+          data_in_dispatch (a, buf + 3, len - 3);
+          break;
+        }
+      break;
+    }
+
+}
+
+
+static int
+process_recvs (ACR3901U * a, struct timeval *tv)
+{
+  fd_set rfds;
+  int n;
+
+  ssize_t len;
+  uint8_t buf[1024];
+
+
+  FD_ZERO (&rfds);
+
+
+  for (;;)
+    {
+      FD_SET (a->sock, &rfds);
+
+      n = select (a->sock + 1, &rfds, NULL, NULL, tv);
+
+      if (!n)
+        return -1;
+
+      len = recv (a->sock, buf, sizeof (buf), 0);
+
+      if (len < 0)
+        return -1;
+
+      if (len)
+        {
+          dispatch (a, buf, len);
+          return 0;
+        }
+    }
+
+  return 0;
+}
+
+static int
+read_loop (ACR3901U * a, int timeout)
+{
+  struct timeval tv = { 0 };
+  tv.tv_sec = timeout;
+
+  while (!process_recvs (a, &tv));
+
+  return 0;
+}
+
+
+static int
+wait_for (ACR3901U * a, int *ptr, int timeout)
+{
+  struct timeval tv = { 0 };
+  tv.tv_sec = timeout;
+
+  *ptr = 0;
+
+  while (!process_recvs (a, &tv))
+    {
+      if (*ptr)
+        return 0;
+    }
+  return -1;
+}
+
+
+/* Data out */
+
+
+static int
+unenc_msg_send (ACR3901U * a, uint8_t type, uint8_t * payload, size_t plen)
+{
+
+  uint8_t buf[1024];
+  size_t len;
+
+
+  if ((plen + 4) > sizeof (buf))
+    {
+      acr3901u_warn ("message too long");
+      return -1;
+    }
+
+  buf[0] = type;
+  write_le16 (&buf[1], plen + 1);
+  if (plen)
+    memcpy (&buf[3], payload, plen);
+
+  len = 3 + plen;
+
+  buf[len] = acr3901u_checksum (buf, len);
+  len++;
+
+#if 0
+  acr3901u_warn ("Sent packet of type %02x: %d bytes of payload", type, plen);
+  hexdump (payload, 0, plen);
+#endif
+
+  return gatt_write_cmd (a, DATA_IN_HANDLE, buf, len);
+
+}
+
+static int
+enc_msg_send (ACR3901U * a, uint8_t type, uint8_t * payload, size_t plen)
+{
+
+  uint8_t buf[1024];
+  uint8_t ebuf[1024];
+  size_t len;
+
+  memset (buf, 0, sizeof (buf));
+
+
+  if ((plen + 4) > sizeof (buf))
+    {
+      acr3901u_warn ("message too long");
+      return -1;
+    }
+
+  buf[0] = type;
+  write_le16 (&buf[1], plen + 1);
+  if (plen)
+    memcpy (&buf[3], payload, plen);
+
+  len = 3 + plen;
+
+  buf[len] = acr3901u_checksum (buf, len);
+  len++;
+
+#if 0
+  acr3901u_warn ("Sent encrypted packet of type %02x: %d bytes of payload",
+                 type, plen);
+  hexdump (payload, 0, plen);
+  hexdump (buf, 0, len);
+#endif
+
+  len = (len + 0xf) & ~0xf;
+
+  encrypt (ebuf, buf, len, a->session_key);
+
+  return unenc_msg_send (a, 0x72, ebuf, len);
+}
+
+
+/* specific outbound messages */
+
+static int
+auth_req_send (ACR3901U * a)
+{
+  return unenc_msg_send (a, SPH_to_RDR_ReqAuth, NULL, 0);
+}
+
+
+static int
+auth_rsp1_reply (ACR3901U * a)
+{
+  uint8_t buf[32], cbuf[32];;
+
+  if (read_random (a->rnd_b, sizeof (a->rnd_b)))
+    return -1;
+
+  memcpy (&buf[0], a->rnd_b, sizeof (a->rnd_b));
+  memcpy (&buf[0x10], a->rnd_a, sizeof (a->rnd_a));
+
+  decrypt (cbuf, buf, sizeof (buf), shared_key);
+
+  return unenc_msg_send (a, SPH_to_RDR_AuthRsp, cbuf, sizeof (cbuf));
+}
+
+
+/* Generic fn */
+
+static int
+acr3901u_make_ready (ACR3901U * a, int timeout)
+{
+
+
+  if (acr3901u_connect (a, timeout))
+    return -1;
+
+  do
+    {
+      if (gatt_write_req_16 (a, BATTERY_CFG_HANDLE, 0x1))
+        break;
+      if (wait_for (a, &a->write_rsp, timeout))
+        break;
+      if (gatt_write_req_16 (a, DATA_OUT_CFG_HANDLE, 0x1))
+        break;
+      if (wait_for (a, &a->write_rsp, timeout))
+        break;
+      if (gatt_write_req_16 (a, CARD_STATUS_CFG_HANDLE, 0x1))
+        break;
+      if (wait_for (a, &a->write_rsp, timeout))
+        break;
+      if (auth_req_send (a))
+        break;
+      if (wait_for (a, &a->authrsp1, timeout))
+        break;
+      if (auth_rsp1_reply (a))
+        break;
+      if (wait_for (a, &a->authenticated, timeout))
+        break;
+      return 0;
+    }
+  while (0);
+
+
+  close (a->sock);
+  a->sock = -1;
+
+
+  return -1;
+}
+
+unsigned int ACR3901U_Rates[] = { ISO_DATA_RATES, 0 };
+
+/* no need to initialize to 0 since it is static */
+static ACR3901U device[CCID_DRIVER_MAX_READERS];
+
+
+static status_t
+check_open (ACR3901U * a)
+{
+  struct timeval tv = { 0 };
+
+  if (!strlen (a->bd_address))
+    return STATUS_COMM_ERROR;
+  if (a->sock != -1)
+    return STATUS_SUCCESS;
+
+  a->rx_len = 0;
+  a->slot_status_stale = 1;
+
+  if (acr3901u_make_ready (a, 2))
+    return STATUS_COMM_ERROR;
+
+
+  if (a->card_status == 0x250)
+    {
+      acr3901u_info ("check_open: card removed");
+      a->powered_up = 0;
+    }
+
+
+  if (a->powered_up)
+    {
+
+      if (enc_msg_send (a, 0x62, NULL, 0))
+        return STATUS_COMM_ERROR;
+
+      /* Drop ATR reply */
+
+      a->rx_valid = 0;
+      tv.tv_sec = 2;
+      while (!process_recvs (a, &tv) && !a->rx_valid);
+      if (!a->rx_valid)
+        {
+          acr3901u_info
+            ("check_open: failed to repower card after comms break");
+        }
+      a->rx_valid = 0;
+
+    }
+
+
+  return STATUS_SUCCESS;
+}
+
+
+static int
+enc_msg_send_retry_once (ACR3901U * a, uint8_t type, uint8_t * payload,
+                         size_t plen)
+{
+  int ret;
+
+  ret = enc_msg_send (a, type, payload, plen);
+
+  if (!ret)
+    return ret;
+
+  if (check_open (a) != STATUS_SUCCESS)
+    {
+      acr3901u_info
+        ("enc_msg_send_retry_once: send failed, reopen failed, failed");
+      return -1;
+    }
+  acr3901u_warn ("enc_msg_send_retry_once: enc_msg_send failed - retrying");
+
+  ret = enc_msg_send (a, type, payload, plen);
+  if (!ret)
+    return ret;
+
+  acr3901u_info
+    ("enc_msg_send_retry_once: send failed, retry failed, failed");
+
+  return ret;
+}
+
+
+static struct timeval
+timeout_tv (ACR3901U * a)
+{
+  struct timeval ret = { 0 };
+  uint32_t timeout = a->ccid.readTimeout;
+
+  if (timeout < 500)
+    timeout = 500;
+
+  ret.tv_sec = timeout / 1000;
+  ret.tv_usec = 1000 * (timeout - ret.tv_sec * 1000);
+
+
+  return ret;
+}
+
+
+/*****************************************************************************
+ *
+ *				WriteAcrBtLe: Send bytes to the card reader
+ *
+ *****************************************************************************/
+status_t
+WriteAcrBtLe (unsigned int reader_index, unsigned int length,
+              unsigned char *buffer)
+{
+  ACR3901U *a = &device[reader_index];
+  status_t ret;
+
+  char debug_header[] = "-> 123456 ";
+
+  (void) snprintf (debug_header, sizeof (debug_header), "-> %06X ",
+                   reader_index);
+
+  if (length < 10)
+    return STATUS_COMM_ERROR;
+
+
+  a->seq = buffer[6];
+
+  ret = check_open (a);
+
+  if (ret != STATUS_SUCCESS)
+    {
+      if ((length == 10) &&
+          (buffer[0] == 0x65) &&
+          (buffer[1] == 0x00) &&
+          (buffer[2] == 0x00) &&
+          (buffer[3] == 0x00) &&
+          (buffer[4] == 0x00) &&
+          (buffer[5] == 0x00) &&
+          (buffer[7] == 0x00) && (buffer[8] == 0x00) && (buffer[9] == 0x00))
+        {
+
+          a->fake_get_slot_status = 1;
+          a->slot_status = 2;
+          return STATUS_SUCCESS;
+        }
+
+      return ret;
+    }
+
+  switch (buffer[0])
+    {
+    case 0x61:                 //Set parameters
+      a->fake_param_protocol_num = buffer[7];
+      a->fake_param_data_len = read_le32 (&buffer[1]);
+
+
+      if ((a->fake_param_data_len + 10) > length)
+        return STATUS_COMM_ERROR;
+
+      if (a->fake_param_data_len > (sizeof (a->fake_param_data) - 1))
+        {
+          acr3901u_warn ("buffer overrun 1");
+          return STATUS_COMM_ERROR;
+        }
+
+      a->fake_param_data[0] = a->fake_param_protocol_num;
+      memcpy (&a->fake_param_data[1], &buffer[10], a->fake_param_data_len);
+
+      /* My version of the firmware V1.09 silently does PPS automatically */
+      /* for both USB and BT interfaces, infact the BT interface doesn't */
+      /* even implement the Card Set Parameters command - JMM 08/2017 */
+
+#if 0
+      if (enc_msg_send_retry_once
+          (a, buffer[0], a->fake_param_data, a->fake_param_data_len + 1))
+        return STATUS_COMM_ERROR;
+#else
+      a->fake_params = 1;
+#endif
+
+      break;
+
+    case 0x62:                 //CCID  power up
+    case 0x63:                 //CCID  power down
+      a->slot_status_stale++;
+      if (enc_msg_send_retry_once (a, buffer[0], NULL, 0))
+        return STATUS_COMM_ERROR;
+
+      break;
+    case 0x65:                 //CCID get slot status - save power by using the notify for this
+      if (a->slot_status_stale)
+        {
+          if (enc_msg_send_retry_once (a, buffer[0], NULL, 0))
+            return STATUS_COMM_ERROR;
+        }
+      else
+        {
+          struct timeval tv = { 0 };
+          while (!process_recvs (a, &tv) && !a->rx_valid);
+          a->fake_get_slot_status = 1;
+        }
+      break;
+
+    case 0x6f:
+      {
+
+        uint32_t pdu_len = read_le32 (&buffer[1]);
+        if ((pdu_len + 10) > length)
+          return STATUS_COMM_ERROR;
+
+        if (enc_msg_send_retry_once (a, buffer[0], &buffer[10], pdu_len))
+          return STATUS_COMM_ERROR;
+      }
+      break;
+
+
+    default:
+      acr3901u_warn ("WriteAcrBtLe - unknown command");
+      hexdump (buffer, 0, length);
+      return STATUS_COMM_ERROR;
+    }
+  return STATUS_SUCCESS;
+}                               /* WriteAcrBtLe */
+
+
+/*****************************************************************************
+ *
+ *				ReadAcrBtLe: Receive bytes from the card reader
+ *
+ *****************************************************************************/
+status_t
+ReadAcrBtLe (unsigned int reader_index,
+             unsigned int *length, unsigned char *buffer)
+{
+  ACR3901U *a = &device[reader_index];
+  char debug_header[] = "<- 123456 ";
+  status_t ret = STATUS_COMM_ERROR;
+  size_t ret_len, copy_len;
+  int again = 0;
+
+  struct timeval tv = timeout_tv (a);
+
+  if (*length < 10)
+    return ret;
+
+
+  do
+    {
+
+      if (a->fake_get_slot_status)
+        {
+          a->fake_get_slot_status = 0;
+
+          *length = 10;
+          buffer[0] = 0x81;
+          buffer[1] = 0x00;
+          buffer[2] = 0x00;
+          buffer[3] = 0x00;
+          buffer[4] = 0x00;
+          buffer[5] = 0x00;
+          buffer[6] = a->seq;
+          buffer[7] = a->slot_status;
+          buffer[8] = 0x00;
+          buffer[9] = 0x00;
+
+          return STATUS_SUCCESS;
+        }
+
+      if (a->fake_params)
+        {
+          a->fake_params = 0;
+
+
+          write_le32 (&buffer[1], a->fake_param_data_len);
+          buffer[0] = 0x82;
+          buffer[5] = 0x00;
+          buffer[6] = a->seq;
+          buffer[7] = a->slot_status;
+          buffer[8] = 0x00;
+          buffer[9] = a->fake_param_protocol_num;
+
+          copy_len = a->fake_param_data_len;
+
+          if ((10 + copy_len) > *length)
+            copy_len = *length - 10;
+
+          *length = 10 + copy_len;
+
+          memcpy (&buffer[10], &a->fake_param_data[1], copy_len);
+
+          return STATUS_SUCCESS;
+        }
+
+
+      ret = check_open (a);
+      if (ret != STATUS_SUCCESS)
+        return ret;
+
+      ret = STATUS_COMM_ERROR;
+
+      while (!process_recvs (a, &tv) && !a->rx_valid);
+
+      if (a->rx_valid)
+        {
+          a->rx_valid = 0;
+
+
+          switch (a->rx_type)
+            {
+            case 0x12:         //Reply to power on 0x62
+              a->powered_up = 1;
+              /*Fall through */
+            case 0x11:         //Reply to APDU command 0x6f
+
+              buffer[0] = 0x80;
+              write_le32 (&buffer[1], a->rx_payload_len);
+              buffer[5] = 0x00;
+              buffer[6] = a->seq;
+              buffer[7] = a->slot_status;
+              buffer[8] = 0x00;
+              buffer[9] = 0x00;
+
+              copy_len = a->rx_payload_len;
+              if ((10 + copy_len) > *length)
+                copy_len = *length - 10;
+
+              *length = 10 + copy_len;
+              memcpy (&buffer[10], a->rx_payload, copy_len);
+
+              ret = STATUS_SUCCESS;
+
+              break;
+            case 0x13:         //Reply to card power off needs to be slot status so request that and go round again
+              a->powered_up = 0;
+              if (!enc_msg_send (a, 0x65, NULL, 0))
+                again++;
+              break;
+            case 0x14:         //Reply to get card presence 0x65
+              if (a->rx_payload_len != 1)
+                break;
+
+              a->slot_status_stale = 0;
+
+              switch (a->rx_payload[0])
+                {
+                case 1:        //Absent
+                  a->slot_status = 2;
+                  break;
+                case 2:        //present inactive
+                  a->slot_status = 1;
+                  break;
+                case 3:        //present active
+                  a->slot_status = 0;
+                  break;
+                default:
+                  a->slot_status = 3;
+                  break;
+                }
+
+
+
+              *length = 10;
+              buffer[0] = 0x81;
+              buffer[1] = 0x00;
+              buffer[2] = 0x00;
+              buffer[3] = 0x00;
+              buffer[4] = 0x00;
+              buffer[5] = 0x00;
+              buffer[6] = a->seq;
+              buffer[7] = a->slot_status;
+              buffer[8] = 0x00;
+              buffer[9] = 0x00;
+
+              ret = STATUS_SUCCESS;
+              break;
+            case 0x16:
+              if (a->rx_payload_len < 1)
+                {
+                  ret = STATUS_COMM_ERROR;
+                  break;
+                }
+
+              buffer[0] = 0x82;
+              write_le32 (&buffer[1], a->rx_payload_len - 1);
+              buffer[5] = 0x00;
+              buffer[6] = a->seq;
+              buffer[7] = a->slot_status;
+              buffer[8] = 0x00;
+
+              copy_len = a->rx_payload_len;
+              if ((9 + copy_len) > *length)
+                copy_len = *length - 9;
+
+              *length = 9 + copy_len;
+              memcpy (&buffer[9], a->rx_payload, copy_len);
+
+              ret = STATUS_SUCCESS;
+
+              ret = STATUS_SUCCESS;
+              break;
+
+            }
+        }
+      /*Timeout falls through */
+    }
+  while (again);
+
+#if 0
+  acr3901u_warn ("CCID out: st=%d", ret);
+  hexdump (buffer, 0, *length);
+#endif
+
+  return ret;
+}                               /* ReadAcrBtLe */
+
+
+
+/*****************************************************************************
+ *
+ *				OpenAcrBtLe: open the port
+ *
+ *****************************************************************************/
+status_t
+OpenAcrBtLe (unsigned int reader_index, int channel)
+{
+// acr3901u_warn("OpenAcrBtLe - all bad");
+  return STATUS_UNSUCCESSFUL;
+}                               /* OpenAcrBtLe */
+
+/*****************************************************************************
+ *
+ *				set_ccid_descriptor: init ccid descriptor
+ *				depending on reader type specified in device.
+ *
+ *				return: STATUS_UNSUCCESSFUL,
+ *						STATUS_SUCCESS,
+ *						-1 (Reader already used)
+ *
+ *****************************************************************************/
+static status_t
+set_ccid_descriptor (ACR3901U * a)
+{
+  int i;
+
+  /* Common to all readers */
+  a->ccid.real_bSeq = 0x77;
+  a->ccid.pbSeq = &a->ccid.real_bSeq;
+
+  a->ccid.readerID = ACR3901U_BTLE;
+
+  a->ccid.bCurrentSlotIndex = 0;
+
+  a->ccid.dwMaxCCIDMessageLength = 0x10f;
+  a->ccid.dwMaxIFSD = 0xfe;
+  a->ccid.dwDefaultClock = 0x12c0;
+  a->ccid.bPINSupport = 0x0;
+  a->ccid.dwMaxDataRate = 0x64ce7;
+  a->ccid.bMaxSlotIndex = 0;
+  a->ccid.arrayOfSupportedDataRates = ACR3901U_Rates;
+
+
+  a->ccid.dwFeatures = 0x000207B8;
+
+
+  a->ccid.readTimeout = DEFAULT_COM_READ_TIMEOUT;
+  a->ccid.dwSlotStatus = IFD_ICC_PRESENT;
+  a->ccid.bVoltageSupport = 0x07; /* 1.8V, 3V and 5V */
+
+  a->ccid.gemalto_firmware_features = NULL;
+
+  return STATUS_SUCCESS;
+}                               /* set_ccid_descriptor  */
+
+
+/*****************************************************************************
+ *
+ *				OpenAcrBtLeByName: open the port
+ *
+ *****************************************************************************/
+status_t
+OpenAcrBtLeByName (unsigned int reader_index, char *dev_name)
+{
+  ACR3901U *a = &device[reader_index];
+  /* 255 is MAX_DEVICENAME in pcscd.h */
+  char reader_name[255] = "ACR 3901U";
+  char *p;
+  status_t ret;
+
+  DEBUG_COMM3 ("Reader index: %X, Device: %s", reader_index, dev_name);
+
+  /* set channel used */
+  a->bd_address = strdup (dev_name);
+
+  ret = set_ccid_descriptor (a);
+  if (STATUS_UNSUCCESSFUL == ret)
+    return STATUS_UNSUCCESSFUL;
+
+  a->open = 1;
+  a->sock = -1;
+
+  a->ccid.sIFD_serial_number = NULL;
+  a->ccid.sIFD_iManufacturer = NULL;
+  a->ccid.IFD_bcdDevice = 0;
+
+// acr3901u_warn("OpenAcrBtLe - all good");
+
+  return STATUS_SUCCESS;
+}                               /* OpenAcrBtLeByName */
+
+
+/*****************************************************************************
+ *
+ *				CloseAcrBtLe: close the port
+ *
+ *****************************************************************************/
+status_t
+CloseAcrBtLe (unsigned int reader_index)
+{
+  ACR3901U *a = &device[reader_index];
+
+  /* device not opened */
+  if (!a->open)
+    return STATUS_UNSUCCESSFUL;
+
+  DEBUG_COMM2 ("Closing bt socket device: %s", a->bd_address);
+
+  a->open--;
+
+  if (!open)
+    {
+      if (a->sock != -1)
+        close (a->sock);
+      a->sock = -1;
+      free (a->bd_address);
+      a->bd_address = NULL;
+    }
+
+  return STATUS_SUCCESS;
+}                               /* CloseAcrBtLe */
+
+
+/*****************************************************************************
+ *
+ *					get_ccid_descriptor
+ *
+ ****************************************************************************/
+_ccid_descriptor *
+get_ccid_descriptor (unsigned int reader_index)
+{
+  ACR3901U *a = &device[reader_index];
+  return &a->ccid;
+}                               /* get_ccid_descriptor */
diff --git a/src/ccid_acr_btle.h b/src/ccid_acr_btle.h
new file mode 100644
index 0000000..1f2ce05
--- /dev/null
+++ b/src/ccid_acr_btle.h
@@ -0,0 +1,35 @@
+/*
+    ccid_hid_serial.h:  Serial access routines
+    Copyright (C) 2003-2008   Ludovic Rousseau
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public License
+	along with this library; if not, write to the Free Software Foundation,
+	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef __CCID_ACR_BTLE_H__
+#define __CCID_ACR_BTLE_H__
+
+status_t OpenAcrBtLe(unsigned int reader_index, int channel);
+
+status_t OpenAcrBtLeByName(unsigned int reader_index, char *dev_name);
+
+status_t WriteAcrBtLe(unsigned int reader_index, unsigned int length,
+	unsigned char *Buffer);
+
+status_t ReadAcrBtLe(unsigned int reader_index, unsigned int *length,
+	unsigned char *Buffer);
+
+status_t CloseAcrBtLe(unsigned int reader_index);
+
+#endif
diff --git a/src/commands.c b/src/commands.c
index adec95a..f1ecbfd 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -108,7 +108,7 @@ RESPONSECODE CmdPowerOn(unsigned int reader_index, unsigned int * nlength,
 	RESPONSECODE return_value = IFD_SUCCESS;
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1035,7 +1035,7 @@ RESPONSECODE CmdPowerOff(unsigned int reader_index)
 	RESPONSECODE return_value = IFD_SUCCESS;
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1124,7 +1124,7 @@ RESPONSECODE CmdGetSlotStatus(unsigned int reader_index, unsigned char buffer[])
 	RESPONSECODE return_value = IFD_SUCCESS;
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1305,7 +1305,7 @@ RESPONSECODE CCID_Transmit(unsigned int reader_index, unsigned int tx_length,
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 	status_t ret;
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		int r;
@@ -1380,7 +1380,7 @@ RESPONSECODE CCID_Receive(unsigned int reader_index, unsigned int *rx_length,
 	_ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index);
 	unsigned int old_timeout;
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 	if (PROTOCOL_ICCD_A == ccid_descriptor->bInterfaceProtocol)
 	{
 		unsigned char pcbuffer[SIZE_GET_SLOT_STATUS];
diff --git a/src/defs.h b/src/defs.h
index d94a39c..6cbeafc 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -107,6 +107,16 @@ typedef enum {
 #define WritePort WriteHidSerial
 #include "ccid_hid_serial.h"
 
+#elif ACR_BTLE
+
+#define OpenPortByName OpenAcrBtLeByName
+#define OpenPort OpenAcrBtLe
+#define ClosePort CloseAcrBtLe
+#define ReadPort ReadAcrBtLe
+#define WritePort WriteAcrBtLe
+
+#include "ccid_acr_btle.h"
+
 #else
 
 #define OpenPortByName OpenUSBByName
diff --git a/src/ifdhandler.c b/src/ifdhandler.c
index 36411d5..03ba901 100644
--- a/src/ifdhandler.c
+++ b/src/ifdhandler.c
@@ -297,7 +297,7 @@ EXTERNAL RESPONSECODE IFDHCloseChannel(DWORD Lun)
 } /* IFDHCloseChannel */
 
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 static RESPONSECODE IFDHPolling(DWORD Lun, int timeout)
 {
 	int reader_index;
@@ -522,7 +522,7 @@ EXTERNAL RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag,
 				*(uint32_t *)Value = get_ccid_descriptor(reader_index) -> dwMaxCCIDMessageLength -10;
 			break;
 
-#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) )
+#if !( defined (TWIN_SERIAL) || defined (HID_SERIAL) || defined (ACR_BTLE) )
 		case TAG_IFD_POLLING_THREAD_WITH_TIMEOUT:
 			{
 				_ccid_descriptor *ccid_desc;


More information about the Pcsclite-muscle mailing list