summaryrefslogtreecommitdiffstats
path: root/scd/ccid-driver.c
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2003-09-05 09:40:41 +0200
committerWerner Koch <wk@gnupg.org>2003-09-05 09:40:41 +0200
commit25430119e832a89cd141fb7c1a0edbfab237c243 (patch)
tree182f0eb5edf6f7fd7d4e3e5922c44326a44c0464 /scd/ccid-driver.c
parent* keygen.c (do_add_key_flags, parse_parameter_usage) (diff)
downloadgnupg2-25430119e832a89cd141fb7c1a0edbfab237c243.tar.xz
gnupg2-25430119e832a89cd141fb7c1a0edbfab237c243.zip
* ccid-driver.c: More work, data can now actually be retrieved.
* ccid-driver.c, ccid-driver.h: Alternativley allow use under BSD conditions.
Diffstat (limited to 'scd/ccid-driver.c')
-rw-r--r--scd/ccid-driver.c444
1 files changed, 280 insertions, 164 deletions
diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
index 03c9bcb95..b18b055b0 100644
--- a/scd/ccid-driver.c
+++ b/scd/ccid-driver.c
@@ -1,5 +1,6 @@
/* ccid-driver.c - USB ChipCardInterfaceDevices driver
* Copyright (C) 2003 Free Software Foundation, Inc.
+ * Written by Werner Koch.
*
* This file is part of GnuPG.
*
@@ -16,6 +17,40 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * ALTERNATIVELY, this file may be distributed under the terms of the
+ * following license, in which case the provisions of this license are
+ * required INSTEAD OF the GNU General Public License. If you wish to
+ * allow use of your version of this file only under the terms of the
+ * GNU General Public License, and not to allow others to use your
+ * version of this file under the terms of the following license,
+ * indicate your decision by deleting this paragraph and the license
+ * below.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@@ -38,6 +73,7 @@
#endif
#if defined(HAVE_LIBUSB) || defined(TEST)
+
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
@@ -46,9 +82,43 @@
#include <usb.h>
-
#include "ccid-driver.h"
+#define DRVNAME "ccid-driver: "
+
+
+#ifdef GNUPG_DEFAULT_SCDAEMON /* This source is used within the
+ gnupg>=1.9 source tree. */
+# include "scdaemon.h"
+
+# define DEBUGOUT(t) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t); } while (0)
+# define DEBUGOUT_1(t,a) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t,(a)); } while (0)
+# define DEBUGOUT_2(t,a,b) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t,(a),(b)); } while (0)
+# define DEBUGOUT_3(t,a,b,c) do { if (DBG_CARD_IO) \
+ log_debug (DRVNAME t,(a),(b),(c));} while (0)
+# define DEBUGOUT_CONT_1(t,a) do { if (DBG_CARD_IO) \
+ log_printf (t,(a)); } while (0)
+# define DEBUGOUT_CONT_3(t,a,b,c) do { if (DBG_CARD_IO) \
+ log_printf (t,(a),(b),(c)); } while (0)
+# define DEBUGOUT_LF() do { if (DBG_CARD_IO) \
+ log_printf ("\n"); } while (0)
+
+#else /* Other usage of this source - don't use gnupg specifics. */
+
+# define DEBUGOUT(t) fprintf (stderr, DRVNAME t)
+# define DEBUGOUT_1(t,a) fprintf (stderr, DRVNAME t, (a))
+# define DEBUGOUT_2(t,a,b) fprintf (stderr, DRVNAME t, (a), (b))
+# define DEBUGOUT_3(t,a,b,c) fprintf (stderr, DRVNAME t, (a), (b), (c))
+# define DEBUGOUT_CONT_1(t,a) fprintf (stderr, t, (a))
+# define DEBUGOUT_CONT_3(t,a,b,c) fprintf (stderr, t, (a), (b), (c))
+# define DEBUGOUT_LF() putc ('\n', stderr)
+
+#endif /* This source not used by scdaemon. */
+
+
enum {
RDR_to_PC_NotifySlotChange= 0x50,
RDR_to_PC_HardwareError = 0x51,
@@ -81,7 +151,8 @@ enum {
struct ccid_driver_s {
usb_dev_handle *idev;
int seqno;
- unsigned char t1_seqno;
+ unsigned char t1_ns;
+ unsigned char t1_nr;
};
@@ -109,35 +180,34 @@ ccid_open_reader (ccid_driver_t *handle, int readerno)
rc = usb_create_match (&match, -1, -1, 11, -1, -1);
if (rc)
{
- fprintf (stderr, "ccid-driver: usb_create_match failed: %d\n", rc);
+ DEBUGOUT_1 ("usb_create_match failed: %d\n", rc);
return -1;
}
while (usb_find_device(match, dev, &dev) >= 0)
{
- fprintf(stderr, "ccid-driver: %-40s %04X/%04X\n", dev->filename,
- dev->descriptor->idVendor, dev->descriptor->idProduct);
+ DEBUGOUT_3 ("%-40s %04X/%04X\n", dev->filename,
+ dev->descriptor->idVendor, dev->descriptor->idProduct);
if (!readerno)
{
rc = usb_open (dev, &idev);
if (rc)
{
- fprintf (stderr, "ccid-driver: usb_open failed: %d\n", rc);
+ DEBUGOUT_1 ("usb_open failed: %d\n", rc);
goto leave;
}
rc = usb_claim_interface (idev, 0);
if (rc)
{
- fprintf (stderr, "ccid-driver: usb_claim_interface failed: %d\n",
- rc);
+ DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
goto leave;
}
*handle = calloc (1, sizeof **handle);
if (!*handle)
{
- fprintf (stderr, "ccid-driver: out of memory\n");
+ DEBUGOUT ("out of memory\n");
rc = -1;
goto leave;
}
@@ -194,22 +264,24 @@ bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen)
return 0;
if (rc == -1)
- fprintf (stderr, "ccid-driver: usb_bulk_write error: %s\n",
- strerror (errno));
+ DEBUGOUT_1 ("usb_bulk_write error: %s\n", strerror (errno));
else
- fprintf (stderr, "ccid-driver: usb_bulk_write failed: %d\n", rc);
+ DEBUGOUT_1 ("usb_bulk_write failed: %d\n", rc);
return -1;
}
/* Read a maximum of LENGTH bytes from the bulk in endpoint into
- BUFFER and return the actual read number if bytes in NREAD.
- Returns 0 on success. */
+ BUFFER and return the actual read number if bytes in NREAD. SEQNO
+ is the sequence number used to send the request and EXPECTED_TYPE
+ the type of message we expect. Does checks on the ccid
+ header. Returns 0 on success. */
static int
bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
- size_t *nread)
+ size_t *nread, int expected_type, int seqno)
{
- int rc;
+ int i, rc;
+ size_t msglen;
rc = usb_bulk_read (handle->idev,
0x82,
@@ -217,12 +289,40 @@ bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
1000 /* ms timeout */ );
if (rc < 0)
{
- fprintf (stderr, "ccid-driver: usb_bulk_read error: %s\n",
- strerror (errno));
+ DEBUGOUT_1 ("usb_bulk_read error: %s\n", strerror (errno));
+ return -1;
+ }
+
+ *nread = msglen = rc;
+
+ if (msglen < 10)
+ {
+ DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen);
+ return -1;
+ }
+ if (buffer[0] != expected_type)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]);
+ return -1;
+ }
+ if (buffer[5] != 0)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]);
+ return -1;
+ }
+ if (buffer[6] != seqno)
+ {
+ DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n",
+ seqno, buffer[6]);
return -1;
}
- *nread = rc;
+ DEBUGOUT_3 ("status: %02X error: %02X clock-status: %02X\n"
+ " data:", buffer[7], buffer[8], buffer[9] );
+ for (i=10; i < msglen; i++)
+ DEBUGOUT_CONT_1 (" %02X", buffer[i]);
+ DEBUGOUT_LF ();
+
return 0;
}
@@ -245,8 +345,7 @@ ccid_poll (ccid_driver_t handle)
if (rc < 0)
{
- fprintf (stderr, "ccid-driver: usb_intr_read error: %s\n",
- strerror (errno));
+ DEBUGOUT_1 ("usb_intr_read error: %s\n", strerror (errno));
return -1;
}
@@ -255,29 +354,28 @@ ccid_poll (ccid_driver_t handle)
if (msglen < 1)
{
- fprintf (stderr, "ccid-driver: intr-in msg too short\n");
+ DEBUGOUT ("intr-in msg too short\n");
return -1;
}
if (msg[0] == RDR_to_PC_NotifySlotChange)
{
- fprintf (stderr, "ccid-driver: notify slot change:");
+ DEBUGOUT ("notify slot change:");
for (i=1; i < msglen; i++)
for (j=0; j < 4; j++)
- fprintf (stderr, " %d:%c%c",
- (i-1)*4+j,
- (msg[i] & (1<<(j*2)))? 'p':'-',
- (msg[i] & (2<<(j*2)))? '*':' ');
- putc ('\n', stderr);
+ DEBUGOUT_CONT_3 (" %d:%c%c",
+ (i-1)*4+j,
+ (msg[i] & (1<<(j*2)))? 'p':'-',
+ (msg[i] & (2<<(j*2)))? '*':' ');
+ DEBUGOUT_LF ();
}
else if (msg[0] == RDR_to_PC_HardwareError)
{
- fprintf (stderr, "ccid-driver: hardware error occured\n");
+ DEBUGOUT ("hardware error occured\n");
}
else
{
- fprintf (stderr, "ccid-driver: unknown intr-in msg of type %02X\n",
- msg[0]);
+ DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]);
}
return 0;
@@ -304,37 +402,9 @@ ccid_slot_status (ccid_driver_t handle)
rc = bulk_out (handle, msg, 10);
if (rc)
return rc;
- rc = bulk_in (handle, msg, sizeof msg, &msglen);
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus, seqno);
if (rc)
return rc;
- if (msglen < 10)
- {
- fprintf (stderr, "ccid-driver: bulk-in msg too short (%u)\n",
- (unsigned int)msglen);
- return -1;
- }
- if (msg[0] != RDR_to_PC_SlotStatus)
- {
- fprintf (stderr, "ccid-driver: unexpected bulk-in msg type (%02x)\n",
- msg[0]);
- return -1;
- }
- if (msg[5] != 0)
- {
- fprintf (stderr, "ccid-driver: unexpected bulk-in slot (%d)\n",
- msg[5]);
- return -1;
- }
- if (msg[6] != seqno)
- {
- fprintf (stderr, "ccid-driver: bulk-in seqno does not match (%d/%d)\n",
- seqno, msg[6]);
- return -1;
- }
-
- fprintf (stderr,
- "ccid-driver: status: %02X error: %02X clock-status: %02X\n",
- msg[7], msg[8], msg[9] );
return 0;
}
@@ -348,7 +418,6 @@ ccid_get_atr (ccid_driver_t handle,
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
- int i;
msg[0] = PC_to_RDR_IccPowerOn;
msg[5] = 0; /* slot */
@@ -362,40 +431,9 @@ ccid_get_atr (ccid_driver_t handle,
rc = bulk_out (handle, msg, msglen);
if (rc)
return rc;
- rc = bulk_in (handle, msg, sizeof msg, &msglen);
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock, seqno);
if (rc)
return rc;
- if (msglen < 10)
- {
- fprintf (stderr, "ccid-driver: bulk-in msg too short (%u)\n",
- (unsigned int)msglen);
- return -1;
- }
- if (msg[0] != RDR_to_PC_DataBlock)
- {
- fprintf (stderr, "ccid-driver: unexpected bulk-in msg type (%02x)\n",
- msg[0]);
- return -1;
- }
- if (msg[5] != 0)
- {
- fprintf (stderr, "ccid-driver: unexpected bulk-in slot (%d)\n",
- msg[5]);
- return -1;
- }
- if (msg[6] != seqno)
- {
- fprintf (stderr, "ccid-driver: bulk-in seqno does not match (%d/%d)\n",
- seqno, msg[6]);
- return -1;
- }
-
- fprintf (stderr,
- "ccid-driver: status: %02X error: %02X clock-status: %02X\n"
- " data:", msg[7], msg[8], msg[9] );
- for (i=10; i < msglen; i++)
- fprintf (stderr, " %02X", msg[i]);
- putc ('\n', stderr);
if (atr)
{
@@ -434,7 +472,7 @@ ccid_get_atr (ccid_driver_t handle,
If node adresses are not used, SAD and DAD should be set to 0 on
the first block sent to the card. If they are used they should
have different values (0 for one is okay); that first block sets up
- the addresses of the node.
+ the addresses of the nodes.
PCB:
Information Block (I-Block):
@@ -470,20 +508,29 @@ ccid_transceive (ccid_driver_t handle,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
- unsigned char msg[10+258], *tpdu, *p;
- size_t msglen;
+ unsigned char send_buffer[10+258], recv_buffer[10+258];
+ unsigned char *msg, *tpdu, *p;
+ size_t msglen, tpdulen, n;
unsigned char seqno;
int i;
unsigned char crc;
+ size_t dummy_nresp;
+ int sending = 1;
+
+ if (!nresp)
+ nresp = &dummy_nresp;
+ *nresp = 0;
/* Construct an I-Block. */
if (apdulen > 254)
return -1; /* Invalid length. */
+ msg = send_buffer;
+
tpdu = msg+10;
tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
- tpdu[1] = ((handle->t1_seqno & 1) << 6); /* I-block */
+ tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
tpdu[2] = apdulen;
memcpy (tpdu+3, apdu, apdulen);
crc = 0;
@@ -491,79 +538,148 @@ ccid_transceive (ccid_driver_t handle,
crc ^= *p++;
tpdu[3+apdulen] = crc;
- handle->t1_seqno ^= 1;
+ tpdulen = apdulen + 4;
- 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, apdulen+4);
- msglen = 10 + apdulen + 4;
+ for (;;)
+ {
+ 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 = 10 + tpdulen;
+
+ DEBUGOUT ("sending");
+ for (i=0; i < msglen; i++)
+ DEBUGOUT_CONT_1 (" %02X", msg[i]);
+ DEBUGOUT_LF ();
+
+ fprintf (stderr, "T1: put %c-block seq=%d\n",
+ ((msg[11] & 0xc0) == 0x80)? 'R' :
+ (msg[11] & 0x80)? 'S' : 'I',
+ ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)));
+
+ 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;
+
+ tpdu = msg + 10;
+ tpdulen = msglen - 10;
+
+ if (tpdulen < 4)
+ {
+ DEBUGOUT ("cannot yet handle short block!!\n");
+ return -1;
+ }
- fprintf (stderr, "ccid-driver: sending");
- for (i=0; i < msglen; i++)
- fprintf (stderr, " %02X", msg[i]);
- putc ('\n', stderr);
+ fprintf (stderr, "T1: got %c-block seq=%d err=%d\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
+ );
+
+ if (!(tpdu[1] & 0x80))
+ { /* This is an I-block. */
+
+ if (sending)
+ { /* last block sent was successful. */
+ handle->t1_ns ^= 1;
+ sending = 0;
+ }
- rc = bulk_out (handle, msg, msglen);
- if (rc)
- return rc;
- rc = bulk_in (handle, msg, sizeof msg, &msglen);
- if (rc)
- return rc;
- if (msglen < 10)
- {
- fprintf (stderr, "ccid-driver: bulk-in msg too short (%u)\n",
- (unsigned int)msglen);
- return -1;
- }
- if (msg[0] != RDR_to_PC_DataBlock)
- {
- fprintf (stderr, "ccid-driver: unexpected bulk-in msg type (%02x)\n",
- msg[0]);
- return -1;
- }
- if (msg[5] != 0)
- {
- fprintf (stderr, "ccid-driver: unexpected bulk-in slot (%d)\n",
- msg[5]);
- return -1;
- }
- if (msg[6] != seqno)
- {
- fprintf (stderr, "ccid-driver: bulk-in seqno does not match (%d/%d)\n",
- seqno, msg[6]);
- return -1;
- }
+ if (!!(tpdu[1] & 0x40) != handle->t1_nr)
+ { /* Reponse does not match our sequence number. */
+ msg = send_buffer;
+ tpdu = msg+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ for (crc=0,i=0,p=tpdu; i < tpdulen; i++)
+ crc ^= *p++;
+ tpdu[tpdulen++] = crc;
+
+ continue;
+ }
- fprintf (stderr,
- "ccid-driver: status: %02X error: %02X clock-status: %02X\n"
- " data:", msg[7], msg[8], msg[9] );
- for (i=10; i < msglen; i++)
- fprintf (stderr, " %02X", msg[i]);
- putc ('\n', stderr);
+ handle->t1_nr ^= 1;
- if (resp)
- {
- size_t n = msglen - 10;
-
- if (n < 4)
- n = 0; /* fixme: this is an empty I-block or some other block
- - we ignore it for now until we have implemented the
- T=1 machinery. */
- else
- {
- p = msg + 10 + 3; /* Skip ccid header and prologue field. */
- n -= 3;
- n--; /* Strip the epilogue field. */
- if (n > maxresplen)
- n = maxresplen; /* fixme: return an error instead of truncating. */
- memcpy (resp, p, n);
+ 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 ("provided buffer too short for received data\n");
+ return -1;
+ }
+
+ 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+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ for (crc=0,i=0,p=tpdu; i < tpdulen; i++)
+ crc ^= *p++;
+ tpdu[tpdulen++] = crc;
+
}
- *nresp = n;
- }
+ else if ((tpdu[1] & 0xc0) == 0x80)
+ { /* This is a R-block. */
+ if ( (tpdu[1] & 0x0f))
+ { /* Error: repeat last block */
+ msg = send_buffer;
+ }
+ else
+ {
+ DEBUGOUT ("unxpectec ACK R-block received\n");
+ return -1;
+ }
+ }
+ else
+ { /* This is a S-block. */
+ DEBUGOUT_2 ("T1 S-block %s received cmd=%d\n",
+ (tpdu[1] & 0x20)? "response": "request",
+ (tpdu[1] & 0x1f));
+ if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
+ { /* Wait time extension request. */
+ unsigned char bwi = tpdu[3];
+ msg = send_buffer;
+ tpdu = msg+10;
+ tpdu[0] = ((1 << 4) | 0); /* NAD: DAD=1, SAD=0 */
+ tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
+ tpdu[2] = 1;
+ tpdu[3] = bwi;
+ tpdulen = 4;
+ for (crc=0,i=0,p=tpdu; i < tpdulen; i++)
+ crc ^= *p++;
+ tpdu[tpdulen++] = crc;
+ DEBUGOUT_1 ("T1 waittime extension of bwi=%d\n", bwi);
+ }
+ else
+ return -1;
+ }
+ } /* end T=1 protocol loop. */
return 0;
}