ccid_transceive (ccid_driver_t handle,
                 const unsigned char *apdu_buf, size_t apdu_buflen,
                 unsigned char *resp, size_t maxresplen, size_t *nresp)
{
  int rc;
  /* The size of the buffer used to be 10+259.  For the via_escape
     hack we need one extra byte, thus 11+259.  */
  unsigned char send_buffer[11+259], recv_buffer[11+259];
  const unsigned char *apdu;
  size_t apdulen;
  unsigned char *msg, *tpdu, *p;
  size_t msglen, tpdulen, last_tpdulen, n;
  unsigned char seqno;
  unsigned int edc;
  int use_crc = 0;
  int hdrlen, pcboff;
  size_t dummy_nresp;
  int via_escape = 0;
  int next_chunk = 1;
  int sending = 1;
  int retries = 0;
  int resyncing = 0;
  int nad_byte;

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

  /* Smarter readers allow to send APDUs directly; divert here. */
  if (handle->apdu_level)
    {
      /* We employ a hack for Omnikey readers which are able to send
         TPDUs using an escape sequence.  There is no documentation
         but the Windows driver does it this way.  Tested using a
         CM6121.  This method works also for the Cherry XX44
         keyboards; however there are problems with the
         ccid_tranceive_secure which leads to a loss of sync on the
         CCID level.  If Cherry wants to make their keyboard work
         again, they should hand over some docs. */
      if ((handle->id_vendor == VENDOR_OMNIKEY
           || (!handle->idev && handle->id_product == TRANSPORT_CM4040))
          && handle->apdu_level < 2
          && is_exlen_apdu (apdu_buf, apdu_buflen))
        via_escape = 1;
      else
        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;
  hdrlen = via_escape? 11 : 10;

  /* NAD: DAD=1, SAD=0 */
  nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0;
  if (via_escape)
    nad_byte = 0;

  last_tpdulen = 0;  /* Avoid gcc warning (controlled by RESYNCING). */
  for (;;)
    {
      if (next_chunk)
        {
          next_chunk = 0;

          apdu = apdu_buf;
          apdulen = apdu_buflen;
          assert (apdulen);

          /* Construct an I-Block. */
          tpdu = msg + hdrlen;
          tpdu[0] = nad_byte;
          tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
          if (apdulen > handle->ifsc )
            {
              apdulen = handle->ifsc;
              apdu_buf += handle->ifsc;
              apdu_buflen -= handle->ifsc;
              tpdu[1] |= (1 << 5); /* Set more bit. */
            }
          tpdu[2] = apdulen;
          memcpy (tpdu+3, apdu, apdulen);
          tpdulen = 3 + apdulen;
          edc = compute_edc (tpdu, tpdulen, use_crc);
          if (use_crc)
            tpdu[tpdulen++] = (edc >> 8);
          tpdu[tpdulen++] = edc;
        }

      if (via_escape)
        {
          msg[0] = PC_to_RDR_Escape;
          msg[5] = 0; /* slot */
          msg[6] = seqno = handle->seqno++;
          msg[7] = 0; /* RFU */
          msg[8] = 0; /* RFU */
          msg[9] = 0; /* RFU */
          msg[10] = 0x1a; /* Omnikey command to send a TPDU.  */
          set_msg_len (msg, 1 + tpdulen);
        }
      else
        {
          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 */
          set_msg_len (msg, tpdulen);
        }
      msglen = hdrlen + tpdulen;
      if (!resyncing)
        last_tpdulen = tpdulen;
      pcboff = hdrlen+1;

      if (debug_level > 1)
        DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
                    ((msg[pcboff] & 0xc0) == 0x80)? 'R' :
                    (msg[pcboff] & 0x80)? 'S' : 'I',
                    ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
                     : !!(msg[pcboff] & 0x40)),
                    (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
                     " [more]":""));

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

      msg = recv_buffer;
      rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
                    via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock,
                    seqno, 5000, 0);
      if (rc)
        return rc;

      tpdu = msg + hdrlen;
      tpdulen = msglen - hdrlen;
      resyncing = 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[pcboff] & 0xc0) == 0x80)? 'R' :
                              (msg[pcboff] & 0x80)? 'S' : 'I',
                    ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
                     : !!(msg[pcboff] & 0x40)),
                    ((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0,
                    (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
                     " [more]":""));

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

          if (!!(tpdu[1] & 0x40) != handle->t1_nr)
            { /* Reponse does not match our sequence number. */
              msg = send_buffer;
              tpdu = msg + hdrlen;
              tpdu[0] = nad_byte;
              tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
              tpdu[2] = 0;
              tpdulen = 3;
              edc = compute_edc (tpdu, tpdulen, use_crc);
              if (use_crc)
                tpdu[tpdulen++] = (edc >> 8);
              tpdu[tpdulen++] = edc;

              continue;
            }

          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. */

          msg = send_buffer;
          tpdu = msg + hdrlen;
          tpdu[0] = nad_byte;
          tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
          tpdu[2] = 0;
          tpdulen = 3;
          edc = compute_edc (tpdu, tpdulen, use_crc);
          if (use_crc)
            tpdu[tpdulen++] = (edc >> 8);
          tpdu[tpdulen++] = edc;
        }
      else if ((tpdu[1] & 0xc0) == 0x80)
        { /* This is a R-block. */
          if ( (tpdu[1] & 0x0f))
            {
              retries++;
              if (via_escape && retries == 1 && (msg[pcboff] & 0x0f))
                {
                  /* Error probably due to switching to TPDU.  Send a
                     resync request.  We use the recv_buffer so that
                     we don't corrupt the send_buffer.  */
                  msg = recv_buffer;
                  tpdu = msg + hdrlen;
                  tpdu[0] = nad_byte;
                  tpdu[1] = 0xc0; /* S-block resync request. */
                  tpdu[2] = 0;
                  tpdulen = 3;
                  edc = compute_edc (tpdu, tpdulen, use_crc);
                  if (use_crc)
                    tpdu[tpdulen++] = (edc >> 8);
                  tpdu[tpdulen++] = edc;
                  resyncing = 1;
                  DEBUGOUT ("T=1: requesting resync\n");
                }
              else if (retries > 3)
                {
                  DEBUGOUT ("T=1: 3 failed retries\n");
                  return CCID_DRIVER_ERR_CARD_IO_ERROR;
                }
              else
                {
                  /* Error: repeat last block */
                  msg = send_buffer;
                  tpdulen = last_tpdulen;
                }
            }
          else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns)
            { /* Response 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 if (sending)
            { /* Send next chunk. */
              retries = 0;
              msg = send_buffer;
              next_chunk = 1;
              handle->t1_ns ^= 1;
            }
          else
            {
              DEBUGOUT ("unexpected ACK R-block received\n");
              return CCID_DRIVER_ERR_CARD_IO_ERROR;
            }
        }
      else
        { /* This is a S-block. */
          retries = 0;
          DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n",
                      (tpdu[1] & 0x20)? "response": "request",
                      (tpdu[1] & 0x1f));
          if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1)
            {
              /* Information field size request.  */
              unsigned char ifsc = tpdu[3];

              if (ifsc < 16 || ifsc > 254)
                return CCID_DRIVER_ERR_CARD_IO_ERROR;

              msg = send_buffer;
              tpdu = msg + hdrlen;
              tpdu[0] = nad_byte;
              tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */
              tpdu[2] = 1;
              tpdu[3] = ifsc;
              tpdulen = 4;
              edc = compute_edc (tpdu, tpdulen, use_crc);
              if (use_crc)
                tpdu[tpdulen++] = (edc >> 8);
              tpdu[tpdulen++] = edc;
              DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc);
            }
          else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
            {
              /* Wait time extension request. */
              unsigned char bwi = tpdu[3];
              msg = send_buffer;
              tpdu = msg + hdrlen;
              tpdu[0] = nad_byte;
              tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
              tpdu[2] = 1;
              tpdu[3] = bwi;
              tpdulen = 4;
              edc = compute_edc (tpdu, tpdulen, use_crc);
              if (use_crc)
                tpdu[tpdulen++] = (edc >> 8);
              tpdu[tpdulen++] = edc;
              DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi);
              print_progress (handle);
            }
          else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2])
            {
              DEBUGOUT ("T=1: resync ack from reader\n");
              /* Repeat previous block.  */
              msg = send_buffer;
              tpdulen = last_tpdulen;
            }
          else
            return CCID_DRIVER_ERR_CARD_IO_ERROR;
        }
    } /* end T=1 protocol loop. */

  return 0;
}