/* xmlintr.c Snoopypro USB XML log parser.
 * (C) 2003 Tuukka Toivonen <tuukkat@ee.oulu.fi>
 * Licensed under the GNU General Public License (GPL).
 */
 
/* This is just a quick hack and the code is horrible.
 * Don't complain.
 * Parses successfully logs generated by Labtec camera
 * (HDCS-1020) and generates pgm-images.
 * It is unknown how to get colors for the image.
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

/* {{{ [fold] error() */
void error(char *s) {
	printf("error: %s\n", s);
	exit(1);
}
/* }}} */
/* {{{ [fold] get_length() */
int get_length(int fd) {
	int l;
	l = lseek(fd, 0, SEEK_END);
	if (l==-1) error("seek failed");
	if (lseek(fd, 0, SEEK_SET)==-1) error("seek 2 failed");
	return l;
}
/* }}} */
/* {{{ [fold] print_len() */
void print_len(unsigned char *c, int len) {
	if (c==NULL) return;
	while (len--) {
		if (iscntrl(*c)) {
			printf("<%02X>", *c);
		} else {
			putchar(*c);
		}
		c++;
	}
}
/* }}} */
/* {{{ [fold] print_bin() */
void print_bin(unsigned char *bin, int bin_len) {
//	int i = 0;
	while (bin_len) {
//		printf("[%02X]",i++);
		printf("%02X", *bin);
//		if (isgraph(*bin)) printf("<%c>", *bin);
		printf(" ");
		bin++, bin_len--;
	}
}
/* }}} */
/* {{{ [fold] str_to_bin() */
void str_to_bin(unsigned char *str, int str_len, unsigned char *bin, int bin_len) {
	unsigned char b, c1, c2;
	if (bin_len*2 < str_len) error("too small buffer");
	if ((str_len & 1)!=0) error("need even length");
	while (str_len>0) {
		c1 = toupper(str[0]);
		c2 = toupper(str[1]);
		if (!isxdigit(c1) || !isxdigit(c2)) error("not a hex digit");
		if (c1 >= 'A') c1 += 10 - 'A'; else c1 -= '0';
		if (c2 >= 'A') c2 += 10 - 'A'; else c2 -= '0';
		b = (c1 << 4) + c2;
		*bin++ = b;
		str += 2;
		str_len -= 2;
	}
}
/* }}} */

/* {{{ [fold] struct urb */
//#define DEBUG
#define MAXPAYLOAD 1024
#define MAXPACKETS 10

struct desc {
	int bytes;
	int ident;
} desc;

struct i2c {
	int addr;	/* I2C address */
	int commands;	/* Number of commands */
	int rw;		/* 1 = write, 3 = read */
} i2c;

struct packet {
	int payload_packet;
	int payloadcount0;	/* Reported in its field (buggy?) */
	int payloadcount;	/* Real count */
	unsigned char payloadbytes[MAXPAYLOAD];
};

struct urb {
	int sequence;
	unsigned char *function;
	int function_len;
	int timestamp;
	int endpoint;
	int packetcount;
	int cur_packet;
	struct packet packets[MAXPACKETS];
	struct desc *desc;
	struct i2c *i2c;
} urb;
/* }}} */

/* {{{ [fold] framedata */
#define MAXFRAMESIZE (360*292)
unsigned char frame[MAXFRAMESIZE];
int frame_pos = 0;
int frame_num = 0;
/* }}} */
/* {{{ [fold] qc_frame_end() */
void qc_frame_end(void *qc) {
	static unsigned char header_39680[] = "P5\n160 248\n255\n";
	static unsigned char header_52096[] = "P5\n176 296\n255\n";
	unsigned char *header = NULL;
	unsigned char name[256];
	FILE *f;
	int r;
	if (frame_pos<=0) return;
	if (frame_pos==39680 || frame_pos==52096) {
		/* Recognized size */
		sprintf(name, "xmlframe%03i.pgm", frame_num);
		header = (frame_pos==39680) ? header_39680 : header_52096;
	} else {
		/* Unknown size */
		sprintf(name, "xmlframe%03i.dat", frame_num);
	}
	f = fopen(name, "wb");
	if (f==NULL) error("open for write failed");
	if (header) {
		r = fwrite(header, strlen(header), 1, f);
		if (r!=1) error("header store failed");
	}
	r = fwrite(frame, frame_pos, 1, f);
	if (r!=1) error("store failed");
	fclose(f);
	printf("Saved %s\n", name);
	frame_num++;
	frame_pos = 0;
}
/* }}} */
/* {{{ [fold] qc_frame_begin() */
void qc_frame_begin(void *qc) {
	frame_pos = 0;
}
/* }}} */
/* {{{ [fold] qc_frame_add() */
int qc_frame_add(void *qc, unsigned char *data, int datalen) {
	//printf("qc_frame_add %i bytes\n", datalen);
	if (datalen+frame_pos>=MAXFRAMESIZE) error("too big frame");
	memcpy(&frame[frame_pos], data, datalen);
	frame_pos += datalen;
	return 0;
}
/* }}} */

/* {{{ [fold] qc_stream_data */
typedef int Bool;
#define FALSE 0
#define TRUE (!FALSE)
struct qc_stream_data {
	Bool capturing;			/* Are we capturing data for a frame? */
	int frameskip;			/* How frequently to capture frames? 0=each frame, 1=every other */
} qc_stream_data;
struct qc_stream_data *sd = &qc_stream_data;
int debug=0;
#define PARANOID 0
#define PDEBUG(fmt, args...) do { printf(fmt, ## args); printf("\n"); } while(0)
#define PRINTK(x,fmt, args...) do { printf(fmt, ## args); printf("\n"); } while(0)
#define IDEBUG_INIT(x)
#define IDEBUG_EXIT(x)
#define IDEBUG_TEST(x)
#define QC_DEBUGUSER		(1<<0)	/* Messages for interaction with user space (system calls) */
#define QC_DEBUGCAMERA		(1<<1)	/* Messages for interaction with the camera */
#define QC_DEBUGINIT		(1<<2)	/* Messages for each submodule initialization/deinit */
#define QC_DEBUGLOGIC		(1<<3)	/* Messages for entering and failing important functions */
#define QC_DEBUGERRORS		(1<<4)	/* Messages for all error conditions */
#define QC_DEBUGADAPTATION	(1<<5)	/* Messages for automatic exposure control workings */
#define QC_DEBUGCONTROLURBS	(1<<6)	/* Messages for sending I2C control messages via USB */
#define QC_DEBUGBITSTREAM	(1<<7)	/* Messages for finding chunk codes from camera bitstream */
#define QC_DEBUGINTERRUPTS	(1<<8)	/* Messages for each interrupt */
#define QC_DEBUGMUTEX		(1<<9)	/* Messages for acquiring/releasing the mutex */
#define QC_DEBUGCOMMON		(1<<10)	/* Messages for some common warnings */
#define QC_DEBUGALL		(~0)	/* Messages for everything */
void *qc = NULL;
/* }}} */
/* {{{ [fold] qc_stream_init(struct quickcam *qc) */
/* Initialize datastream processing */
static int qc_stream_init(void)
{
	if (debug&QC_DEBUGLOGIC || debug&QC_DEBUGINIT) PDEBUG("qc_stream_init(quickcam=%p)",qc);
	sd->capturing = FALSE;
	sd->frameskip = 0;
	IDEBUG_INIT(qc->stream_data);
	return 0;
}
/* }}} */
/* {{{ [fold] qc_stream_exit(struct quickcam *qc) */
/* Stop datastream processing, after this qc_stream_add should not be called */
static void qc_stream_exit(void)
{
	if (debug&QC_DEBUGLOGIC || debug&QC_DEBUGINIT) PDEBUG("qc_stream_exit(quickcam=%p)",qc);
	if (sd->capturing)
		qc_frame_end(qc);
	IDEBUG_EXIT(qc->stream_data);
}
/* }}} */
/* {{{ [fold] qc_stream_error(struct quickcam *qc) */
/* This is called when there are data lost due to errors in the stream */
void qc_stream_error(void *qc)
{
	/* Skip rest of data for this frame */
	if (debug&QC_DEBUGERRORS) PDEBUG("qc_stream_error(qc=%p)", qc);
	if (sd->capturing)
		qc_frame_end(qc);
	IDEBUG_EXIT(qc->stream_data);
	qc_stream_init();
}
/* }}} */
/* {{{ [fold] qc_stream_add(struct quickcam *qc, unsigned char *data, int datalen) */
/*
 * Analyse an USB packet of the data stream and store it appropriately.
 * Each packet contains an integral number of chunks. Each chunk has
 * 2-bytes identification, followed by 2-bytes that describe the chunk
 * length. Known/guessed chunk identifications are:
 * 8001/8005/C001/C005 - Begin new frame
 * 8002/8006/C002/C006 - End frame
 * 0200/4200           - Contains actual image data, bayer or compressed
 * 0005                - 11 bytes of unknown data
 * 0100                - 2 bytes of unknown data
 * The 0005 and 0100 chunks seem to appear only in compressed stream.
 * Return the amount of image data received or negative value on error.
 */
static int qc_stream_add(unsigned char *data, int datalen)
{
	int id, len, error, totaldata = 0;
	
	IDEBUG_TEST(*sd);
	while (datalen) {
		if (datalen < 4) {
			if (debug&QC_DEBUGBITSTREAM) PRINTK(KERN_ERR,"missing chunk header");
			break;
		}
		id  = (data[0]<<8) | data[1];
		len = (data[2]<<8) | data[3];
		data    += 4;
		datalen -= 4;
		if (datalen < len) {
			if (debug&QC_DEBUGBITSTREAM) PRINTK(KERN_ERR,"missing chunk contents");
			break;
		}
		switch (id) {
		case 0x8001:
		case 0x8005:
		case 0xC001:
		case 0xC005:
			/* Begin new frame, len should be zero */
			if (PARANOID && len!=0) PDEBUG("New frame: len!=0");
			if (sd->capturing) {
				if (debug&QC_DEBUGBITSTREAM) PDEBUG("Missing frame end mark in stream");
				qc_frame_end(qc);
			}
			sd->capturing = TRUE;
			if (--sd->frameskip < 0) sd->frameskip = 0;
			if (sd->frameskip==0) qc_frame_begin(qc);
			break;
		case 0x8002:
		case 0x8006:
		case 0xC002:
		case 0xC006:
			/* End frame, len should be zero */
			if (PARANOID && len!=0) PDEBUG("End frame: len!=0");
			if (sd->capturing) {
				if (sd->frameskip==0) qc_frame_end(qc);
			} else {
				if (debug&QC_DEBUGBITSTREAM) PDEBUG("Missing frame begin mark in stream");
			}
			sd->capturing = FALSE;
			break;
		case 0x0200:
		case 0x4200:
			/* Image data */
			if (!sd->capturing && (debug&QC_DEBUGBITSTREAM)) PDEBUG("Chunk of data outside frames!");
			if (sd->capturing && sd->frameskip==0) {
				error = qc_frame_add(qc, data, len);
			} else {
				error = 0;
			}
			if (error) {
				/* If qc_frame_add returns error, there is more data than the frame may have,
				 * in which case we assume stream is corrupted and skip rest packet */
				if (debug&QC_DEBUGERRORS) PDEBUG("qc_frame_add error %i",error);
			} else {
				totaldata += len;
			}
			break;
		default:
			/* Unknown chunk */
			#ifndef NDEBUG
			if (debug&QC_DEBUGBITSTREAM) {
				static char dump[4*1024];
				char *dump_p = dump;
				int i;
				for (i=0; i<len && (3*i+9)<sizeof(dump); i++) dump_p+=sprintf(dump_p, "%02X ", data[i]);
				PDEBUG("Unknown chunk %04X: %s", id, dump);
			}
			#endif
		}
		data    += len;
		datalen -= len;
	}
	return totaldata;
}
/* }}} */

/* {{{ [fold] print_urb() */
void print_urb(void) {
	int i;
	printf("Sequence: %i\n", urb.sequence);
	printf("Function: ");
	print_len(urb.function, urb.function_len);
	printf("\n");
	printf("Timestamp: %i\n", urb.timestamp);
	printf("Endpoint: %i\n", urb.endpoint);
	printf("Packetcount: %i\n", urb.packetcount);
	for (i=0; i<=urb.cur_packet; i++) {
		printf("  Payload_packet: %i\n", urb.packets[i].payload_packet);
		if (urb.packets[i].payloadcount0 != urb.packets[i].payloadcount) {
			printf("  Payloadcount:   %i (0x%02X) (buggy?)\n", urb.packets[i].payloadcount0, urb.packets[i].payloadcount0);
		}
		printf("  Payloadcount:   %i (0x%02X)\n", urb.packets[i].payloadcount, urb.packets[i].payloadcount);
		printf("  Payloadbytes:   ");
		if (urb.packets[i].payloadcount < 200/3) {
			print_bin(urb.packets[i].payloadbytes, urb.packets[i].payloadcount);
		} else {
			printf("<skipped>");
		}
		printf("\n");
	}
}
/* }}} */
/* {{{ [fold] next_urb() */
void next_urb(void) {
	int i;
	int skip = 0;
	urb.desc = NULL;
	urb.i2c = NULL;
	if (strncmp(urb.function, "GET_DESCRIPTOR_FROM_DEVICE", urb.function_len)==0) {
		/* DEVICE/CONFIGURATION/STRING */
	} else if (strncmp(urb.function, "CONTROL_TRANSFER", urb.function_len)==0) {
		if (urb.packetcount!=1) error("bad packetcount");
		if (urb.packets[0].payloadcount>=2) {
			desc.bytes = urb.packets[0].payloadbytes[0];
			desc.ident = urb.packets[0].payloadbytes[1];
			urb.desc = &desc;
		}
	} else if (strncmp(urb.function, "SELECT_CONFIGURATION", urb.function_len)==0) {
		/* SET CONFIGURATION? */
	} else if (strncmp(urb.function, "VENDOR_DEVICE", urb.function_len)==0) {
		if (urb.packetcount!=1) error("bad packetcount");
		if (urb.packets[0].payloadcount==0x23) {
			i2c.addr     = urb.packets[0].payloadbytes[0x20];
			i2c.commands = urb.packets[0].payloadbytes[0x21] + 1;
			i2c.rw       = urb.packets[0].payloadbytes[0x22];
			if (i2c.addr!=0xAA && i2c.addr!=0xBA && i2c.addr!=0x20) error("bad i2c addr");
			urb.i2c = &i2c;
		}
	} else if (strncmp(urb.function, "SELECT_INTERFACE", urb.function_len)==0) {
		/* SET INTERFACE? */
		if (urb.packetcount==0) skip = 1;
	} else if (strncmp(urb.function, "RESET_PIPE", urb.function_len)==0) {
	} else if (strncmp(urb.function, "ISOCH_TRANSFER", urb.function_len)==0) {
		skip = 1;
		for (i=0; i<urb.packetcount; i++) {
			qc_stream_add(urb.packets[i].payloadbytes, urb.packets[i].payloadcount);
		}
	} else if (strncmp(urb.function, "BULK_OR_INTERRUPT_TRANSFER", urb.function_len)==0) {
	} else if (strncmp(urb.function, "ABORT_PIPE", urb.function_len)==0) {
	} else {
		printf("Unknown function: ");
		print_len(urb.function, urb.function_len);
		printf("\n");
		error("");
	}
if (urb.i2c) skip = 1;
//if (skip) return;
	print_urb();
	if (urb.desc) {
		printf("Descriptor:\n");
		printf("  Bytes: %i\n", urb.desc->bytes);
		printf("  Ident: %02X\n", urb.desc->ident);
	}
	if (urb.i2c) {
		printf("I2C Packet:\n");
		printf("  Address:    %02X\n", urb.i2c->addr);
		printf("  Commands:   %02X\n", urb.i2c->commands);
		printf("  Read/Write: %02X", urb.i2c->rw);
		if (urb.i2c->rw==1) printf(" (W)");
		else if (urb.i2c->rw==3) printf(" (R)");
		else printf(" (?)");
		printf("\n");
		printf("  Registers:  ");
		for (i=0; i<urb.i2c->commands; i++) {
			printf("[%02X%c]%02X ",urb.packets[0].payloadbytes[i]>>1,
			                       (urb.packets[0].payloadbytes[i]&1) ? 'r' : 'w',
			                       urb.packets[0].payloadbytes[i+0x10]);
		}
		printf("\n");
	}
	printf("\n");

}
/* }}} */

unsigned char *m;
int len;

/* {{{ [fold] xml_tag() */
void xml_tag(unsigned char *tok0, int tok0_len, unsigned char *val0, int val0_len, int depth) {
	unsigned char *data;
	int data_len;
	unsigned char *tok;
	int tok_len;
	unsigned char *val = NULL;
	int val_len;
	int in_str, is_data;

	if (strncmp(tok0, "urb", tok0_len)==0) {
		memset(&urb, 0, sizeof(urb));
		urb.cur_packet = -1;
		if (val0) urb.sequence = atoi(val0);
	} else if (strncmp(tok0, "payload", tok0_len)==0) {
		urb.cur_packet++;
		if (val0) {
			urb.packets[urb.cur_packet].payload_packet = atoi(val0);
			if (urb.packets[urb.cur_packet].payload_packet != urb.cur_packet) error("packet # mismatch");
		}
	}

	while (len>0) {
		/* Find next tag and data for the previous tag */
		data = m;
		data_len = 0;
		is_data = 0;
		while (*m!='<' && len>0) {
			if (isgraph(*m)) is_data = 1;
			m++, len--, data_len++;
		}
		m++, len--;
		if (!is_data) { data = NULL; data_len = 0; }
#ifdef DEBUG
		if (data) {
			printf("Data: \"");
			print_len(data, data_len);
			printf("\"\n");
		}
#endif
		if (strncmp(tok0, "snoopyprolog", tok0_len)==0) {
			/* Do nothing */
		} else if (strncmp(tok0, "urb", tok0_len)==0) {
			/* Do nothing */
		} else if (strncmp(tok0, "function", tok0_len)==0) {
			urb.function = data;
			urb.function_len = data_len;
		} else if (strncmp(tok0, "timestamp", tok0_len)==0) {
			if (data) urb.timestamp = atoi(data);
		} else if (strncmp(tok0, "endpoint", tok0_len)==0) {
			if (data) urb.endpoint = atoi(data);
		} else if (strncmp(tok0, "packetcount", tok0_len)==0) {
			if (data) urb.packetcount = atoi(data);

		} else if (strncmp(tok0, "payload", tok0_len)==0) {
			/* Do nothing */
		} else if (strncmp(tok0, "payloadcount", tok0_len)==0) {
			if (data) urb.packets[urb.cur_packet].payloadcount0 = atoi(data);
		} else if (strncmp(tok0, "payloadbytes", tok0_len)==0) {
			//if (urb.packets[urb.cur_packet].payloadcount*2!=data_len) printf("warning: payloadcount mismatch\n");
			urb.packets[urb.cur_packet].payloadcount = data_len/2;
			if (data) str_to_bin(data, data_len, urb.packets[urb.cur_packet].payloadbytes, MAXPAYLOAD);
		} else {
			printf("warning: unknown token: ");
			print_len(tok0, tok0_len);
			printf("\n");
			exit(1);
		}

		if (len<=0) return;
		if (*m=='/') {
			/* End tag */
			while (*m!='>' && len>0) m++, len--;
			m++, len--;
			return;
		}
		tok = m;

		/* Find tag length */
		while (isalpha(*m) && len>0) m++, len--;
		tok_len = m - tok;

		/* Find end of tag and possible value and its length */
		val = NULL;
		val_len = 0;
		in_str = 0;
		while (*m!='>' && len>0) {
			if (*m=='"') in_str = !in_str;
			if (*m=='"' && in_str) { val = m+1; val_len = -1; }
			if (in_str) val_len++;
			m++, len--;
		}
		m++, len--;

		/* End tag? */
		if (tok_len==0 && tok[1]=='/') return;

		/* New tag? */
		if (tok_len>0) {
#ifdef DEBUG
			printf("(%i) New tag: \"", depth);
			print_len(tok, tok_len);
			printf("\"");
			if (val) {
				printf(" Val: \"");
				print_len(val, val_len);
				printf("\"");
			}
			printf("\n");
#endif
			xml_tag(tok, tok_len, val, val_len, depth+1);
			if (strncmp(tok, "urb", tok_len)==0) {
				if (urb.cur_packet+1 != urb.packetcount) {
					printf("%i vs. %i\n", urb.cur_packet+1, urb.packetcount);
					error("packetcount mismatch");
				}
				next_urb();
			} else if (strncmp(tok, "payload", tok_len)==0) {
				if (urb.cur_packet>=MAXPACKETS) error("too many packets");
			}
		}
	}
}
/* }}} */
/* {{{ [fold] main() */
int main(int argc, char *argv[]) {
	int fd;
	unsigned char *name;
	qc_stream_init();
	if (argc!=2) {
		printf("Usage: %s <xml-log>\n", argv[0]);
		exit(0);
	}
	name = argv[1];
	fd = open(name, O_RDONLY);
	if (fd==-1) error("file open failed");
	len = get_length(fd);
	printf("File name:   %s\n", name);
	printf("File length: %i\n", len);
	printf("\n");
	m = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
	if (m==MAP_FAILED) error("mmap failed");
	xml_tag(NULL, 0, NULL, 0, 0);
	qc_stream_exit();
	return 0;
}
/* }}} */

/* EOF */
