#!/bin/sh # the next line restarts using wish \ if [ -z `which wish` ] ; then exec tclsh "$0" -- "$@" ; else exec wish "$0" -- "$@" ; fi # $Id$ package require Tk # Global: IPAddress() loose tap server # -- port number of this program (tap) and real owserver (server). loose is stray garbage set SocketVars {string version type payload size sg offset tokenlength totallength paylength typetext ping state sock versiontext flagtext persist return id } set MessageList {ERROR NOP READ WRITE DIR SIZE PRESENCE DIRALL GET DIRALLSLASH GETSLASH} set MessageListPlus $MessageList lappend MessageListPlus PING BadHeader Unknown Total # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html set HttpList [list "GET " POST HEAD CONN "PUT " DELE TRAC] set stats(clientlist) {} set setup_flags(detail_list) {} # Global: setup_flags => For object-oriented initialization # Global: serve => information on current transaction # serve($sock.string) -- message to this point # serve($sock. version size payload sg offset type tokenlength ) -- message parsed parts # serve($sock.version size # serve(sockets) -- list of active sockets for timeout # Global: stats => statistical counts and flags for stats windows # Global: circ_buffer => data for the last n transactions displayed in the listboxes #Main procedure. We actually start it at the end, to allow Proc-s to be defined first. proc Main { argv } { ArgumentProcess CircBufferSetup 50 DisplaySetup StatsSetup SetupTap } # Command line processing # looks at command line arguments # two options "p" and "s" for (this) tap's _p_ort, and the target ow_s_erver port # Can handle command lines like # -p 3000 -s 3001 # -p3000 -s 3001 # etc proc ArgumentProcess { } { global IPAddress set mode "loose" # "Clear" ports # INADDR_ANY # tk_messageBox -message "$::argv" -type ok set IPAddress(tap.ip) "0.0.0.0" set IPAddress(tap.port) "0" set IPAddress(server.ip) "0.0.0.0" set IPAddress(server.port) "4304" foreach a $::argv { if { [regexp -- {^-p(.*)$} $a whole address] } { set mode "tap" } elseif { [regexp -- {^-s(.*)$} $a whole address] } { set mode "server" } else { set address $a } IPandPort $mode $address } MainTitle $IPAddress(tap.ip):$IPAddress(tap.port) $IPAddress(server.ip):$IPAddress(server.port) } proc IPandPort { mode argument_string } { global IPAddress if { [regexp -- {^(.*?):(.*)$} $argument_string wholestring firstpart secondpart] } { if { $firstpart != "" } { set IPAddress($mode.ip) $firstpart } if { $secondpart != "" } { set IPAddress($mode.port) $secondpart } } else { if { $argument_string != "" } { set IPAddress($mode.port) $argument_string } } } # Accept from client (our "server" portion) proc SetupTap { } { global IPAddress StatusMessage "Attempting to open surrogate server on $IPAddress(tap.ip):$IPAddress(tap.port)" if {[catch {socket -server TapAccept -myaddr $IPAddress(tap.ip) $IPAddress(tap.port)} result] } { ErrorMessage $result } StatusMessage "Success. Tap server address=[PrettySock $result]" MainTitle [PrettySock $result] $IPAddress(server.ip):$IPAddress(server.port) } #Main loop. Called whenever the server (listen) port accepts a connection. proc TapAccept { sock addr port } { global serve global stats # Start the State machine set serve($sock.state) "Open client" while {1} { #puts $serve($sock.state) switch $serve($sock.state) { "Open client" { StatusMessage "Reading client request from $addr port $port" 0 set persist 0 fconfigure $sock -buffering full -translation binary -encoding binary -blocking 0 set serve($sock.state) "Persistent loop" } "Persistent loop" { TapSetup $sock set serve($sock.sock) $sock set current [CircBufferAllocate] set serve($sock.state) "Read client" } "Read client" { # wait a long time (3hr) for very first packet ResetSockTimer $sock 10000000 fileevent $sock readable [list TapProcess $sock] # ShowMessage $sock vwait serve($sock.state) } "Process client packet" { fileevent $sock readable {} ClearSockTimer $sock StatusMessage "Success reading client request" 0 set message_type $serve($sock.typetext) CircBufferEntryRequest $current "$addr:$port $message_type $serve($sock.payload) bytes" $serve($sock.string) AddClient $addr:$port #stats RequestStatsIncr $sock 0 # now owserver if {$persist>0} { set serve($sock.state) "Send to server" } else { set serve($sock.state) "Open server" } } "Client early end" { StatusMessage "Client dropped connection" CircBufferEntryRequest $current "Client done" $serve($sock.string) CircBufferEntryResponse $current "" RequestStatsIncr $sock 1 set serve($sock.state) "Done with client" } "Web client" { StatusMessage "Error: owtap is not a web server" CircBufferEntryRequest $current "Not a web server" CircBufferEntryResponse $current "" RequestStatsIncr $sock 1 WebResponse $sock set serve($sock.state) "Done with client" } "Open server" { global IPAddress StatusMessage "Attempting to open connection to OWSERVER" 0 if {[catch {socket $IPAddress(server.ip) $IPAddress(server.port)} relay] } { set serve($sock.state) "Unopened server" } else { set serve($sock.state) "Send to server" } } "Unopened server" { StatusMessage "OWSERVER error: $relay at $IPAddress(server.ip):$IPAddress(server.port)" set serve($relay.string) {} CircBufferEntryResponse $current "owserver not responding" set serve($sock.state) "Done with client" } "Send to server" { StatusMessage "Sending client request to OWSERVER" 0 fconfigure $relay -translation binary -buffering full -encoding binary -blocking 0 set serve($relay.sock) $sock puts -nonewline $relay $serve($sock.string) flush $relay set serve($sock.state) "Read from server" } "Read from server" { StatusMessage "Reading OWSERVER response" 0 TapSetup $relay ResetSockTimer $relay fileevent $relay readable [list RelayProcess $relay] vwait serve($sock.state) } "Server early end" { StatusMessage "FAILURE reading OWSERVER response" 0 ResponseStatsIncr $relay 1 CircBufferEntryResponse $current "network read error" $serve($relay.string) set serve($sock.state) "Done with server" } "Process server packet" { StatusMessage "Success reading OWSERVER response" 0 ResponseAdd $relay fileevent $relay readable {} CircBufferEntryResponse $current $serve($relay.return) $serve($relay.string) #stats ResponseStatsIncr $relay 0 set serve($sock.state) "Send to client" } "Send to client" { ClearSockTimer $relay StatusMessage "Sending OWSERVER response to client" 0 puts -nonewline $sock $serve($relay.string) flush $sock # filter out the multi-response types and continue listening if { $serve($relay.ping) == 1 } { set serve($sock.state) "Ping received" } elseif { ( $message_type=="DIR" ) && ($serve($relay.paylength)>0)} { set serve($sock.state) "Dir element received" } else { set serve($sock.state) "Persistent test" } } "Ping received" { set serve($sock.state) "Read from server" } "Dir element received" { set serve($sock.state) "Read from server" } "Persistent test" { if { $serve($relay.persist)==1 } { StatPersistCounter $persist incr persist ClearTap $sock ClearTap $relay set serve($sock.state) "Persistent loop" } else { set serve($sock.state) "Done with server" } } "Server timeout" { CircBufferEntryResponse $current "owserver read timeout" set serve($sock.state) "Done with server" } "Done with server" { CloseSock $relay set serve($sock.state) "Done with client" } "Client timeout" { CircBufferEntryRequest $current "client read timeout" CircBufferEntryResponse $current "" set serve($sock.state) "Done with client" } "Done with client" { CloseSock $sock set serve($sock.state) "Done with all" } "Done with all" { StatusMessage "Ready" 0 return } default { StatusMessage "Internal error -- bad state: $serve($sock.state)" 1 return } } } } # initialize statistics proc StatsSetup { } { global stats global MessageListPlus foreach x $MessageListPlus { set stats($x.tries) 0 set stats($x.errors) 0 set stats($x.rate) 0 set stats($x.request_bytes) 0 set stats($x.response_bytes) 0 } foreach x { request_yes request_no grant refuse -1 0 denominator max request_rate grant_rate } { set stats(persistence_length.$x) 0 } set stats(persistence_length.15plus) 0 } # increment stats for request proc RequestStatsIncr { sock is_error} { global stats global serve set message_type $serve($sock.typetext) set length [string length $serve($sock.string)] incr stats($message_type.tries) incr stats($message_type.errors) $is_error set stats($message_type.rate) [expr {100 * $stats($message_type.errors) / $stats($message_type.tries)} ] incr stats($message_type.request_bytes) $length incr stats(Total.tries) incr stats(Total.errors) $is_error set stats(Total.rate) [expr {100 * $stats(Total.errors) / $stats(Total.tries)} ] incr stats(Total.request_bytes) $length # persistence stats if { [info exist serve($sock.persist)] } { if { $serve($sock.persist) == 0 } { incr stats(persistence_length.request_no) } else { incr stats(persistence_length.request_yes) incr stats(persistence_length.refuse) set stats(persistence_length.grant_rate) [expr {100 * $stats(persistence_length.grant) / $stats(persistence_length.request_yes)}] } set stats(persistence_length.request_rate) [expr { 100 * $stats(persistence_length.request_yes) / $stats(Total.tries) }] } } # increment stats for request proc ResponseStatsIncr { sock is_error} { global stats global serve set message_type $serve($serve($sock.sock).typetext) set length [string length $serve($sock.string)] incr stats($message_type.errors) $is_error set stats($message_type.rate) [expr {100 * $stats($message_type.errors) / $stats($message_type.tries)} ] incr stats($message_type.response_bytes) $length incr stats(Total.errors) $is_error set stats(Total.rate) [expr {100 * $stats(Total.errors) / $stats(Total.tries)} ] incr stats(Total.response_bytes) $length } # Counter of Persistence lengths proc StatPersistCounter { persist } { global stats # max persistence length if { $persist > $stats(persistence_length.max) } { set stats(persistence_length.max) $persist set stats(persistence_length.$persist) 0 } # increment this bin (and decrement prior) if { $persist==0 } { incr stats(persistence_length.denominator) } set old_persist [expr {$persist-1}] incr stats(persistence_length.$old_persist) -1 if { $persist == 16 } { incr stats(persistence_length.15plus) } incr stats(persistence_length.$persist) incr stats(persistence_length.grant) incr stats(persistence_length.refuse) -1 set stats(persistence_length.grant_rate) [expr {100 * $stats(persistence_length.grant) / $stats(persistence_length.request_yes)}] set length_sum 0 for {set x 0} {$x <= $stats(persistence_length.max)} {incr x} { incr length_sum [expr {$stats(persistence_length.$x) * $x}] } set stats(persistence_length.mean) [expr {($length_sum)/$stats(persistence_length.denominator)}] } # Initialize array for client request proc TapSetup { sock } { global serve set serve($sock.string) "" set serve($sock.totallength) 0 } # Clear out client request array after a connection (frees memory) proc ClearTap { sock } { global serve global SocketVars foreach x $SocketVars { if { [info exist serve($sock.$x)] } { unset serve($sock.$x) } } if { [info exist serve($sock.num] } { for {set i $serve($sock.num)} {$i >= 0} {incr i -1} { unset serve($sock.$i) } unset serve($sock.num) } } proc ResponseAdd { sock } { global serve if { [info exist serve($sock.num)] } { incr serve($sock.num) } else { set serve($sock.num) 0 } set serve($sock.$serve($sock.num)) $serve($sock.string) } # close client request socket proc SockTimeout { sock } { global serve switch $serve($serve($sock.sock).state) { "Read client" { set serve($serve($sock.sock).state) "Client timeout" } "Read from server" { set serve($serve($sock.sock).state) "Server timeout" } default { ErrorMessage "Strange timeout for $sock state=$serve($serve($sock.sock).state)" set serve($serve($sock.sock).state) "Server timeout" } } StatusMessage "Network read timeout [PrettySock $sock]" 1 } # close client request socket proc CloseSock { sock } { global serve ClearSockTimer $sock close $sock ClearTap $sock } proc ClearSockTimer { sock } { global serve if { [info exist serve($sock.id)] } { after cancel $serve($sock.id) unset serve($sock.id) } } proc ResetSockTimer { sock { msec 2000 } } { global serve ClearSockTimer $sock set serve($sock.id) [after $msec [list SockTimeout $sock ]] } # Wrapper for processing -- either change a vwait var, or just return waiting for more network traffic proc TapProcess { sock } { global serve set read_value [ReadProcess $sock] switch $read_value { "Web client" { set serve($sock.state) "Web client" } "Client early end" { set serve($sock.state) "Client early end" } "Packet reloop" { return } "Process packet" { set serve($sock.state) "Process client packet" } } TypeParser serve $sock } # Process a oncomming owserver packet, adjusting size from header information proc ReadProcess { sock } { global serve # test eof if { [eof $sock] } { return "Client early end" } # read what's waiting set new_string [read $sock] if { $new_string == {} } { return "Packet reloop" } append serve($sock.string) $new_string ResetSockTimer $sock set len [string length $serve($sock.string)] if { $len < 24 } { #do nothing -- reloop return "Packet reloop" } elseif { $serve($sock.totallength) == 0 } { # Look for web browser global HttpList if { [lsearch -exact $HttpList [string range $serve($sock.string) 0 3]] >= 0 } { return "Web client" } # at least header is in HeaderParser serve $sock $serve($sock.string) } #already in payload (and token) portion if { $len < $serve($sock.totallength) } { #do nothing -- reloop return "Packet reloop" } # Fully parsed set new_length [string length $serve($sock.string)] return "Process packet" } # Wrapper for processing -- either change a vwait var, or just return waiting for more network traffic proc RelayProcess { relay } { global serve set read_value [ReadProcess $relay] #puts "Current length [string length $serve($relay.string)] return val=$read_value" switch $read_value { "Web client" - "Client early end" { set serve($serve($relay.sock).state) "Server early end"} "Packet reloop" { return } "Process packet" { set serve($serve($relay.sock).state) "Process server packet" } } ErrorParser serve $relay #puts $serve($serve($relay.sock).typetext) # ShowMessage $relay } # Debugging routine -- show all the packet info proc ShowMessage { sock } { global serve global SocketVars foreach x $SocketVars { if { [info exist serve($sock.$x)] } { puts "\t$sock.$x = $serve($sock.$x)" } } } # callback from scrollbar, moves each listbox field proc ScrollTogether { args } { eval {.log.request_list yview} $args eval {.log.response_list yview} $args } # scroll other listbox and scrollbar proc Left_ScrollByKey { args } { eval { .log.transaction_scroll set } $args .log.response_list yview moveto [lindex [.log.request_list yview] 0 ] .log.response_list activate [.log.request_list index active ] } # scroll other listbox and scrollbar proc Right_ScrollByKey { args } { eval { .log.transaction_scroll set } $args .log.request_list yview moveto [lindex [.log.response_list yview] 0 ] .log.request_list activate [.log.response_list index active ] } # Selection from listbox proc SelectionMade { widget y } { set index [ $widget nearest $y ] if { $index >= 0 } { TransactionDetail [current_from_index $index] } } # create visual aspects of program proc DisplaySetup { } { global circ_buffer # Top pane, tranaction logs frame .log -bg yellow scrollbar .log.transaction_scroll -command [ list ScrollTogether ] label .log.request_title -text "Client request" -bg yellow -relief ridge label .log.response_title -text "Owserver response" -bg yellow -relief ridge listbox .log.request_list -width 40 -height 10 -selectmode single -yscroll [list Left_ScrollByKey ] -bg lightyellow listbox .log.response_list -width 40 -height 10 -selectmode single -yscroll [list Right_ScrollByKey] -bg lightyellow foreach lb {request_list response_list} { bind .log.$lb {+ SelectionMade %W %y } bind .log.$lb {+ SelectionMade %W } } grid .log.request_title -row 0 -column 0 -sticky news grid .log.response_title -row 0 -column 1 -sticky news grid .log.request_list -row 1 -column 0 -sticky news grid .log.response_list -row 1 -column 1 -sticky news grid .log.transaction_scroll -row 1 -column 2 -sticky news pack .log -side top -fill x -expand true #bottom pane, status label .status -anchor w -width 80 -relief sunken -height 1 -textvariable current_status -bg white pack .status -side bottom -fill x bind .status [list .main_menu.view invoke "Status messages"] SetupMenu } # Menu construction proc SetupMenu { } { global stats # toplevel . -menu .main_menu menu .main_menu -tearoff 0 . config -menu .main_menu # file menu menu .main_menu.file -tearoff 0 .main_menu add cascade -label File -menu .main_menu.file -underline 0 .main_menu.file add command -label "Log to File..." -underline 0 -command SaveLog -state disabled .main_menu.file add command -label "Stop logging" -underline 0 -command SaveAsLog -state disabled .main_menu.file add separator .main_menu.file add command -label "Restart" -underline 0 -command Restart .main_menu.file add separator .main_menu.file add command -label "Quit" -underline 0 -command exit # statistics menu menu .main_menu.view -tearoff 0 .main_menu add cascade -label View -menu .main_menu.view -underline 0 .main_menu.view add checkbutton -label "Statistics by Message type" -underline 14 -indicatoron 1 -command {StatByType} .main_menu.view add checkbutton -label "Persistence rates" -underline 12 -indicatoron 1 -command {RatePersist} .main_menu.view add checkbutton -label "Persistence lengths" -underline 12 -indicatoron 1 -command {LengthPersist} .main_menu.view add separator .main_menu.view add checkbutton -label "Detail window list" -underline 0 -indicatoron 1 -command {DetailList} .main_menu.view add separator .main_menu.view add checkbutton -label "Clients" -underline 0 -indicatoron 1 -command {StatByClient} .main_menu.view add separator .main_menu.view add checkbutton -label "Status messages" -underline 0 -indicatoron 1 -command {StatusWindow} # help menu menu .main_menu.help -tearoff 0 .main_menu add cascade -label Help -menu .main_menu.help -underline 0 .main_menu.help add command -label "About OWTAP" -underline 0 -command About .main_menu.help add command -label "Command Line" -underline 0 -command CommandLine .main_menu.help add command -label "OWSERVER Protocol" -underline 0 -command Protocol .main_menu.help add command -label "Version" -underline 0 -command Version } # error routine -- popup and exit proc ErrorMessage { msg } { StatusMessage "Fatal error -- $msg" tk_messageBox -title "Fatal error" -message $msg -type ok -icon error exit 1 } # status -- set status message # possibly store past messages # Use priority to test if should be stored proc StatusMessage { msg { priority 1 } } { global current_status set current_status $msg if { $priority > 0 } { global status_messages lappend status_messages $msg if { [llength $status_messages] > 50 } { set status_messages [lreplace $status_messages 0 0] } } } # Circular buffer for past connections # size is number of elements proc CircBufferSetup { size } { global circ_buffer set circ_buffer(size) $size set circ_buffer(total) -1 } # Save a spot for the coming connection proc CircBufferAllocate { } { global circ_buffer set size $circ_buffer(size) incr circ_buffer(total) set total $circ_buffer(total) set cb_index [ expr { $total % $size } ] if { $total >= $size } { # delete top listbox entry (oldest) .log.request_list delete 0 .log.response_list delete 0 # clear old entry if { [info exist circ_buffer($cb_index.num)] } { set num $circ_buffer($cb_index.num) for {set x 0} { $x < $num } {incr x} { unset circ_buffer($cb_index.response.$x) } } } set circ_buffer($cb_index.num) 0 set circ_buffer($cb_index.request) "" .log.request_list insert end "$total: pending" .log.response_list insert end "$total: pending" return $total } # place a new request packet proc CircBufferEntryRequest { current request {transaction_string "" } } { global circ_buffer set size $circ_buffer(size) set total $circ_buffer(total) if { [expr {$current + $size}] <= $total } { StatusMessage "Packet buffer history overflow. (nonfatal)" 0 return } # Still filling for the first time? if { $total < $size } { set index $current } else { set index [ expr $size - $total + $current - 1 ] } .log.request_list insert $index $request .log.request_list delete [expr $index + 1 ] #Now store packet set cb_index [ expr { $current % $size } ] set circ_buffer($cb_index.request) $transaction_string } # place a new response packet proc CircBufferEntryResponse { current response {transaction_string "" } } { global circ_buffer set size $circ_buffer(size) set total $circ_buffer(total) if { [expr {$current + $size}] <= $total } { StatusMessage "Packet buffer history overflow. (nonfatal)" 0 return } # Still filling for the first time? if { $total < $size } { set index $current } else { set index [ expr $size - $total + $current - 1 ] } .log.response_list insert $index "$current: $response" .log.response_list delete [expr $index + 1 ] #Now store packet set cb_index [ expr { $current % $size } ] set circ_buffer($cb_index.response.$circ_buffer($cb_index.num)) $transaction_string incr circ_buffer($cb_index.num) } # get the slot in the circ_buffer from the listbox index proc cb_from_index { index } { global circ_buffer set size $circ_buffer(size) set total $circ_buffer(total) if { $total < $size } { return $index } return [expr { ($total + $index) % $size }] } # get the total list from listbox index proc current_from_index { index } { global circ_buffer set size $circ_buffer(size) set total $circ_buffer(total) if { $total < $size } { return $index } return [expr { $total + $index - $size + 1 }] } # Popup giving attribution proc About { } { tk_messageBox -type ok -title {About owtap} -message { Program: owtap Synopsis: owserver protocol inspector Description: owtap is interposed between owserver and client. The communication is logged and shown on screen. All messages are transparently forwarded between client and server. Author: Paul H Alfille Copyright: July 2007 GPL 2.0 license Website: http://www.owfs.org } } # Popup giving commandline proc CommandLine { } { tk_messageBox -type ok -title {owtap command line} -message { syntax: owtap.tcl -s serverport -p tapport server port is the address of owserver tapport is the port assigned this program Usage (owdir example) If owserver was invoked as: owserver -p 3000 -u a client (say owdir) would normally call: owdir -s 3000 / To use owtap, invoke it with owtap.tcl -s 3000 -p 4000 and now call owdir with: owdir -s 4000 / } } # Popup giving version proc Version { } { regsub -all {[$:a-zA-Z]} {$Revision$} {} Version regsub -all {[$:a-zA-Z]} {$Date$} {} Date tk_messageBox -type ok -title {owtap version} -message " Version $Version Date $Date " } # Popup on protocol proc Protocol { } { tk_messageBox -type ok -title {owserver protocol} -message { The owserver protocol is a tcp/ip protocol for communication between owserver and clients. It is recognized as a "well known port" by the IANA (4304) and has an associated mDNS service (_owserver._tcp). Datails can be found at: http://www.owfs.org/index.php?page=owserver-protocol } } # Show a list of detail windows proc DetailList { } { set window_name .detaillist set menu_name .main_menu.view set menu_index "Detail window list" if { [ WindowAlreadyExists $window_name $menu_name $menu_index ] } { return } global setup_flags listbox $window_name.lb -listvariable setup_flags(detail_list) -width 30 -yscrollcommand [list $window_name.sb set] -selectmode extended -bg lightyellow scrollbar $window_name.sb -command [list $window_name.lb yview] set f [frame $window_name.f] set all [button $f.all -text All -command [list $window_name.lb selection set 0 end]] set none [button $f.none -text None -command [list $window_name.lb selection clear 0 end]] set delete [button $f.close -text "Close selected" -command [list DetailClear $window_name.lb]] pack $all -side left pack $none -side left pack $delete -side right pack $f -side bottom -fill x pack $window_name.sb -side right -fill y pack $window_name.lb -side left -fill both -expand true } proc DetailClear { list_box } { foreach i [$list_box curselection] { lappend windows [$list_box get $i] } foreach w $windows { DetailDelete $w } } proc DetailDelete { window_name } { global setup_flags set i [lsearch -exact $setup_flags(detail_list) $window_name] if { $i >= 0 } { set setup_flags(detail_list) [lreplace $setup_flags(detail_list) $i $i] } destroy $window_name } # Client list proc AddClient { client } { global ClientList if { [info exist ClientList($client)] } { incr ClientList($client) } else { set ClientList($client) 1 } global setup_flags if { [ info exist setup_flags(.clientlist) ] } { BuildClientList } } proc BuildClientList { } { global stats global ClientList set l {} foreach {k v} [array get ClientList] { lappend l [format {%20.20s %8d} $k $v] } set stats(clientlist) $l } # Show a table of Past status messages proc StatByClient { } { set window_name .clientlist set menu_name .main_menu.view set menu_index "Clients" if { [ WindowAlreadyExists $window_name $menu_name $menu_index ] } { return } global stats BuildClientList listbox $window_name.lb -listvariable stats(clientlist) -width 30 -yscrollcommand [list $window_name.sb set] -bg lightyellow scrollbar $window_name.sb -command [list $window_name.lb yview] pack $window_name.sb -side right -fill y pack $window_name.lb -side left -fill both -expand true } # Show a table of Past status messages proc LengthPersist { } { set window_name .lengthpersistwindow set menu_name .main_menu.view set menu_index "Persistence lengths" if { [ WindowAlreadyExists $window_name $menu_name $menu_index ] } { return } global stats label $window_name.at -text "Length" -bg blue -fg white grid $window_name.at -row 0 -column 0 -sticky news label $window_name.bt -text "Count" -bg blue -fg white grid $window_name.bt -row 0 -column 1 -sticky news label $window_name.ax -textvariable stats(persistence_length.max) -bg lightyellow grid $window_name.ax -row 1 -column 0 -sticky news label $window_name.bx -text "Max" -bg lightyellow grid $window_name.bx -row 1 -column 1 -sticky news label $window_name.am -textvariable stats(persistence_length.mean) -bg lightyellow grid $window_name.am -row 2 -column 0 -sticky news label $window_name.bm -text "Mean" -bg lightyellow grid $window_name.bm -row 2 -column 1 -sticky news set row 3 set bg white for {set x 0} {$x < 16} {incr x} { label $window_name.a${x} -text $x -bg $bg grid $window_name.a${x} -row $row -column 0 -sticky news label $window_name.b${x} -textvariable stats(persistence_length.$x) -bg $bg grid $window_name.b${x} -row $row -column 1 -sticky news if {$bg=="white"} { set bg lightyellow} else {set bg white} incr row } label $window_name.ap -text "> 15" -bg $bg grid $window_name.ap -row $row -column 0 -sticky news label $window_name.bp -textvariable stats(persistence_length.15plus) -bg $bg grid $window_name.bp -row $row -column 1 -sticky news } # Show a table of Past status messages proc RatePersist { } { set window_name .ratepersistwindow set menu_name .main_menu.view set menu_index "Persistence rates" if { [ WindowAlreadyExists $window_name $menu_name $menu_index ] } { return } global stats label $window_name.a0 -text "Persistence" -bg blue -fg white grid $window_name.a0 -row 0 -column 0 -sticky news label $window_name.a1 -text "Requests" -bg lightblue grid $window_name.a1 -row 1 -column 0 -sticky news label $window_name.a2 -text "Granted" -bg lightblue grid $window_name.a2 -row 2 -column 0 -sticky news label $window_name.b0 -text "number" -bg yellow grid $window_name.b0 -row 0 -column 1 -sticky news label $window_name.b1 -textvariable stats(persistence_length.request_yes) -bg white grid $window_name.b1 -row 1 -column 1 -sticky news label $window_name.b2 -textvariable stats(persistence_length.grant) -bg white grid $window_name.b2 -row 2 -column 1 -sticky news label $window_name.c0 -text "total" -bg orange grid $window_name.c0 -row 0 -column 2 -sticky news label $window_name.c1 -textvariable stats(Total.tries) -bg lightyellow grid $window_name.c1 -row 1 -column 2 -sticky news label $window_name.c2 -textvariable stats(persistence_length.request_yes) -bg lightyellow grid $window_name.c2 -row 2 -column 2 -sticky news label $window_name.d0 -text "rate %" -bg yellow grid $window_name.d0 -row 0 -column 3 -sticky news label $window_name.d1 -textvariable stats(persistence_length.request_rate) -bg white grid $window_name.d1 -row 1 -column 3 -sticky news label $window_name.d2 -textvariable stats(persistence_length.grant_rate) -bg white grid $window_name.d2 -row 2 -column 3 -sticky news } # Show a table of Past status messages proc StatusWindow { } { set window_name .statuswindow set menu_name .main_menu.view set menu_index "Status messages" if { [ WindowAlreadyExists $window_name $menu_name $menu_index ] } { return } global status_messages # create status window scrollbar $window_name.xsb -orient horizontal -command [list $window_name.lb xview] pack $window_name.xsb -side bottom -fill x -expand 1 scrollbar $window_name.ysb -orient vertical -command [list $window_name.lb yview] pack $window_name.ysb -fill y -expand 1 -side right listbox $window_name.lb -listvar status_messages -bg white -yscrollcommand [list $window_name.ysb set] -xscrollcommand [list $window_name.xsb set] -width 80 pack $window_name.lb -fill both -expand 1 -side left } #proc window handler for statistics and status windows #return 1 if old, 0 if new proc WindowAlreadyExists { window_name menu_name menu_index } { global setup_flags if { [ info exist setup_flags($window_name) ] } { if { $setup_flags($window_name) } { # hide window wm withdraw $window_name set setup_flags($window_name) 0 } else { # show window wm deiconify $window_name set setup_flags($window_name) 1 } return 1 } # create window toplevel $window_name wm title $window_name $menu_index # delete handler wm protocol $window_name WM_DELETE_WINDOW [list $menu_name invoke $menu_index] # now set flag set setup_flags($window_name) 1 return 0 } # Show a table of packets and bytes by type (DIR, READ,...) # Separate window that is pretty self contained. # Data values use -textvariable so auto-update # linked by "globals" for variables and types # linked my menu position and index (checkbox) # catches delete and hides instead (via menu action) proc StatByType { } { set window_name .statbytype set menu_name .main_menu.view set menu_index "Statistics by Message type" if { [ WindowAlreadyExists $window_name $menu_name $menu_index ] } { return } global stats global MessageListPlus # create stats window set column_number 0 label $window_name.l${column_number}0 -text "Type" -bg blue -fg white grid $window_name.l${column_number}0 -row 0 -column $column_number -sticky news label $window_name.l${column_number}1 -text "Packets" -bg lightblue grid $window_name.l${column_number}1 -row 1 -column $column_number -sticky news label $window_name.l${column_number}2 -text "Errors" -bg lightblue grid $window_name.l${column_number}2 -row 2 -column $column_number -sticky news label $window_name.l${column_number}3 -text "Error %" -bg lightblue grid $window_name.l${column_number}3 -row 3 -column $column_number -sticky news frame $window_name.ff4 -bg blue grid $window_name.ff4 -column 1 -row 0 label $window_name.l${column_number}5 -text "bytes in" -bg lightblue grid $window_name.l${column_number}5 -row 5 -column $column_number -sticky news label $window_name.l${column_number}6 -text "bytes out" -bg lightblue grid $window_name.l${column_number}6 -row 6 -column $column_number -sticky news set bgcolor white set bgcolor2 yellow foreach x $MessageListPlus { incr column_number label $window_name.${column_number}0 -text $x -bg $bgcolor2 grid $window_name.${column_number}0 -row 0 -column $column_number -sticky news label $window_name.${column_number}1 -textvariable stats($x.tries) -bg $bgcolor grid $window_name.${column_number}1 -row 1 -column $column_number -sticky news label $window_name.${column_number}2 -textvariable stats($x.errors) -bg $bgcolor grid $window_name.${column_number}2 -row 2 -column $column_number -sticky news label $window_name.${column_number}3 -textvariable stats($x.rate) -bg $bgcolor grid $window_name.${column_number}3 -row 3 -column $column_number -sticky news label $window_name.${column_number}5 -textvariable stats($x.request_bytes) -bg $bgcolor grid $window_name.${column_number}5 -row 5 -column $column_number -sticky news label $window_name.${column_number}6 -textvariable stats($x.response_bytes) -bg $bgcolor grid $window_name.${column_number}6 -row 6 -column $column_number -sticky news if { $bgcolor == "white" } { set bgcolor lightyellow } else { set bgcolor white } if { $bgcolor2 == "yellow" } { set bgcolor2 orange } else { set bgcolor2 yellow } } frame $window_name.f4 -bg yellow grid $window_name.f4 -column 1 -row 4 -columnspan [llength MessageListPlus] } #make a window that has to be dismissed by hand proc TransactionDetail { index } { # make a unique window name global setup_flags set window_name .transaction_$index # Does the window exist? if { [winfo exists $window_name] == 1 } { raise $window_name return } # Make the window toplevel $window_name -bg white wm title $window_name "Transaction $index" set cb_index [cb_from_index $index] RequestDetail $window_name $cb_index ResponseDetail $window_name $cb_index # Add to a list set l [concat $setup_flags(detail_list) $window_name] set setup_flags(detail_list) [lsort -unique $l] # delete handler wm protocol $window_name WM_DELETE_WINDOW [list DetailDelete $window_name ] } # Parse for return HeaderParser proc ErrorParser { array_name prefix } { upvar 1 $array_name a_name set a_name($prefix.return) [DetailReturn a_name $prefix] } # Parse for TYPE after HeaderParser proc TypeParser { array_name prefix } { upvar 1 $array_name a_name global MessageList if { $a_name($prefix.totallength) < 24 } { set a_name($prefix.typetext) BadHeader return } set type [lindex $MessageList $a_name($prefix.type)] if { $type == {} } { set a_name($prefix.typetext) Unknown return } set a_name($prefix.typetext) $type } #Parse header information and place in array # works for request or response (though type is "ret" in response) proc HeaderParser { array_name prefix string_value } { upvar 1 $array_name a_name set length [string length $string_value] foreach x {version payload type flags size offset typetext} { set a_name($prefix.$x) "" } foreach x {paylength tokenlength totallength ping} { set a_name($prefix.$x) 0 } binary scan $string_value {IIIIII} a_name($prefix.version) a_name($prefix.payload) a_name($prefix.type) a_name($prefix.flags) a_name($prefix.size) a_name($prefix.offset) if { $length < 24 } { set a_name($prefix.totallength) $length set a_name($prefix.typetext) BadHeader return } if { $a_name($prefix.payload) == -1 } { set a_name($prefix.paylength) 0 set a_name($prefix.ping) 1 } else { set a_name($prefix.paylength) $a_name($prefix.payload) } set version $a_name($prefix.version) set flags $a_name($prefix.flags) set tok [expr { $version & 0xFFFF}] set ver [expr { $version >> 16}] set a_name($prefix.persist) [expr {($flags&0x04)?1:0}] set a_name($prefix.versiontext) "T$tok V$ver" set a_name($prefix.flagtext) [DetailFlags $flags] set a_name($prefix.tokenlength) [expr {$tok * 16} ] set a_name($prefix.totallength) [expr {$a_name($prefix.tokenlength)+$a_name($prefix.paylength)+24}] } # Request portion proc RequestDetail { window_name cb_index } { global circ_buffer DetailRow $window_name yellow orange version payload type flags size offset # get request data HeaderParser q x $circ_buffer($cb_index.request) # request headers DetailRow $window_name white lightyellow $q(x.version) $q(x.payload) $q(x.type) $q(x.flags) $q(x.size) $q(x.offset) # request headers if { [string length $circ_buffer($cb_index.request)] >= 24 } { TypeParser q x DetailRow $window_name white lightyellow $q(x.versiontext) $q(x.paylength) $q(x.typetext) $q(x.flagtext) $q(x.size) $q(x.offset) if { $q(x.paylength) > 0 } { switch $q(x.typetext) { "WRITE" { DetailPayloadPlus $window_name lightyellow white $circ_buffer($cb_index.request) $q(x.paylength) $q(x.size) } default { DetailPayload $window_name lightyellow $circ_buffer($cb_index.request) $q(x.paylength) } } } wm title $window_name "[wm title $window_name]: $q(x.typetext)" } } # Response portion proc ResponseDetail { window_name cb_index } { global circ_buffer DetailRow $window_name #a6dcff #a6b1ff version payload return flags size offset # get response data set num $circ_buffer($cb_index.num) for {set i 0} {$i < $num} {incr i} { HeaderParser r x $circ_buffer($cb_index.response.$i) set offset $r(x.offset) DetailRowPlus $window_name white #EEEEFF $i $r(x.version) $r(x.payload) $r(x.type) $r(x.flags) $r(x.size) $offset if { [string length $circ_buffer($cb_index.response.$i)] >= 24 } { ErrorParser r x switch [$window_name.x22 cget -text] { "DIR" - "DIRALL" {set offset [DetailOffset $offset]} "DIRALLSLASH" {set offset [DetailOffset $offset]} } DetailRow $window_name white #EEEEFF $r(x.versiontext) [expr {$r(x.ping)?"PING":$r(x.paylength)}] $r(x.return) $r(x.flagtext) $r(x.size) $offset DetailPayload $window_name #EEEEFF $circ_buffer($cb_index.response.$i) $r(x.paylength) } } } proc DetailPayload { window_name color full_string payload } { DetailText $window_name $color [string range $full_string 24 [expr {$payload + 24}] ] } proc DetailPayloadPlus { window_name color1 color2 full_string payload size } { set endpay [expr {24+$payload-$size-1}] DetailText $window_name $color1 [string range $full_string 24 $endpay ] incr endpay DetailText $window_name $color2 [string range $full_string $endpay [expr {$payload + 24}] ] } proc DetailText { window_name color text_string } { set row [lindex [grid size $window_name] 1] set columns [lindex [grid size $window_name] 0] label $window_name.t${row} -text $text_string -bg $color -relief ridge -wraplength 640 -justify left -anchor w grid $window_name.t${row} -row $row -columnspan $columns -sticky news } # Standard detail row, one cell per value proc DetailRow { window_name color1 color2 v0 v1 v2 v3 v4 v5 } { set row [lindex [grid size $window_name] 1] set w0 [label $window_name.x${row}0 -text $v0 -bg $color1] DetailRowRest $window_name $color1 $color2 $row $w0 $v1 $v2 $v3 $v4 $v5 } # Augmented detail row, one cell per value, plus num in blue proc DetailRowPlus { window_name color1 color2 num v0 v1 v2 v3 v4 v5 } { set row [lindex [grid size $window_name] 1] set w0 [frame $window_name.x${row}0 -bd 0 -relief flat] set n0 [label $w0.n -text $num -bg #a6b1ff -fg white] set m0 [label $w0.m -text $v0 -bg $color1] pack $n0 -side left -anchor w -fill none pack $m0 -side right -fill x -expand 1 DetailRowRest $window_name $color1 $color2 $row $w0 $v1 $v2 $v3 $v4 $v5 } # first element could be augmented by packet number # adds row and widget for first cell instead of value proc DetailRowRest { window_name color1 color2 row w0 v1 v2 v3 v4 v5 } { label $window_name.x${row}1 -text $v1 -bg $color2 label $window_name.x${row}2 -text $v2 -bg $color1 label $window_name.x${row}3 -text $v3 -bg $color2 label $window_name.x${row}4 -text $v4 -bg $color1 label $window_name.x${row}5 -text $v5 -bg $color2 grid $w0 $window_name.x${row}1 $window_name.x${row}2 $window_name.x${row}3 $window_name.x${row}4 $window_name.x${row}5 -row $row -sticky news } proc DetailReturn { array_name prefix } { upvar 1 $array_name a_name if { [info exists a_name($prefix.type)] } { set ret $a_name($prefix.type) } else { return "ESHORT" } if { $ret >= 0 } { return "OK" } switch -- $ret { -1 { return "EPERM"} -2 { return "ENOENT"} -5 { return "EIO"} -12 { return "ENOMEM"} -14 { return "EFAULT"} -19 { return "ENODEV"} -20 { return "ENOTDIR"} -21 { return "EISDIR"} -22 { return "EINVAL"} -34 { return "ERANGE"} -42 { return "ENOMSG"} default { return "ERROR"} } } proc DetailOffset { offset } { return [expr {$offset&0x0001?"resume ":""}][expr {$offset&0x0002?"alarm ":""}][expr {$offset&0x0004?"ovdr ":""}][expr {$offset&0x8000?"temp ":""}][expr {$offset&0x4000?"volt ":""}][expr {$offset&0x2000?"chain ":""}] } proc DetailFlags { flags } { switch [expr {($flags >>16)&0xFF}] { 0 {set T "C"} 1 {set T "F"} 2 {set T "K"} 3 {set T "R"} default {set T "?temp"} } switch [expr {$flags >>24}] { 0 {set F " f.i"} 1 {set F " fi"} 2 {set F " f.i.c"} 3 {set F " f.ic"} 4 {set F " fi.c"} 5 {set F " fic"} default {set F " ?format"} } return $T$F[expr {$flags&0x10?" safe":""}][expr {$flags&0x08?" alias":""}][expr {$flags&0x04?" persist":""}][expr {$flags&0x02?" bus":""}][expr {$flags&0x01?" cache":""}] } proc WebResponse { sock } { set R [lindex {$Revision$} 1] fconfigure $sock -buffering full -translation crlf -blocking 0 puts $sock "HTTP/1.0 400 Bad Request" puts $sock "Date: [clock format [clock seconds] -gmt 1]" puts $sock "Server: OWTAP-$R" puts $sock "Last-Modified: [clock format [clock seconds] -gmt 1]" puts $sock "Content-Type: text/html" puts $sock "" puts $sock "OWTAP-$R owserver protocol inspector" puts $sock "

Attempt to access OWTAP directly. You probably meant to access OWHTTPD, the 1-wire web server.

" puts $sock "

For more information see OWFS website or the Sourceforge site." flush $sock } # socket name in readable format proc PrettySock { sock } { set socklist [fconfigure $sock -sockname] return [lindex $socklist 1]:[lindex $socklist 2] } proc PrettyPeer { sock } { set socklist [fconfigure $sock -peername] return [lindex $socklist 1]:[lindex $socklist 2] } proc MainTitle { tap server } { wm title . "OWTAP ($tap) tap of owserver ($server)" } proc Restart { } { # foreach ch [chan names] { close $ch } foreach channel [file channels] { if { [regexp -- {^std(out|in|err)$} $channel ] == 0 } { # change to non-blocking and close if { [ catch { fconfigure $channel -blocking 0 ; close $channel; } reason ] == 1 } { StatusMessage "Error closing channel $channel $reason" 1 } } } # exec [info nameofexecutable] $::argv0 "--" {*}$::argv & if { [info nameofexecutable] eq $::argv0 } { eval exec [list [info nameofexecutable]] "--" $::argv & } else { eval exec [list [info nameofexecutable]] [list $::argv0] "--" $::argv "&" } exit } #Finally, all the Proc-s have been defined, so run everything. Main $argv