/* Name: opendevice.c
 * Project: V-USB host-side library
 * Author: Christian Starkjohann
 * Creation Date: 2008-04-10
 * Tabsize: 4
 * Copyright: (c) 2008 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: GNU GPL v2 (see License.txt), GNU GPL v3 or proprietary (CommercialLicense.txt)
 * This Revision: $Id: opendevice.c 740 2009-04-13 18:23:31Z cs $
 */

/*
General Description:
The functions in this module can be used to find and open a device based on
libusb or libusb-win32.
*/

#include <stdio.h>
#include <ctype.h>
#include "opendevice.h"

/* ------------------------------------------------------------------------- */

#define MATCH_SUCCESS           1
#define MATCH_FAILED            0
#define MATCH_ABORT             -1

/* private interface: match text and p, return MATCH_SUCCESS, MATCH_FAILED, or MATCH_ABORT. */
static int _shellStyleMatch(char *text, char *p)
{
	int last, matched, reverse;

	for (; *p; text++, p++) {
		if (*text == 0 && *p != '*')
			return MATCH_ABORT;
		switch (*p) {
		case '\\':
			/* Literal match with following character. */
			p++;
			/* FALLTHROUGH */
		default:
			if (*text != *p)
				return MATCH_FAILED;
			continue;
		case '?':
			/* Match anything. */
			continue;
		case '*':
			while (*++p == '*')
				/* Consecutive stars act just like one. */
				continue;
			if (*p == 0)
				/* Trailing star matches everything. */
				return MATCH_SUCCESS;
			while (*text)
				if ((matched = _shellStyleMatch(text++, p)) != MATCH_FAILED)
					return matched;
			return MATCH_ABORT;
		case '[':
			reverse = p[1] == '^';
			if (reverse)		/* Inverted character class. */
				p++;
			matched = MATCH_FAILED;
			if (p[1] == ']' || p[1] == '-')
				if (*++p == *text)
					matched = MATCH_SUCCESS;
			for (last = *p; *++p && *p != ']'; last = *p)
				if (*p == '-' && p[1] != ']' ? *text <= *++p && *text >= last : *text == *p)
					matched = MATCH_SUCCESS;
			if (matched == reverse)
				return MATCH_FAILED;
			continue;
		}
	}
	return *text == 0;
}

/* public interface for shell style matching: returns 0 if fails, 1 if matches */
static int shellStyleMatch(char *text, char *pattern)
{
	if (pattern == NULL)		/* NULL pattern is synonymous to "*" */
		return 1;
	return _shellStyleMatch(text, pattern) == MATCH_SUCCESS;
}

/* ------------------------------------------------------------------------- */

int usbGetStringAscii(usb_dev_handle * dev, int index, char *buf, int buflen)
{
	char buffer[256];
	int rval, i;

	if ((rval = usb_get_string_simple(dev, index, buf, buflen)) >= 0) {
		/* use libusb version if it works */
		return rval;
	}

	rval = usb_control_msg(dev, USB_ENDPOINT_IN,
						   USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index,
						   0x0409, buffer, sizeof(buffer), 5000);

	if (rval < 0) {
		return rval;
	}

	if (buffer[1] != USB_DT_STRING) {
		*buf = 0;
		return 0;
	}

	if ((unsigned char) buffer[0] < rval)
		rval = (unsigned char) buffer[0];
	rval /= 2;

	/* lossy conversion to ISO Latin1: */
	for (i = 1; i < rval; i++) {
		if (i > buflen)			/* destination buffer overflow */
			break;
		buf[i - 1] = buffer[2 * i];
		if (buffer[2 * i + 1] != 0)	/* outside of ISO Latin1 range */
			buf[i - 1] = '?';
	}
	buf[i - 1] = 0;
	return i - 1;
}

/* ------------------------------------------------------------------------- */

int usbOpenDevice(usb_dev_handle ** device, int vendorID, char *vendorNamePattern, int productID,
				  char *productNamePattern, char *serialNamePattern, FILE * printMatchingDevicesFp,
				  FILE * warningsFp)
{
	struct usb_bus *bus;
	struct usb_device *dev;
	usb_dev_handle *handle = NULL;
	int errorCode = USBOPEN_ERR_NOTFOUND;
	int retry;

	usb_find_busses();
	usb_find_devices();
	for (bus = usb_get_busses(); bus; bus = bus->next) {
		for (dev = bus->devices; dev; dev = dev->next) {	/* iterate over all devices on all busses */
			if ((vendorID == 0 || dev->descriptor.idVendor == vendorID)
				&& (productID == 0 || dev->descriptor.idProduct == productID)) {
				char vendor[256], product[256], serial[256];
				int len;

				handle = usb_open(dev);	/* we need to open the device in order to query strings */
				if (!handle) {
					errorCode = USBOPEN_ERR_ACCESS;
					if (warningsFp != NULL)
						fprintf(warningsFp, "Warning: cannot open VID=%04X PID=%04X: %s\n",
								dev->descriptor.idVendor, dev->descriptor.idProduct,
								usb_strerror());
					continue;
				}
				for (retry = 0; retry < 3; retry++) {
					/* now check whether the names match: */
					len = vendor[0] = 0;
					if (dev->descriptor.iManufacturer > 0) {
						len =
							usbGetStringAscii(handle, dev->descriptor.iManufacturer, vendor,
											  sizeof(vendor));
					}
					if (len > 0) {
						break;
					} else if (retry < 3) {
						Sleep(500);
						continue;
					}
					errorCode = USBOPEN_ERR_ACCESS;
					if (warningsFp != NULL) {
						fprintf(warningsFp,
								"VID=%04X PID=%04X (Warning: cannot query manufacturer).\n",
								dev->descriptor.idVendor, dev->descriptor.idProduct);
						fprintf(warningsFp, "--> %s\n", usb_strerror());
					}
				}

				if (len > 0) {
					errorCode = USBOPEN_ERR_NOTFOUND;
					/* printf("seen device from vendor ->%s<-\n", vendor); */
					if (shellStyleMatch(vendor, vendorNamePattern)) {
						len = product[0] = 0;
						if (dev->descriptor.iProduct > 0) {
							len =
								usbGetStringAscii(handle, dev->descriptor.iProduct, product,
												  sizeof(product));
						}
						if (len < 0) {
							errorCode = USBOPEN_ERR_ACCESS;
							if (warningsFp != NULL)
								fprintf(warningsFp,
										"Warning: cannot query product for VID=%04X PID=%04X: %s\n",
										dev->descriptor.idVendor, dev->descriptor.idProduct,
										usb_strerror());
								fprintf(warningsFp,
										"Warning: cannot query product for VID=%04X PID=%04X: %s\n",
										dev->descriptor.idVendor, dev->descriptor.idProduct,
										usb_strerror());
						} else {
							errorCode = USBOPEN_ERR_NOTFOUND;
							/* printf("seen product ->%s<-\n", product); */
							if (shellStyleMatch(product, productNamePattern)) {
								len = serial[0] = 0;
								if (dev->descriptor.iSerialNumber > 0) {
									len =
										usbGetStringAscii(handle, dev->descriptor.iSerialNumber,
														  serial, sizeof(serial));
								}
								if (len < 0) {
									errorCode = USBOPEN_ERR_ACCESS;
									if (warningsFp != NULL)
										fprintf(warningsFp,
												"Warning: cannot query serial for VID=%04X PID=%04X: %s\n",
												dev->descriptor.idVendor, dev->descriptor.idProduct,
												usb_strerror());
								}
								if (shellStyleMatch(serial, serialNamePattern)) {
									if (printMatchingDevicesFp != NULL) {
										if (serial[0] == 0) {
											fprintf(printMatchingDevicesFp,
													"VID=%04X PID=%04X Desc=\"%s\",\"%s\"\n",
													dev->descriptor.idVendor,
													dev->descriptor.idProduct, vendor, product);
										} else {
#if 1
											char upper_serial[256];
											int i, lower;

											lower = 0;
											for (i = 0; serial[i]; i++) {
												if (isalpha(serial[i])) {
													if (islower(serial[i])) {
														lower++;
													}
												}
											}
											if (lower) {
												strcpy(upper_serial, serial);
												strupr(upper_serial);
												fprintf(printMatchingDevicesFp,
														"VID=%04X PID=%04X Desc=\"%s\",\"%s\" serial=\"%s\",(%s)\n",
														dev->descriptor.idVendor,
														dev->descriptor.idProduct, vendor, product,
														upper_serial, serial);
											} else {
												fprintf(printMatchingDevicesFp,
														"VID=%04X PID=%04X Desc=\"%s\",\"%s\" serial=\"%s\"\n",
														dev->descriptor.idVendor,
														dev->descriptor.idProduct, vendor, product,
														serial);
											}

#else
											fprintf(printMatchingDevicesFp,
													"VID=%04X PID=%04X vendor=\"%s\" product=\"%s\" serial=\"%s\"\n",
													dev->descriptor.idVendor,
													dev->descriptor.idProduct, vendor, product,
													serial);
#endif
										}
									} else {
										break;
									}
								}
							}
						}
					}
				}
				usb_close(handle);
				handle = NULL;
			}
		}
		if (handle)				/* we have found a deice */
			break;
	}
	if (handle != NULL) {
		errorCode = 0;
		*device = handle;
	}
	if (printMatchingDevicesFp != NULL)	/* never return an error for listing only */
		errorCode = 0;
	return errorCode;
}

/* ------------------------------------------------------------------------- */
