/*
 * snoopdecode.c
 *
 * Decodes traces captured by 'usbsnoop'
 * (http://benoit.papillault.free.fr/usbsnoop/index.php)
 *
 * See usbsnoop.txt for details.
 */

/*
 * Copyright (C) 2004-2005 Adam Kropelin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * 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 program; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1335, USA.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_LINE 1024

char line[MAX_LINE] = "";
FILE* file;

enum
{
	DIR_UNKNOWN = 0,
	DIR_TO_DEVICE,
	DIR_FROM_DEVICE
};

enum
{
	FUNC_UNKNOWN = 0,
	FUNC_CONTROL_TXFER,
	FUNC_CLASS_INTERFACE,
	FUNC_GET_DESC,
	FUNC_SELECT_CONFIG,
	FUNC_GET_DESC_FROM_IFACE,
	FUNC_BULK_TXFER,
	FUNC_RESET_PIPE,
	FUNC_ABORT_PIPE,
};

enum
{
	REQ_GET_REPORT = 1,
	REQ_GET_IDLE = 2,
	REQ_GET_PROTOCOL = 3,
	REQ_SET_REPORT = 9,
	REQ_SET_IDLE = 10,
	REQ_SET_PROTOCOL = 11
};

struct urb
{
	int seq;
	unsigned long len;
	unsigned char* data;
	unsigned long flags;
	unsigned long index;
	unsigned long type;
	unsigned long reserved;
	unsigned long request;
	unsigned long value;
	int time;
	int dir;
	int func;
};

struct rpt
{
	struct rpt* next;
	unsigned char* data;
	int seq;
	unsigned short len;
	unsigned char id;
	unsigned long value;
};

struct rpt* rpts = NULL;

/*
[197 ms]  >>>  URB 1 going down  >>> 
-- URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE:
  TransferBufferLength = 00000012
  TransferBuffer       = 864c9208
  TransferBufferMDL    = 00000000
  Index                = 00000000
  DescriptorType       = 00000001 (USB_DEVICE_DESCRIPTOR_TYPE)
  LanguageId           = 00000000
[203 ms] UsbSnoop - MyInternalIOCTLCompletion(f7b91db0) : fido=00000000, Irp=863df850, Context=86405d10, IRQL=2

[203 ms]  <<<  URB 1 coming back  <<< 
-- URB_FUNCTION_CONTROL_TRANSFER:
  PipeHandle           = 863e4150
  TransferFlags        = 0000000b (USBD_TRANSFER_DIRECTION_IN, USBD_SHORT_TRANSFER_OK)
  TransferBufferLength = 00000012
  TransferBuffer       = 864c9208
  TransferBufferMDL    = 8640f108
    00000000: 12 01 10 01 00 00 00 08 1d 05 02 00 06 00 03 01
    00000010: 02 01
  UrbLink              = 00000000
*/

/*
[59563 ms]  >>>  URB 346 going down  >>> 
-- URB_FUNCTION_CLASS_INTERFACE:
  TransferFlags          = 00000001 (USBD_TRANSFER_DIRECTION_IN, ~USBD_SHORT_TRANSFER_OK)
  TransferBufferLength = 00000005
  TransferBuffer       = f7f12ba0
  TransferBufferMDL    = 00000000
  UrbLink                 = 00000000
  RequestTypeReservedBits = 00000022
  Request                 = 00000001
  Value                   = 0000032f
  Index                   = 00000000
[59567 ms] UsbSnoop - MyInternalIOCTLCompletion(f7b91db0) : fido=86397288, Irp=85ff4b40, Context=85f4b3d8, IRQL=2
[59567 ms]  <<<  URB 346 coming back  <<< 
-- URB_FUNCTION_CONTROL_TRANSFER:
  PipeHandle           = 863e4150
  TransferFlags        = 0000000b (USBD_TRANSFER_DIRECTION_IN, USBD_SHORT_TRANSFER_OK)
  TransferBufferLength = 00000002
  TransferBuffer       = f7f12ba0
  TransferBufferMDL    = 85f102f0
    00000000: 2f 02
  UrbLink              = 00000000
  SetupPacket          =
    00000000: a1 01 2f 03 00 00 05 00
*/

int fetch_line()
{
	return !!fgets(line, sizeof(line), file);
}

int find_urb()
{
	do
	{
		if (line[0] == '[' && strstr(line, " URB ") &&
		   (strstr(line, "going down") || strstr(line, "coming back")))
		{
			return 1;
		}
	}
	while( fetch_line() );
	
	return 0;
}

int init_urb(struct urb* urb)
{
	memset(urb, 0, sizeof(*urb));
	
	if (!find_urb())
		return 0;
	
	urb->time = atoi(line+1);
	
	if (strstr(line, "going down"))
		urb->dir = DIR_TO_DEVICE;
	else if(strstr(line, "coming back"))
		urb->dir = DIR_FROM_DEVICE;
	else
		urb->dir = DIR_UNKNOWN;
		
	urb->seq = atoi(strstr(line,"URB ")+4);

	return 1;
}

unsigned long get_hex_value()
{
	char* ptr = strchr(line, '=');
	if (!ptr)
		return 0xffff;

	return strtoul(ptr+2, NULL, 16);
}

unsigned char* parse_data_dump(int len)
{
	int count = 0;
	char* ptr= NULL;
	
	unsigned char* data = (unsigned char *)malloc(len);	
	if (!data)
		return NULL;
		
	while(count < len)
	{
		if ((count % 16) == 0)
		{
			if (count)
			{
				if (!fetch_line())
				{
					free(data);
					return NULL;
				}
			}

			ptr = strchr(line, ':');
			if (!ptr)
			{
				free(data);
				return NULL;
			}

			ptr += 2;
		}

		data[count++] = strtoul(ptr, NULL, 16);
		ptr += 3;
	}
	
	return data;
}

void parse_urb_body(struct urb* urb)
{
	int setup_packet = 0;

	while (fetch_line())
	{
		if (line[0] == '[')
			break;
			
		if (strstr(line, "TransferBufferLength"))
		{
			urb->len = get_hex_value();
		}
		else if (strstr(line, "TransferFlags"))
		{
			urb->flags = get_hex_value();
		}
		else if (strstr(line, "Index"))
		{
			urb->index = get_hex_value();
		}
		else if (strstr(line, "DescriptorType"))
		{
			urb->type = get_hex_value();
		}
		else if (strstr(line, "RequestTypeReservedBits"))
		{
			urb->reserved = get_hex_value();
		}
		else if (strstr(line, "Request"))
		{
			urb->request = get_hex_value();
		}
		else if (strstr(line, "Value"))
		{
			urb->value = get_hex_value();
		}
		else if (strstr(line, "SetupPacket"))
		{
			setup_packet = 1;
		}
		else if (strstr(line, "00000000:"))
		{
			if (!setup_packet && !urb->data)
				urb->data = parse_data_dump(urb->len);
		}
		else if (strstr(line, "-- URB_FUNCTION_"))
		{
			if (strstr(line, "URB_FUNCTION_CONTROL_TRANSFER"))
				urb->func = FUNC_CONTROL_TXFER;
			else if (strstr(line, "URB_FUNCTION_CLASS_INTERFACE"))
				urb->func = FUNC_CLASS_INTERFACE;
			else if (strstr(line, "URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE"))
				urb->func = FUNC_GET_DESC;
			else if (strstr(line, "URB_FUNCTION_SELECT_CONFIGURATION"))
				urb->func = FUNC_SELECT_CONFIG;
			else if (strstr(line, "URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE"))
				urb->func = FUNC_GET_DESC_FROM_IFACE;
			else if (strstr(line, "URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER"))
				urb->func = FUNC_BULK_TXFER;
			else if (strstr(line, "URB_FUNCTION_RESET_PIPE"))
				urb->func = FUNC_RESET_PIPE;
			else if (strstr(line, "URB_FUNCTION_ABORT_PIPE"))
				urb->func = FUNC_ABORT_PIPE;
			else
			{
				urb->func = FUNC_UNKNOWN;
				printf("Unknown FUNC: %s\n", line);
				exit(0);
			}
		}
	}
}

void free_urb(struct urb* urb)
{
	free(urb->data);
}

void print_urb(struct urb* urb)
{
	unsigned int x;
	printf("[%08d ms] %05d %c ", urb->time, urb->seq,
		(urb->dir == DIR_TO_DEVICE) ? '>' : (urb->dir == DIR_FROM_DEVICE) ? '<' : '?');
		
	printf("req=%04lx value=%04lx", urb->request, urb->value);
	
	if( urb->data )
	{
		printf(" [");
		for(x=0; x<urb->len; x++)
		{
			printf( "%02x", urb->data[x] );
			if (x < urb->len-1)
				printf(" ");
		}
		printf("]");
	}
	
	printf("\n");
}

struct rpt* find_report_by_seq(int seq)
{
	struct rpt* rpt = rpts;

	while( rpt )
	{
		if (rpt->seq == seq)
			break;
			
		rpt = rpt->next;
	}
	
	return rpt;
}

struct rpt* find_report_by_id(unsigned char id)
{
	struct rpt* rpt = rpts;

	while( rpt )
	{
		if (rpt->id == id)
			break;

		rpt = rpt->next;
	}
	
	return rpt;
}

void del_report(struct urb* urb)
{
	unsigned char id = urb->data[0];
	struct rpt* rpt = rpts;
	struct rpt** prev = &rpts;
	
	while( rpt )
	{
		if (rpt->id == id)
			break;
			
		prev = &(rpt->next);
		rpt = rpt->next;
	}
	
	if (rpt)
	{
		*prev = rpt->next;
		free(rpt);
	}
}

void add_report(struct urb* urb)
{
	struct rpt* rpt;
	unsigned char id;

	if (urb->data)
		id = urb->data[0];
	else
		id = urb->value & 0xff;

	if ((rpt = find_report_by_id(id)))
	{
		rpt->seq = urb->seq;
		rpt->value = urb->value;
		return;
	}

	rpt = (struct rpt*)malloc(sizeof(*rpt));
	memset(rpt, 0, sizeof(*rpt));

	rpt->id = id;
	rpt->seq = urb->seq;
	rpt->value = urb->value;

	rpt->next = rpts;
	rpts = rpt;
}

int check_and_update_report(struct urb* urb)
{
	struct rpt* rpt;

	rpt = find_report_by_seq(urb->seq);
	if (!rpt)
		return 0;

	if (rpt->data && !memcmp(rpt->data, urb->data+1, urb->len-1))
		return 0;

	free(rpt->data);
	rpt->len = urb->len-1;
	rpt->data = (unsigned char *)malloc(rpt->len);
	memcpy(rpt->data, urb->data+1, rpt->len);

	return 1;
}

void display_report(struct urb* urb)
{
	unsigned long data = 0;
	char buf[9];
	unsigned int n;

	n = printf( "[%04d s] %05d %c %s 0x%02x (%03u) ",
		urb->time/1000, urb->seq, urb->func == FUNC_BULK_TXFER ? '*' : ' ',
		urb->dir==DIR_TO_DEVICE ? "WRITE" : " READ", urb->data[0], urb->data[0]);

	switch (urb->len)
	{
		case 5:
			data = (data << 8) | urb->data[4];
		case 4:
			data = (data << 8) | urb->data[3];
		case 3:
			data = (data << 8) | urb->data[2];
		case 2:
			data = (data << 8) | urb->data[1];
			sprintf(buf, "%0*lx", (int)(urb->len-1)*2, data);
			n += printf( "%8s (%lu)", buf, data );
			break;
			
		default:
			n += printf( "-------- (----------)" );
			break;
	}

	printf( "%*c", 55-n, ' ');
	for (n=0; n<urb->len-1; n++)
		printf("%02x ", urb->data[n+1]);
	printf("\n");
	fflush(stdout);
}

void update_reports(struct urb* urb)
{
	if (urb->dir == DIR_TO_DEVICE)
	{
		// Only care about class requests
		if (urb->func != FUNC_CLASS_INTERFACE)
			return;

		if (urb->request == REQ_GET_REPORT)
		{
			add_report(urb);
		}
		else if (urb->request == REQ_SET_REPORT)
		{
			display_report(urb);
			del_report(urb);
		}
	}
	else
	{
		if (urb->func == FUNC_CONTROL_TXFER)
		{
			if (check_and_update_report(urb))
				display_report(urb);
		}
		else if (urb->data && urb->func == FUNC_BULK_TXFER)
		{
			add_report(urb);
			if (check_and_update_report(urb))
				display_report(urb);
		}
	}
}

int main(int argc, char* argv[])
{
	struct urb urb;

	file = stdin;

	while(init_urb(&urb))
	{
		parse_urb_body(&urb);
		
		update_reports(&urb);
		
		free_urb(&urb);
	}

	return 0;
}
