Version 25 of i2p

Updated 2010-01-04 02:27:37 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}
    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 [lindex [split $buffer "\n"] 0]
        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 [lindex [split $buffer "\n"] 0]
        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_callback] bridge protocol unknown_message_type $session_id]
            return
          }
        }
      }
    }
    proc session_recive {session_id} {}
    proc stream_recive {session_id} {
      variable sessions
      set sock [dict get $sessions $session_id socket]
      set buffer [dict get $sessions $session_id raw_buffer]
      set line [lindex [split $buffer "\n"] 0]
      switch -exact -- [lindex $buffer 1] {
        "STATUS" {
          set done 0
          set MESSAGE ""
          foreach pair [split [string range $line 14 end] " "] {
            lassign [split $pair "="] key value
            if {[string equal $key "RESULT"]} { incr done; set RESULT $value }
            if {[string equal $key "ID"]} { incr done; set ID $value }
          }
          if {$done != 2} {
            after 0 [list {*}[dict get $sessions $session_id condition_callback] bridge protocol statusInvalidParams $session_id]
            return
          }
          if {[string equal $RESULT "OK"]} {
               set p "STREAM RECEIVE ID="
            append p $ID
            append p " LIMIT=NONE\n"
            raw_send $sock $p
          }
          after 0 [list {*}[dict get $sessions $session_id streams $ID callback] connected $RESULT]
          dict set sessions $session_id raw_buffer [string range $buffer [expr {[string first $buffer "\n"] + 1}] end]
          return
        }
        "CONNECTED" {}
        "SEND" {}
        "READY_TO_SEND" {}
        "CLOSED" {}
        "RECEIVED" {}
      }
    }
    proc datagram_recive {session_id} {}
    proc rawgram_recive {session_id} {}
    proc naming_recive {session_id} {}
    proc dest_recive {session_id} {}

    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
      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"
      }
    }
  }