# $Id: ibb.tcl 1307 2007-11-10 15:04:17Z sergei $
#
# In-Band Bytestreams (XEP-0047) transport for SI
#

###############################################################################

namespace eval ibb {}

set ::NS(ibb) http://jabber.org/protocol/ibb

###############################################################################

proc ibb::connect {stream chunk_size command} {
    upvar #0 $stream state

    set_status [::msgcat::mc "Opening IBB connection"]

    jlib::send_iq set \
	[jlib::wrapper:createtag open \
	     -vars [list xmlns $::NS(ibb) \
			 sid $state(id) \
			 block-size $chunk_size]] \
	-to $state(jid) \
	-connection $state(connid) \
	-command [list [namespace current]::recv_connect_response \
		       $stream $command]
}

proc ibb::recv_connect_response {stream command res child} {
    upvar #0 $stream state

    if {$res != "OK"} {
	uplevel #0 $command [list [list 0 [error_to_string $child]]]
	return
    }

    jlib::wrapper:splitxml $child tag vars isempty chdata children

    set state(seq) 0
    uplevel #0 $command 1
}

###############################################################################

package require base64

proc ibb::send_data {stream data command} {
    upvar #0 $stream state

    jlib::send_msg $state(jid) \
	-xlist [list [jlib::wrapper:createtag data \
			  -vars [list xmlns $::NS(ibb) \
				      sid $state(id) \
				      seq $state(seq)] \
			  -chdata [base64::encode $data]]] \
	-connection $state(connid)

    set state(seq) [expr {($state(seq) + 1) % 65536}]

    after 2000 [list uplevel #0 $command 1]
}

###############################################################################

proc ibb::close {stream} {
    upvar #0 $stream state

    jlib::send_iq set \
	[jlib::wrapper:createtag close \
	     -vars [list xmlns $::NS(ibb) \
			 sid $state(id)]] \
	-to $state(jid) \
	-connection $state(connid)
}

###############################################################################

proc ibb::iq_set_handler {connid from lang child} {
    jlib::wrapper:splitxml $child tag vars isempty chdata children

    set id [jlib::wrapper:getattr $vars sid]
    if {[catch {si::in $connid $from $id} stream]} {
	return [list error modify bad-request \
		     -text [::trans::trans $lang \
					   "Stream ID has not been negotiated"]]
    }
    upvar #0 $stream state

    switch -- $tag {
	open {
	    set state(block-size) [jlib::wrapper:getattr $vars block-size]
	    set state(seq) 0
	}
	close {
	    si::closed $stream
	}
	data {
	    set seq [jlib::wrapper:getattr $vars seq]
	    if {$seq != $state(seq)} {
		si::closed $stream
		return [list error modify bad-request \
			     -text [::trans::trans $lang \
					"Unexpected packet sequence number"]]
	    } else {
		set state(seq) [expr {($state(seq) + 1) % 65536}]
	    }
	    set data $chdata

	    if {[catch {set decoded [base64::decode $data]}]} {
		debugmsg si "IBB: WRONG DATA"
		si::closed $stream
		return [list error modify bad-request \
			     -text [::trans::trans $lang \
					"Cannot decode recieved data"]]
	    } else {
		debugmsg si "IBB: RECV DATA [list $data]"
		if {![si::recv_data $stream $decoded]} {
		    si::closed $stream
		    return [list error cancel not-allowed \
				 -text [::trans::trans $lang \
					    "File transfer is aborted"]]
		}
	    }
	}
	default {
	    return [list error modify bad-request]
	}
    }

    return [list result ""]
}

iq::register_handler set "" $::NS(ibb) [namespace current]::ibb::iq_set_handler

###############################################################################

proc ibb::return_error {connid jid id error} {
    if {$id == ""} return

    jlib::send_msg $jid \
	-type error \
	-id $id \
	-xlist [eval stanzaerror::error $error] \
	-connection $connid
}

###############################################################################

proc ibb::message_handler {connid from mid type is_subject subject body \
				 err thread priority x} {
    if {$type == "error"} return

    foreach item $x {
	jlib::wrapper:splitxml $item tag vars isempty chdata children

	set xmlns [jlib::wrapper:getattr $vars xmlns]

	if {[string equal $xmlns $::NS(ibb)]} {
	    set id [jlib::wrapper:getattr $vars sid]
	    if {[catch {si::in $connid $from $id} stream]} {
		# Unknown Stream ID
		return_error $connid $from $mid \
			     [list modify bad-request \
				   -text [::trans::trans \
					      "Stream ID has not been negotiated"]]
		return stop
	    }

	    upvar #0 $stream state
	    set seq [jlib::wrapper:getattr $vars seq]

	    if {$seq != $state(seq)} {
		# Incorrect sequence number
		si::closed $stream
		return_error $connid $from $mid \
			     [list modify bad-request \
				   -text [::trans::trans \
					      "Unexpected packet sequence number"]]
		return stop
	    }

	    set state(seq) [expr {($state(seq) + 1) % 65536}]
	    set data $chdata

	    if {[catch {set decoded [base64::decode $data]}]} {
		debugmsg si "IBB: WRONG DATA"
		si::closed $stream
		return_error $connid $from $mid \
			     [list modify bad-request \
				   -text [::trans::trans \
					      "Cannot decode recieved data"]]
	    } else {
		debugmsg si "IBB: RECV DATA [list $data]"
		if {![si::recv_data $stream $decoded]} {
		    si::closed $stream
		    return_error $connid $from $mid \
				 [list cancel not-allowed \
				       -text [::trans::trans \
						  "File transfer is aborted"]]
		}
	    }
	    return stop
	}
    }
}

hook::add process_message_hook [namespace current]::ibb::message_handler 50

###############################################################################

si::register_transport $::NS(ibb) $::NS(ibb) 75 \
    [namespace current]::ibb::connect \
    [namespace current]::ibb::send_data \
    [namespace current]::ibb::close

###############################################################################

