b7461adfe95a4b424cf14db82047240dfb17b97e
[project/usbmode.git] / main.c
1 #include <stdio.h>
2 #include <getopt.h>
3 #include <stdbool.h>
4 #include <ctype.h>
5
6 #include <libubox/blobmsg_json.h>
7 #include <libubox/avl.h>
8 #include <libubox/avl-cmp.h>
9 #include "switch.h"
10
11 #define DEFAULT_CONFIG "/etc/usb-mode.json"
12
13 struct device {
14         struct avl_node avl;
15         struct blob_attr *data;
16 };
17
18 static int verbose = 0;
19 static const char *config_file = DEFAULT_CONFIG;
20 static struct blob_buf conf;
21
22 struct blob_attr **messages = NULL;
23 int n_messages = 0;
24
25 static struct avl_tree devices;
26
27 static struct libusb_context *usb;
28 static struct libusb_device **usbdevs;
29 static int n_usbdevs;
30
31 static int hex2num(char c)
32 {
33         if (c >= '0' && c <= '9')
34                 return c - '0';
35
36         c = toupper(c);
37         if (c >= 'A' && c <= 'F')
38                 return c - 'A' + 10;
39
40         return -1;
41 }
42
43 static int hex2byte(const char *hex)
44 {
45         int a, b;
46
47         a = hex2num(*hex++);
48         if (a < 0)
49                 return -1;
50
51         b = hex2num(*hex++);
52         if (b < 0)
53                 return -1;
54
55         return (a << 4) | b;
56 }
57
58 static int hexstr2bin(const char *hex, char *buffer, int len)
59 {
60         const char *ipos = hex;
61         char *opos = buffer;
62         int i, a;
63
64         for (i = 0; i < len; i++) {
65                 a = hex2byte(ipos);
66                 if (a < 0)
67                         return -1;
68
69                 *opos++ = a;
70                 ipos += 2;
71         }
72
73         return 0;
74 }
75
76 static bool convert_message(struct blob_attr *attr)
77 {
78         char *data;
79         int len;
80
81         if (!attr)
82                 return true;
83
84         data = blobmsg_data(attr);
85         len = strlen(data);
86         if (len % 2)
87                 return false;
88
89         return !hexstr2bin(data, data, len / 2);
90 }
91
92 static int parse_config(void)
93 {
94         enum {
95                 CONF_MESSAGES,
96                 CONF_DEVICES,
97                 __CONF_MAX
98         };
99         static const struct blobmsg_policy policy[__CONF_MAX] = {
100                 [CONF_MESSAGES] = { .name = "messages", .type = BLOBMSG_TYPE_ARRAY },
101                 [CONF_DEVICES] = { .name = "devices", .type = BLOBMSG_TYPE_TABLE },
102         };
103         struct blob_attr *tb[__CONF_MAX];
104         struct blob_attr *cur;
105         struct device *dev;
106         int rem;
107
108         blobmsg_parse(policy, __CONF_MAX, tb, blob_data(conf.head), blob_len(conf.head));
109         if (!tb[CONF_MESSAGES] || !tb[CONF_DEVICES]) {
110                 fprintf(stderr, "Configuration incomplete\n");
111                 return -1;
112         }
113
114         blobmsg_for_each_attr(cur, tb[CONF_MESSAGES], rem)
115                 n_messages++;
116
117         messages = calloc(n_messages, sizeof(*messages));
118         n_messages = 0;
119         blobmsg_for_each_attr(cur, tb[CONF_MESSAGES], rem) {
120                 if (!convert_message(cur)) {
121                         fprintf(stderr, "Invalid data in message %d\n", n_messages);
122                         return -1;
123                 }
124                 messages[n_messages++] = cur;
125         }
126
127         blobmsg_for_each_attr(cur, tb[CONF_DEVICES], rem) {
128             dev = calloc(1, sizeof(*dev));
129             dev->avl.key = blobmsg_name(cur);
130             dev->data = cur;
131             avl_insert(&devices, &dev->avl);
132         }
133
134         return 0;
135 }
136
137 static int usage(const char *prog)
138 {
139         fprintf(stderr, "Usage: %s <command> <options>\n"
140                 "Commands:\n"
141                 "       -l              List matching devices\n"
142                 "       -s              Modeswitch matching devices\n"
143                 "\n"
144                 "Options:\n"
145                 "       -v              Verbose output\n"
146                 "       -c <file>       Set configuration file to <file> (default: %s)\n"
147                 "\n", prog, DEFAULT_CONFIG);
148         return 1;
149 }
150
151 typedef void (*cmd_cb_t)(struct usbdev_data *data);
152
153 static struct blob_attr *
154 find_dev_data(struct usbdev_data *data, struct device *dev)
155 {
156         struct blob_attr *cur;
157         int rem;
158
159         blobmsg_for_each_attr(cur, dev->data, rem) {
160                 const char *name = blobmsg_name(cur);
161                 const char *next;
162                 char *val;
163
164                 if (!strcmp(blobmsg_name(cur), "*"))
165                         return cur;
166
167                 next = strchr(name, '=');
168                 if (!next)
169                         continue;
170
171                 next++;
172                 if (!strncmp(name, "uMa", 3)) {
173                         val = data->mfg;
174                 } else if (!strncmp(name, "uPr", 3)) {
175                         val = data->prod;
176                 } else if (!strncmp(name, "uSe", 3)) {
177                         val = data->serial;
178                 } else {
179                         /* ignore unsupported scsi attributes */
180                         return cur;
181                 }
182
183                 if (!strcmp(val, next))
184                         return cur;
185         }
186
187         return NULL;
188 }
189
190 static void
191 parse_interface_config(libusb_device *dev, struct usbdev_data *data)
192 {
193         struct libusb_config_descriptor *config;
194         const struct libusb_interface *iface;
195         const struct libusb_interface_descriptor *alt;
196         int i;
197
198         data->interface = -1;
199         if (libusb_get_config_descriptor(dev, 0, &config))
200                 return;
201
202         data->config = config;
203         if (!config->bNumInterfaces)
204                 return;
205
206         iface = &config->interface[0];
207         if (!iface->num_altsetting)
208                 return;
209
210         alt = &iface->altsetting[0];
211         data->interface = alt->bInterfaceNumber;
212
213         for (i = 0; i < alt->bNumEndpoints; i++) {
214                 const struct libusb_endpoint_descriptor *ep = &alt->endpoint[i];
215                 bool out = false;
216
217                 if (data->msg_endpoint && data->response_endpoint)
218                         break;
219
220                 if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) !=
221                     LIBUSB_TRANSFER_TYPE_BULK)
222                         continue;
223
224                 out = (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) ==
225                       LIBUSB_ENDPOINT_OUT;
226
227                 if (!data->msg_endpoint && out)
228                         data->msg_endpoint = ep->bEndpointAddress;
229                 if (!data->response_endpoint && !out)
230                         data->response_endpoint = ep->bEndpointAddress;
231         }
232 }
233
234 static void iterate_devs(cmd_cb_t cb)
235 {
236         struct usbdev_data data;
237         struct device *dev;
238         int i;
239
240         if (!cb)
241                 return;
242
243         for (i = 0; i < n_usbdevs; i++) {
244                 memset(&data, 0, sizeof(data));
245
246                 if (libusb_get_device_descriptor(usbdevs[i], &data.desc))
247                         continue;
248
249                 sprintf(data.idstr, "%04x:%04x", data.desc.idVendor, data.desc.idProduct);
250
251                 dev = avl_find_element(&devices, data.idstr, dev, avl);
252                 if (!dev)
253                         continue;
254
255                 if (libusb_open(usbdevs[i], &data.devh))
256                         continue;
257
258                 libusb_get_string_descriptor_ascii(
259                         data.devh, data.desc.iManufacturer,
260                         (void *) data.mfg, sizeof(data.mfg));
261                 libusb_get_string_descriptor_ascii(
262                         data.devh, data.desc.iProduct,
263                         (void *) data.prod, sizeof(data.prod));
264                 libusb_get_string_descriptor_ascii(
265                         data.devh, data.desc.iSerialNumber,
266                         (void *) data.serial, sizeof(data.serial));
267
268                 parse_interface_config(usbdevs[i], &data);
269
270                 data.info = find_dev_data(&data, dev);
271                 if (data.info)
272                         cb(&data);
273
274                 if (data.config)
275                         libusb_free_config_descriptor(data.config);
276
277                 libusb_close(data.devh);
278         }
279 }
280
281 static void handle_list(struct usbdev_data *data)
282 {
283         fprintf(stderr, "Found device: %s (Manufacturer: \"%s\", Product: \"%s\", Serial: \"%s\")\n",
284                 data->idstr, data->mfg, data->prod, data->serial);
285 }
286
287 int main(int argc, char **argv)
288 {
289         cmd_cb_t cb = NULL;
290         int ret;
291         int ch;
292
293         avl_init(&devices, avl_strcmp, false, NULL);
294
295         while ((ch = getopt(argc, argv, "lsc:v")) != -1) {
296                 switch (ch) {
297                 case 'l':
298                         cb = handle_list;
299                         break;
300                 case 's':
301                         cb = handle_switch;
302                         break;
303                 case 'c':
304                         config_file = optarg;
305                         break;
306                 case 'v':
307                         verbose++;
308                         break;
309                 default:
310                         return usage(argv[0]);
311                 }
312         }
313
314         blob_buf_init(&conf, 0);
315         if (!blobmsg_add_json_from_file(&conf, config_file) ||
316             parse_config()) {
317                 fprintf(stderr, "Failed to load config file\n");
318                 return 1;
319         }
320
321         ret = libusb_init(&usb);
322         if (ret) {
323                 fprintf(stderr, "Failed to initialize libusb: %s\n", libusb_error_name(ret));
324                 return 1;
325         }
326
327         n_usbdevs = libusb_get_device_list(usb, &usbdevs);
328         iterate_devs(cb);
329         libusb_free_device_list(usbdevs, 1);
330         libusb_exit(usb);
331
332         return 0;
333 }