qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RFC] usb-ccid: add smart card reader CCID usb device


From: Alon Levy
Subject: [Qemu-devel] [RFC] usb-ccid: add smart card reader CCID usb device
Date: Sun, 15 Aug 2010 13:31:53 -0400 (EDT)

Hi,

 This patch adds a usb CCID device, a common standard for smart cards that is
supported by linux and windows (and probably others that I didn't test).

 It is intended to be used by SPICE and RHEV-M, but of course it is valuable
independently from those. Uses include usage of client smartcard at both the
client and guest machines simultaneously and introducing completely virtual
smart card into guest.

 It has been tested with the virtual card emulator below to work with the 
coolkey
provider using a linux host and linux guest. The smart card itself doesn't have
to be on the qemu host machine.

Please test and review,

Alon

commit message below:
------

The usb-ccid device talks the protocol in vscard_common.h through the chardev
it is attached to. A program implementing a virtual card based on static
certificates or a real card on the client is available at:

 http://cgit.freedesktop.org/~alon/cac_card (temporary home)

cac_card written by Robert Relyea <address@hidden>

Signed-off-by: Alon Levy <address@hidden>
---
 Makefile.objs   |    2 +-
 hw/usb-ccid.c   |  998 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vscard_common.h |  118 +++++++
 3 files changed, 1117 insertions(+), 1 deletions(-)
 create mode 100644 hw/usb-ccid.c
 create mode 100644 vscard_common.h

diff --git a/Makefile.objs b/Makefile.objs
index 4a1eaa1..8baa8fe 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -72,7 +72,7 @@ common-obj-y += eeprom93xx.o
 common-obj-y += scsi-disk.o cdrom.o
 common-obj-y += scsi-generic.o scsi-bus.o
 common-obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o 
usb-wacom.o
-common-obj-y += usb-serial.o usb-net.o usb-bus.o
+common-obj-y += usb-serial.o usb-net.o usb-ccid.o usb-bus.o
 common-obj-$(CONFIG_SSI) += ssi.o
 common-obj-$(CONFIG_SSI_SD) += ssi-sd.o
 common-obj-$(CONFIG_SD) += sd.o
diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c
new file mode 100644
index 0000000..ace2e28
--- /dev/null
+++ b/hw/usb-ccid.c
@@ -0,0 +1,998 @@
+/*
+ * CCID Device emulation
+ *
+ * Based on usb-serial.c:
+ * Copyright (c) 2006 CodeSourcery.
+ * Copyright (c) 2008 Samuel Thibault <address@hidden>
+ * Written by Paul Brook, reused for FTDI by Samuel Thibault,
+ * reused for CCID by Alon Levy.
+ * Copyright (c) 2010 Red Hat.
+ *
+ * This code is licenced under the LGPL.
+ */
+
+/* References:
+ *
+ * CCID Specification Revision 1.1 April 22nd 2005
+ *  "Universal Serial Bus, Device Class: Smart Card"
+ *  Specification for Integrated Circuit(s) Cards Interface Devices
+ *
+ * KNOWN BUGS
+ * 1. remove/insert can sometimes result in removed state instead of inserted.
+ *
+ * TODO:
+ *
+ * 1. Discard additional answers (only one outstanding question per slot at a
+ *   time according to spec).
+ * 2. Don't change answer while sending a multiple packet BULK_IN (answer_len >
+ *   single_packet_len == 64)
+ * 3. ATR / card remove during pending answer - send back a failed response.
+ */
+
+#include "qemu-common.h"
+#include "qemu-error.h"
+#include "usb.h"
+#include "qemu-char.h"
+
+#include "vscard_common.h"
+
+//#define DEBUG_CCID
+
+#ifdef DEBUG_CCID
+#define DPRINTF(fmt, ...) \
+do { printf("usb-ccid: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+#define RECV_BUF 384
+
+#define DeviceOutVendor    ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
+#define DeviceInVendor     ((USB_DIR_IN |USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8)
+
+#define CCID_CONTROL_ABORT                  0x1
+#define CCID_CONTROL_GET_CLOCK_FREQUENCIES  0x2
+#define CCID_CONTROL_GET_DATA_RATES         0x3
+
+#define IMPERSONATE_ATHENA
+#ifdef IMPERSONATE_ATHENA
+#define CCID_PRODUCT_DESCRIPTION        "ASEDrive CCID"
+#define CCID_VENDOR_DESCRIPTION         "QEMU " QEMU_VERSION
+#define CCID_INTERFACE_NAME             "CCID Interface"
+#define CCID_SERIAL_NUMBER_STRING       "ASE016"
+#define CCID_VENDOR_ID                  0x0dc3
+#define CCID_PRODUCT_ID                 0x1004
+#define CCID_DEVICE_VERSION             0x0502
+#else
+#define CCID_PRODUCT_DESCRIPTION        "QEMU USB CCID"
+#define CCID_VENDOR_DESCRIPTION         "QEMU " QEMU_VERSION
+#define CCID_INTERFACE_NAME             "CCID Interface"
+#define CCID_SERIAL_NUMBER_STRING       "1"
+#error TBD Vendor id and Product id for emulated card
+#define CCID_VENDOR_ID                  TBD
+#define CCID_PRODUCT_ID                 TBD
+#define CCID_DEVICE_VERSION             0x0000
+#endif
+
+// BULK_OUT messages from PC to Reader
+// Defined in CCID Rev 1.1 6.1 (page 26)
+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn              0x62
+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOff             0x63
+#define CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus           0x65
+#define CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock                0x6f
+#define CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters           0x6c
+#define CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters         0x6d
+#define CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters           0x61
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Escape                  0x6b
+#define CCID_MESSAGE_TYPE_PC_to_RDR_IccClock                0x6e
+#define CCID_MESSAGE_TYPE_PC_to_RDR_T0APDU                  0x6a
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Secure                  0x69
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Mechanical              0x71
+#define CCID_MESSAGE_TYPE_PC_to_RDR_Abort                   0x72
+#define CCID_MESSAGE_TYPE_PC_to_RDR_SetDataRateAndClockFrequency 0x73
+
+// BULK_IN messages from Reader to PC
+// Defined in CCID Rev 1.1 6.2 (page 48)
+#define CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock               0x80
+#define CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus              0x81
+#define CCID_MESSAGE_TYPE_RDR_to_PC_Parameters              0x82
+#define CCID_MESSAGE_TYPE_RDR_to_PC_Escape                  0x83
+#define CCID_MESSAGE_TYPE_RDR_to_PC_DataRateAndClockFrequency 0x84
+
+// INTERRUPT_IN messages from Reader to PC
+// Defined in CCID Rev 1.1 6.3 (page 56)
+#define CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange        0x50
+#define CCID_MESSAGE_TYPE_RDR_to_PC_HardwareError           0x51
+
+// Endpoints for CCID - addresses are up to us to decide.
+// To support slot insertion and removal we must have an interrupt in ep
+// in addition we need a bulk in and bulk out ep
+// 5.2, page 20
+#define CCID_INT_IN_EP       1
+#define CCID_BULK_IN_EP      2
+#define CCID_BULK_OUT_EP     3
+
+// bmSlotICCState masks
+#define SLOT_0_STATE_MASK    1
+#define SLOT_0_CHANGED_MASK  2
+
+// Status codes that go in bStatus (see 6.2.6)
+enum {
+    ICC_STATUS_PRESENT_ACTIVE = 0,
+    ICC_STATUS_PRESENT_INACTIVE,
+    ICC_STATUS_NOT_PRESENT
+};
+
+enum {
+    COMMAND_STATUS_NO_ERROR = 0,
+    COMMAND_STATUS_FAILED,
+    COMMAND_STATUS_TIME_EXTENSION_REQUIRED
+};
+
+// Error codes that go in bError (see 6.2.6)
+// There is no such thing as ERROR_NONE, just easy to
+// spot places where the author thought there is no error code
+// to transmit.
+#define ERROR_NONE 0
+enum {
+    ERROR_CMD_ABORTED       = -1,
+    ERROR_ICC_MUTE          = -2,
+    ERROR_XFR_PARITY_ERROR  = -3,
+    ERROR_XFR_OVERRUN       = -4,
+    ERROR_HW_ERROR          = -5,
+};
+
+// 6.2.6 RDR_to_PC_SlotStatus definitions
+enum {
+    CLOCK_STATUS_RUNNING = 0,
+    // 0 - Clock Running, 1 - Clock stopped in State L, 2 - H,
+    // 3 - unkonwn state. rest are RFU
+};
+
+typedef struct __attribute__ ((__packed__)) {
+    uint8_t     bMessageType;
+    uint32_t    dwLength;
+    uint8_t     bSlot;
+    uint8_t     bSeq;
+} CCID_Header;
+
+typedef struct __attribute__ ((__packed__)) {
+    CCID_Header hdr;
+    uint8_t     bStatus;        // Only used in BULK_IN
+    uint8_t     bError;         // Only used in BULK_IN
+} CCID_BULK_IN;
+
+typedef struct __attribute__ ((__packed__)) {
+    CCID_BULK_IN b;
+    uint8_t     bClockStatus;
+} CCID_SlotStatus;
+
+typedef struct __attribute__ ((__packed__)) {
+    CCID_BULK_IN b;
+    uint8_t     bProtocolNum;
+    uint8_t     abProtocolDataStructure[0];
+} CCID_Parameter;
+
+typedef struct __attribute__ ((__packed__)) {
+    CCID_BULK_IN b;
+    uint8_t      bChainParameter;
+    uint8_t      abData[0];
+} CCID_DataBlock;
+
+// 6.1.4 PC_to_RDR_XfrBlock
+typedef struct __attribute__ ((__packed__)) {
+    CCID_Header  hdr;
+    uint8_t      bBWI; // Block Waiting Timeout
+    uint16_t     wLevelParameter;
+    uint8_t      abData[0];
+} CCID_XferBlock;
+
+typedef struct __attribute__ ((__packed__)) {
+    CCID_Header hdr;
+    uint8_t     bPowerSelect;
+    uint16_t    abRFU;
+} CCID_IccPowerOn;
+
+typedef struct __attribute__ ((__packed__)) {
+    CCID_Header hdr;
+    uint8_t     bProtocolNum;
+    uint8_t    abProtocolDataStructure[0];
+} CCID_SetParameter;
+
+typedef struct {
+    uint8_t     bMessageType; // CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange
+    uint8_t     bmSlotICCState;
+} CCID_Notify_Slot_Change;
+
+#define MAX_ATR_SIZE        40
+#define MAX_PROTOCOL_SIZE   7
+typedef struct {
+    USBDevice dev;
+    CharDriverState *cs;
+    uint8_t  atr[MAX_ATR_SIZE];
+    uint8_t  atr_length;
+    uint8_t  recv_buf[RECV_BUF];
+    uint16_t recv_ptr;
+    uint16_t recv_used;
+    uint8_t  bmSlotICCState;
+    bool     notify_slot_change;
+    bool     waiting_for_answer;
+    uint64_t last_answer_error;
+    uint8_t  answer_slot;    // used for DataBlock response to XferBlock
+    uint8_t  answer_seq;     // ditto
+    uint8_t  bError;
+    uint8_t  bmCommandStatus;
+    uint8_t  bProtocolNum;
+    uint8_t  abProtocolDataStructure[MAX_PROTOCOL_SIZE];
+    uint32_t ulProtocolDataStructureSize;
+    bool     got_reader_add_message;
+} USBCCIDState;
+
+/* Slot specific variables. We emulate a single slot card reader.
+ */
+
+uint8_t DEFAULT_ATR[] = {
+// From some example somewhere
+// 0x3B, 0xB0, 0x18, 0x00, 0xD1, 0x81, 0x05, 0xB1, 0x40, 0x38, 0x1F, 0x03, 0x28
+
+// From an Athena smart card
+ 0x3B, 0xD5, 0x18, 0xFF, 0x80, 0x91, 0xFE, 0x1F, 0xC3, 0x80, 0x73, 0xC8, 0x21, 
0x13, 0x08
+
+}; // maximum size of ATR - from 7816-3
+
+
+/* CCID Spec chapter 4: CCID uses a standard device descriptor per Chapter 9,
+ * "USB Device Framework", section 9.6.1, in the Universal Serial Bus
+ * Specification.
+ *
+ * This device implemented based on the spec and with an Athena Smart Card
+ * Reader as reference:
+ *   0dc3:1004 Athena Smartcard Solutions, Inc.
+ */
+
+static const uint8_t qemu_ccid_dev_descriptor[] = {
+        0x12,       /*  u8 bLength; */
+        USB_DT_DEVICE, /*  u8 bDescriptorType; Device */
+        0x10, 0x01, /*  u16 bcdUSB; v1.1 */
+
+        0x00,       /*  u8  bDeviceClass; */
+        0x00,       /*  u8  bDeviceSubClass; */
+        0x00,       /*  u8  bDeviceProtocol; [ low/full speeds only ] */
+        0x40,       /*  u8  bMaxPacketSize0; 8 Bytes (valid: 8,16,32,64) */
+
+        /* Vendor and product id are arbitrary.  */
+                    /*  u16 idVendor  */
+        CCID_VENDOR_ID & 0xff, CCID_VENDOR_ID >> 8,
+                    /*  u16 idProduct */
+        CCID_PRODUCT_ID & 0xff, CCID_PRODUCT_ID >> 8,
+                    /*  u16 bcdDevice */
+        CCID_DEVICE_VERSION & 0xff, CCID_DEVICE_VERSION >> 8,
+        0x01,       /*  u8  iManufacturer; */
+        0x02,       /*  u8  iProduct; */
+        0x03,       /*  u8  iSerialNumber; */
+        0x01,       /*  u8  bNumConfigurations; */
+};
+
+static const uint8_t qemu_ccid_config_descriptor[] = {
+
+        /* one configuration */
+        0x09,       /*  u8  bLength; */
+        USB_DT_CONFIG, /*  u8  bDescriptorType; Configuration */
+        0x5d, 0x00, /*  u16 wTotalLength; 9+9+54+7+7+7 */
+        0x01,       /*  u8  bNumInterfaces; (1) */
+        0x01,       /*  u8  bConfigurationValue; */
+        0x00,       /*  u8  iConfiguration; */
+        0x80,       /*  u8  bmAttributes;
+                                 Bit 7: must be set,
+                                     6: Self-powered,
+                                     5: Remote wakeup,
+                                     4..0: resvd */
+        100/2,      /*  u8  MaxPower; 50 == 100mA */
+
+        /* one interface */
+        0x09,       /*  u8  if_bLength; */
+        USB_DT_INTERFACE, /*  u8  if_bDescriptorType; Interface */
+        0x00,       /*  u8  if_bInterfaceNumber; */
+        0x00,       /*  u8  if_bAlternateSetting; */
+        0x03,       /*  u8  if_bNumEndpoints; */
+        0x0b,       /*  u8  if_bInterfaceClass; Smart Card Device Class */
+        0x00,       /*  u8  if_bInterfaceSubClass; Subclass code */
+        0x00,       /*  u8  if_bInterfaceProtocol; Protocol code */
+        0x04,       /*  u8  if_iInterface; Index of string descriptor */
+
+        /* Smart Card Device Class Descriptor */
+        0x36,       /*  u8  bLength; */
+        0x21,       /*  u8  bDescriptorType; Functional */
+        0x10, 0x01, /*  u16 bcdCCID; CCID Specification Release Number. */
+        0x00,       /*  u8  bMaxSlotIndex; The index of the highest available
+                        slot on this device. All slots are consecutive starting
+                        at 00h. */
+        0x07,       /*  u8  bVoltageSupport; 01h - 5.0v, 02h - 3.0, 03 - 1.8 */
+
+        0x03, 0x00, /*  u32 dwProtocols; RRRR PPPP. RRRR = 0000h.*/
+        0x00, 0x00, /*  PPPP: 0001h = Protocol T=0, 0002h = Protocol T=1 */
+                    /*  u32 dwDefaultClock; in kHZ (0x1000 is 4 MHz) */
+        0x00, 0x10, 0x00, 0x00,
+                    /*  u32 dwMaximumClock; */
+        0x00, 0x10, 0x00, 0x00,
+        0x00,       /*  u8 bNumClockSupported; 0 means just the default and 
max. */
+                    /*  u32 dwDataRate ;bps. 9600 == 00002580h, 115200 = 
0001C200 */
+        0x00, 0xC2, 0x01, 0x00,
+                    /*  u32 dwMaxDataRate ; 9600 bps == 00002580h */
+        0x00, 0xC2, 0x01, 0x00,
+        0x00,       /*  u8  bNumDataRatesSupported; 00 means all rates between
+                     *      default and max */
+                    /*  u32 dwMaxIFSD; maximum IFSD supported by CCID for 
protocol
+                     *      T=1 TODO (copied from Athena) */
+        0xfc, 0x00, 0x00, 0x00,
+                    /*  u32 dwSyncProtocols; 1 - 2-wire, 2 - 3-wire, 4 - I2C */
+        0x00, 0x00, 0x00, 0x00,
+                    /*  u32 dwMechanical;  0 - no special characteristics. */
+        0x00, 0x00, 0x00, 0x00,
+                    /*  u32 dwFeatures;
+                     *  0 - No special characteristics
+                     *  + 2 Automatic parameter configuration based on ATR data
+                     *  4 Automatic activation of ICC on insertinc
+                     *  8 Automatic ICC voltage selection
+                     *  + 10 Automatic ICC clock frequency change
+                     *  + 20 Automatic baud rate change
+                     *  40 Automatic parameters negotiation made by the CCID
+                     *  80 automatic PPS made by the CCID
+                     *  100 CCID can set ICC in clock stop mode
+                     *  200 NAD value other then 00 accepted (T=1 protocol)
+                     *  400 Automatic IFSD exchange as first exchange (T=1)
+                     *  One of the following only:
+                     *  10000 TPDU level exchanges with CCID
+                     *  20000 Short APDU level exchange with CCID
+                     *  + 40000 Short and Extended APDU level exchange with 
CCID
+                     *
+                     *  100000 USB Wake up signaling supported on card 
insertion
+                     *  and removal. Must set bit 5 in bmAttributes in 
Configuration
+                     *  descriptor if 100000 is set.*/
+        0x32, 0x00, 0x04, 0x00,
+                    /*  u32 dwMaxCCIDMessageLength; For extended APDU in [261 
+ 10
+                     *  , 65544 + 10]. Otherwise the minimum is wMaxPacketSize 
of
+                     *  the Bulk-OUT endpoint */
+        0x12, 0x00, 0x01, 0x00,
+        0xFF,       /*  u8  bClassGetResponse; Significant only for CCID that
+                     *  offers an APDU level for exchanges. Indicates the 
default
+                     *  class value used by the CCID when it sends a Get 
Response
+                     *  command to perform the transportation of an APDU by T=0
+                     *  protocol
+                     *  FFh indicates that the CCID echos the class of the 
APDU.
+                     */
+        0xFF,       /*  u8  bClassEnvelope; EAPDU only. Envelope command for 
T=0 */
+        0x00, 0x00, /*  u16 wLcdLayout; XXYY Number of lines (XX) and chars per
+                     *  line for LCD display used for PIN entry. 0000 - no LCD 
*/
+        0x01,       /*  u8  bPINSupport; 01h PIN Verification,
+                     *                   02h PIN Modification */
+        0x01,       /*  u8  bMaxCCIDBusySlots; */
+
+        /* Interrupt-IN endpoint */
+        0x07,       /*  u8  ep_bLength; */
+                    /*  u8  ep_bDescriptorType; Endpoint */
+        USB_DT_ENDPOINT,
+                    /*  u8  ep_bEndpointAddress; IN Endpoint 1 */
+        0x80 | CCID_INT_IN_EP,
+        0x03,       /*  u8  ep_bmAttributes; Interrupt */
+        0x40, 0x00, /*  u16 ep_wMaxPacketSize; */
+        0xff,       /*  u8  ep_bInterval; */
+
+        /* Bulk-In endpoint */
+        0x07,       /*  u8  ep_bLength; */
+                    /*  u8  ep_bDescriptorType; Endpoint */
+        USB_DT_ENDPOINT,
+                    /*  u8  ep_bEndpointAddress; IN Endpoint 2 */
+        0x80 | CCID_BULK_IN_EP,
+        0x02,       /*  u8  ep_bmAttributes; Bulk */
+        0x40, 0x00, /*  u16 ep_wMaxPacketSize; */
+        0x00,       /*  u8  ep_bInterval; */
+
+        /* Bulk-Out endpoint */
+        0x07,       /*  u8  ep_bLength; */
+                    /*  u8  ep_bDescriptorType; Endpoint */
+        USB_DT_ENDPOINT,
+                    /*  u8  ep_bEndpointAddress; OUT Endpoint 3 */
+        CCID_BULK_OUT_EP,
+        0x02,       /*  u8  ep_bmAttributes; Bulk */
+        0x40, 0x00, /*  u16 ep_wMaxPacketSize; */
+        0x00,       /*  u8  ep_bInterval; */
+
+};
+
+static void ccid_reset(USBCCIDState *s)
+{
+    s->recv_ptr = 0;
+    s->recv_used = 0;
+    s->got_reader_add_message = false;
+}
+
+static void ccid_handle_reset(USBDevice *dev)
+{
+    USBCCIDState *s = (USBCCIDState *)dev;
+
+    DPRINTF("Reset\n");
+
+    ccid_reset(s);
+    /* TODO: Reset qemu chardev, send BREAK? SCMsg? */
+}
+
+static int ccid_handle_control(USBDevice *dev, int request, int value,
+                                  int index, int length, uint8_t *data)
+{
+    int ret = 0;
+
+    DPRINTF("got control %x, value %x\n",request, value);
+    switch (request) {
+    case DeviceRequest | USB_REQ_GET_STATUS:
+        data[0] = (0 << USB_DEVICE_SELF_POWERED) |
+            (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP);
+        data[1] = 0x00;
+        ret = 2;
+        break;
+    case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+        if (value == USB_DEVICE_REMOTE_WAKEUP) {
+            dev->remote_wakeup = 0;
+        } else {
+            goto fail;
+        }
+        ret = 0;
+        break;
+    case DeviceOutRequest | USB_REQ_SET_FEATURE:
+        if (value == USB_DEVICE_REMOTE_WAKEUP) {
+            dev->remote_wakeup = 1;
+        } else {
+            goto fail;
+        }
+        ret = 0;
+        break;
+    case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+        dev->addr = value;
+        ret = 0;
+        break;
+    case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+        switch(value >> 8) {
+        case USB_DT_DEVICE:
+            memcpy(data, qemu_ccid_dev_descriptor,
+                   sizeof(qemu_ccid_dev_descriptor));
+            ret = sizeof(qemu_ccid_dev_descriptor);
+            break;
+        case USB_DT_CONFIG:
+            memcpy(data, qemu_ccid_config_descriptor,
+                   sizeof(qemu_ccid_config_descriptor));
+            ret = sizeof(qemu_ccid_config_descriptor);
+            break;
+        case USB_DT_STRING:
+            switch(value & 0xff) {
+            case 0:
+                /* language ids */
+                data[0] = 4;
+                data[1] = 3;
+                data[2] = 0x09;
+                data[3] = 0x04;
+                ret = 4;
+                break;
+            case 1:
+                /* vendor description */
+                ret = set_usb_string(data, CCID_VENDOR_DESCRIPTION);
+                break;
+            case 2:
+                /* product description */
+                ret = set_usb_string(data, CCID_PRODUCT_DESCRIPTION);
+                break;
+            case 3:
+                /* serial number */
+                ret = set_usb_string(data, CCID_SERIAL_NUMBER_STRING);
+                break;
+            case 4:
+                /* interface name */
+                ret = set_usb_string(data, CCID_INTERFACE_NAME);
+                break;
+            default:
+                goto fail;
+            }
+            break;
+        default:
+            goto fail;
+        }
+        break;
+    case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+        data[0] = 1;
+        ret = 1;
+        break;
+    case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+        /* Only one configuration - we just ignore the request */
+        ret = 0;
+        break;
+    case DeviceRequest | USB_REQ_GET_INTERFACE:
+        data[0] = 0;
+        ret = 1;
+        break;
+    case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+        ret = 0;
+        break;
+    case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+        ret = 0;
+        break;
+
+        /* Class specific requests.  */
+    case DeviceOutVendor | CCID_CONTROL_ABORT:
+        DPRINTF("ccid_control abort UNIMPLEMENTED\n");
+        ret = USB_RET_STALL;
+        break;
+    case DeviceOutVendor | CCID_CONTROL_GET_CLOCK_FREQUENCIES:
+        DPRINTF("ccid_control get clock frequencies UNIMPLEMENTED\n");
+        ret = USB_RET_STALL;
+        break;
+    case DeviceOutVendor | CCID_CONTROL_GET_DATA_RATES:
+        DPRINTF("ccid_control get data rates UNIMPLEMENTED\n");
+        ret = USB_RET_STALL;
+        break;
+    default:
+    fail:
+        DPRINTF("got unsupported/bogus control %x, value %x\n", request, 
value);
+        ret = USB_RET_STALL;
+        break;
+    }
+    return ret;
+}
+
+static uint8_t* ccid_reserve_recv_buf(USBCCIDState* s, uint16_t len)
+{
+    if (len + s->recv_used > RECV_BUF) {
+        printf("usb-ccid.c: %s: overflow of receive buf. bailing out.\n",
+                __func__);
+        exit(-1);
+    }
+    if (s->recv_used == 0) {
+        s->recv_ptr = 0;
+    }
+    s->recv_used += len;
+    return s->recv_buf + s->recv_ptr;
+}
+
+static bool ccid_card_inserted(USBCCIDState *s)
+{
+    return s->bmSlotICCState & SLOT_0_STATE_MASK;
+}
+
+static uint8_t ccid_calc_status(USBCCIDState *s)
+{
+    // page 55, 6.2.6, calculation of bStatus from bmICCStatus and
+    // bmCommandStatus
+    return (ccid_card_inserted(s)
+            ? ICC_STATUS_PRESENT_ACTIVE
+            : ICC_STATUS_NOT_PRESENT)
+         | (s->bmCommandStatus << 6);
+}
+
+static void ccid_reset_error_status(USBCCIDState* s)
+{
+    s->bError = ERROR_NONE;
+    s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+}
+
+static void ccid_write_slot_status(USBCCIDState* s, CCID_Header* recv)
+{
+    CCID_SlotStatus *h = (CCID_SlotStatus*)ccid_reserve_recv_buf(s, 10);
+    h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_SlotStatus;
+    h->b.hdr.dwLength = 0;
+    h->b.hdr.bSlot = recv->bSlot;
+    h->b.hdr.bSeq = recv->bSeq;
+    h->b.bStatus = ccid_calc_status(s);
+    h->b.bError = s->bError;
+    h->bClockStatus = CLOCK_STATUS_RUNNING;
+    ccid_reset_error_status(s);
+}
+
+static void ccid_write_parameters(USBCCIDState* s, CCID_Header* recv)
+{
+    CCID_Parameter *h;
+    uint32_t len = s->ulProtocolDataStructureSize;
+
+    h = (CCID_Parameter *)ccid_reserve_recv_buf(s, 10+len);
+    h->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_Parameters;
+    h->b.hdr.dwLength = 0;
+    h->b.hdr.bSlot = recv->bSlot;
+    h->b.hdr.bSeq = recv->bSeq;
+    h->b.bStatus = ccid_calc_status(s);
+    h->b.bError = s->bError;
+    h->bProtocolNum = s->bProtocolNum;
+    memcpy(h->abProtocolDataStructure, s->abProtocolDataStructure, len);
+    ccid_reset_error_status(s);
+}
+
+static void ccid_write_data_block(
+    USBCCIDState* s, uint8_t slot, uint8_t seq,
+    const uint8_t* data, uint32_t len)
+{
+    CCID_DataBlock *p;
+
+    if (sizeof(s->recv_buf) < sizeof(*p) + len) {
+        abort();
+    }
+    p = (CCID_DataBlock*)ccid_reserve_recv_buf(s, sizeof(*p) + len);
+    p->b.hdr.bMessageType = CCID_MESSAGE_TYPE_RDR_to_PC_DataBlock;
+    p->b.hdr.dwLength = len;
+    p->b.hdr.bSlot = slot;
+    p->b.hdr.bSeq = seq;
+    p->b.bStatus = ccid_calc_status(s);
+    p->b.bError = s->bError;
+    memcpy(p->abData, data, len);
+    ccid_reset_error_status(s);
+}
+
+static void ccid_write_data_block_answer(USBCCIDState* s, const uint8_t* data, 
uint32_t len)
+{
+    ccid_write_data_block(s, s->answer_slot, s->answer_seq, data, len);
+}
+
+static void ccid_write_data_block_atr(USBCCIDState* s, CCID_Header* recv)
+{
+    ccid_write_data_block(s, recv->bSlot, recv->bSeq, s->atr, s->atr_length);
+}
+
+static void ccid_set_parameters(USBCCIDState *s, CCID_Header *recv)
+{
+    CCID_SetParameter *ph = (CCID_SetParameter *) recv;
+    uint32_t len = 0;
+    if (ph->bProtocolNum == 0) {
+        len = 5;
+    }
+    if (ph->bProtocolNum == 1) {
+        len = 7;
+    }
+    if (len == 0) {
+        s->bmCommandStatus = COMMAND_STATUS_FAILED;
+        s->bError = 7; /* Protocol invalid or not supported */
+        return;
+    }
+    s->bProtocolNum = ph->bProtocolNum;
+    memcpy(s->abProtocolDataStructure, ph->abProtocolDataStructure, len);
+    s->ulProtocolDataStructureSize = len;
+}
+
+/* must be 5 bytes for T=0, 7 bytes for T=1 */
+static const uint8_t abDefaultProtocolDataStructure[5] =
+    { 0x77, 0x00, 0x00, 0x00, 0x00 };
+
+static void ccid_reset_parameters(USBCCIDState *s)
+{
+   uint32_t len = sizeof(abDefaultProtocolDataStructure);
+   s->bProtocolNum = 0; /* T=0 */
+   s->ulProtocolDataStructureSize = len;
+   memcpy(s->abProtocolDataStructure, abDefaultProtocolDataStructure, len);
+}
+
+static void ccid_on_slot_change(USBCCIDState* s, bool full)
+{
+    // RDR_to_PC_NotifySlotChange, 6.3.1 page 56
+    uint8_t current = s->bmSlotICCState;
+    if (full) {
+        s->bmSlotICCState |= SLOT_0_STATE_MASK;  // Slot 0 status full
+    } else {
+        s->bmSlotICCState &= ~SLOT_0_STATE_MASK; // Slot 0 status empty
+    }
+    if (current != s->bmSlotICCState) {
+        s->bmSlotICCState |= SLOT_0_CHANGED_MASK;  // Slot 0 changed
+    }
+    s->notify_slot_change = true;
+}
+
+static void ccid_write_data_block_error(
+    USBCCIDState *s, uint8_t slot, uint8_t seq)
+{
+    ccid_write_data_block(s, slot, seq, NULL, 0);
+}
+
+static void ccid_vscard_send_msg(
+    USBCCIDState *s, VSCMsgType type, uint32_t reader_id,
+        uint8_t* payload, uint32_t length)
+{
+    VSCMsgHeader scr_msg_header;
+    scr_msg_header.type = type;
+    scr_msg_header.reader_id = reader_id;
+    scr_msg_header.length = length;
+    qemu_chr_write(s->cs, (uint8_t*)&scr_msg_header, sizeof(VSCMsgHeader));
+    qemu_chr_write(s->cs, payload, length);
+}
+
+static void ccid_vscard_send_apdu(
+    USBCCIDState *s, uint8_t* apdu, uint32_t length)
+{
+    ccid_vscard_send_msg(s, VSC_APDU, VSCARD_MINIMAL_READER_ID, apdu, length);
+}
+
+static void ccid_vscard_send_error(
+    USBCCIDState *s, uint32_t reader_id, VSCErrorCode code)
+{
+    VSCMsgError msg = {.code=code};
+    ccid_vscard_send_msg(s, VSC_Error, reader_id, (uint8_t*)&msg, sizeof(msg));
+}
+
+static void ccid_vscard_send_init(USBCCIDState *s)
+{
+    VSCMsgInit msg = {.version=VSCARD_VERSION};
+    ccid_vscard_send_msg(s, VSC_Init, VSCARD_UNDEFINED_READER_ID,
+                         (uint8_t*)&msg, sizeof(msg));
+}
+
+static void ccid_on_apdu_from_guest(USBCCIDState *s, CCID_XferBlock* recv)
+{
+    if (!s->cs) {
+        printf("usb-ccid: discarding apdu length %d\n", recv->hdr.dwLength);
+        return;
+    }
+    if (!ccid_card_inserted(s)) {
+        printf("usb-ccid: not sending apdu to client, no card connected\n");
+        ccid_write_data_block_error(s, recv->hdr.bSlot, recv->hdr.bSeq);
+        return;
+    }
+    s->answer_slot = recv->hdr.bSlot;
+    s->answer_seq = recv->hdr.bSeq;
+    DPRINTF("%s: seq %d, len %d\n", __FUNCTION__,
+                recv->hdr.bSeq, recv->hdr.dwLength);
+    ccid_vscard_send_apdu(s, recv->abData, recv->hdr.dwLength);
+    s->waiting_for_answer = true;
+}
+
+static void ccid_handle_bulk_out(USBCCIDState *s, USBPacket *p)
+{
+    static uint8_t data[70000]; // TODO - set to correct max size of packet.
+    static uint32_t len = 0;
+    static CCID_Header* ccid_header = (CCID_Header*)data;
+
+    memcpy(data + len, p->data, p->len);
+    len += p->len;
+    if (p->len == 64) {
+        printf("usb-ccid: bulk_in: expecting more packets (%d/%d)\n",
+            len, ccid_header->dwLength);
+        return;
+    }
+    if (len < 10) {
+        DPRINTF("handle_data: bad USB_TOKEN_OUT length, should be at least 10 
bytes\n");
+    } else {
+        switch (ccid_header->bMessageType) {
+            case CCID_MESSAGE_TYPE_PC_to_RDR_GetSlotStatus:
+                ccid_write_slot_status(s, ccid_header);
+                break;
+            case CCID_MESSAGE_TYPE_PC_to_RDR_IccPowerOn:
+                // We need to send an ATR back
+                ccid_write_data_block_atr(s, ccid_header);
+                break;
+            case CCID_MESSAGE_TYPE_PC_to_RDR_XfrBlock:
+                ccid_on_apdu_from_guest(s, (CCID_XferBlock*)data);
+                break;
+            case CCID_MESSAGE_TYPE_PC_to_RDR_SetParameters:
+                s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+                ccid_set_parameters(s, ccid_header);
+                ccid_write_parameters(s, ccid_header);
+            break;
+            case CCID_MESSAGE_TYPE_PC_to_RDR_ResetParameters:
+                s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+                ccid_reset_parameters(s);
+                ccid_write_parameters(s, ccid_header);
+            break;
+            case CCID_MESSAGE_TYPE_PC_to_RDR_GetParameters:
+                s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+                ccid_write_parameters(s, ccid_header);
+            break;
+            default:
+                DPRINTF("handle_data: ERROR: unhandled message type %Xh\n",
+                    ccid_header->bMessageType);
+                break;
+        }
+    }
+    len = 0;
+}
+
+static int ccid_handle_data(USBDevice *dev, USBPacket *p)
+{
+    USBCCIDState *s = (USBCCIDState *)dev;
+    int ret = 0;
+    int first_len;
+    uint8_t *data = p->data;
+    int len = p->len;
+
+    switch (p->pid) {
+    case USB_TOKEN_OUT:
+        ccid_handle_bulk_out(s, p);
+        break;
+
+    case USB_TOKEN_IN:
+        switch (p->devep & 0xf) {
+            case CCID_BULK_IN_EP:
+                first_len = RECV_BUF - s->recv_ptr;
+                if (len > s->recv_used)
+                    len = s->recv_used;
+                if (!len) {
+                    ret = USB_RET_NAK;
+                    break;
+                }
+                if (first_len > len)
+                    first_len = len;
+                memcpy(data, s->recv_buf + s->recv_ptr, first_len);
+                if (len > first_len)
+                    memcpy(data + first_len, s->recv_buf, len - first_len);
+                s->recv_used -= len;
+                s->recv_ptr = (s->recv_ptr + len) % RECV_BUF;
+                ret = len;
+                break;
+            case CCID_INT_IN_EP:
+                if (s->notify_slot_change) {
+                    // page 56, RDR_to_PC_NotifySlotChange
+                    data[0] = CCID_MESSAGE_TYPE_RDR_to_PC_NotifySlotChange;
+                    data[1] = s->bmSlotICCState;
+                    ret = 2;
+                    s->notify_slot_change = false;
+                    s->bmSlotICCState &= ~ SLOT_0_CHANGED_MASK; // next notify 
won't say "changed"
+                    DPRINTF("handle_data: int_in: notify_slot_change %X\n", 
s->bmSlotICCState);
+                }
+                break;
+            default:
+                DPRINTF("Bad endpoint\n");
+                break;
+        }
+        break;
+    default:
+        DPRINTF("Bad token\n");
+        ret = USB_RET_STALL;
+        break;
+    }
+
+    return ret;
+}
+
+static void ccid_handle_destroy(USBDevice *dev)
+{
+    USBCCIDState *s = (USBCCIDState *)dev;
+
+    if (s->cs) {
+        qemu_chr_close(s->cs);
+    }
+}
+
+/* APDU chardev */
+
+static uint8_t readbuf[10000];
+static uint32_t readpos = 0;
+
+static int ccid_vscard_can_read(void *opaque)
+{
+    return sizeof(readbuf) - readpos > 0 ? sizeof(readbuf) - readpos : 0;
+}
+
+static void ccid_vscard_read(void *opaque, const uint8_t *buf, int size)
+{
+    USBCCIDState *s = opaque;
+    const VSCMsgHeader* scr_msg_header = (const VSCMsgHeader*)readbuf;
+    const uint8_t* data = readbuf + sizeof(VSCMsgHeader);
+    uint32_t available = size + readpos;
+
+    assert(readpos + size <= sizeof(readbuf));
+    memcpy(readbuf + readpos, buf, size);
+    readpos += size;
+
+    if (available < sizeof(VSCMsgHeader) ||
+        available - sizeof(VSCMsgHeader) < scr_msg_header->length) {
+        return;
+    }
+
+    readpos = 0; // ready for next message
+
+    switch (scr_msg_header->type) {
+        case VSC_ATR:
+            DPRINTF("VSC_ATR %d\n", scr_msg_header->length);
+            assert(scr_msg_header->length <= MAX_ATR_SIZE);
+            memcpy(s->atr, data, scr_msg_header->length);
+            s->atr_length = scr_msg_header->length;
+            s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+            if (s->waiting_for_answer) {
+                ccid_write_data_block_answer(s, NULL, 0);
+            }
+            ccid_on_slot_change(s, true);
+            break;
+        case VSC_APDU:
+            if (!s->waiting_for_answer) {
+                DPRINTF("VSC_APDU: ERROR: got an APDU while not 
waiting_for_answer\n");
+                break;
+            }
+            s->bmCommandStatus = COMMAND_STATUS_NO_ERROR;
+            DPRINTF("VSC_APDU %d (answer seq %d, slot %d)\n",
+                scr_msg_header->length,
+                s->answer_seq, s->answer_slot);
+            ccid_write_data_block_answer(s, data, scr_msg_header->length);
+            break;
+        case VSC_CardRemove:
+            DPRINTF("VSC_CardRemove\n");
+            ccid_on_slot_change(s, false);
+            if (s->waiting_for_answer) {
+                ccid_write_data_block_answer(s, NULL, 0);
+            }
+            break;
+        case VSC_Init:
+            ccid_vscard_send_init(s);
+            break;
+        case VSC_ReaderAdd:
+            if (!s->got_reader_add_message) {
+                s->got_reader_add_message = true;
+            } else {
+                ccid_vscard_send_error(s, VSCARD_UNDEFINED_READER_ID,
+                                          VSC_CANNOT_ADD_MORE_READERS);
+            }
+        case VSC_Error:
+            // TODO - actually look at the error
+            s->bmCommandStatus = COMMAND_STATUS_FAILED;
+            s->last_answer_error = *(uint64_t*)data;
+            DPRINTF("VSC_Error: %lX\n", s->last_answer_error);
+            ccid_write_data_block_answer(s, NULL, 0);
+            break;
+        default:
+            printf("usb-ccid: chardev: unexpected message of type %X\n",
+                   scr_msg_header->type);
+    }
+    s->waiting_for_answer = false;
+}
+
+static void ccid_vscard_event(void *opaque, int event)
+{
+    USBCCIDState *s = opaque;
+    switch (event) {
+        case CHR_EVENT_BREAK:
+            break;
+        case CHR_EVENT_FOCUS:
+            break;
+        case CHR_EVENT_OPENED:
+            ccid_reset(s);
+            break;
+    }
+}
+
+static int ccid_initfn(USBDevice *dev)
+{
+    USBCCIDState *s = DO_UPCAST(USBCCIDState, dev, dev);
+    s->dev.speed = USB_SPEED_FULL;
+    s->notify_slot_change = false;
+    s->answer_slot = -1;
+    s->answer_seq = -1;
+    s->last_answer_error = 0;
+    s->waiting_for_answer = false;
+    ccid_reset_error_status(s);
+    assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE);
+    memcpy(s->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR));
+    s->atr_length = sizeof(DEFAULT_ATR);
+    s->got_reader_add_message = false;
+    if (s->cs) {
+        DPRINTF("initing chardev\n");
+        qemu_chr_add_handlers(s->cs,
+            ccid_vscard_can_read,
+            ccid_vscard_read,
+            ccid_vscard_event, s);
+    }
+    ccid_reset_parameters(s);
+    ccid_handle_reset(dev);
+    return 0;
+}
+
+static struct USBDeviceInfo ccid_info = {
+    .product_desc   = "QEMU USB CCID",
+    .qdev.name      = "usb-ccid",
+    .qdev.size      = sizeof(USBCCIDState),
+    .init           = ccid_initfn,
+    .handle_packet  = usb_generic_handle_packet,
+    .handle_reset   = ccid_handle_reset,
+    .handle_control = ccid_handle_control,
+    .handle_data    = ccid_handle_data,
+    .handle_destroy = ccid_handle_destroy,
+    .usbdevice_name = "ccid",
+    .qdev.props     = (Property[]) {
+        DEFINE_PROP_CHR("chardev",     USBCCIDState, cs),
+        DEFINE_PROP_END_OF_LIST(),
+    },
+};
+
+static void ccid_register_devices(void)
+{
+    usb_qdev_register(&ccid_info);
+}
+device_init(ccid_register_devices)
diff --git a/vscard_common.h b/vscard_common.h
new file mode 100644
index 0000000..3a79eab
--- /dev/null
+++ b/vscard_common.h
@@ -0,0 +1,118 @@
+/* Virtual Smart Card protocol definition
+ *
+ * This protocol is between a host implementing one or more virtual smart card
+ * readers, and a client implementing one or more virtual smart cards, or pass
+ * through to real cards.
+ *
+ * The current implementation passes the raw APDU's from 7816 and additionally
+ * contains messages to setup and teardown readers, handle insertion and
+ * removal of cards, negotiate the protocol and provide for error responses.
+ *
+ * Copyright (c) 2010 Red Hat.
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#ifndef _VSCARD_COMMON_H
+#define _VSCARD_COMMON_H
+
+
+#define VERSION_MAJOR_BITS 11
+#define VERSION_MIDDLE_BITS 11
+#define VERSION_MINOR_BITS 10
+
+#define MAKE_VERSION(major, middle, minor) \
+     (  (major  << (VERSION_MINOR_BITS + VERSION_MIDDLE_BITS)) \
+      | (middle <<  VERSION_MINOR_BITS) \
+      | (minor)  )
+
+/** IMPORTANT NOTE on VERSION
+ *
+ * The version below MUST be changed whenever a change in this file is made.
+ *
+ * The last digit, the minor, is for bug fix changes only.
+ *
+ * The middle digit is for backward / forward compatible changes, updates
+ * to the existing messages, addition of fields.
+ *
+ * The major digit is for a breaking change of protocol, presumably
+ * something that cannot be accomodated with the existing protocol.
+ */
+
+#define VSCARD_VERSION MAKE_VERSION(0,0,1)
+
+#define VSCARD_UNDEFINED_READER_ID -1
+#define VSCARD_MINIMAL_READER_ID    0
+
+typedef enum {
+    VSC_Init,
+    VSC_Error,
+    VSC_ReaderAdd,
+    VSC_ReaderAddResponse,
+    VSC_ReaderRemove,
+    VSC_ATR,
+    VSC_CardRemove,
+    VSC_APDU
+} VSCMsgType;
+
+typedef enum {
+    VSC_GENERAL_ERROR=1,
+    VSC_CANNOT_ADD_MORE_READERS,
+} VSCErrorCode;
+
+typedef struct VSCMsgHeader {
+    VSCMsgType type;
+    uint32_t   reader_id;
+    uint32_t   length;
+} VSCMsgHeader;
+
+/* VSCMsgInit               Client <-> Host
+ * Host replies with allocated reader id in ReaderAddResponse
+ * */
+typedef struct VSCMsgInit {
+    uint32_t   version;
+} VSCMsgInit;
+
+/* VSCMsgError              Client <-> Host
+ * */
+typedef struct VSCMsgError {
+    VSCErrorCode   code;
+} VSCMsgError;
+
+/* VSCMsgReaderAdd          Client -> Host
+ * Host replies with allocated reader id in ReaderAddResponse
+ * */
+typedef struct VSCMsgReaderAdd {
+    uint8_t    name[0];
+} VSCMsgReaderAdd;
+
+/* VSCMsgReaderAddResponse  Host -> Client
+ * Reply to ReaderAdd
+ * */
+typedef struct VSCMsgReaderAddResponse {
+} VSCMsgReaderAddResponse;
+
+/* VSCMsgReaderRemove       Client -> Host
+ * */
+typedef struct VSCMsgReaderRemove {
+} VSCMsgReaderRemove;
+
+/* VSCMsgATR                Client -> Host
+ * Answer to reset. Sent for card insertion or card reset.
+ * */
+typedef struct VSCMsgATR {
+    uint8_t     atr[0];
+} VSCMsgATR;
+
+/* VSCMsgCardRemove         Client -> Host
+ * */
+typedef struct VSCMsgCardRemove {
+} VSCMsgCardRemove;
+
+/* VSCMsgAPDU               Client <-> Host
+ * */
+typedef struct VSCMsgAPDU {
+    uint8_t    data[0];
+} VSCMsgAPDU;
+
+#endif
-- 
1.7.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]