ccid_transceive_secure (ccid_driver_t handle,
                        const unsigned char *apdu_buf, size_t apdu_buflen,
                        pininfo_t *pininfo,
                        unsigned char *resp, size_t maxresplen, size_t *nresp)
{
  int rc;
  unsigned char send_buffer[10+259], recv_buffer[10+259];
  unsigned char *msg, *tpdu, *p;
  size_t msglen, tpdulen, n;
  unsigned char seqno;
  size_t dummy_nresp;
  int testmode;
  int cherry_mode = 0;
  int enable_varlen = 0;

  testmode = !resp && !nresp;

  if (!nresp)
    nresp = &dummy_nresp;
  *nresp = 0;

  if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1))
    ;
  else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2))
    ;
  else
    return CCID_DRIVER_ERR_NO_PINPAD;

  if (!pininfo->minlen)
    pininfo->minlen = 1;
  if (!pininfo->maxlen)
    pininfo->maxlen = 15;

  /* Note that the 25 is the maximum value the SPR532 allows.  */
  if (pininfo->minlen < 1 || pininfo->minlen > 25
      || pininfo->maxlen < 1 || pininfo->maxlen > 25
      || pininfo->minlen > pininfo->maxlen)
    return CCID_DRIVER_ERR_INV_VALUE;

  /* We have only tested a few readers so better don't risk anything
     and do not allow the use with other readers. */
  switch (handle->id_vendor)
    {
    case VENDOR_SCM:  /* Tested with SPR 532. */
    case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */
    case VENDOR_FSIJ: /* Tested with Gnuk (0.21). */
      pininfo->maxlen = 25;
      enable_varlen = 1;
      break;
    case VENDOR_REINER:/* Tested with cyberJack go */
    case VENDOR_VASCO: /* Tested with DIGIPASS 920 */
      enable_varlen = 1;
      break;
    case VENDOR_CHERRY:
      pininfo->maxlen = 25;
      enable_varlen = 1;
      /* The CHERRY XX44 keyboard echos an asterisk for each entered
         character on the keyboard channel.  We use a special variant
         of PC_to_RDR_Secure which directs these characters to the
         smart card's bulk-in channel.  We also need to append a zero
         Lc byte to the APDU.  It seems that it will be replaced with
         the actual length instead of being appended before the APDU
         is send to the card. */
      if (handle->id_product != CHERRY_ST2000)
        cherry_mode = 1;
      break;
    default:
      if ((handle->id_vendor == VENDOR_GEMPC &&
           handle->id_product == GEMPC_PINPAD)
          || (handle->id_vendor == VENDOR_VEGA &&
              handle->id_product == VEGA_ALPHA))
        {
          enable_varlen = 0;
          pininfo->minlen = 4;
          pininfo->maxlen = 8;
          break;
        }
     return CCID_DRIVER_ERR_NOT_SUPPORTED;
    }

  if (enable_varlen)
    pininfo->fixedlen = 0;

  if (testmode)
    return 0; /* Success */

  if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
    return CCID_DRIVER_ERR_NOT_SUPPORTED;

  msg = send_buffer;
  if (handle->id_vendor == VENDOR_SCM)
    {
      DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n");
      rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3,
                            NULL, 0, NULL);
      if (rc)
        return rc;
    }

  msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure;
  msg[5] = 0; /* slot */
  msg[6] = seqno = handle->seqno++;
  msg[7] = 0; /* bBWI */
  msg[8] = 0; /* RFU */
  msg[9] = 0; /* RFU */
  msg[10] = apdu_buf[1] == 0x20 ? 0 : 1;
               /* Perform PIN verification or PIN modification. */
  msg[11] = 0; /* Timeout in seconds. */
  msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
  if (handle->id_vendor == VENDOR_SCM)
    {
      /* For the SPR532 the next 2 bytes need to be zero.  We do this
         for all SCM products.  Kudos to Martin Paljak for this
         hint.  */
      msg[13] = msg[14] = 0;
    }
  else
    {
      msg[13] = pininfo->fixedlen; /* bmPINBlockString:
                                      0 bits of pin length to insert.
                                      PIN block size by fixedlen.  */
      msg[14] = 0x00; /* bmPINLengthFormat:
                         Units are bytes, position is 0. */
    }

  msglen = 15;
  if (apdu_buf[1] == 0x24)
    {
      msg[msglen++] = 0;    /* bInsertionOffsetOld */
      msg[msglen++] = pininfo->fixedlen;    /* bInsertionOffsetNew */
    }

  /* The following is a little endian word. */
  msg[msglen++] = pininfo->maxlen;   /* wPINMaxExtraDigit-Maximum.  */
  msg[msglen++] = pininfo->minlen;   /* wPINMaxExtraDigit-Minimum.  */

  if (apdu_buf[1] == 0x24)
    msg[msglen++] = apdu_buf[2] == 0 ? 0x03 : 0x01;
              /* bConfirmPIN
               *    0x00: new PIN once
               *    0x01: new PIN twice (confirmation)
               *    0x02: old PIN and new PIN once
               *    0x03: old PIN and new PIN twice (confirmation)
               */

  msg[msglen] = 0x02; /* bEntryValidationCondition:
                         Validation key pressed */
  if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
    msg[msglen] |= 0x01; /* Max size reached.  */
  msglen++;

  if (apdu_buf[1] == 0x20)
    msg[msglen++] = 0x01; /* bNumberMessage. */
  else
    msg[msglen++] = 0x03; /* bNumberMessage. */

  msg[msglen++] = 0x09; /* wLangId-Low:  English FIXME: use the first entry. */
  msg[msglen++] = 0x04; /* wLangId-High. */

  if (apdu_buf[1] == 0x20)
    msg[msglen++] = 0;    /* bMsgIndex. */
  else
    {
      msg[msglen++] = 0;    /* bMsgIndex1. */
      msg[msglen++] = 1;    /* bMsgIndex2. */
      msg[msglen++] = 2;    /* bMsgIndex3. */
    }

  /* Calculate Lc.  */
  n = pininfo->fixedlen;
  if (apdu_buf[1] == 0x24)
    n += pininfo->fixedlen;

  /* bTeoProlog follows: */
  msg[msglen++] = handle->nonnull_nad? ((1 << 4) | 0): 0;
  msg[msglen++] = ((handle->t1_ns & 1) << 6); /* I-block */
  if (n)
    msg[msglen++] = n + 5; /* apdulen should be filled for fixed length.  */
  else
    msg[msglen++] = 0; /* The apdulen will be filled in by the reader.  */
  /* APDU follows:  */
  msg[msglen++] = apdu_buf[0]; /* CLA */
  msg[msglen++] = apdu_buf[1]; /* INS */
  msg[msglen++] = apdu_buf[2]; /* P1 */
  msg[msglen++] = apdu_buf[3]; /* P2 */
  if (cherry_mode)
    msg[msglen++] = 0;
  else if (pininfo->fixedlen != 0)
    {
      msg[msglen++] = n;
      memset (&msg[msglen], 0xff, n);
      msglen += n;
    }
  /* An EDC is not required. */
  set_msg_len (msg, msglen - 10);

  rc = bulk_out (handle, msg, msglen, 0);
  if (rc)
    return rc;

  msg = recv_buffer;
  rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
                RDR_to_PC_DataBlock, seqno, 30000, 0);
  if (rc)
    return rc;

  tpdu = msg + 10;
  tpdulen = msglen - 10;

  if (handle->apdu_level)
    {
      if (resp)
        {
          if (tpdulen > maxresplen)
            {
              DEBUGOUT_2 ("provided buffer too short for received data "
                          "(%u/%u)\n",
                          (unsigned int)tpdulen, (unsigned int)maxresplen);
              return CCID_DRIVER_ERR_INV_VALUE;
            }

          memcpy (resp, tpdu, tpdulen);
          *nresp = tpdulen;
        }
      return 0;
    }

  if (tpdulen < 4)
    {
      usb_clear_halt (handle->idev, handle->ep_bulk_in);
      return CCID_DRIVER_ERR_ABORTED;
    }
  if (debug_level > 1)
    DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
                ((msg[11] & 0xc0) == 0x80)? 'R' :
                          (msg[11] & 0x80)? 'S' : 'I',
                ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)),
                ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
                (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));

  if (!(tpdu[1] & 0x80))
    { /* This is an I-block. */
      /* Last block sent was successful. */
      handle->t1_ns ^= 1;

      if (!!(tpdu[1] & 0x40) != handle->t1_nr)
        { /* Reponse does not match our sequence number. */
          DEBUGOUT ("I-block with wrong seqno received\n");
          return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }

      handle->t1_nr ^= 1;

      p = tpdu + 3; /* Skip the prologue field. */
      n = tpdulen - 3 - 1; /* Strip the epilogue field. */
      /* fixme: verify the checksum. */
      if (resp)
        {
          if (n > maxresplen)
            {
              DEBUGOUT_2 ("provided buffer too short for received data "
                          "(%u/%u)\n",
                          (unsigned int)n, (unsigned int)maxresplen);
              return CCID_DRIVER_ERR_INV_VALUE;
            }

          memcpy (resp, p, n);
          resp += n;
          *nresp += n;
          maxresplen -= n;
        }

      if (!(tpdu[1] & 0x20))
        return 0; /* No chaining requested - ready. */

      DEBUGOUT ("chaining requested but not supported for Secure operation\n");
      return CCID_DRIVER_ERR_CARD_IO_ERROR;
    }
  else if ((tpdu[1] & 0xc0) == 0x80)
    { /* This is a R-block. */
      if ( (tpdu[1] & 0x0f))
        { /* Error: repeat last block */
          DEBUGOUT ("No retries supported for Secure operation\n");
          return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }
      else if (!!(tpdu[1] & 0x10) == handle->t1_ns)
        { /* Reponse does not match our sequence number. */
          DEBUGOUT ("R-block with wrong seqno received on more bit\n");
          return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }
      else
        { /* Send next chunk. */
          DEBUGOUT ("chaining not supported on Secure operation\n");
          return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }
    }
  else
    { /* This is a S-block. */
      DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n",
                  (tpdu[1] & 0x20)? "response": "request",
                  (tpdu[1] & 0x1f));
      return CCID_DRIVER_ERR_CARD_IO_ERROR;
    }

  return 0;
}