blob: dddf5cf116511956a031dedae455e65498775d0c [file] [log] [blame]
Kévin Redonefbcf382018-07-07 17:35:15 +02001/* simtrace2-sniff - main program for the host PC to communicate with the
2 * SIMtrace 2 firmware in sniffer mode
3 *
4 * (C) 2016 by Harald Welte <hwelte@hmw-consulting.de>
5 * (C) 2018 by sysmocom -s.f.m.c. GmbH, Author: Kevin Redon <kredon@sysmocom.de>
6 *
7 * This program is free software; you can redistribute it and/or
Kévin Redon6e3f1122018-07-01 18:24:42 +02008 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Kévin Redon6e3f1122018-07-01 18:24:42 +020020 */
21#include <errno.h>
22#include <unistd.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <stdint.h>
27#include <signal.h>
28#include <time.h>
29#define _GNU_SOURCE
30#include <getopt.h>
31
32#include <sys/time.h>
33#include <sys/types.h>
34#include <sys/socket.h>
35#include <netinet/in.h>
36#include <arpa/inet.h>
37
38#include <libusb.h>
39
40#include "libusb_util.h"
41#include "simtrace.h"
42#include "simtrace_usb.h"
43#include "simtrace_prot.h"
44#include "simtrace2-discovery.h"
45
46#include <osmocom/core/gsmtap.h>
47#include <osmocom/core/gsmtap_util.h>
48#include <osmocom/core/utils.h>
49#include <osmocom/core/socket.h>
50#include <osmocom/core/msgb.h>
51#include <osmocom/sim/class_tables.h>
52#include <osmocom/sim/sim.h>
53
Harald Welte82b62852018-08-26 11:40:46 +020054/* as of August 26, 2018 we don't have any released libosmocore version which includes those
55 * definitions yet. Let's ensure some backwards compatibility: */
56#ifndef GSMTAP_SIM_APDU
57#define GSMTAP_SIM_APDU 0x00 /* APDU data (complete APDU) */
58#define GSMTAP_SIM_ATR 0x01 /* card ATR data */
59#define GSMTAP_SIM_PPS_REQ 0x02 /* PPS request data */
60#define GSMTAP_SIM_PPS_RSP 0x03 /* PPS response data */
61#define GSMTAP_SIM_TPDU_HDR 0x04 /* TPDU command header */
62#define GSMTAP_SIM_TPDU_CMD 0x05 /* TPDU command body */
63#define GSMTAP_SIM_TPDU_RSP 0x06 /* TPDU response body */
64#define GSMTAP_SIM_TPDU_SW 0x07 /* TPDU response trailer */
65#endif
66
Kévin Redon6e3f1122018-07-01 18:24:42 +020067/* transport to a SIMtrace device */
68struct st_transport {
69 /* USB */
70 struct libusb_device_handle *usb_devh;
71 struct {
72 uint8_t in;
73 uint8_t out;
74 uint8_t irq_in;
75 } usb_ep;
76};
77
78/* global GSMTAP instance */
79static struct gsmtap_inst *g_gti;
80
Kévin Redond1c65362018-07-26 14:51:15 +020081static int gsmtap_send_sim(uint8_t sub_type, const uint8_t *data, unsigned int len)
Kévin Redon6e3f1122018-07-01 18:24:42 +020082{
83 struct gsmtap_hdr *gh;
84 unsigned int gross_len = len + sizeof(*gh);
85 uint8_t *buf = malloc(gross_len);
86 int rc;
87
88 if (!buf)
89 return -ENOMEM;
90
91 memset(buf, 0, sizeof(*gh));
92 gh = (struct gsmtap_hdr *) buf;
93 gh->version = GSMTAP_VERSION;
94 gh->hdr_len = sizeof(*gh)/4;
95 gh->type = GSMTAP_TYPE_SIM;
Kévin Redond1c65362018-07-26 14:51:15 +020096 gh->sub_type = sub_type;
Kévin Redon6e3f1122018-07-01 18:24:42 +020097
Kévin Redond1c65362018-07-26 14:51:15 +020098 memcpy(buf + sizeof(*gh), data, len);
Kévin Redon6e3f1122018-07-01 18:24:42 +020099
100 rc = write(gsmtap_inst_fd(g_gti), buf, gross_len);
101 if (rc < 0) {
102 perror("write gsmtap");
103 free(buf);
104 return rc;
105 }
106
107 free(buf);
108 return 0;
109}
110
Kévin Redon31ed8022018-07-10 16:04:00 +0200111const struct value_string change_flags[] = {
112 {
113 .value = SNIFF_CHANGE_FLAG_CARD_INSERT,
114 .str = "card inserted",
115 },
116 {
117 .value = SNIFF_CHANGE_FLAG_CARD_EJECT,
118 .str = "card ejected",
119 },
120 {
Kévin Redon8e84f812018-07-26 15:34:03 +0200121 .value = SNIFF_CHANGE_FLAG_RESET_ASSERT,
122 .str = "reset asserted",
Kévin Redon31ed8022018-07-10 16:04:00 +0200123 },
124 {
Kévin Redon8e84f812018-07-26 15:34:03 +0200125 .value = SNIFF_CHANGE_FLAG_RESET_DEASSERT,
126 .str = "reset de-asserted",
Kévin Redon31ed8022018-07-10 16:04:00 +0200127 },
128 {
129 .value = SNIFF_CHANGE_FLAG_TIMEOUT_WT,
130 .str = "data transfer timeout",
131 },
132 {
133 .value = 0,
134 .str = NULL,
135 },
136};
137
138const struct value_string data_flags[] = {
139 {
140 .value = SNIFF_DATA_FLAG_ERROR_INCOMPLETE,
141 .str = "incomplete",
142 },
143 {
144 .value = SNIFF_DATA_FLAG_ERROR_MALFORMED,
145 .str = "malformed",
146 },
147 {
Kévin Redonf66af0c2018-07-11 10:27:13 +0200148 .value = SNIFF_DATA_FLAG_ERROR_CHECKSUM,
149 .str = "checksum error",
150 },
Kévin Redon69719962018-07-28 17:11:21 +0200151 {
152 .value = 0,
153 .str = NULL,
154 },
Kévin Redon31ed8022018-07-10 16:04:00 +0200155};
156
157static void print_flags(const struct value_string* flag_meanings, uint32_t nb_flags, uint32_t flags) {
158 uint32_t i;
159 for (i = 0; i < nb_flags; i++) {
160 if (flags & flag_meanings[i].value) {
161 printf(flag_meanings[i].str);
162 flags &= ~flag_meanings[i].value;
163 if (flags) {
164 printf(", ");
165 }
166 }
167 }
168}
169
Kévin Redon709a4312018-07-03 16:10:04 +0200170static int process_change(const uint8_t *buf, int len)
Kévin Redon6e3f1122018-07-01 18:24:42 +0200171{
172 /* check if there is enough data for the structure */
Kévin Redonf82f0f62018-07-08 15:10:23 +0200173 if (len < sizeof(struct sniff_change)) {
Kévin Redon6e3f1122018-07-01 18:24:42 +0200174 return -1;
175 }
176 struct sniff_change *change = (struct sniff_change *)buf;
177
Kévin Redon709a4312018-07-03 16:10:04 +0200178 printf("Card state change: ");
Kévin Redon31ed8022018-07-10 16:04:00 +0200179 if (change->flags) {
180 print_flags(change_flags, ARRAY_SIZE(change_flags), change->flags);
181 printf("\n");
182 } else {
183 printf("no changes\n");
Kévin Redon6e3f1122018-07-01 18:24:42 +0200184 }
Kévin Redon6e3f1122018-07-01 18:24:42 +0200185
186 return 0;
187}
188
189/* Table 7 of ISO 7816-3:2006 */
190static const uint16_t fi_table[] = { 372, 372, 558, 744, 1116, 1488, 1860, 0, 0, 512, 768, 1024, 1536, 2048, 0, 0, };
191
192/* Table 8 from ISO 7816-3:2006 */
193static const uint8_t di_table[] = { 0, 1, 2, 4, 8, 16, 32, 64, 12, 20, 2, 4, 8, 16, 32, 64, };
194
Kévin Redon709a4312018-07-03 16:10:04 +0200195static int process_fidi(const uint8_t *buf, int len)
Kévin Redon6e3f1122018-07-01 18:24:42 +0200196{
197 /* check if there is enough data for the structure */
198 if (len<sizeof(struct sniff_fidi)) {
199 return -1;
200 }
201 struct sniff_fidi *fidi = (struct sniff_fidi *)buf;
202
203 printf("Fi/Di switched to %u/%u\n", fi_table[fidi->fidi>>4], di_table[fidi->fidi&0x0f]);
204 return 0;
205}
206
Kévin Redonf82f0f62018-07-08 15:10:23 +0200207static int process_data(enum simtrace_msg_type_sniff type, const uint8_t *buf, int len)
Kévin Redon6e3f1122018-07-01 18:24:42 +0200208{
209 /* check if there is enough data for the structure */
Kévin Redonf82f0f62018-07-08 15:10:23 +0200210 if (len < sizeof(struct sniff_data)) {
Kévin Redon6e3f1122018-07-01 18:24:42 +0200211 return -1;
212 }
Kévin Redonf82f0f62018-07-08 15:10:23 +0200213 struct sniff_data *data = (struct sniff_data *)buf;
Kévin Redon6e3f1122018-07-01 18:24:42 +0200214
215 /* check if the data is available */
Kévin Redonf82f0f62018-07-08 15:10:23 +0200216 if (len < sizeof(struct sniff_data) + data->length) {
Kévin Redon6e3f1122018-07-01 18:24:42 +0200217 return -2;
218 }
219
Kévin Redonf82f0f62018-07-08 15:10:23 +0200220 /* check type */
221 if (type != SIMTRACE_MSGT_SNIFF_ATR && type != SIMTRACE_MSGT_SNIFF_PPS && type != SIMTRACE_MSGT_SNIFF_TPDU) {
222 return -3;
223 }
224
225 /* Print message */
226 switch (type) {
227 case SIMTRACE_MSGT_SNIFF_ATR:
228 printf("ATR");
229 break;
230 case SIMTRACE_MSGT_SNIFF_PPS:
231 printf("PPS");
232 break;
233 case SIMTRACE_MSGT_SNIFF_TPDU:
234 printf("TPDU");
235 break;
236 default:
237 printf("???");
238 break;
239 }
240 if (data->flags) {
241 printf(" (");
Kévin Redon31ed8022018-07-10 16:04:00 +0200242 print_flags(data_flags, ARRAY_SIZE(data_flags), data->flags);
Kévin Redonf82f0f62018-07-08 15:10:23 +0200243 printf(")");
244 }
245 printf(": ");
Kévin Redon6e3f1122018-07-01 18:24:42 +0200246 uint16_t i;
Kévin Redonf82f0f62018-07-08 15:10:23 +0200247 for (i = 0; i < data->length; i++) {
248 printf("%02x ", data->data[i]);
Kévin Redon6e3f1122018-07-01 18:24:42 +0200249 }
250 printf("\n");
Kévin Redon709a4312018-07-03 16:10:04 +0200251
Kévin Redond1c65362018-07-26 14:51:15 +0200252 /* Send message as GSNTAP */
253 switch (type) {
254 case SIMTRACE_MSGT_SNIFF_ATR:
255 gsmtap_send_sim(GSMTAP_SIM_ATR, data->data, data->length);
256 break;
257 case SIMTRACE_MSGT_SNIFF_TPDU:
258 /* TPDU is now considered as APDU since SIMtrace sends complete TPDU */
259 gsmtap_send_sim(GSMTAP_SIM_APDU, data->data, data->length);
260 break;
261 default:
262 break;
Kévin Redonf82f0f62018-07-08 15:10:23 +0200263 }
264
Kévin Redon6e3f1122018-07-01 18:24:42 +0200265 return 0;
266}
267
268/*! \brief Process an incoming message from the SIMtrace2 */
Kévin Redon709a4312018-07-03 16:10:04 +0200269static int process_usb_msg(const uint8_t *buf, int len)
Kévin Redon6e3f1122018-07-01 18:24:42 +0200270{
271 /* check if enough data for the header is present */
Kévin Redonf82f0f62018-07-08 15:10:23 +0200272 if (len < sizeof(struct simtrace_msg_hdr)) {
Kévin Redon6e3f1122018-07-01 18:24:42 +0200273 return 0;
274 }
275
276 /* check if message is complete */
277 struct simtrace_msg_hdr *msg_hdr = (struct simtrace_msg_hdr *)buf;
Kévin Redonf82f0f62018-07-08 15:10:23 +0200278 if (len < msg_hdr->msg_len) {
Kévin Redon6e3f1122018-07-01 18:24:42 +0200279 return 0;
280 }
281 //printf("msg: %s\n", osmo_hexdump(buf, msg_hdr->msg_len));
282
283 /* check for message class */
Kévin Redonf82f0f62018-07-08 15:10:23 +0200284 if (SIMTRACE_MSGC_SNIFF != msg_hdr->msg_class) { /* we only care about sniffing messages */
Kévin Redon6e3f1122018-07-01 18:24:42 +0200285 return msg_hdr->msg_len; /* discard non-sniffing messaged */
286 }
287
288 /* process sniff message payload */
289 buf += sizeof(struct simtrace_msg_hdr);
290 len -= sizeof(struct simtrace_msg_hdr);
291 switch (msg_hdr->msg_type) {
292 case SIMTRACE_MSGT_SNIFF_CHANGE:
293 process_change(buf, len);
294 break;
295 case SIMTRACE_MSGT_SNIFF_FIDI:
296 process_fidi(buf, len);
297 break;
298 case SIMTRACE_MSGT_SNIFF_ATR:
Kévin Redon6e3f1122018-07-01 18:24:42 +0200299 case SIMTRACE_MSGT_SNIFF_PPS:
Kévin Redon6e3f1122018-07-01 18:24:42 +0200300 case SIMTRACE_MSGT_SNIFF_TPDU:
Kévin Redonf82f0f62018-07-08 15:10:23 +0200301 process_data(msg_hdr->msg_type, buf, len);
Kévin Redon6e3f1122018-07-01 18:24:42 +0200302 break;
303 default:
304 printf("unknown SIMtrace msg type 0x%02x\n", msg_hdr->msg_type);
305 break;
306 }
307
308 return msg_hdr->msg_len;
309}
310
311/*! Transport to SIMtrace device (e.g. USB handle) */
312static struct st_transport _transp;
313
314static void run_mainloop()
315{
316 int rc;
317 uint8_t buf[16*256];
318 unsigned int i, buf_i = 0;
319 int xfer_len;
320
321 printf("Entering main loop\n");
322
323 while (true) {
324 /* read data from SIMtrace2 device (via USB) */
325 rc = libusb_bulk_transfer(_transp.usb_devh, _transp.usb_ep.in,
326 &buf[buf_i], sizeof(buf)-buf_i, &xfer_len, 100000);
327 if (rc < 0 && rc != LIBUSB_ERROR_TIMEOUT &&
328 rc != LIBUSB_ERROR_INTERRUPTED &&
329 rc != LIBUSB_ERROR_IO) {
330 fprintf(stderr, "BULK IN transfer error; rc=%d\n", rc);
331 return;
332 }
333 /* dispatch any incoming data */
334 if (xfer_len > 0) {
335 //printf("URB: %s\n", osmo_hexdump(&buf[buf_i], xfer_len));
336 buf_i += xfer_len;
Kévin Redon3b7624c2018-07-10 16:03:27 +0200337 if (buf_i >= sizeof(buf)) {
Kévin Redon6e3f1122018-07-01 18:24:42 +0200338 perror("preventing USB buffer overflow");
339 return;
340 }
Kévin Redon3b7624c2018-07-10 16:03:27 +0200341 int processed;
342 while ((processed = process_usb_msg(buf, buf_i)) > 0) {
343 if (processed > buf_i) {
344 break;
345 }
Kévin Redon6e3f1122018-07-01 18:24:42 +0200346 for (i = processed; i < buf_i; i++) {
347 buf[i-processed] = buf[i];
348 }
349 buf_i -= processed;
350 }
351 }
352 }
353}
354
355static void print_welcome(void)
356{
357 printf("simtrace2-sniff - Phone-SIM card communication sniffer \n"
358 "(C) 2010-2017 by Harald Welte <laforge@gnumonks.org>\n"
359 "(C) 2018 by Kevin Redon <kredon@sysmocom.de>\n"
360 "\n"
361 );
362}
363
364static void print_help(void)
365{
366 printf(
367 "\t-h\t--help\n"
368 "\t-i\t--gsmtap-ip\tA.B.C.D\n"
369 "\t-k\t--keep-running\n"
370 "\t-V\t--usb-vendor\tVENDOR_ID\n"
371 "\t-P\t--usb-product\tPRODUCT_ID\n"
372 "\t-C\t--usb-config\tCONFIG_ID\n"
373 "\t-I\t--usb-interface\tINTERFACE_ID\n"
374 "\t-S\t--usb-altsetting ALTSETTING_ID\n"
375 "\t-A\t--usb-address\tADDRESS\n"
376 "\n"
377 );
378}
379
380static const struct option opts[] = {
381 { "help", 0, 0, 'h' },
382 { "gsmtap-ip", 1, 0, 'i' },
383 { "keep-running", 0, 0, 'k' },
384 { "usb-vendor", 1, 0, 'V' },
385 { "usb-product", 1, 0, 'P' },
386 { "usb-config", 1, 0, 'C' },
387 { "usb-interface", 1, 0, 'I' },
388 { "usb-altsetting", 1, 0, 'S' },
389 { "usb-address", 1, 0, 'A' },
390 { NULL, 0, 0, 0 }
391};
392
393/* Known USB device with SIMtrace firmware supporting sniffer */
394static const struct dev_id compatible_dev_ids[] = {
395 { USB_VENDOR_OPENMOKO, USB_PRODUCT_SIMTRACE2 },
396 { 0, 0 }
397};
398
399static void signal_handler(int signal)
400{
401 switch (signal) {
402 case SIGINT:
403 exit(0);
404 break;
405 default:
406 break;
407 }
408}
409
410int main(int argc, char **argv)
411{
412 int i, rc, ret;
413 print_welcome();
414
415 /* Parse arguments */
416 char *gsmtap_host = "127.0.0.1";
417 int keep_running = 0;
418 int vendor_id = -1, product_id = -1, addr = -1, config_id = -1, if_num = -1, altsetting = -1;
419
420 while (1) {
421 int option_index = 0;
422
423 char c = getopt_long(argc, argv, "hi:kV:P:C:I:S:A:", opts, &option_index);
424 if (c == -1)
425 break;
426 switch (c) {
427 case 'h':
428 print_help();
429 exit(0);
430 break;
431 case 'i':
432 gsmtap_host = optarg;
433 break;
434 case 'k':
435 keep_running = 1;
436 break;
437 case 'V':
438 vendor_id = strtol(optarg, NULL, 16);
439 break;
440 case 'P':
441 product_id = strtol(optarg, NULL, 16);
442 break;
443 case 'C':
444 config_id = atoi(optarg);
445 break;
446 case 'I':
447 if_num = atoi(optarg);
448 break;
449 case 'S':
450 altsetting = atoi(optarg);
451 break;
452 case 'A':
453 addr = atoi(optarg);
454 break;
455 }
456 }
457
458 /* Scan for available SIMtrace USB devices supporting sniffing */
459 rc = libusb_init(NULL);
460 if (rc < 0) {
461 fprintf(stderr, "libusb initialization failed\n");
462 goto do_exit;
463 }
464 struct usb_interface_match ifm_scan[16];
465 int num_interfaces = usb_match_interfaces(NULL, compatible_dev_ids,
466 USB_CLASS_PROPRIETARY, SIMTRACE_SNIFFER_USB_SUBCLASS, -1, ifm_scan, ARRAY_SIZE(ifm_scan));
467 if (num_interfaces <= 0) {
468 perror("No compatible USB devices found");
469 goto do_exit;
470 }
471
472 /* Only keep USB matching arguments */
473 struct usb_interface_match ifm_filtered[ARRAY_SIZE(ifm_scan)];
474 int num_filtered = 0;
475 for (i = 0; i < num_interfaces; i++) {
476 if (vendor_id>=0 && vendor_id!=ifm_scan[i].vendor) {
477 continue;
478 }
479 if (product_id>=0 && product_id!=ifm_scan[i].product) {
480 continue;
481 }
482 if (config_id>=0 && config_id!=ifm_scan[i].configuration) {
483 continue;
484 }
485 if (if_num>=0 && if_num!=ifm_scan[i].interface) {
486 continue;
487 }
488 if (altsetting>=0 && altsetting!=ifm_scan[i].altsetting) {
489 continue;
490 }
491 if (addr>=0 && addr!=ifm_scan[i].addr) {
492 continue;
493 }
494 ifm_filtered[num_filtered++] = ifm_scan[i];
495 }
496 if (1!=num_filtered) {
497 perror("No individual matching USB devices found");
498 printf("Available USB devices:\n");
499 for (i = 0; i < num_interfaces; i++) {
500 printf("\t%04x:%04x Addr=%u, Path=%s, Cfg=%u, Intf=%u, Alt=%u: %d/%d/%d ",
501 ifm_scan[i].vendor, ifm_scan[i].product, ifm_scan[i].addr, ifm_scan[i].path,
502 ifm_scan[i].configuration, ifm_scan[i].interface, ifm_scan[i].altsetting,
503 ifm_scan[i].class, ifm_scan[i].sub_class, ifm_scan[i].protocol);
504 libusb_device_handle *dev_handle;
505 rc = libusb_open(ifm_scan[i].usb_dev, &dev_handle);
506 if (rc < 0) {
507 printf("\n");
508 perror("Cannot open device");
509 continue;
510 }
511 char strbuf[256];
512 rc = libusb_get_string_descriptor_ascii(dev_handle, ifm_scan[i].string_idx,
513 (unsigned char *)strbuf, sizeof(strbuf));
514 libusb_close(dev_handle);
515 if (rc < 0) {
516 printf("\n");
517 perror("Cannot read string");
518 continue;
519 }
520 printf("(%s)\n", strbuf);
521 }
522 goto do_exit;
523 }
524 struct usb_interface_match ifm_selected = ifm_filtered[0];
525 printf("Using USB device %04x:%04x Addr=%u, Path=%s, Cfg=%u, Intf=%u, Alt=%u: %d/%d/%d ",
526 ifm_selected.vendor, ifm_selected.product, ifm_selected.addr, ifm_selected.path,
527 ifm_selected.configuration, ifm_selected.interface, ifm_selected.altsetting,
528 ifm_selected.class, ifm_selected.sub_class, ifm_selected.protocol);
529 libusb_device_handle *dev_handle;
530 rc = libusb_open(ifm_selected.usb_dev, &dev_handle);
531 if (rc < 0) {
532 printf("\n");
533 perror("Cannot open device");
534 }
535 char strbuf[256];
536 rc = libusb_get_string_descriptor_ascii(dev_handle, ifm_selected.string_idx,
537 (unsigned char *)strbuf, sizeof(strbuf));
538 libusb_close(dev_handle);
539 if (rc < 0) {
540 printf("\n");
541 perror("Cannot read string");
542 }
543 printf("(%s)\n", strbuf);
544
545 g_gti = gsmtap_source_init(gsmtap_host, GSMTAP_UDP_PORT, 0);
546 if (!g_gti) {
547 perror("unable to open GSMTAP");
548 goto close_exit;
549 }
550 gsmtap_source_add_sink(g_gti);
551
552 signal(SIGINT, &signal_handler);
553
554 do {
555 _transp.usb_devh = usb_open_claim_interface(NULL, &ifm_selected);
556 if (!_transp.usb_devh) {
557 fprintf(stderr, "can't open USB device\n");
558 goto close_exit;
559 }
560
561 rc = libusb_claim_interface(_transp.usb_devh, ifm_selected.interface);
562 if (rc < 0) {
563 fprintf(stderr, "can't claim interface %d; rc=%d\n", ifm_selected.interface, rc);
564 goto close_exit;
565 }
566
567 rc = get_usb_ep_addrs(_transp.usb_devh, ifm_selected.interface, &_transp.usb_ep.out,
568 &_transp.usb_ep.in, &_transp.usb_ep.irq_in);
569 if (rc < 0) {
570 fprintf(stderr, "can't obtain EP addrs; rc=%d\n", rc);
571 goto close_exit;
572 }
573
574 run_mainloop();
575 ret = 0;
576
577 if (_transp.usb_devh)
578 libusb_release_interface(_transp.usb_devh, 0);
579close_exit:
580 if (_transp.usb_devh)
581 libusb_close(_transp.usb_devh);
582 if (keep_running)
583 sleep(1);
584 } while (keep_running);
585
586 libusb_exit(NULL);
587do_exit:
588 return ret;
589}