http://i2p2.de%|%I2P%|% [Zarutian]: An start of an http://www.i2p2.de/samv2.html%|%SAMv2%|% library for Tcl ====== # I, Zarutian, hereby put this code into the International public domain. package require Tcl 8.5 package provide i2p/sam2 0.1 namespace eval i2p {} namespace eval i2p::sam2 { variable defaults { bridge {host localhost port none} style STREAM} variable session_counter 0 variable sessions proc new_session {params} { variable session_counter set i session[incr session_counter] variable defaults if {![dict exists $params style]} { dict set params style [dict get $defaults style] } if {![dict exists $params bridge]} { dict set params bridge [dict get $defaults bridge] } if {![dict exists $params bridge host]} { dict set params bridge host [dict get $defaults bridge host] } if {![dict exists $params bridge port]} { dict set params bridge port [dict get $defaults bridge port] } if {![dict exists $params stream]} { dict set params stream [dict get $defaults stream] } if {![dict exists $params condition_callback} { error "an condition_callback must be provided" } if {![dict exists $params my_name]} { error "my_name must be provided" } variable sessions dict set sessions $i condition_callback [dict get $params condition_callback] dict set sessions $i my_name [dict get $params my_name] dict set sessions $i style [dict get $params style] dict set sessions $i stream_counter 0 dict set sessions $i socket [set sock [socket -async [dict get $params bridge host] [dict get $params bridge port]]] fconfigure $sock -buffering none -translation binary -encoding binary -blocking no fileevent $sock readable [list i2p::sam2::raw_recive_stage1 $i] raw_send $sock "HELLO VERSION MIN=2.0 MAX=2.0\n" return $i } proc raw_recive_stage1 {session_id} { variable sessions set sock [dict get $sessions $session_id socket] if {[eof $sock]} { close $sock after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection closed $session_id ] return } dict append sessions $session_id raw_buffer [set buffer [read $sock]] if {[string first $buffer "\n"] != -1} { if {![string equal [string range $buffer 0 12] "HELLO REPLY "]} { after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection noHelloReply $session_id] return } set line [split $buffer "\n"] set done 0 foreach pair [split [string range $line 13 end] " "] { lassign [split $pair "="] key value if {[string equal $key "RESULT"]} { incr done; set RESULT $value } if {[string equal $key "VERSION"]} { incr done; set VERSION $value } } if {$done != 2} { after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection missingHelloReplyParams $session_id] return } if {[string equal $RESULT "NOVERSION]} { after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection noversion $session_id] return } if {![string equal $RESULT "OK"]} { after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection resultNotOk $session_id] return } if {![string equal $VERSION "2.0"]} { after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection versionNot2.0 $session_id] return } #--- dict set sessions $session_id raw_buffer [string range [dict get $sessions $session_id raw_buffer] \ [expr {[string first $buffer "\n"] + 1} end] set p "SESSION CREATE STYLE=" append p [string toupper [dict get $sessions $session_id style]] append p " DESTINATION=" append p [dict get $sessions $session_id my_name] append p "\n" raw_send $sock $p fileevent $sock readable [list i2p::sam2::raw_recive_stage2 $session_id] } } proc raw_recive_stage2 {session_id} { variable sessions set sock [dict get $sessions $session_id socket] if {[eof $sock]} { close $sock after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection closed $session_id ] return } dict append sessions $session_id raw_buffer [set buffer [read $sock]] if {[string first $buffer "\n"] != -1} { if {![string equal "SESSION STATUS " [string range $buffer 0 14]]} { after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection noSessionStatus $session_id] return } set line [split $buffer "\n"] set continue? no foreach pair [split [string range $buffer 15 end] " "] { lassign [split $pair "="] key value if {[string equal $key "RESULT"]} { switch -exact -- $value { "OK" {} "DUPLICATED_DEST" {} "I2P_ERROR" {} "INVALID_KEY" {} } } elseif {[string equal $key "DESTINATION"]} { } elseif {[string equal $key "MESSAGE"]} {} } if {$continue?} { #--- dict set sessions $session_id raw_buffer [string range [dict get $sessions $session_id raw_buffer] \ [expr {[string first $buffer "\n"] + 1} end] fileevent $sock readable [list i2p::sam2::raw_recive_stage3 $session_id] } } } proc raw_recive_stage3 {session_id} { variable sessions set sock [dict get $sessions $session_id socket] if {[eof $sock]} { close $sock after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge connection closed $session_id ] return } dict append sessions $session_id raw_buffer [set buffer [read $sock]] if {[string first $buffer "\n"] != -1} { switch -exact -- [lindex $buffer 0] { "SESSION" { session_recive $session_id } "STREAM" { stream_recive $session_id } "DATAGRAM" { datagram_recive $session_id } "RAW" { rawgram_recive $session_id } "NAMING" { naming_recive $session_id } "DEST" { dest_recive $session_id } default { after 0 [list {*}[dict get $sessions $session_id condition_ } } } } proc raw_send {socket data} { catch { puts -nonewline $socket $data; flush $socket } } proc new_stream {session_id destination callback} { variable sessions if {![string equal "STREAM" [dict get $sessions $session_id style]]} { error "cant open an new stream in session $session_id as its style isnt STREAM" } set sock [dict get $sessions $session_id socket] set i [expr {[dict get $sessions $session_id stream_counter] + 1}] dict set sessions $session_id stream_counter $i set p "STREAM CONNECT ID=" append p $i append p " DESTINATION=" append p $destination append p "\n" raw_send $sock $p set p "STREAM RECEIVE ID=" append p $i append p " LIMIT=NONE dict set sessions $session_id streams $i destination $destination dict set sessions $session_id streams $i callback $callback dict set sessions $session_id streams $i status connecting dict set sessions $session_id streams $i ready_to_send no return $i } proc send_on_stream {session_id stream_id data} { variable sessions if {![string equal "STREAM" [dict get $sessions $session_id style]]} { error "cant send on stream $stream_id in session $session_id as its style isnt STREAM" } set sock [dict get $sessions $session_id socket] if {[dict get $sessions $session_id streams $stream_id ready_to_send]} { set p "STREAM SEND ID=" append p $stream_id append p " SIZE=" if {[string length $data] <= 32768} { append p [string length $data] append p "\n" append p $data dict set sessions $sessions_id streams $stream_id more2send no } else { append p 32768 append p "\n" append p [string range $data 0 32767] dict append sessions $session_id streams $stream_id out_buffer [string range $data 32768 end] dict set sessions $session_id streams $stream_id more2send yes } raw_send $sock $p } else { dict append sessions $session_id streams $stream_id out_buffer $data dict set sessions $session_id streams $stream_id more2send yes } } proc close_stream {session_id stream_id} { variable sessions if {![string equal "STREAM" [dict get $sessions $session_id style]]} { error "cant close stream $stream_id in session $session_id as its style isnt STREAM" } set sock [dict get $sessions $session_id socket] if {[dict get $sessions $session_id streams $stream_id more2send]} { dict set sessions $session_id streams $stream_id closeAfterAllIsSent yes } else { raw_send $sock "STREAM CLOSE ID=[set stream_id]\n" } } } ======