#!/bin/sh
# -*- tcl -*- \
exec tclsh "$0" ${1+"$@"}

proc usage {} {
	puts "Parse a USB trace created by sniffbin or usbsnoop
Usage:
	./parsnoop.tcl \[options\] <usbsnoop file>
Options:
	-nb	Don't display the Bulk/Interrupt messages
	-ni	Don't display the Isochronous messages
	-f	Display the full USB parameters
	-t	Display the delta time between exchanges"
	exit
}

proc display_usb buf {
	global nousb
#puts $buf
	set xfer [list Control Isochronous Bulk Interrupt]
	set i 0
	while {$i < [llength $buf]} {
		set l 0x[lindex $buf $i]
		if {$l + $i > [llength $buf]} {
			puts "truncated descriptor v:$l i:$i l:[llength $buf]"
			break
		}
		set t [lindex $buf $i+1]
		switch $t {
		  01 {
			if {$nousb} {
			    set v 0x[lindex $buf $i+9][lindex $buf $i+8]
			    set v2 0x[lindex $buf $i+11][lindex $buf $i+10]
			    puts [format {*** %04x:%04x ***} $v $v2]
			} else {
			    puts {Device Descriptor:}
			    puts [format {  bLength              %4d} $l]
			    puts [format {  bDescriptorType      %4d} $t]
			    set v 0x[lindex $buf $i+2]
			    set v2 0x[lindex $buf $i+3]
			    puts [format {  bcdUSB              %2x.%02x} $v2 $v]
			    set v 0x[lindex $buf $i+4]
			    if {$v == 1} {
				puts [format {  bDeviceClass         %4d (Audio)} $v]
			    } else {
				puts [format {  bDeviceClass         %4d} $v]
			    }
			    set v 0x[lindex $buf $i+5]
			    puts [format {  bDeviceSubClass      %4d} $v]
			    set v 0x[lindex $buf $i+6]
			    puts [format {  bDeviceProtocol      %4d} $v]
			    set v 0x[lindex $buf $i+7]
			    puts [format {  bMaxPacketSize0      %4d} $v]
			    set v 0x[lindex $buf $i+9][lindex $buf $i+8]
			    puts [format {  idVendor           0x%04x} $v]
			    set v 0x[lindex $buf $i+11][lindex $buf $i+10]
			    puts [format {  idProduct          0x%04x} $v]
			    set v 0x[lindex $buf $i+12]
			    set v2 0x[lindex $buf $i+13]
			    puts [format {  bcdDevice           %2x.%02x} $v2 $v]
			    set v 0x[lindex $buf $i+14]
			    puts [format {  iManufacturer        %4d} $v]
			    set v 0x[lindex $buf $i+15]
			    puts [format {  iProduct             %4d} $v]
			    set v 0x[lindex $buf $i+16]
			    puts [format {  iSerial              %4d} $v]
			    set v 0x[lindex $buf $i+17]
			    puts [format {  bNumConfigurations   %4d} $v]
			}
		  }
		  02 {
			set l2 0x[lindex $buf $i+3][lindex $buf $i+2]
			if {$l2 != [llength $buf]} break	;# truncated
			if {$nousb} {
			} else {
			    puts {Configuration Descriptor:}
			    puts [format {  bLength              %4d} $l]
			    puts [format {  bDescriptorType      %4d} $t]
			    puts [format {  wTotalLength         %4d} $l2]
			    set v 0x[lindex $buf $i+4]
			    puts [format {  bNumInterfaces       %4d} $v]
			    set v 0x[lindex $buf $i+5]
			    puts [format {  bConfigurationValue  %4d} $v]
			    set v 0x[lindex $buf $i+6]
			    puts [format {  iConfiguration       %4d} $v]
			    set v 0x[lindex $buf $i+7]
#			    set v2 $v
			    if {$v & 0x40} {
				set v "$v  (Self Powered)"
			    }
			    puts "  bmAttributes         $v"
			    set v 0x[lindex $buf $i+8]
			    puts [format {  bMaxPower            %4dmA} $v]
			}
		  }
		  03 {
			if {$nousb} {
			} else {
			    puts {  String Descriptor:}
			    puts [format {    bLength              %4d} $l]
			    puts [format {    bDescriptorType      %4d} $t]
			    set v 0x[lindex $buf $i+2]
			    puts -nonewline {    _le16                }
			    set j $i
			   for {incr j 2} {$j < $i + $l} {incr j 2} {
				puts -nonewline [format {%c} 0x[lindex $buf $j]]
			   }
			   puts {}
			}
		  }
		  04 {
			if {$nousb} {
			    set v 0x[lindex $buf $i+2]
			    set x 0x[lindex $buf $i+3]
			    set v2 0x[lindex $buf $i+4]
			    puts [format {	intf %d alt %d - class %d} $v $x $v2]
			} else {
			    puts {  Interface Descriptor:}
			    puts [format {    bLength              %4d} $l]
			    puts [format {    bDescriptorType      %4d} $t]
			    set v 0x[lindex $buf $i+2]
			    puts [format {    bInterfaceNumber     %4d} $v]
			    set v 0x[lindex $buf $i+3]
			    puts [format {    bAlternateSetting    %4d} $v]
			    set v 0x[lindex $buf $i+4]
			    puts [format {    bNumEndpoints        %4d} $v]
			    set v 0x[lindex $buf $i+5]
			    puts [format {    bInterfaceClass      %4d} $v]
			    set v 0x[lindex $buf $i+5]
			    puts [format {    bInterfaceSubClass   %4d} $v]
			    set v 0x[lindex $buf $i+7]
			    puts [format {    bInterfaceProtocol   %4d} $v]
			    set v 0x[lindex $buf $i+8]
			    puts [format {    iInterface           %4d} $v]
			}
		  }
		  05 {
			if {$nousb} {
			    set v [lindex $buf $i+2]
			    set v2 0x[lindex $buf $i+3]
			    set x [lindex $xfer [expr {$v2 & 3}]]
			    set v2 0x[lindex $buf $i+5][lindex $buf $i+4]
			    puts [format "\t\tep $v %-11s $v2" $x]
			} else {
			    puts {    Endpoint Descriptor:}
			    puts [format {      bLength              %4d} $l]
			    puts [format {      bDescriptorType      %4d} $t]
			    set v 0x[lindex $buf $i+2]
			    puts [format {      bEndpointAddress      0x%02x} $v]
			    set v 0x[lindex $buf $i+3]
			    set x [expr {$v & 3}]
			    set v "$v  [lindex $xfer $x]"
			    puts "      bmAttributes          $v"
			    set v 0x[lindex $buf $i+5][lindex $buf $i+4]
			    puts [format {      wMaxPacketSize      0x%04x  1x %d bytes} $v $v]
			    set v 0x[lindex $buf $i+6]
			    puts [format {      bInterval             %4d} $v]
			}
		  }
		  24 {
			if {$nousb} {
			} else {
			    puts {    AudioControl Interface Descriptor:}
			    puts [format {      bLength              %4d} $l]
			    puts [format {      bDescriptorType      %4d} $t]
			    set v 0x[lindex $buf $i+2]
			    puts [format {      bDescriptorSubtype   %04d} $v]
			}
		  }
		  25 {
			if {$nousb} {
			} else {
			    puts {    AudioControl Endpoint Descriptor:}
			    puts [format {      bLength              %4d} $l]
			    puts [format {      bDescriptorType      %4d} $t]
			    set v 0x[lindex $buf $i+2]
			    puts [format {      bDescriptorSubtype   %04d} $v]
			}
		  }
		  default {
			puts "(unknown desc $t)"
		  }
		}
		incr i $l
	}
}

proc isoc {fd} {
	global deltatime noisoc
	set in 0
	set npkt 0
	while {[gets $fd line] >= 0} {
		switch -regexp -- $line {
		    "  URB " break
		    StartFrame {
			if {[string compare [lindex $line 2] 00000000] != 0} {
				set in 1
			}
		    }
		    TransferBufferLength {
			set l [lindex $line 2]
		    }
		    NumberOfPackets {
			set n [lindex $line 2]
		    }
		    Length {
			if {[lindex $line 2] != 0} {
				incr npkt
			}
		    }
		}
	}
	if {!$in || $noisoc} {
		return $line
	}
	puts -nonewline $deltatime
	set ld 0x$l
	if {$npkt == 0} {
		set m "-"
	} else {
		set m [expr {$ld / $npkt}]
	}
	puts [format "<isoc \[%d/%d\] l:%d (m:$m)" $npkt 0x$n $ld]
	return $line
}

proc vendor {fd} {
# outgoing message
	global deltatime
	set out 0
	set b {}
	while {[gets $fd line] >= 0} {
		switch -regexp -- $line {
		    "  URB " break
		    DIRECTION_OUT {
			set out 1
		    }
		    TransferBufferLength {
#			set l 0x[lindex $line 3]
		    }
		    00000..0: {
			if {$out} {
				if {[string length $b] != 0} {
					append b "\n\t\t  "
				}
				append b [lrange $line 1 end]
			}
		    }
		    "Request" {
			set r [format %02x 0x[lindex $line 2]]
		    }
		    "Value" {
			set v [format %04x 0x[lindex $line 2]]
		    }
		    "Index" {
			set i [format %04x 0x[lindex $line 2]]
		    }
		}
	}
	if {$out} {
		puts -nonewline $deltatime
		puts " SET $r $v $i $b"
	}
	return $line
}

proc ctrl {fd in} {
# incoming message
	global deltatime desc nousb
#	set in 0
	set b {}
	set setup 0
	while {[gets $fd line] >= 0} {
		switch -regexp -- $line {
		    "  URB " break
		    DIRECTION_IN {
			set in 1
		    }
		    SetupPacket {
			set setup 1
		    }
		    "  00000" {
			if {!$in} continue
			if {!$setup} {
				if {[string length $b] == 0} {
					set b [lrange $line 1 end]
				} else {
				    if {$desc} {
					append b {  }
				    } else {
					append b "\n<\t\t  "
				    }
				    append b [lrange $line 1 end]
				}
			} else {
				set r [lindex $line 2]
				set v [lindex $line 4][lindex $line 3]
				set i [lindex $line 6][lindex $line 5]
			}
		    }
		}
	}
	if {$in} {
	    if {$desc} {
		if {[string length $b] != 0} {
			display_usb $b
			set desc 0
		}
	    } else {
		puts -nonewline $deltatime
		puts "<GET $r $v $i $b"
	    }
	}
	return $line
}

proc interf {fd} {
# select interface
	global deltatime
	set i {??}
	set a {??}
	while {[gets $fd line] >= 0} {
		switch -regexp -- $line {
		    "  URB " break
		    InterfaceNumber {
			set i [format %02x 0x[lindex $line 3]]
		    }
		    AlternateSetting {
			set a [format %02x 0x[lindex $line 3]]
		    }
		}
	}
	puts -nonewline $deltatime
	puts " intf $i alt $a"
	return $line
}

proc feature {fd} {
	global deltatime
	while {[gets $fd line] >= 0} {
		switch -regexp -- $line {
		    "  URB " break
		}
	}
puts -nonewline $deltatime
puts "feature"
	return $line
}

proc transf {fd} {
# bulk or interrupt transfer
	global deltatime nobulk
	set in 0
	set b {}
	while {[gets $fd line] >= 0} {
		switch -regexp -- $line {
		    DIRECTION_IN {
			set in 1
		    }
		    "  000000" {
			if {!$nobulk} {
			    if {[string length $b] == 0} {
				set b [lrange $line 1 end]
			    } else {
				append b "\n\t      "
				append b [lrange $line 1 end]
			    }
			}
		    }
		    "  00000100" {
			if {!$nobulk} {
				append b "\n\t      ..."
			}
		    }
		    "  URB " break
		}
	}
	if {$nobulk || [string length $b] == 0} {
		return $line
	}
	puts -nonewline $deltatime
	if {$in} {
		puts "<Bulk/Int IN  $b"
	} else {
		puts " Bulk/Int OUT $b"
	}
	return $line
}

proc main {argv} {
	global nowtime prevtime withtime deltatime nobulk noisoc desc nousb
	set withtime 0
	set nobulk 0
	set noisoc 0
	set desc 0
	set nousb 1
	set deltatime {}
	set fn {}
	foreach a $argv {
		switch -- $a {
		    -t {
			set withtime 1
		    }
		    -nb {
			set nobulk 1
		    }
		    -ni {
			set noisoc 1
		    }
		    -f {
			set nousb 0
		    }
		    default {
			if {[string length $fn] != 0} usage
			set fn $a
		    }
		}
	}
	if {[string length $fn] == 0} usage
	if {[catch {open $fn r} fd]} {
		puts stderr "cannot open '$fn'"
		exit 1
	}
	set nowtime 0
	set prevtime 0
	set nisoc 0
	puts "-- file $fn --"
	while {[gets $fd line] >= 0} {
		set isoc 0
		switch -regexp -- $line {
		    URB_FUNCTION_ISOCH_TRANSFER {
			set line [isoc $fd]
			set isoc 1
			incr nisoc
		    }
		    URB_FUNCTION_VENDOR - URB_FUNCTION_CLASS {
			set line [vendor $fd]
		    }
		    URB_FUNCTION_CONTROL_TRANSFER {
			set line [ctrl $fd 0]
		    }
		    URB_FUNCTION_SELECT_INTERFACE {
			set line [interf $fd]
		    }
		    URB_FUNCTION_SET_FEATURE_TO_DEVICE {
			set line [feature $fd]
		    }
		    URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER {
			set line [transf $fd]
		    }
		    URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE {
			set desc 1
			set line [ctrl $fd 1]
		    }
		    URB_FUNCTION_RESET_PIPE {
			puts -nonewline $deltatime
			puts " reset pipe"
		    }
		}
		if {$noisoc && !$isoc && $nisoc != 0} {
			puts -nonewline $deltatime
			puts "$nisoc isoc"
			set nisoc 0
		}
		if {[regexp {\[([0-9]+) ms\]} $line dum ntime]} {
			set prevtime $nowtime
			set nowtime $ntime
			if {[string first down $line] > 0} {
				if {$withtime} {
					set deltatime [format "%4d " \
						[expr {$nowtime - $prevtime}]]
				} elseif {$nowtime > $prevtime + 2} {
					puts "== +[expr {$nowtime - $prevtime}] ms"
				}
			}
			if {$nowtime > $prevtime + 200} {
				puts "== \[$nowtime ms\]"
			}
		}
	}
}

main $argv
