2009-04-17 18:40:32 +00:00
|
|
|
/* ccidmon.c - CCID monitor for use with the Linux usbmon facility.
|
2021-06-22 20:41:47 +02:00
|
|
|
* Copyright (C) 2009, 2016, 2019 Werner Koch
|
|
|
|
* Copyright (C) 2021 g10 Code GmbH
|
|
|
|
* Copyright (C) 2009 Free Software Foundation, Inc.
|
2009-04-17 18:40:32 +00:00
|
|
|
*
|
|
|
|
* This file is part of GnuPG.
|
|
|
|
*
|
|
|
|
* GnuPG is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* GnuPG is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2016-11-05 12:02:19 +01:00
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2021-06-22 20:41:47 +02:00
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
2009-04-17 18:40:32 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/* This utility takes the output of usbmon, filters out the bulk data
|
|
|
|
and prints the CCID messages in a human friendly way.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <signal.h>
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef PACKAGE_VERSION
|
|
|
|
# define PACKAGE_VERSION "[build on " __DATE__ " " __TIME__ "]"
|
|
|
|
#endif
|
|
|
|
#ifndef PACKAGE_BUGREPORT
|
|
|
|
# define PACKAGE_BUGREPORT "devnull@example.org"
|
|
|
|
#endif
|
|
|
|
#define PGM "ccidmon"
|
2016-09-13 08:26:56 +02:00
|
|
|
#ifndef GNUPG_NAME
|
|
|
|
# define GNUPG_NAME "GnuPG"
|
|
|
|
#endif
|
2009-04-17 18:40:32 +00:00
|
|
|
|
|
|
|
/* Option flags. */
|
|
|
|
static int verbose;
|
|
|
|
static int debug;
|
|
|
|
static int skip_escape;
|
|
|
|
static int usb_bus, usb_dev;
|
2009-06-29 15:56:07 +00:00
|
|
|
static int sniffusb;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
|
|
|
|
/* Error counter. */
|
|
|
|
static int any_error;
|
|
|
|
|
|
|
|
/* Data storage. */
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
int is_bi;
|
2019-04-30 08:26:59 +02:00
|
|
|
char timestamp[20];
|
2009-04-17 18:40:32 +00:00
|
|
|
char address[50];
|
|
|
|
int count;
|
2019-04-30 08:26:59 +02:00
|
|
|
char data[16000];
|
2009-04-17 18:40:32 +00:00
|
|
|
} databuffer;
|
|
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
RDR_to_PC_NotifySlotChange= 0x50,
|
|
|
|
RDR_to_PC_HardwareError = 0x51,
|
|
|
|
|
|
|
|
PC_to_RDR_SetParameters = 0x61,
|
|
|
|
PC_to_RDR_IccPowerOn = 0x62,
|
|
|
|
PC_to_RDR_IccPowerOff = 0x63,
|
|
|
|
PC_to_RDR_GetSlotStatus = 0x65,
|
|
|
|
PC_to_RDR_Secure = 0x69,
|
|
|
|
PC_to_RDR_T0APDU = 0x6a,
|
|
|
|
PC_to_RDR_Escape = 0x6b,
|
|
|
|
PC_to_RDR_GetParameters = 0x6c,
|
|
|
|
PC_to_RDR_ResetParameters = 0x6d,
|
|
|
|
PC_to_RDR_IccClock = 0x6e,
|
|
|
|
PC_to_RDR_XfrBlock = 0x6f,
|
|
|
|
PC_to_RDR_Mechanical = 0x71,
|
|
|
|
PC_to_RDR_Abort = 0x72,
|
|
|
|
PC_to_RDR_SetDataRate = 0x73,
|
|
|
|
|
|
|
|
RDR_to_PC_DataBlock = 0x80,
|
|
|
|
RDR_to_PC_SlotStatus = 0x81,
|
|
|
|
RDR_to_PC_Parameters = 0x82,
|
|
|
|
RDR_to_PC_Escape = 0x83,
|
|
|
|
RDR_to_PC_DataRate = 0x84
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2016-09-13 08:26:56 +02:00
|
|
|
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
|
2009-04-17 18:40:32 +00:00
|
|
|
#define hexdigitp(a) (digitp (a) \
|
2016-09-13 08:26:56 +02:00
|
|
|
|| (*(a) >= 'A' && *(a) <= 'F') \
|
|
|
|
|| (*(a) >= 'a' && *(a) <= 'f'))
|
2009-04-17 18:40:32 +00:00
|
|
|
#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
|
|
|
|
#define xtoi_1(p) ((p) <= '9'? ((p)- '0'): \
|
|
|
|
(p) <= 'F'? ((p)-'A'+10):((p)-'a'+10))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Print diagnostic message and exit with failure. */
|
|
|
|
static void
|
|
|
|
die (const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list arg_ptr;
|
|
|
|
|
|
|
|
fflush (stdout);
|
|
|
|
fprintf (stderr, "%s: ", PGM);
|
|
|
|
|
|
|
|
va_start (arg_ptr, format);
|
|
|
|
vfprintf (stderr, format, arg_ptr);
|
|
|
|
va_end (arg_ptr);
|
|
|
|
putc ('\n', stderr);
|
|
|
|
|
|
|
|
exit (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Print diagnostic message. */
|
|
|
|
static void
|
|
|
|
err (const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list arg_ptr;
|
|
|
|
|
|
|
|
any_error = 1;
|
|
|
|
|
|
|
|
fflush (stdout);
|
|
|
|
fprintf (stderr, "%s: ", PGM);
|
|
|
|
|
|
|
|
va_start (arg_ptr, format);
|
|
|
|
vfprintf (stderr, format, arg_ptr);
|
|
|
|
va_end (arg_ptr);
|
|
|
|
putc ('\n', stderr);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Convert a little endian stored 4 byte value into an unsigned
|
|
|
|
integer. */
|
2011-02-04 12:57:53 +01:00
|
|
|
static unsigned int
|
2009-04-17 18:40:32 +00:00
|
|
|
convert_le_u32 (const unsigned char *buf)
|
|
|
|
{
|
2015-02-11 10:27:57 +01:00
|
|
|
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Convert a little endian stored 2 byte value into an unsigned
|
|
|
|
integer. */
|
2011-02-04 12:57:53 +01:00
|
|
|
static unsigned int
|
2009-04-17 18:40:32 +00:00
|
|
|
convert_le_u16 (const unsigned char *buf)
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
return buf[0] | (buf[1] << 8);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_pr_data (const unsigned char *data, size_t datalen, size_t off)
|
|
|
|
{
|
|
|
|
int needlf = 0;
|
|
|
|
int first = 1;
|
|
|
|
|
|
|
|
for (; off < datalen; off++)
|
|
|
|
{
|
|
|
|
if (!(off % 16) || first)
|
|
|
|
{
|
|
|
|
if (needlf)
|
|
|
|
putchar ('\n');
|
2016-09-13 08:26:56 +02:00
|
|
|
printf (" [%04lu] ", (unsigned long)off);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
printf (" %02X", data[off]);
|
|
|
|
needlf = 1;
|
|
|
|
first = 0;
|
|
|
|
}
|
|
|
|
if (needlf)
|
|
|
|
putchar ('\n');
|
|
|
|
}
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2021-06-22 20:41:47 +02:00
|
|
|
static void
|
|
|
|
print_as_ascii (const unsigned char *buf, unsigned int buflen,unsigned int fill)
|
|
|
|
{
|
|
|
|
unsigned int n;
|
|
|
|
|
|
|
|
if (!buflen)
|
|
|
|
return;
|
|
|
|
if (buflen > 16)
|
|
|
|
buflen = 16;
|
|
|
|
|
|
|
|
for (n = buflen; n < fill; n++)
|
|
|
|
fputs (" ", stdout);
|
|
|
|
fputs (" |", stdout);
|
|
|
|
for (n = 0; n < buflen; n++, buf++)
|
|
|
|
if (*buf >= 32 && *buf < 127 && *buf != '|')
|
|
|
|
putchar (*buf);
|
|
|
|
else
|
|
|
|
putchar ('.');
|
|
|
|
putchar ('|');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2021-06-25 09:51:24 +02:00
|
|
|
print_t1_block (const unsigned char *msg, size_t msglen, int to_rdr)
|
2021-06-22 20:41:47 +02:00
|
|
|
{
|
|
|
|
unsigned int count, len;
|
|
|
|
unsigned char buf[16];
|
|
|
|
|
|
|
|
if (msglen < 4)
|
|
|
|
{
|
|
|
|
printf (" T=1 ..: invalid block\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
printf (" T=1 ..: NAD=%02x", msg[0]);
|
|
|
|
if (!(msg[1] & 0x80))
|
|
|
|
{
|
|
|
|
printf (" I-block seq=%d%s\n",
|
|
|
|
!!(msg[1] & 0x40), (msg[1] & 0x20)? " chaining":"");
|
|
|
|
len = msg[2];
|
|
|
|
msg += 3;
|
|
|
|
msglen -= 3;
|
|
|
|
|
2021-06-25 09:51:24 +02:00
|
|
|
printf (" APDU-%c:", to_rdr? 's':'r');
|
2021-06-22 20:41:47 +02:00
|
|
|
count = 0;
|
|
|
|
while (msglen > 1 && len)
|
|
|
|
{
|
|
|
|
if (count == 16)
|
|
|
|
{
|
|
|
|
print_as_ascii (buf, count, count);
|
|
|
|
printf ("\n ");
|
|
|
|
count = 0;
|
|
|
|
}
|
|
|
|
buf[count] = msg[0];
|
|
|
|
printf (" %02X", msg[0]);
|
|
|
|
msg++;
|
|
|
|
msglen--;
|
|
|
|
len--;
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
print_as_ascii (buf, count, 16);
|
|
|
|
putchar ('\n');
|
|
|
|
}
|
|
|
|
else if (!(msg[1] & 0x40))
|
|
|
|
printf (" R-block seq=%d%s\n",
|
|
|
|
!!(msg[1] & 0x10),
|
|
|
|
(msg[1] & 0x0f) == 0 ? "":
|
|
|
|
(msg[1] & 0x0f) == 1 ? "EDC error":
|
|
|
|
(msg[1] & 0x0f) == 2 ? "other error": "?");
|
|
|
|
else
|
|
|
|
printf (" S-block %s %s\n",
|
|
|
|
(msg[1] & 0x1f) == 0 ? "resync":
|
|
|
|
(msg[1] & 0x1f) == 1 ? "info_field_size":
|
|
|
|
(msg[1] & 0x1f) == 2 ? "abort":
|
|
|
|
(msg[1] & 0x1f) == 2 ? "BWT_extension":
|
|
|
|
(msg[1] & 0x1f) == 2 ? "VPP_error": "?",
|
|
|
|
(msg[1] & 0x20)? "response":"request");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
static void
|
|
|
|
print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
printf ("%s:\n", name);
|
|
|
|
if (msglen < 7)
|
|
|
|
return;
|
|
|
|
printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
|
|
|
|
printf (" bSlot .............: %u\n", msg[5]);
|
|
|
|
printf (" bSeq ..............: %u\n", msg[6]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
|
|
|
|
msg[7] == 0? "auto":
|
|
|
|
msg[7] == 1? "5.0 V":
|
|
|
|
msg[7] == 2? "3.0 V":
|
|
|
|
msg[7] == 3? "1.8 V":"");
|
|
|
|
print_pr_data (msg, msglen, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
unsigned int val;
|
|
|
|
|
|
|
|
print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bBWI ..............: 0x%02x\n", msg[7]);
|
|
|
|
val = convert_le_u16 (msg+8);
|
|
|
|
printf (" wLevelParameter ...: 0x%04x%s\n", val,
|
|
|
|
val == 1? " (continued)":
|
|
|
|
val == 2? " (continues+ends)":
|
|
|
|
val == 3? " (continues+continued)":
|
|
|
|
val == 16? " (DataBlock-expected)":"");
|
|
|
|
print_pr_data (msg, msglen, 10);
|
2021-06-22 20:41:47 +02:00
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
msg += 10;
|
|
|
|
msglen -= 10;
|
2021-06-25 09:51:24 +02:00
|
|
|
print_t1_block (msg, msglen, 1);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_getparameters (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_setparameters (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bProtocolNum ......: 0x%02x\n", msg[7]);
|
|
|
|
print_pr_data (msg, msglen, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_escape (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
if (skip_escape)
|
|
|
|
return;
|
|
|
|
print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_iccclock (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bClockCommand .....: 0x%02x\n", msg[7]);
|
|
|
|
print_pr_data (msg, msglen, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bmChanges .........: 0x%02x\n", msg[7]);
|
|
|
|
printf (" bClassGetResponse .: 0x%02x\n", msg[8]);
|
|
|
|
printf (" bClassEnvelope ....: 0x%02x\n", msg[9]);
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_secure (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
unsigned int val;
|
|
|
|
|
|
|
|
print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bBMI ..............: 0x%02x\n", msg[7]);
|
|
|
|
val = convert_le_u16 (msg+8);
|
|
|
|
printf (" wLevelParameter ...: 0x%04x%s\n", val,
|
|
|
|
val == 1? " (continued)":
|
|
|
|
val == 2? " (continues+ends)":
|
|
|
|
val == 3? " (continues+continued)":
|
|
|
|
val == 16? " (DataBlock-expected)":"");
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_mechanical (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bFunction .........: 0x%02x\n", msg[7]);
|
|
|
|
print_pr_data (msg, msglen, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_abort (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
print_pr_data (msg, msglen, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r_unknown (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
2009-06-29 15:56:07 +00:00
|
|
|
char buf[100];
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
snprintf (buf, sizeof buf, "Unknown PC_to_RDR command 0x%02X",
|
2009-06-29 15:56:07 +00:00
|
|
|
msglen? msg[0]:0);
|
|
|
|
print_p2r_header (buf, msg, msglen);
|
2009-04-17 18:40:32 +00:00
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
print_pr_data (msg, msglen, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_p2r (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
switch (msglen? msg[0]:0)
|
|
|
|
{
|
|
|
|
case PC_to_RDR_IccPowerOn:
|
|
|
|
print_p2r_iccpoweron (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_IccPowerOff:
|
|
|
|
print_p2r_iccpoweroff (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_GetSlotStatus:
|
|
|
|
print_p2r_getslotstatus (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_XfrBlock:
|
|
|
|
print_p2r_xfrblock (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_GetParameters:
|
|
|
|
print_p2r_getparameters (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_ResetParameters:
|
|
|
|
print_p2r_resetparameters (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_SetParameters:
|
|
|
|
print_p2r_setparameters (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_Escape:
|
|
|
|
print_p2r_escape (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_IccClock:
|
|
|
|
print_p2r_iccclock (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_T0APDU:
|
|
|
|
print_p2r_to0apdu (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_Secure:
|
|
|
|
print_p2r_secure (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_Mechanical:
|
|
|
|
print_p2r_mechanical (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_Abort:
|
|
|
|
print_p2r_abort (msg, msglen);
|
|
|
|
break;
|
|
|
|
case PC_to_RDR_SetDataRate:
|
|
|
|
print_p2r_setdatarate (msg, msglen);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
print_p2r_unknown (msg, msglen);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
printf ("%s:\n", name);
|
|
|
|
if (msglen < 9)
|
|
|
|
return;
|
|
|
|
printf (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
|
|
|
|
printf (" bSlot .............: %u\n", msg[5]);
|
|
|
|
printf (" bSeq ..............: %u\n", msg[6]);
|
|
|
|
printf (" bStatus ...........: %u\n", msg[7]);
|
|
|
|
if (msg[8])
|
|
|
|
printf (" bError ............: %u\n", msg[8]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_datablock (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
if (msg[9])
|
|
|
|
printf (" bChainParameter ...: 0x%02x%s\n", msg[9],
|
|
|
|
msg[9] == 1? " (continued)":
|
|
|
|
msg[9] == 2? " (continues+ends)":
|
|
|
|
msg[9] == 3? " (continues+continued)":
|
|
|
|
msg[9] == 16? " (XferBlock-expected)":"");
|
|
|
|
print_pr_data (msg, msglen, 10);
|
2021-06-22 20:41:47 +02:00
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
msg += 10;
|
|
|
|
msglen -= 10;
|
2021-06-25 09:51:24 +02:00
|
|
|
print_t1_block (msg, msglen, 0);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bClockStatus ......: 0x%02x%s\n", msg[9],
|
|
|
|
msg[9] == 0? " (running)":
|
|
|
|
msg[9] == 1? " (stopped-L)":
|
|
|
|
msg[9] == 2? " (stopped-H)":
|
|
|
|
msg[9] == 3? " (stopped)":"");
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_parameters (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
|
|
|
|
printf (" protocol ..........: T=%d\n", msg[9]);
|
|
|
|
if (msglen == 17 && msg[9] == 1)
|
|
|
|
{
|
|
|
|
/* Protocol T=1. */
|
|
|
|
printf (" bmFindexDindex ....: %02X\n", msg[10]);
|
|
|
|
printf (" bmTCCKST1 .........: %02X\n", msg[11]);
|
|
|
|
printf (" bGuardTimeT1 ......: %02X\n", msg[12]);
|
|
|
|
printf (" bmWaitingIntegersT1: %02X\n", msg[13]);
|
|
|
|
printf (" bClockStop ........: %02X\n", msg[14]);
|
|
|
|
printf (" bIFSC .............: %d\n", msg[15]);
|
|
|
|
printf (" bNadValue .........: %d\n", msg[16]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_escape (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
if (skip_escape)
|
|
|
|
return;
|
|
|
|
print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" buffer[9] .........: %02X\n", msg[9]);
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_datarate (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
|
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
if (msglen >= 18)
|
|
|
|
{
|
|
|
|
printf (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
|
|
|
|
printf (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
|
|
|
|
print_pr_data (msg, msglen, 18);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p_unknown (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
2009-06-29 15:56:07 +00:00
|
|
|
char buf[100];
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
snprintf (buf, sizeof buf, "Unknown RDR_to_PC command 0x%02X",
|
2009-06-29 15:56:07 +00:00
|
|
|
msglen? msg[0]:0);
|
|
|
|
print_r2p_header (buf, msg, msglen);
|
2009-04-17 18:40:32 +00:00
|
|
|
if (msglen < 10)
|
|
|
|
return;
|
|
|
|
printf (" bMessageType ......: %02X\n", msg[0]);
|
|
|
|
printf (" buffer[9] .........: %02X\n", msg[9]);
|
|
|
|
print_pr_data (msg, msglen, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
print_r2p (const unsigned char *msg, size_t msglen)
|
|
|
|
{
|
|
|
|
switch (msglen? msg[0]:0)
|
|
|
|
{
|
|
|
|
case RDR_to_PC_DataBlock:
|
|
|
|
print_r2p_datablock (msg, msglen);
|
|
|
|
break;
|
|
|
|
case RDR_to_PC_SlotStatus:
|
|
|
|
print_r2p_slotstatus (msg, msglen);
|
|
|
|
break;
|
|
|
|
case RDR_to_PC_Parameters:
|
|
|
|
print_r2p_parameters (msg, msglen);
|
|
|
|
break;
|
|
|
|
case RDR_to_PC_Escape:
|
|
|
|
print_r2p_escape (msg, msglen);
|
|
|
|
break;
|
|
|
|
case RDR_to_PC_DataRate:
|
|
|
|
print_r2p_datarate (msg, msglen);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
print_r2p_unknown (msg, msglen);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
flush_data (void)
|
|
|
|
{
|
|
|
|
if (!databuffer.count)
|
|
|
|
return;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
if (verbose)
|
2019-04-30 08:26:59 +02:00
|
|
|
{
|
|
|
|
printf ("Timestamp: %s\n", databuffer.timestamp);
|
|
|
|
printf ("Address..: %s\n", databuffer.address);
|
|
|
|
}
|
2009-04-17 18:40:32 +00:00
|
|
|
if (databuffer.is_bi)
|
|
|
|
{
|
|
|
|
print_r2p (databuffer.data, databuffer.count);
|
|
|
|
if (verbose)
|
|
|
|
putchar ('\n');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
print_p2r (databuffer.data, databuffer.count);
|
|
|
|
|
|
|
|
databuffer.count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2019-04-30 08:26:59 +02:00
|
|
|
collect_data (char *hexdata, const char *timestamp,
|
|
|
|
const char *address, unsigned int lineno)
|
2009-04-17 18:40:32 +00:00
|
|
|
{
|
|
|
|
size_t length;
|
|
|
|
int is_bi;
|
|
|
|
char *s;
|
|
|
|
unsigned int value;
|
|
|
|
|
|
|
|
is_bi = (*address && address[1] == 'i');
|
|
|
|
|
|
|
|
if (databuffer.is_bi != is_bi || strcmp (databuffer.address, address))
|
|
|
|
flush_data ();
|
|
|
|
databuffer.is_bi = is_bi;
|
2019-04-30 08:26:59 +02:00
|
|
|
if (strlen (timestamp) >= sizeof databuffer.timestamp)
|
|
|
|
die ("timestamp field too long");
|
|
|
|
strcpy (databuffer.timestamp, timestamp);
|
2009-04-17 18:40:32 +00:00
|
|
|
if (strlen (address) >= sizeof databuffer.address)
|
|
|
|
die ("address field too long");
|
|
|
|
strcpy (databuffer.address, address);
|
|
|
|
|
|
|
|
length = databuffer.count;
|
|
|
|
for (s=hexdata; *s; s++ )
|
|
|
|
{
|
|
|
|
if (ascii_isspace (*s))
|
|
|
|
continue;
|
2016-09-13 08:26:56 +02:00
|
|
|
if (!hexdigitp (s))
|
2009-04-17 18:40:32 +00:00
|
|
|
{
|
|
|
|
err ("invalid hex digit in line %u - line skipped", lineno);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
value = xtoi_1 (*s) * 16;
|
|
|
|
s++;
|
2016-09-13 08:26:56 +02:00
|
|
|
if (!hexdigitp (s))
|
2009-04-17 18:40:32 +00:00
|
|
|
{
|
|
|
|
err ("invalid hex digit in line %u - line skipped", lineno);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
value += xtoi_1 (*s);
|
|
|
|
|
|
|
|
if (length >= sizeof (databuffer.data))
|
|
|
|
{
|
2019-04-30 08:26:59 +02:00
|
|
|
err ("too much data at line %u - can handle only up to %zu bytes",
|
2009-04-17 18:40:32 +00:00
|
|
|
lineno, sizeof (databuffer.data));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
databuffer.data[length++] = value;
|
|
|
|
}
|
|
|
|
databuffer.count = length;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
parse_line (char *line, unsigned int lineno)
|
|
|
|
{
|
|
|
|
char *p;
|
2019-04-30 08:26:59 +02:00
|
|
|
char *timestamp, *event_type, *address, *data, *status, *datatag;
|
|
|
|
|
|
|
|
if (*line == '#' || !*line)
|
|
|
|
return;
|
2009-04-17 18:40:32 +00:00
|
|
|
|
|
|
|
if (debug)
|
2012-06-05 19:29:22 +02:00
|
|
|
printf ("line[%u] ='%s'\n", lineno, line);
|
2009-04-17 18:40:32 +00:00
|
|
|
|
|
|
|
p = strtok (line, " ");
|
|
|
|
if (!p)
|
2019-04-30 08:26:59 +02:00
|
|
|
die ("invalid line %d (no URB)", lineno);
|
|
|
|
timestamp = strtok (NULL, " ");
|
|
|
|
if (!timestamp)
|
|
|
|
die ("invalid line %d (no timestamp)", lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
event_type = strtok (NULL, " ");
|
|
|
|
if (!event_type)
|
2019-04-30 08:26:59 +02:00
|
|
|
die ("invalid line %d (no event type)", lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
address = strtok (NULL, " ");
|
|
|
|
if (!address)
|
2019-04-30 08:26:59 +02:00
|
|
|
die ("invalid line %d (no address", lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
if (usb_bus || usb_dev)
|
|
|
|
{
|
|
|
|
int bus, dev;
|
|
|
|
|
|
|
|
p = strchr (address, ':');
|
|
|
|
if (!p)
|
2019-04-30 08:26:59 +02:00
|
|
|
die ("invalid line %d (invalid address", lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
p++;
|
|
|
|
bus = atoi (p);
|
|
|
|
p = strchr (p, ':');
|
|
|
|
if (!p)
|
2019-04-30 08:26:59 +02:00
|
|
|
die ("invalid line %d (invalid address", lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
p++;
|
|
|
|
dev = atoi (p);
|
|
|
|
|
|
|
|
if ((usb_bus && usb_bus != bus) || (usb_dev && usb_dev != dev))
|
|
|
|
return; /* We don't want that one. */
|
|
|
|
}
|
2019-04-30 08:26:59 +02:00
|
|
|
if (*address == 'B' && (address[1] == 'o' || address[1] == 'i'))
|
|
|
|
; /* We want block ind and out. */
|
|
|
|
else if (*address == 'C' && (address[1] == 'o' || address[1] == 'i'))
|
|
|
|
; /* We want control ind and out. */
|
|
|
|
else
|
|
|
|
return; /* But nothing else. */
|
2009-04-17 18:40:32 +00:00
|
|
|
status = strtok (NULL, " ");
|
|
|
|
if (!status)
|
|
|
|
return;
|
|
|
|
if (!strchr ("-0123456789", *status))
|
|
|
|
return; /* Setup packet. */
|
|
|
|
/* We don't support "Z[io]" types thus we don't need to check here. */
|
|
|
|
p = strtok (NULL, " ");
|
|
|
|
if (!p)
|
|
|
|
return; /* No data length. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
datatag = strtok (NULL, " ");
|
|
|
|
if (datatag && *datatag == '=')
|
|
|
|
{
|
|
|
|
data = strtok (NULL, "");
|
2019-04-30 08:26:59 +02:00
|
|
|
collect_data (data?data:"", timestamp, address, lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-29 15:56:07 +00:00
|
|
|
static void
|
|
|
|
parse_line_sniffusb (char *line, unsigned int lineno)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
if (debug)
|
2012-06-05 19:29:22 +02:00
|
|
|
printf ("line[%u] ='%s'\n", lineno, line);
|
2009-06-29 15:56:07 +00:00
|
|
|
|
2009-07-01 18:30:33 +00:00
|
|
|
p = strtok (line, " \t");
|
2009-06-29 15:56:07 +00:00
|
|
|
if (!p)
|
|
|
|
return;
|
2009-07-01 18:30:33 +00:00
|
|
|
p = strtok (NULL, " \t");
|
2009-06-29 15:56:07 +00:00
|
|
|
if (!p)
|
2011-02-04 12:57:53 +01:00
|
|
|
return;
|
2009-07-01 18:30:33 +00:00
|
|
|
p = strtok (NULL, " \t");
|
2009-06-29 15:56:07 +00:00
|
|
|
if (!p)
|
2011-02-04 12:57:53 +01:00
|
|
|
return;
|
2009-06-29 15:56:07 +00:00
|
|
|
|
2016-09-13 08:26:56 +02:00
|
|
|
if (hexdigitp (p+0) && hexdigitp (p+1)
|
|
|
|
&& hexdigitp (p+2) && hexdigitp (p+3)
|
2009-06-29 15:56:07 +00:00
|
|
|
&& p[4] == ':' && !p[5])
|
|
|
|
{
|
|
|
|
size_t length;
|
|
|
|
unsigned int value;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-06-29 15:56:07 +00:00
|
|
|
length = databuffer.count;
|
2009-07-01 18:30:33 +00:00
|
|
|
while ((p=strtok (NULL, " \t")))
|
2009-06-29 15:56:07 +00:00
|
|
|
{
|
2016-09-13 08:26:56 +02:00
|
|
|
if (!hexdigitp (p+0) || !hexdigitp (p+1))
|
2009-06-29 15:56:07 +00:00
|
|
|
{
|
|
|
|
err ("invalid hex digit in line %u (%s)", lineno,p);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
value = xtoi_1 (p[0]) * 16 + xtoi_1 (p[1]);
|
|
|
|
|
|
|
|
if (length >= sizeof (databuffer.data))
|
|
|
|
{
|
|
|
|
err ("too much data at line %u - can handle only up to % bytes",
|
|
|
|
lineno, sizeof (databuffer.data));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
databuffer.data[length++] = value;
|
|
|
|
}
|
|
|
|
databuffer.count = length;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (!strcmp (p, "TransferFlags"))
|
|
|
|
{
|
|
|
|
flush_data ();
|
|
|
|
|
|
|
|
*databuffer.address = 0;
|
2009-07-01 18:30:33 +00:00
|
|
|
while ((p=strtok (NULL, " \t(,)")))
|
2009-06-29 15:56:07 +00:00
|
|
|
{
|
|
|
|
if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN"))
|
|
|
|
{
|
|
|
|
databuffer.is_bi = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (!strcmp (p, "USBD_TRANSFER_DIRECTION_OUT"))
|
|
|
|
{
|
|
|
|
databuffer.is_bi = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
static void
|
|
|
|
parse_input (FILE *fp)
|
|
|
|
{
|
|
|
|
char line[2000];
|
|
|
|
size_t length;
|
|
|
|
unsigned int lineno = 0;
|
|
|
|
|
|
|
|
while (fgets (line, sizeof (line), fp))
|
|
|
|
{
|
|
|
|
lineno++;
|
|
|
|
length = strlen (line);
|
|
|
|
if (length && line[length - 1] == '\n')
|
|
|
|
line[--length] = 0;
|
|
|
|
else
|
|
|
|
err ("line number %u too long or last line not terminated", lineno);
|
|
|
|
if (length && line[length - 1] == '\r')
|
|
|
|
line[--length] = 0;
|
2009-06-29 15:56:07 +00:00
|
|
|
if (sniffusb)
|
|
|
|
parse_line_sniffusb (line, lineno);
|
|
|
|
else
|
|
|
|
parse_line (line, lineno);
|
2009-04-17 18:40:32 +00:00
|
|
|
}
|
|
|
|
flush_data ();
|
|
|
|
if (ferror (fp))
|
|
|
|
err ("error reading input at line %u: %s", lineno, strerror (errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
int
|
2009-04-17 18:40:32 +00:00
|
|
|
main (int argc, char **argv)
|
|
|
|
{
|
|
|
|
int last_argc = -1;
|
2009-06-29 15:56:07 +00:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
if (argc)
|
|
|
|
{
|
|
|
|
argc--; argv++;
|
|
|
|
}
|
|
|
|
while (argc && last_argc != argc )
|
|
|
|
{
|
|
|
|
last_argc = argc;
|
|
|
|
if (!strcmp (*argv, "--"))
|
|
|
|
{
|
|
|
|
argc--; argv++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (!strcmp (*argv, "--version"))
|
|
|
|
{
|
2016-09-13 08:26:56 +02:00
|
|
|
fputs (PGM " (" GNUPG_NAME ") " PACKAGE_VERSION "\n", stdout);
|
2009-04-17 18:40:32 +00:00
|
|
|
exit (0);
|
|
|
|
}
|
|
|
|
else if (!strcmp (*argv, "--help"))
|
|
|
|
{
|
|
|
|
puts ("Usage: " PGM " [BUS:DEV]\n"
|
|
|
|
"Parse the output of usbmod assuming it is CCID compliant.\n\n"
|
|
|
|
" --skip-escape do not show escape packets\n"
|
2009-06-29 15:56:07 +00:00
|
|
|
" --sniffusb Assume output from Sniffusb.exe\n"
|
2009-04-17 18:40:32 +00:00
|
|
|
" --verbose enable extra informational output\n"
|
|
|
|
" --debug enable additional debug output\n"
|
|
|
|
" --help display this help and exit\n\n"
|
|
|
|
"Report bugs to " PACKAGE_BUGREPORT ".");
|
|
|
|
exit (0);
|
|
|
|
}
|
|
|
|
else if (!strcmp (*argv, "--verbose"))
|
|
|
|
{
|
|
|
|
verbose = 1;
|
|
|
|
argc--; argv++;
|
|
|
|
}
|
|
|
|
else if (!strcmp (*argv, "--debug"))
|
|
|
|
{
|
|
|
|
verbose = debug = 1;
|
|
|
|
argc--; argv++;
|
|
|
|
}
|
|
|
|
else if (!strcmp (*argv, "--skip-escape"))
|
|
|
|
{
|
|
|
|
skip_escape = 1;
|
|
|
|
argc--; argv++;
|
|
|
|
}
|
2009-06-29 15:56:07 +00:00
|
|
|
else if (!strcmp (*argv, "--sniffusb"))
|
|
|
|
{
|
|
|
|
sniffusb = 1;
|
|
|
|
argc--; argv++;
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
}
|
2009-06-29 15:56:07 +00:00
|
|
|
|
|
|
|
if (argc && sniffusb)
|
|
|
|
die ("no arguments expected when using --sniffusb\n");
|
|
|
|
else if (argc > 1)
|
2009-04-17 18:40:32 +00:00
|
|
|
die ("usage: " PGM " [BUS:DEV] (try --help for more information)\n");
|
|
|
|
|
|
|
|
if (argc == 1)
|
|
|
|
{
|
|
|
|
const char *s = strchr (argv[0], ':');
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
usb_bus = atoi (argv[0]);
|
|
|
|
if (s)
|
|
|
|
usb_dev = atoi (s+1);
|
|
|
|
if (usb_bus < 1 || usb_bus > 999 || usb_dev < 1 || usb_dev > 999)
|
|
|
|
die ("invalid bus:dev specified");
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
2009-04-17 18:40:32 +00:00
|
|
|
|
|
|
|
signal (SIGPIPE, SIG_IGN);
|
|
|
|
|
|
|
|
parse_input (stdin);
|
|
|
|
|
|
|
|
return any_error? 1:0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Local Variables:
|
|
|
|
compile-command: "gcc -Wall -Wno-pointer-sign -g -o ccidmon ccidmon.c"
|
|
|
|
End:
|
|
|
|
*/
|