(find_endpoint): New.
[gnupg.git] / g10 / ccid-driver.c
index 01c8a99..0694fe7 100644 (file)
@@ -198,6 +198,10 @@ struct ccid_driver_s
   unsigned short id_vendor;
   unsigned short id_product;
   unsigned short bcd_device;
+  int ifc_no;
+  int ep_bulk_out;
+  int ep_bulk_in;
+  int ep_intr;
   int seqno;
   unsigned char t1_ns;
   unsigned char t1_nr;
@@ -207,6 +211,7 @@ struct ccid_driver_s
   int ifsd;
   int powered_off;
   int has_pinpad;
+  int apdu_level;     /* Reader supports short APDU level exchange.  */
 };
 
 
@@ -260,6 +265,7 @@ parse_ccid_descriptor (ccid_driver_t handle,
   handle->max_ifsd = 32;
   handle->ifsd = 0;
   handle->has_pinpad = 0;
+  handle->apdu_level = 0;
   DEBUGOUT_3 ("idVendor: %04X  idProduct: %04X  bcdDevice: %04X\n",
               handle->id_vendor, handle->id_product, handle->bcd_device);
   if (buflen < 54 || buf[0] < 54)
@@ -372,9 +378,15 @@ parse_ccid_descriptor (ccid_driver_t handle,
       have_tpdu = 1;
     } 
   else if ((us & 0x00020000))
-    DEBUGOUT ("    Short APDU level exchange\n");
+    {
+      DEBUGOUT ("    Short APDU level exchange\n");
+      handle->apdu_level = 1;
+    }
   else if ((us & 0x00040000))
-    DEBUGOUT ("    Short and extended APDU level exchange\n");
+    {
+      DEBUGOUT ("    Short and extended APDU level exchange\n");
+      handle->apdu_level = 1;
+    }
   else if ((us & 0x00070000))
     DEBUGOUT ("    WARNING: conflicting exchange levels\n");
 
@@ -421,10 +433,10 @@ parse_ccid_descriptor (ccid_driver_t handle,
     DEBUGOUT_LF ();
   }
 
-  if (!have_t1 || !have_tpdu || !have_auto_conf)
+  if (!have_t1 || !(have_tpdu  || handle->apdu_level) || !have_auto_conf)
     {
       DEBUGOUT ("this drivers requires that the reader supports T=1, "
-                "TPDU level exchange and auto configuration - "
+                "TPDU or APDU level exchange and auto configuration - "
                 "this is not available\n");
       return -1;
     }
@@ -546,6 +558,36 @@ make_reader_id (usb_dev_handle *idev,
 }
 
 
+/* Helper to find the endpoint from an interface descriptor.  */
+static int
+find_endpoint (struct usb_interface_descriptor *ifcdesc, int mode)
+{
+  int no;
+  int want_bulk_in = 0;
+
+  if (mode == 1)
+    want_bulk_in = 0x80;
+  for (no=0; no < ifcdesc->bNumEndpoints; no++)
+    {
+      struct usb_endpoint_descriptor *ep = ifcdesc->endpoint + no;
+      if (ep->bDescriptorType != USB_DT_ENDPOINT)
+        ;
+      else if (mode == 2
+          && ((ep->bmAttributes & USB_ENDPOINT_TYPE_MASK)
+              == USB_ENDPOINT_TYPE_INTERRUPT)
+          && (ep->bEndpointAddress & 0x80))
+        return (ep->bEndpointAddress & 0x0f);
+      else if (((ep->bmAttributes & USB_ENDPOINT_TYPE_MASK)
+                == USB_ENDPOINT_TYPE_BULK)
+               && (ep->bEndpointAddress & 0x80) == want_bulk_in)
+        return (ep->bEndpointAddress & 0x0f);
+    }
+  /* Should never happen.  */
+  return mode == 2? 0x83 : mode == 1? 0x82 :1;
+}
+
+
+
 /* Combination function to either scan all CCID devices or to find and
    open one specific device. 
 
@@ -579,7 +621,9 @@ scan_or_find_devices (int readerno, const char *readerid,
                       char **r_rid,
                       struct usb_device **r_dev,
                       unsigned char **ifcdesc_extra,
-                      size_t *ifcdesc_extra_len)
+                      size_t *ifcdesc_extra_len,
+                      int *interface_number,
+                      int *ep_bulk_out, int *ep_bulk_in, int *ep_intr)
 {
   char *rid_list = NULL;
   int count = 0;
@@ -597,6 +641,8 @@ scan_or_find_devices (int readerno, const char *readerid,
     *ifcdesc_extra = NULL;
   if (ifcdesc_extra_len)
     *ifcdesc_extra_len = 0;
+  if (interface_number)
+    *interface_number = 0;
 
   /* See whether we want scan or find mode. */
   if (scan_mode) 
@@ -721,6 +767,16 @@ scan_or_find_devices (int readerno, const char *readerid,
                                               ifcdesc->extralen);
                                       *ifcdesc_extra_len = ifcdesc->extralen;
                                     }
+                                  if (interface_number)
+                                    *interface_number = (ifcdesc->
+                                                         bInterfaceNumber);
+                                  if (ep_bulk_out)
+                                    *ep_bulk_out = find_endpoint (ifcdesc, 0);
+                                  if (ep_bulk_in)
+                                    *ep_bulk_in = find_endpoint (ifcdesc, 1);
+                                  if (ep_intr)
+                                    *ep_intr = find_endpoint (ifcdesc, 2);
+
 
                                   if (r_dev)
                                     *r_dev = dev;
@@ -787,7 +843,8 @@ ccid_get_reader_list (void)
       initialized_usb = 1;
     }
 
-  scan_or_find_devices (-1, NULL, &reader_list, NULL, NULL, NULL);
+  scan_or_find_devices (-1, NULL, &reader_list, NULL, NULL, NULL, NULL,
+                        NULL, NULL, NULL);
   return reader_list;
 }
 
@@ -804,6 +861,7 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid)
   unsigned char *ifcdesc_extra = NULL;
   size_t ifcdesc_extra_len;
   int readerno;
+  int ifc_no, ep_bulk_out, ep_bulk_in, ep_intr;
 
   *handle = NULL;
 
@@ -832,7 +890,8 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid)
     readerno = 0;  /* Default. */
 
   idev = scan_or_find_devices (readerno, readerid, &rid, &dev,
-                               &ifcdesc_extra, &ifcdesc_extra_len);
+                               &ifcdesc_extra, &ifcdesc_extra_len,
+                               &ifc_no, &ep_bulk_out, &ep_bulk_in, &ep_intr);
   if (!idev)
     {
       if (readerno == -1)
@@ -856,6 +915,10 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid)
   (*handle)->id_vendor = dev->descriptor.idVendor;
   (*handle)->id_product = dev->descriptor.idProduct;
   (*handle)->bcd_device = dev->descriptor.bcdDevice;
+  (*handle)->ifc_no = ifc_no;
+  (*handle)->ep_bulk_out = ep_bulk_out;
+  (*handle)->ep_bulk_in = ep_bulk_in;
+  (*handle)->ep_intr = ep_intr;
 
   DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n",  readerno, rid );
 
@@ -867,9 +930,7 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid)
       goto leave;
     }
   
-  /* fixme: Do we need to claim and set the interface as
-     determined above? */
-  rc = usb_claim_interface (idev, 0);
+  rc = usb_claim_interface (idev, ifc_no);
   if (rc)
     {
       DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
@@ -877,9 +938,6 @@ ccid_open_reader (ccid_driver_t *handle, const char *readerid)
       goto leave;
     }
   
-  /* FIXME: Do we need to get the endpoint addresses from the
-     structure and store them with the handle? */
-              
  leave:
   free (ifcdesc_extra);
   if (rc)
@@ -921,7 +979,7 @@ do_close_reader (ccid_driver_t handle)
     }
   if (handle->idev)
     {
-      usb_release_interface (handle->idev, 0);
+      usb_release_interface (handle->idev, handle->ifc_no);
       usb_close (handle->idev);
       handle->idev = NULL;
     }
@@ -944,6 +1002,7 @@ ccid_shutdown_reader (ccid_driver_t handle)
   usb_dev_handle *idev = NULL;
   unsigned char *ifcdesc_extra = NULL;
   size_t ifcdesc_extra_len;
+  int ifc_no, ep_bulk_out, ep_bulk_in, ep_intr;
 
   if (!handle || !handle->rid)
     return CCID_DRIVER_ERR_INV_VALUE;
@@ -951,7 +1010,8 @@ ccid_shutdown_reader (ccid_driver_t handle)
   do_close_reader (handle);
 
   idev = scan_or_find_devices (-1, handle->rid, NULL, &dev,
-                               &ifcdesc_extra, &ifcdesc_extra_len);
+                               &ifcdesc_extra, &ifcdesc_extra_len,
+                               &ifc_no, &ep_bulk_out, &ep_bulk_in, &ep_intr);
   if (!idev)
     {
       DEBUGOUT_1 ("no CCID reader with ID %s\n", handle->rid);
@@ -960,6 +1020,10 @@ ccid_shutdown_reader (ccid_driver_t handle)
 
 
   handle->idev = idev;
+  handle->ifc_no = ifc_no;
+  handle->ep_bulk_out = ep_bulk_out;
+  handle->ep_bulk_in = ep_bulk_in;
+  handle->ep_intr = ep_intr;
 
   if (parse_ccid_descriptor (handle, ifcdesc_extra, ifcdesc_extra_len))
     {
@@ -968,9 +1032,7 @@ ccid_shutdown_reader (ccid_driver_t handle)
       goto leave;
     }
   
-  /* fixme: Do we need to claim and set the interface as
-     determined above? */
-  rc = usb_claim_interface (idev, 0);
+  rc = usb_claim_interface (idev, ifc_no);
   if (rc)
     {
       DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
@@ -1022,7 +1084,7 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen)
   int rc;
 
   rc = usb_bulk_write (handle->idev, 
-                       1, /*endpoint */
+                       handle->ep_bulk_out,
                        msg, msglen,
                        1000 /* ms timeout */);
   if (rc == msglen)
@@ -1053,7 +1115,7 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
   memset (buffer, 0, length);
  retry:
   rc = usb_bulk_read (handle->idev, 
-                      0x82,
+                      handle->ep_bulk_in,
                       buffer, length,
                       10000 /* ms timeout */ );
   /* Fixme: instead of using a 10 second timeout we should better
@@ -1160,7 +1222,7 @@ ccid_poll (ccid_driver_t handle)
   int i, j;
 
   rc = usb_bulk_read (handle->idev, 
-                      0x83,
+                      handle->ep_intr,
                       msg, sizeof msg,
                       0 /* ms timeout */ );
   if (rc < 0 && errno == ETIMEDOUT)
@@ -1402,6 +1464,78 @@ compute_edc (const unsigned char *data, size_t datalen, int use_crc)
 }
 
 
+/* Helper for ccid_transceive used for APDU level exchanges.  */
+static int
+ccid_transceive_apdu_level (ccid_driver_t handle,
+                            const unsigned char *apdu_buf, size_t apdu_buflen,
+                            unsigned char *resp, size_t maxresplen,
+                            size_t *nresp)
+{
+  int rc;
+  unsigned char send_buffer[10+259], recv_buffer[10+259];
+  const unsigned char *apdu;
+  size_t apdulen;
+  unsigned char *msg;
+  size_t msglen;
+  unsigned char seqno;
+  int i;
+
+  msg = send_buffer;
+
+  apdu = apdu_buf;
+  apdulen = apdu_buflen;
+  assert (apdulen);
+
+  if (apdulen > 254)
+    return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
+
+  msg[0] = PC_to_RDR_XfrBlock;
+  msg[5] = 0; /* slot */
+  msg[6] = seqno = handle->seqno++;
+  msg[7] = 4; /* bBWI */
+  msg[8] = 0; /* RFU */
+  msg[9] = 0; /* RFU */
+  memcpy (msg+10, apdu, apdulen);
+  set_msg_len (msg, apdulen);
+  msglen = 10 + apdulen;
+
+  DEBUGOUT ("sending");
+  for (i=0; i < msglen; i++)
+    DEBUGOUT_CONT_1 (" %02X", msg[i]);
+  DEBUGOUT_LF ();
+  
+  rc = bulk_out (handle, msg, msglen);
+  if (rc)
+    return rc;
+
+  msg = recv_buffer;
+  rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
+                RDR_to_PC_DataBlock, seqno);
+  if (rc)
+    return rc;
+      
+  apdu = msg + 10;
+  apdulen = msglen - 10;
+      
+  if (resp)
+    {
+      if (apdulen > maxresplen)
+        {
+          DEBUGOUT_2 ("provided buffer too short for received data "
+                      "(%u/%u)\n",
+                      (unsigned int)apdulen, (unsigned int)maxresplen);
+          return CCID_DRIVER_ERR_INV_VALUE;
+        }
+      
+      memcpy (resp, apdu, apdulen); 
+      *nresp = apdulen;
+    }
+          
+  return 0;
+}
+
+
+
 /*
   Protocol T=1 overview
 
@@ -1478,6 +1612,13 @@ ccid_transceive (ccid_driver_t handle,
     nresp = &dummy_nresp;
   *nresp = 0;
 
+  /* Smarter readers allow to send APDUs directly; divert here. */
+  if (handle->apdu_level)
+    return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen,
+                                       resp, maxresplen, nresp);
+
+  /* The other readers we support require sending TPDUs.  */
+
   tpdulen = 0; /* Avoid compiler warning about no initialization. */
   msg = send_buffer;
   for (;;)
@@ -1828,7 +1969,7 @@ ccid_transceive_secure (ccid_driver_t handle,
   
   if (tpdulen < 4) 
     {
-      usb_clear_halt (handle->idev, 0x82);
+      usb_clear_halt (handle->idev, handle->ep_bulk_in);
       return CCID_DRIVER_ERR_ABORTED; 
     }
 #ifdef DEBUG_T1
@@ -1977,6 +2118,7 @@ main (int argc, char **argv)
   int no_pinpad = 0;
   int verify_123456 = 0;
   int did_verify = 0;
+  int no_poll = 0;
 
   if (argc)
     {
@@ -2001,6 +2143,11 @@ main (int argc, char **argv)
           ccid_set_debug_level (1);
           argc--; argv++;
         }
+      else if ( !strcmp (*argv, "--no-poll"))
+        {
+          no_poll = 1;
+          argc--; argv++;
+        }
       else if ( !strcmp (*argv, "--no-pinpad"))
         {
           no_pinpad = 1;
@@ -2019,7 +2166,8 @@ main (int argc, char **argv)
   if (rc)
     return 1;
 
-  ccid_poll (ccid);
+  if (!no_poll)
+    ccid_poll (ccid);
   fputs ("getting ATR ...\n", stderr);
   rc = ccid_get_atr (ccid, NULL, 0, NULL);
   if (rc)
@@ -2028,7 +2176,8 @@ main (int argc, char **argv)
       return 1;
     }
 
-  ccid_poll (ccid);
+  if (!no_poll)
+    ccid_poll (ccid);
   fputs ("getting slot status ...\n", stderr);
   rc = ccid_slot_status (ccid, &slotstat);
   if (rc)
@@ -2037,7 +2186,8 @@ main (int argc, char **argv)
       return 1;
     }
 
-  ccid_poll (ccid);
+  if (!no_poll)
+    ccid_poll (ccid);
 
   fputs ("selecting application OpenPGP ....\n", stderr);
   {
@@ -2050,7 +2200,8 @@ main (int argc, char **argv)
   }
   
 
-  ccid_poll (ccid);
+  if (!no_poll)
+    ccid_poll (ccid);
 
   fputs ("getting OpenPGP DO 0x65 ....\n", stderr);
   {