#include #include #include #include #include #include #include #ifndef _VERSION #define _VERSION "none" #endif #define S_ENQ 0x05 #define S_ACK 0x06 #define S_NAK 0x15 #define ISSTATUS(c) (*c == S_ENQ || *c == S_ACK || *c == S_NAK) #define USBERR(msg, rc) { dprintf(2, "%s: %s\n", msg, libusb_strerror(rc)); } #define USBDEBUG2(msg, rc) { if (debug >= 3) { dprintf(2, "%s: %s\n", msg, libusb_strerror(rc)); } } #define DEBUG(...) { if (debug >= 1) { dprintf(2, __VA_ARGS__); } } #define DEBUG2(...) { if (debug >= 2) { dprintf(2, __VA_ARGS__); } } #define DEBUG3(...) { if (debug >= 3) { dprintf(2, __VA_ARGS__); } } #define HEXBUF(msg, buf, n) { if (debug >= 3) { dprintf(2, "%s:", msg); for (uint8_t *__h = buf; __h < buf+n;++__h) { dprintf(2, " %02x", *__h); } dprintf(2, "\n"); } } uint16_t vendor_id = 0; uint16_t product_id = 0; char *serial_number = NULL; uint8_t reset = 0; uint8_t oneline = 0; uint8_t onlyargs = 0; uint8_t debug = 0; int infd = 0; int outfd = 1; uint8_t explicit_in = 0; libusb_device_handle *devh = NULL; uint8_t endpoint = 0; uint8_t ints[32] = { 0 }; uint8_t inti = 0; struct tokenizer { char tokbuf[1024]; char *tok; uint8_t eot; uint8_t status; int (*cb)(const char *, uint8_t , uint8_t); }; void tok_init(struct tokenizer *t, int (*cb)(const char *, uint8_t, uint8_t)) { memset(t->tokbuf, 0, 1024); t->tok = t->tokbuf; t->eot = 0; t->status = 0; t->cb = cb; } int tok_read(struct tokenizer *t, char *buf, size_t n) { char *start = buf; char *end = NULL, *tmpend = NULL; while (!t->eot) { // non-final tokens: terminated by ; end = strchr(start, ';'); // last token: terminated by . tmpend = strchr(start, '.'); if (tmpend && (tmpend < end || !end)) { end = tmpend; t->eot = 1; } if (end) { if (end > start) { for (char *c = start; c < end; ++c) { if (ISSTATUS(c)) { t->status = *c; } else { *(t->tok++) = *c; } } } *(t->tok) = 0; if (t->cb(t->tokbuf, t->status, t->eot)) { return 1; } t->tok = t->tokbuf; *(t->tok) = 0; start = end + 1; continue; } // incomplete tokens continued in the next message end = strchr(start, '?'); if (end) { *end = 0; } for (char *c = start; *c != 0; ++c) { if (ISSTATUS(c)) { t->status = *c; } else { *(t->tok++) = *c; } } *(t->tok) = 0; break; } return 0; } struct tokenizer in_tok; struct tokenizer out_tok; int print_token(const char *tok, uint8_t status, uint8_t last) { switch (status) { case S_ACK: if (onlyargs && (strlen(tok) <= 6 || !strncmp("REV", tok, 3))) { return 0; } int rc; if (oneline && !last) { rc = dprintf(outfd, "%s;", tok); } else { rc = dprintf(outfd, "%s.\n", tok); } return rc < 0; case S_ENQ: dprintf(2, "command not recognized by device: %s\n", tok); return 1; case S_NAK: dprintf(2, "command data out of bounds: %s\n", tok); return 1; default: dprintf(2, "device responded to command %s with unknown status 0x%02x\n", tok, status); return 1; } } int send_token(const char *tok, uint8_t status, uint8_t last) { in_tok.eot = 0; uint8_t cmd[65] = { 0 }; uint8_t *cmdend = cmd + 64; uint8_t *cmdptr = cmd + 5; int rc; memset(cmdptr, 0, 60); for (uint8_t i = 0; i < strlen(tok) && cmdptr < cmdend; ++i) { if (isprint(tok[i])) { *(cmdptr++) = tok[i]; } } if (cmdptr >= cmdend) { dprintf(2, "token too long: %s\n", tok); return 1; } *cmdptr = '.'; cmd[0] = 0xfd; cmd[1] = cmdptr - cmd - 1; cmd[2] = 0x16; cmd[3] = 0x4d; cmd[4] = 0x0d; HEXBUF(">", cmd, 64); rc = libusb_control_transfer(devh, 0x21, 0x09, 0x02fd, 1, cmd, 64, 0); if (rc < 0) { USBERR("failed to send control message", rc); return 1; } else { DEBUG2("> %s\n", cmd+5); } int tx; tok_init(&out_tok, print_token); // Repeat until a "." (EOT) is encountered in the response. while (!out_tok.eot) { rc = libusb_interrupt_transfer(devh, endpoint, cmd, 64, &tx, 1000); if (rc) { USBERR("failed to receive response from device", rc); return 1; } HEXBUF("<", cmd, tx); // skip first 5 bytes (report id, length, 3*AIMID) DEBUG2("< %s\n", cmd+5); if (tok_read(&out_tok, (char *) cmd+5, tx-5)) { return 1; } } return 0; } int config(int argc, char **args) { // If commands are provided on the CLI, execute only those if (argc > 0) { for (int i = 0; i < argc; ++i) { if (send_token(args[i], S_ACK, i >= argc-1)) { return 1; } } return 0; } // If input is a tty, dump config // Can be overwritten with "-i -" on the cli if (isatty(infd) && !explicit_in) { return send_token("?", S_ACK, 1); } // Otherwhise parse tokens from stdin char buf[65]; ssize_t n = 1; tok_init(&in_tok, send_token); while (n) { n = read(infd, buf, 64); if (n < 0) { perror("failed to read from input file"); return 1; } buf[n] = 0; if (tok_read(&in_tok, buf, n)) { return 1; } } return 0; } int check_device(libusb_device *dev) { struct libusb_device_descriptor devd = { 0 }; struct libusb_config_descriptor *devc = NULL; const struct libusb_interface *devi = NULL; const struct libusb_interface_descriptor *devid = NULL; const struct libusb_endpoint_descriptor *deved = NULL; char devdesc[256] = { 0 }; // Top-level checks against VID, PID and Serial int rc = libusb_get_device_descriptor(dev, &devd); if (rc) { USBDEBUG2("failed to get device descriptor", rc); return rc; } if (vendor_id && vendor_id != devd.idVendor) { return 1; } if (product_id && product_id != devd.idProduct) { return 1; } rc = libusb_open(dev, &devh); if (rc < 0) { USBDEBUG2("failed to open device", rc); return rc; } if (serial_number) { rc = libusb_get_string_descriptor_ascii(devh, devd.iSerialNumber, (uint8_t *) devdesc, 256); if (rc < 0) { libusb_close(devh); devh = NULL; USBDEBUG2("failed to get serial id string descriptor", rc); return rc; } if (strncmp(serial_number, devdesc, strlen(serial_number))) { libusb_close(devh); devh = NULL; return 1; } } // Iterate all configurations, interfaces and endpoints to find the REM/EZConfig endpoint for (uint8_t c = 0; c < devd.bNumConfigurations; ++c) { libusb_free_config_descriptor(devc); rc = libusb_get_config_descriptor(dev, c, &devc); if (rc) { USBDEBUG2("failed to get config descriptor", rc); continue; } rc = libusb_get_string_descriptor_ascii(devh, devc->iConfiguration, (uint8_t *) devdesc, 256); if (rc < 0) { USBDEBUG2("failed to get config string descriptor", rc); continue; } DEBUG2("- configuration %d: %s\n", c, devdesc); inti = 0; for (uint8_t i = 0; i < devc->bNumInterfaces; ++i) { DEBUG2(" - interface %d\n", i); devi = &(devc->interface[i]); for (uint8_t a = 0; a < devi->num_altsetting; ++a) { devid = &(devi->altsetting[a]); rc = libusb_get_string_descriptor_ascii(devh, devid->iInterface, (uint8_t *) devdesc, 256); if (rc < 0) { USBDEBUG2("failed to get interface string descriptor", rc); continue; } DEBUG2(" - altsetting %d: interface=%d, desc=%s class=%d subclass=%d protocol=%d\n", devid->bAlternateSetting, devid->bInterfaceNumber, devdesc, devid->bInterfaceClass, devid->bInterfaceSubClass, devid->bInterfaceProtocol); for (uint8_t e = 0; e < devid->bNumEndpoints; ++e) { deved = &(devid->endpoint[e]); char type = "csbi"[deved->bmAttributes & 0b00000011]; DEBUG2(" - endpoint %x: t=%c\n", deved->bEndpointAddress, type); // build a list of interfaces so that we can claim all of them ints[inti++] = devid->bInterfaceNumber; // endpoint selection: if ((deved->bmAttributes & 0b11) == LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT // must be an interrupt endpoint && ((deved->bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) // must be a device to host endpoint && !strncmp("REM", devdesc, 3) // human readable description of the interface must be "REM" (Honeywell "Remote MasterMind" control interface) ) { endpoint = deved->bEndpointAddress; // Print device info rc = libusb_get_string_descriptor_ascii(devh, devd.iManufacturer, (uint8_t *) devdesc, 256); if (rc < 0) { USBDEBUG2("failed to get manufacturer string descriptor", rc); libusb_close(devh); devh = NULL; return 1; } DEBUG("Manufacturer: %s\n", devdesc); rc = libusb_get_string_descriptor_ascii(devh, devd.iProduct, (uint8_t *) devdesc, 256); if (rc < 0) { USBDEBUG2("failed to get product string descriptor", rc); libusb_close(devh); devh = NULL; return 1; } DEBUG("Product: %s\n", devdesc); rc = libusb_get_string_descriptor_ascii(devh, devd.iSerialNumber, (uint8_t *) devdesc, 256); if (rc < 0) { USBDEBUG2("failed to get serial number string descriptor", rc); libusb_close(devh); devh = NULL; return 1; } DEBUG("Serial Number: %s\n", devdesc); DEBUG2("chose endpoint %x\n", endpoint); return 0; } } } } } libusb_close(devh); devh = NULL; return 1; } int usb(int argc, char **args) { libusb_context *ctx = NULL; libusb_device **list = NULL; int rc = 0, fail = 0; // // Find and open the device // rc = libusb_init(&ctx); if (!ctx) { USBERR("failed to initialize libusb", rc); fail = 1; goto fail; } // // Iterate list of USB devices // ssize_t ndev = libusb_get_device_list(ctx, &list); if (ndev < 0) { USBERR("failed to list usb devices", ndev); fail = 1; goto fail; } for (size_t i = 0; i < ndev; ++i) { rc = check_device(list[i]); if (!rc) { break; } } if (!devh) { dprintf(2, "failed to find compatible device\n"); fail = 1; goto fail; } // // Disconnect usbhid driver from both device interfaces // rc = libusb_set_auto_detach_kernel_driver(devh, 1); if (rc) { USBERR("auto-attaching kernel driver not possible; please replug USB when done", rc); for (uint8_t i = 0; i < inti; ++i) { rc = libusb_kernel_driver_active(devh, ints[i]); if (rc < 0) { USBERR("failed to query kernel driver state", rc); fail = 1; goto fail; } if (rc > 0) { rc = libusb_detach_kernel_driver(devh, ints[i]); if (rc) { USBERR("failed to detach kernel driver", rc); fail = 1; goto fail; } } } } for (uint8_t i = 0; i < inti; ++i) { rc = libusb_claim_interface(devh, ints[i]); if (rc) { USBERR("failed to claim interface", rc); fail = 1; goto fail; } } // // Prepare and send a message on the control channel for each config argument // rc = config(argc, args); if (rc) { fail = 1; goto fail; } // // Cleanup // fail: if (devh) { for (uint8_t i = 0; i < inti; ++i) { rc = libusb_release_interface(devh, ints[i]); if (rc) { USBERR("failed to release interface", rc); } } if (reset) { libusb_reset_device(devh); } libusb_close(devh); } if (list) { libusb_free_device_list(list, 1); } if (ctx) { libusb_exit(ctx); } return fail; } int main(int argc, char **argv) { int opt; char *end; long parsed; while ((opt = getopt(argc, argv, "v:p:s:1crdi:o:hV")) != -1) { switch (opt) { case 'v': end = optarg; parsed = strtol(optarg, &end, 16); if (*end != 0) { perror("failed to parse vendor id"); return 1; } if (parsed <= 0 || parsed > 0xffff) { puts("invalid vendor id"); return 1; } vendor_id = (uint16_t) parsed; break; case 'p': end = optarg; parsed = strtol(optarg, &end, 16); if (*end != 0) { perror("failed to parse product id"); return 1; } if (parsed <= 0 || parsed > 0xffff) { puts("invalid product id"); return 1; } product_id = (uint16_t) parsed; break; case 's': serial_number = strdup(optarg); break; case 'r': reset = 1; break; case '1': oneline = 1; break; case 'c': onlyargs = 1; break; case 'd': ++debug; break; case 'i': explicit_in = 1; if (!strcmp("-", optarg)) { infd = 0; } else { infd = open(optarg, O_CLOEXEC|O_RDONLY); if (infd < 0) { perror("failed to open input file"); exit(1); } } break; case 'o': if (!strcmp("-", optarg)) { outfd = 1; } else { outfd = open(optarg, O_CLOEXEC|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if (outfd < 0) { perror("failed to open output file"); exit(1); } } break; case 'V': puts(_VERSION); exit(0); break; default: printf("%s [-v vendor] [-p product] [-1] [-c] [-r] [-d] [-i infile] [-o outfile] [command1 [... commandN]]\n", argv[0]); puts(" -v vendor\tUSB vendor ID of the barcode scanner to configure."); puts(" -p product\tUSB product ID of the barcode scanner to configure."); puts(" -s serial\tUSB serial number of the barcode scanner to configure."); puts(" -1\t\tPrint response on one line, rather than one token per line."); puts(" -c\t\tPrint only received commands that contain an argument. Useful for config backups."); puts(" -r\t\tReset device after configuration."); puts(" -d\t\tDebug mode (more verbose output). Can be provided repeatedly to increase verbosity."); puts(" -i infile\tRead input from infile instead of stdin."); puts(" -o outfile\tWrite output to outfile instead of stdout."); puts(" -h \t\tShow this help and exit."); puts(" -V \t\tShow the version number and exit."); return 1; } } return usb(argc-optind, argv+optind); }