/* usbredirtestclient.c simple usb network redirection test client (guest). Copyright 2010-2011 Red Hat, Inc. Red Hat Authors: Hans de Goede This program 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 2 of the License, or (at your option) any later version. This program 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 along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbredirparser.h" /* Macros to go from an endpoint address to an index for our ep array */ #define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f)) #define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f)) #define TESTCLIENT_VERSION "usbredirtestclient " PACKAGE_VERSION static void usbredirtestclient_device_connect(void *priv, struct usb_redir_device_connect_header *device_connect); static void usbredirtestclient_device_disconnect(void *priv); static void usbredirtestclient_interface_info(void *priv, struct usb_redir_interface_info_header *interface_info); static void usbredirtestclient_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info); static void usbredirtestclient_configuration_status(void *priv, uint64_t id, struct usb_redir_configuration_status_header *configuration_status); static void usbredirtestclient_alt_setting_status(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status); static void usbredirtestclient_iso_stream_status(void *priv, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status); static void usbredirtestclient_interrupt_receiving_status(void *priv, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status); static void usbredirtestclient_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status); static void usbredirtestclient_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len); static void usbredirtestclient_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len); static void usbredirtestclient_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len); static void usbredirtestclient_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len); /* id's for all the test commands we send */ enum { reset_id, get_config_id, set_config_id, get_alt_id, set_alt_id, first_cmdline_id }; static int verbose = usbredirparser_info; /* 3 */ static int client_fd, running = 1; static struct usbredirparser *parser; static int id = first_cmdline_id; static const struct option longopts[] = { { "port", required_argument, NULL, 'p' }, { "verbose", required_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 } }; static void usbredirtestclient_log(void *priv, int level, const char *msg) { if (level <= verbose) fprintf(stderr, "%s\n", msg); } static int usbredirtestclient_read(void *priv, uint8_t *data, int count) { int r = read(client_fd, data, count); if (r < 0) { if (errno == EAGAIN) return 0; return -1; } if (r == 0) { /* Server disconnected */ close(client_fd); client_fd = -1; } return r; } static int usbredirtestclient_write(void *priv, uint8_t *data, int count) { int r = write(client_fd, data, count); if (r < 0) { if (errno == EAGAIN) return 0; if (errno == EPIPE) { /* Server disconnected */ close(client_fd); client_fd = -1; return 0; } return -1; } return r; } static void usbredirtestclient_hello(void *priv, struct usb_redir_hello_header *h) { /* Queue a reset + set config the other test commands will be send in response to the status packets of previous commands */ usbredirparser_send_reset(parser); usbredirparser_send_get_configuration(parser, get_config_id); } static void usage(int exit_code, char *argv0) { fprintf(exit_code? stderr:stdout, "Usage: %s [-p|--port ] [-v|--verbose <0-3>] \n", argv0); exit(exit_code); } static void run_main_loop(void) { fd_set readfds, writefds; int n, nfds; while (running && client_fd != -1) { FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(client_fd, &readfds); if (usbredirparser_has_data_to_write(parser)) { FD_SET(client_fd, &writefds); } nfds = client_fd + 1; n = select(nfds, &readfds, &writefds, NULL, NULL); if (n == -1) { if (errno == EINTR) { continue; } perror("select"); break; } if (FD_ISSET(client_fd, &readfds)) { if (usbredirparser_do_read(parser)) { break; } } if (FD_ISSET(client_fd, &writefds)) { if (usbredirparser_do_write(parser)) { break; } } } if (client_fd != -1) { /* Broken out of the loop because of an error ? */ close(client_fd); client_fd = -1; } } static void quit_handler(int sig) { running = 0; } int main(int argc, char *argv[]) { int o, flags; char *endptr, *server; struct addrinfo *r, *res, hints; struct sigaction act; char port_str[16]; int port = 4000; uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; while ((o = getopt_long(argc, argv, "hp:", longopts, NULL)) != -1) { switch (o) { case 'p': port = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Inalid value for --port: '%s'\n", optarg); usage(1, argv[0]); } break; case 'v': verbose = strtol(optarg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "Inalid value for --verbose: '%s'\n", optarg); usage(1, argv[0]); } break; case '?': case 'h': usage(o == '?', argv[0]); break; } } if (optind == argc) { fprintf(stderr, "Missing server argument\n"); usage(1, argv[0]); } server = argv[optind]; optind++; if (optind != argc) { fprintf(stderr, "Excess non option arguments\n"); usage(1, argv[0]); } memset(&act, 0, sizeof(act)); act.sa_handler = quit_handler; sigaction(SIGINT, &act, NULL); sigaction(SIGHUP, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGQUIT, &act, NULL); memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; sprintf(port_str, "%d", port); if (getaddrinfo(server, port_str, &hints, &res) != 0) { perror("getaddrinfo"); exit(1); } for (r = res; r != NULL; r = r->ai_next) { client_fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); if (client_fd == -1) continue; if (connect(client_fd, r->ai_addr, r->ai_addrlen) == 0) break; close(client_fd); } freeaddrinfo(res); if (r == NULL) { fprintf(stderr, "Could not connect to: [%s]:%s\n", server, port_str); exit(1); } flags = fcntl(client_fd, F_GETFL); if (flags == -1) { perror("fcntl F_GETFL"); exit(1); } flags = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); if (flags == -1) { perror("fcntl F_SETFL O_NONBLOCK"); exit(1); } parser = usbredirparser_create(); if (!parser) { exit(1); } parser->log_func = usbredirtestclient_log; parser->read_func = usbredirtestclient_read; parser->write_func = usbredirtestclient_write; parser->hello_func = usbredirtestclient_hello; parser->device_connect_func = usbredirtestclient_device_connect; parser->device_disconnect_func = usbredirtestclient_device_disconnect; parser->interface_info_func = usbredirtestclient_interface_info; parser->ep_info_func = usbredirtestclient_ep_info; parser->configuration_status_func = usbredirtestclient_configuration_status; parser->alt_setting_status_func = usbredirtestclient_alt_setting_status; parser->iso_stream_status_func = usbredirtestclient_iso_stream_status; parser->interrupt_receiving_status_func = usbredirtestclient_interrupt_receiving_status; parser->bulk_streams_status_func = usbredirtestclient_bulk_streams_status; parser->control_packet_func = usbredirtestclient_control_packet; parser->bulk_packet_func = usbredirtestclient_bulk_packet; parser->iso_packet_func = usbredirtestclient_iso_packet; parser->interrupt_packet_func = usbredirtestclient_interrupt_packet; usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); usbredirparser_init(parser, TESTCLIENT_VERSION, caps, USB_REDIR_CAPS_SIZE, 0); run_main_loop(); exit(0); } static void usbredirtestclient_cmdline_help(void) { printf("Avaiable commands:\n" "ctrl [data]\n" "quit\n" "help\n"); } static int usbredirtestclient_cmdline_ctrl(void) { struct usb_redir_control_packet_header control_packet; char *arg, *endptr = NULL; uint8_t *data = NULL; int data_len; arg = strtok(NULL, " \t\n"); if (arg) { control_packet.endpoint = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid endpoint\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.request = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid request\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.requesttype = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid request type\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.value = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid value\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.index = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid index\n"); return 0; } arg = strtok(NULL, " \t\n"); if (arg) { control_packet.length = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid length\n"); return 0; } if (!(control_packet.endpoint & 0x80)) { int i; data = malloc(control_packet.length); if (!data) { fprintf(stderr, "Out of memory!\n"); close(client_fd); client_fd = -1; return 0; } for (i = 0; i < control_packet.length; i++) { arg = strtok(NULL, " \t\n"); if (arg) { data[i] = strtol(arg, &endptr, 0); } if (!arg || *endptr != '\0') { printf("Missing or invalid data byte(s)\n"); return 0; } } data_len = control_packet.length; } else { data_len = 0; } usbredirparser_send_control_packet(parser, id, &control_packet, data, data_len); free(data); printf("Send control packet with id: %u\n", id); id++; return 1; } static void usbredirtestclient_cmdline_parse(void) { char buf[128]; char *cmd; while (running && client_fd != -1) { printf("> "); if (!fgets(buf, sizeof(buf), stdin)) { close(client_fd); client_fd = -1; return; } cmd = strtok(buf, " \t\n"); if (!cmd) continue; if (!strcmp(cmd, "help")) { usbredirtestclient_cmdline_help(); } else if (!strcmp(cmd, "quit")) { close(client_fd); client_fd = -1; return; } else if (!strcmp(cmd, "ctrl")) { if (usbredirtestclient_cmdline_ctrl()) { return; /* Run main loop until an answer is received */ } } else { printf("unknown command: '%s', type 'help' for help\n", cmd); } } } static void usbredirtestclient_device_connect(void *priv, struct usb_redir_device_connect_header *device_connect) { switch (device_connect->speed) { case usb_redir_speed_low: printf("device info: speed: low\n"); break; case usb_redir_speed_full: printf("device info: speed: full\n"); break; case usb_redir_speed_high: printf("device info: speed: high\n"); break; case usb_redir_speed_super: printf("device info: speed: super\n"); break; default: printf("device info: speed: unknown\n"); } printf(" class %2d subclass %2d protocol %2d\n", device_connect->device_class, device_connect->device_subclass, device_connect->device_protocol); printf(" vendor 0x%04x product %04x\n", device_connect->vendor_id, device_connect->product_id); } static void usbredirtestclient_device_disconnect(void *priv) { printf("device disconnected"); close(client_fd); client_fd = -1; } static void usbredirtestclient_interface_info(void *priv, struct usb_redir_interface_info_header *info) { int i; for (i = 0; i < info->interface_count; i++) { printf("interface %d class %2d subclass %2d protocol %2d\n", info->interface[i], info->interface_class[i], info->interface_subclass[i], info->interface_protocol[i]); } } static void usbredirtestclient_ep_info(void *priv, struct usb_redir_ep_info_header *ep_info) { int i; for (i = 0; i < 32; i++) { if (ep_info->type[i] != usb_redir_type_invalid) { printf("endpoint: %02X, type: %d, interval: %d, interface: %d max-packetsize: %d\n", I2EP(i), (int)ep_info->type[i], (int)ep_info->interval[i], (int)ep_info->interface[i], ep_info->max_packet_size[i]); } } } static void usbredirtestclient_configuration_status(void *priv, uint64_t id, struct usb_redir_configuration_status_header *config_status) { struct usb_redir_set_configuration_header set_config; struct usb_redir_get_alt_setting_header get_alt; switch (id) { case get_config_id: printf("Get config: %d, status: %d\n", config_status->configuration, config_status->status); set_config.configuration = config_status->configuration; usbredirparser_send_set_configuration(parser, set_config_id, &set_config); break; case set_config_id: printf("Set config: %d, status: %d\n", config_status->configuration, config_status->status); get_alt.interface = 0; /* Assume the device has an interface 0 */ usbredirparser_send_get_alt_setting(parser, get_alt_id, &get_alt); break; default: fprintf(stderr, "Unexpected configuration status packet, id: %" PRIu64"\n", id); } } static void usbredirtestclient_alt_setting_status(void *priv, uint64_t id, struct usb_redir_alt_setting_status_header *alt_setting_status) { struct usb_redir_set_alt_setting_header set_alt; switch (id) { case get_alt_id: printf("Get alt: %d, interface: %d, status: %d\n", alt_setting_status->alt, alt_setting_status->interface, alt_setting_status->status); set_alt.interface = alt_setting_status->interface; set_alt.alt = alt_setting_status->alt; usbredirparser_send_set_alt_setting(parser, set_alt_id, &set_alt); break; case set_alt_id: printf("Set alt: %d, interface: %d, status: %d\n", alt_setting_status->alt, alt_setting_status->interface, alt_setting_status->status); /* Auto tests done, go interactive */ usbredirtestclient_cmdline_parse(); break; default: fprintf(stderr, "Unexpected alt status packet, id: %"PRIu64"\n", id); } } static void usbredirtestclient_iso_stream_status(void *priv, uint64_t id, struct usb_redir_iso_stream_status_header *iso_stream_status) { } static void usbredirtestclient_interrupt_receiving_status(void *priv, uint64_t id, struct usb_redir_interrupt_receiving_status_header *interrupt_receiving_status) { } static void usbredirtestclient_bulk_streams_status(void *priv, uint64_t id, struct usb_redir_bulk_streams_status_header *bulk_streams_status) { } static void usbredirtestclient_control_packet(void *priv, uint64_t id, struct usb_redir_control_packet_header *control_packet, uint8_t *data, int data_len) { int i; printf("Control packet id: %"PRIu64", status: %d", id, control_packet->status); if (data_len) { printf(", data:"); } for (i = 0; i < data_len; i++) { printf(" %02X", (unsigned int)data[i]); } printf("\n"); usbredirparser_free_packet_data(parser, data); /* Ask what to send next */ usbredirtestclient_cmdline_parse(); } static void usbredirtestclient_bulk_packet(void *priv, uint64_t id, struct usb_redir_bulk_packet_header *bulk_packet, uint8_t *data, int data_len) { } static void usbredirtestclient_iso_packet(void *priv, uint64_t id, struct usb_redir_iso_packet_header *iso_packet, uint8_t *data, int data_len) { } static void usbredirtestclient_interrupt_packet(void *priv, uint64_t id, struct usb_redir_interrupt_packet_header *interrupt_packet, uint8_t *data, int data_len) { }