Version 16 of i2p

Updated 2010-01-04 01:11:52 by Zarutian

I2P

Zarutian: An start of an 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 stream {limit none}}
    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 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 conditional_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 conditional_callback] bridge connection missingHelloReplyParams $session_id]
          return
        }
        if {[string equal $RESULT "NOVERSION]} {
          after 0 [list {*}[dict get $sessions $session_id conditional_callback] bridge connection noversion $session_id]
          return
        }
        if {![string equal $RESULT "OK"]} {
          after 0 [list {*}[dict get $sessions $session_id conditional_callback] bridge connection resultNotOk $session_id]
          return
        }
        if {![string equal $VERSION "2.0"]} {
          after 0 [list {*}[dict get $sessions $session_id conditional_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 conditional_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} {}
    proc raw_send {socket data} { catch { puts -nonewline $socket $data; flush $socket } }
    proc new_stream {session_id destination callback} {
      variable sessions
      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 i [set session_id]::stream[set i]

      return $i
    }
  }