/*
 * 15. Oct. 2008 - Now with iPod touch/iPhone support
 */

/*
 * Copyright (c) 2008, Henrik 'hrkfrd' Friedrichsen
 * http://50hz.ws
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the names of the company nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY <Henrik Friedrichsen> ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <Henrik Friedrichsen> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This is to retrieve the iSerialNumber string, needed by libgpod to generate a valid iTunesDB
 * for some iPod models.
 * Simply provide the usb bus (e.g. /dev/usb1) your iPod is attached to.
 */

#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <dev/usb/usb.h>

#if defined(__FreeBSD__)
#include <sys/ioctl.h>
#endif

int
get_desc(int fd, int addr, int type, int index, int len, void* buf, int langid)
{
	struct usb_ctl_request req;
	memset(&req, 0, sizeof(struct usb_ctl_request));
	memset(&req.ucr_request, 0, sizeof(usb_device_request_t));

	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;

	USETW2(req.ucr_request.wValue, type, index);
	USETW(req.ucr_request.wIndex, langid);
	USETW(req.ucr_request.wLength, len);

	req.ucr_addr = addr;
	req.ucr_data = buf;
	req.ucr_flags = USBD_SHORT_XFER_OK;

	if(ioctl(fd, USB_REQUEST, &req)) {
		fprintf(stderr, "error in get_desc(): %s\n", strerror(errno));
		return 0;
	}

	return req.ucr_actlen;
}

void
dumpdev(int fd, int addr)
{
	int langid = 0;
	struct usb_device_info di;
	unsigned i;
	char* s, *buf;

	usb_device_descriptor_t dd;
	usb_string_descriptor_t sd;

	memset(&di, 0, sizeof(struct usb_device_info));
	memset(&dd, 0, sizeof(usb_device_descriptor_t));
	memset(&sd, 0, sizeof(usb_string_descriptor_t));

	di.udi_addr = addr;
	if(ioctl(fd, USB_DEVICEINFO, &di)) {
		close(fd);
		exit(EXIT_FAILURE);
	}

	if(di.udi_vendorNo != 0x05ac) // dunno if udi_productNo changes depending on the ipod model, so vendor check should suffice
		return;

	printf("[%i:%i] %s - %s\n", di.udi_bus, di.udi_addr, di.udi_vendor, di.udi_product);


	get_desc(fd, addr, UDESC_DEVICE, 0, USB_DEVICE_DESCRIPTOR_SIZE, (void*)&dd, 0);
	
	i = get_desc(fd, addr, UDESC_STRING, USB_LANGUAGE_TABLE, sizeof(usb_string_descriptor_t), &sd, 0);
	if(i >= 4) {
		langid = UGETW(sd.bString[0]);
	}

	get_desc(fd, addr, UDESC_STRING, dd.iSerialNumber, 2, (void*)&sd, langid);
	get_desc(fd, addr, UDESC_STRING, dd.iSerialNumber, sd.bLength, (void*)&sd, langid);

	s = buf = malloc(sd.bLength);
	for(i = 0; i < sd.bLength; i++) {
		char c = UGETW(sd.bString[i]);
		if((c & 0xff00) == 0)
			*s++ = c;
		else if((c & 0x00f) == 0)
			*s++ = c >> 8;
		else
			*s++ = '?';
	}

	*s++ = 0;
	printf("iSerialNumber: %s\n", buf);
	free(buf);
}

int
main(int argc, char* argv[])
{
	unsigned i;

	if(argc < 2) {
		fprintf(stderr, "usage: %s <device>\n", argv[0]);
		return EXIT_FAILURE;
	}

	int fd = open(argv[1], O_RDONLY);
	if(fd == -1) {
		fprintf(stderr, "could not open %s (%s)\n", argv[1], strerror(errno));
		return EXIT_FAILURE;
	}

	for(i = 1; i < USB_MAX_DEVICES; i++)
		dumpdev(fd, i);

	close(fd);
}
