# 
#  Copyright (c) 1997 by the University of Southern California
#  All rights reserved.
# 
#  Permission to use, copy, modify, and distribute this software and its
#  documentation in source and binary forms for non-commercial purposes
#  and without fee is hereby granted, provided that the above copyright
#  notice appear in all copies and that both the copyright notice and
#  this permission notice appear in supporting documentation. and that
#  any documentation, advertising materials, and other materials related
#  to such distribution and use acknowledge that the software was
#  developed by the University of Southern California, Information
#  Sciences Institute.  The name of the University may not be used to
#  endorse or promote products derived from this software without
#  specific prior written permission.
# 
#  THE UNIVERSITY OF SOUTHERN CALIFORNIA makes no representations about
#  the suitability of this software for any purpose.  THIS SOFTWARE IS
#  PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
#  INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# 
#  Other copyrights might apply to parts of this software and are so
#  noted when applicable.
# 
# Source srm-debug.tcl if you want to turn on tracing of events.
# Do not insert probes directly in this code unless you are sure
# that what you want is not available there.
#
#	Author:		Kannan Varadhan	<kannan@isi.edu>
#	Version Date:	Mon Jun 30 15:51:33 PDT 1997
#

Agent instproc traffic-source agent {
	$self instvar tg_
	set tg_ $agent
	$tg_ target $self
	$tg_ set addr_ [$self set addr_]
}

Agent/SRM set packetSize_  1024	;# assume default message size for repair is 1K
Agent/SRM set groupSize_   1

Agent/SRM set distanceCompute_	ewma

Agent/SRM set C1_	2.0
Agent/SRM set C2_	2.0
Agent/SRM set requestFunction_	"SRM/request"
Agent/SRM set requestBackoffLimit_	5

Agent/SRM set D1_	1.0
Agent/SRM set D2_	1.0
Agent/SRM set repairFunction_	"SRM/repair"

Agent/SRM set sessionDelay_ 1.0
Agent/SRM set sessionFunction_	"SRM/session"

Class Agent/SRM/Deterministic -superclass Agent/SRM
Agent/SRM/Deterministic set C2_ 0.0
Agent/SRM/Deterministic set D2_ 0.0

Class Agent/SRM/Probabilistic -superclass Agent/SRM
Agent/SRM/Probabilistic set C1_ 0.0
Agent/SRM/Probabilistic set D1_ 0.0

Class Agent/SRM/Fixed -superclass Agent/SRM

Class SRM
Class SRM/request -superclass SRM
Class SRM/repair -superclass SRM
Class SRM/session -superclass SRM

source ../mcast/srm-adaptive.tcl

###
Agent/SRM instproc init {} {
	$self next
	$self instvar ns_ requestFunction_ repairFunction_
	set ns_ [Simulator instance]
	$self init-instvar sessionDelay_
	foreach var {C1_ C2_ D1_ D2_} {
		$self init-instvar $var
	}
	$self init-instvar requestFunction_
	$self init-instvar repairFunction_
	$self init-instvar sessionFunction_
	$self init-instvar requestBackoffLimit_
	$self init-instvar distanceCompute_

	$self array set stats_ [list		\
			dup-req		-1	ave-dup-req	-1	\
			dup-rep		-1	ave-dup-rep	-1	\
			req-delay	0.0	ave-req-delay	-1	\
			rep-delay	0.0	ave-rep-delay	-1	\
			]
}

Agent/SRM instproc delete {} {
	$self instvar ns_ pending_ done_ session_ tg_
	foreach i [array names pending_] {
		$pending_($i) cancel DELETE
		delete $pending_($i)
	}
	$self cleanup
	delete $session_
	if [info exists tg_] {
		delete $tg_
	}
}

Agent/SRM instproc start {} {
	$self instvar node_ dst_	;# defined in Agent base class
	set dst_ [expr $dst_]		;# get rid of possibly leading 0x etc.
	$self cmd start

	$node_ join-group $self $dst_

	$self instvar ns_ session_ sessionFunction_
	set session_ [new $sessionFunction_ $ns_ $self]
	$session_ schedule
}

Agent/SRM instproc start-source {} {
	$self instvar tg_
	if ![info exists tg_] {
		error "No source defined for agent $self"
	}
	$tg_ start
}

Agent/SRM instproc sessionFunction f {
	$self instvar sessionFunction_
	set sessionFunction_ $f
}

Agent/SRM instproc requestFunction f {
	$self instvar requestFunction_
	set requestFunction_ $f
}

Agent/SRM instproc repairFunction f {
	$self instvar repairFunction_
	set repairFunction_ $f
}

Agent/SRM instproc groupSize? {} {
	$self set groupSize_
}

global alpha
if ![info exists alpha] {
	set alpha	0.25
}

proc ewma {ave cur} {
	if {$ave < 0} {
		return $cur
	} else {
		global alpha
		return [expr (1 - $alpha) * $ave + $alpha * $cur]
	}
}

proc instantaneous {ave cur} {
	set cur
}

Agent/SRM instproc compute-ave var {
	$self instvar stats_
	set stats_(ave-$var) [ewma $stats_(ave-$var) $stats_($var)]
}

####

Agent/SRM instproc recv {type args} {
	eval $self recv-$type $args
}

Agent/SRM instproc recv-data {sender msgid} {
	$self instvar pending_ nid_
	if ![info exists pending_($sender:$msgid)] {
		# This is a very late data of some wierd sort.
		# How did we get here?
		error "Oy vey!  How did we get here?"
	}
	if {[$pending_($sender:$msgid) set round_] == 1} {
	    if {$msgid==1 && $nid_!= 1} {
	    } else {
		$pending_($sender:$msgid) cancel DATA
		$pending_($sender:$msgid) evTrace Q DATA
		delete $pending_($sender:$msgid)
		unset pending_($sender:$msgid)
	    }
	} else {
		$pending_($sender:$msgid) recv-repair
	}
}

Agent/SRM instproc mark-period period {
	$self compute-ave $period
	$self set stats_($period) 0
}

Agent/SRM instproc request {sender args} {
	# called when agent detects a lost packet
	$self instvar pending_ ns_ requestFunction_
	set newReq 0
	foreach msgid $args {
		if [info exists pending_($sender:$msgid)] {
			error "duplicate loss detection in agent"
		}
		set pending_($sender:$msgid) [new $requestFunction_ $ns_ $self]
		$pending_($sender:$msgid) set-params $sender $msgid
		$pending_($sender:$msgid) schedule
		
		if ![info exists old_($sender:$msgid)] {
			incr newReq
		}
	}
	if $newReq {
		$self mark-period dup-req
	}
}

Agent/SRM instproc update-ave {type delay} {
	$self instvar stats_
	set stats_(${type}-delay) $delay
	$self compute-ave ${type}-delay
}

Agent/SRM instproc recv-request {requestor round sender msgid} {
	# called when agent receives a control message for an old ADU
	$self instvar pending_ stats_
	if [info exists pending_($sender:$msgid)] {
		# Either a request or a repair is pending.
		# Possibly mark duplicates.
		if { $round == 1 } {
			incr stats_(dup-req) [$pending_($sender:$msgid)	\
					dup-request?]
		}
		$pending_($sender:$msgid) recv-request
	} else {
		# We have no events pending, only if the ADU is old, and
		# we have received that ADU.
		$self repair $requestor $sender $msgid
	}
}

Agent/SRM instproc repair {requestor sender msgid} {
	# called whenever agent receives a request, and packet exists
	$self instvar pending_ ns_ repairFunction_
	if [info exists pending_($sender:$msgid)] {
		error "duplicate repair detection in agent??  really??"
	}
	set pending_($sender:$msgid) [new $repairFunction_ $ns_ $self]
	$pending_($sender:$msgid) set requestor_ $requestor
	$pending_($sender:$msgid) set-params $sender $msgid
	$pending_($sender:$msgid) schedule
	$self mark-period dup-rep
}

Agent/SRM instproc recv-repair {round sender msgid} {
	$self instvar pending_ stats_
	if ![info exists pending_($sender:$msgid)] {
		# 1.  We didn't hear the request for the older ADU, or
		# 2.  This is a very late repair beyond the $3 d_{S,B}$ wait
		# What should we do?
		#if { $round == 1 } { incr stats_(dup-rep) }
		$self instvar trace_ ns_ node_ 
		if [info exists trace_] {
			# puts -nonewline $trace_ [format "%7.4f" [$ns_ now]]
			# puts $trace_ " n [$node_ id] m <$sender:$msgid> r $round X REPAIR late dup"
		}
	} else {
		if { $round == 1 } {
			incr stats_(dup-rep) [$pending_($sender:$msgid)	\
					dup-repair?]
		}
		$pending_($sender:$msgid) recv-repair
	}
}

Agent/SRM/Fixed instproc repair args {
	$self set D1_ [expr log10([$self set groupSize_])]
	$self set D2_ [expr log10([$self set groupSize_])]
	eval $self next $args
}

####

Agent/SRM instproc clear {obj s m} {
	$self instvar pending_ done_ old_ logfile_
	set done_($s:$m) $obj
	set old_($s:$m) [$obj set round_]
	if [info exists logfile_] {
		$obj dump-stats $logfile_
	}
	unset pending_($s:$m)
	if {[array size done_] > 32} {
		$self instvar ns_
		$ns_ at [expr [$ns_ now] + 0.01] "$self cleanup"
	}
}

Agent/SRM instproc round? {s m} {
	$self instvar old_
	if [info exists old_($s:$m)] {
		return $old_($s:$m)
	} else {
		return 0
	}
}

Agent/SRM instproc cleanup {} {
	# We need to do this somewhere...now seems a good time
	$self instvar done_
	if [info exists done_] {
		foreach i [array names done_] {
			delete $done_($i)
		}
		unset done_
	}
}

Agent/SRM instproc trace file {
	$self set trace_ $file
}

Agent/SRM instproc log file {
	$self set logfile_ $file
}

#
#
# Note that the SRM event handlers are not rooted as TclObjects.
#
SRM instproc init {ns agent} {
	$self next
	$self instvar ns_ agent_ nid_ distf_
	set ns_ $ns
	set agent_ $agent
	set nid_ [[$agent_ set node_] id]
	set distf_ [$agent_ set distanceCompute_]
	if ![catch "$agent_ set trace_" traceVar] {
		$self set trace_ $traceVar
	}
	$self array set times_ [list		\
			startTime [$ns_ now] serviceTime -1 distance -1]
}

SRM instproc set-params {sender msgid} {
	$self next
	$self instvar agent_ sender_ msgid_ round_ sent_
	set sender_ $sender
	set msgid_  $msgid
	set round_  [$agent_ round? $sender_ $msgid_]
	set sent_	0
}

SRM instproc cancel {} {
	$self instvar ns_ eventID_
	if [info exists eventID_] {
		$ns_ cancel $eventID_
		unset eventID_
	}
}

SRM instproc schedule {} {
	$self instvar round_
	incr round_
}

SRM instproc distance? node {
	$self instvar agent_ times_ distf_
	set times_(distance) [$distf_ $times_(distance)	\
				[$agent_ distance? $node]]
}

SRM instproc serviceTime {} {
	$self instvar ns_ times_
	set times_(serviceTime) [expr ([$ns_ now] - $times_(startTime)) / \
			( 2 * $times_(distance))]
}

# Some routines to make SRM tracing standard
SRM instproc logpfx fp {
	$self instvar ns_ nid_ sender_ msgid_ round_
	puts -nonewline $fp [format "%7.4f" [$ns_ now]]
	puts -nonewline $fp " n $nid_ m <$sender_:$msgid_> r $round_ "
}

SRM instproc dump-stats fp {
	$self instvar times_ statistics_
	$self logpfx $fp
	puts -nonewline $fp "type [string range [$self info class] 4 end] "
	puts $fp "[array get times_] [array get statistics_]"
}

SRM instproc evTrace {tag type args} {
	$self instvar trace_
	if [info exists trace_] {
		$self logpfx $trace_
		puts -nonewline $trace_ "$tag $type"
		foreach elem $args {
			puts -nonewline $trace_ " $elem"
		}
		puts $trace_ {}
	}
}

####

SRM/request instproc init args {
	eval $self next $args
	$self array set statistics_ "dupRQST 0 dupREPR 0 #sent 0 backoff 0"
}

SRM/request instproc set-params args {
	eval $self next $args
	$self instvar agent_ sender_
	foreach var {C1_ C2_} {
		if ![catch "$agent_ set $var" val] {
			$self instvar $var
			set $var $val
		}
	}
	$self distance? $sender_
	$self instvar backoff_ backoffCtr_ backoffLimit_
	set backoff_ 1
	set backoffCtr_ 0
	set backoffLimit_ [$agent_ set requestBackoffLimit_]

	$self evTrace Q DETECT
}

SRM/request instproc dup-request? {} {
	$self instvar ns_ round_ ignore_
	if {$round_ == 2 && [$ns_ now] <= $ignore_} {
		return 1
	} else {
		return 0
	}
}

SRM/request instproc dup-repair? {} {
	return 0
}

SRM/request instproc backoff? {} {
	$self instvar backoff_ backoffCtr_ backoffLimit_
	set retval $backoff_
	if {[incr backoffCtr_] <= $backoffLimit_} {
		incr backoff_ $backoff_
	}
	set retval
}

SRM/request instproc compute-delay {} {
	$self instvar C1_ C2_
	set rancomp [expr $C1_ + $C2_ * [uniform 0 1]]

	$self instvar sender_ backoff_
	set dist [$self distance? $sender_]
	$self evTrace Q INTERVALS C1 $C1_ C2 $C2_ d $dist i $backoff_
	set delay [expr $rancomp * $dist]
}

SRM/request instproc schedule {} {
	$self instvar ns_ eventID_ delay_
	$self next
	set now [$ns_ now]
	set delay_ [expr [$self compute-delay] * [$self backoff?]]
	set fireTime [expr $now + $delay_]

	$self evTrace Q NTIMER at $fireTime

	set eventID_ [$ns_ at $fireTime "$self send-request"]
}

SRM/request instproc cancel type {
	$self next
	if {$type == "REQUEST" || $type == "REPAIR"} {
		$self instvar agent_ round_
		if {$round_ == 1} {
			$agent_ update-ave req [$self serviceTime]
		}
	}
}

SRM/request instproc send-request {} {
	$self instvar agent_ round_ sender_ msgid_ sent_ round_
	$self evTrace Q SENDNACK

	$agent_ send request $round_ $sender_ $msgid_

	$self instvar statistics_
	incr statistics_(#sent)
	set sent_ $round_
}

SRM/request instproc recv-request {} {
	$self instvar ns_ agent_ round_ delay_ ignore_ statistics_
	if {[info exists ignore_] && [$ns_ now] < $ignore_} {
		incr statistics_(dupRQST)
#		$self evTrace Q NACK dup
	} else {
		$self cancel REQUEST
		$self schedule          ;# or rather, reschedule-rqst 
		set ignore_ [expr [$ns_ now] + ($delay_ / 2)]
		incr statistics_(backoff)
		$self evTrace Q NACK IGNORE-BACKOFF $ignore_
	}
}

SRM/request instproc recv-repair {} {
	$self instvar ns_ agent_ sender_ msgid_ ignore_ eventID_
	if [info exists eventID_] {
		$self serviceTime
		set ignore_ [expr [$ns_ now] + 3 * [$self distance? $sender_]]
		$ns_ at $ignore_ "$agent_ clear $self $sender_ $msgid_"
		$self cancel REPAIR
		$self evTrace Q REPAIR IGNORES $ignore_
	} else {		;# we must be in the 3dS,B holdDown interval
		$self instvar statistics_
		incr statistics_(dupREPR)
#		$self evTrace Q REPAIR dup
	}
}

#
SRM/repair instproc init args {
	eval $self next $args
	$self array set statistics_ "dupRQST 0 dupREPR 0 #sent 0"
}

SRM/repair instproc set-params args {
	eval $self next $args
	$self instvar agent_ requestor_
	foreach var {D1_ D2_} {
		if ![catch "$agent_ set $var" val] {
			$self instvar $var
			set $var $val
		}
	}
	$self distance? $requestor_
	$self evTrace P NACK from $requestor_
}

SRM/repair instproc dup-request? {} {
	return 0
}

SRM/repair instproc dup-repair? {} {
	$self instvar ns_ round_
	if {$round_ == 1} {		;# because repairs do not reschedule
		return 1
	} else {
		return 0
	}
}
#
SRM/repair instproc compute-delay {} {
	$self instvar D1_ D2_
	set rancomp [expr $D1_ + $D2_ * [uniform 0 1]]

	$self instvar requestor_
	set dist [$self distance? $requestor_]
	$self evTrace P INTERVALS D1 $D1_ D2 $D2_ d $dist
	set delay [expr $rancomp * $dist]
}

SRM/repair instproc schedule {} {
	$self instvar ns_ eventID_
	$self next
	set fireTime [expr [$ns_ now] + [$self compute-delay]]

	$self evTrace P RTIMER at $fireTime

	set eventID_ [$ns_ at $fireTime "$self send-repair"]
}

SRM/repair instproc cancel type {
	$self next
	if {$type == "REQUEST" || $type == "REPAIR"} {
		$self instvar agent_ round_
		if {$round_ == 1} {
			$agent_ update-ave rep [$self serviceTime]
		}
	}
}

SRM/repair instproc send-repair {} {
	$self instvar ns_ agent_ round_ sender_ msgid_ requestor_ sent_ round_
	$self evTrace P SENDREP

	$agent_ set requestor_ $requestor_
	$agent_ send repair $round_ $sender_ $msgid_

	$self instvar statistics_
	incr statistics_(#sent)
	set sent_ $round_
}

SRM/repair instproc recv-request {} {
	# duplicate (or multiple) requests
	$self instvar statistics_
	incr statistics_(dupRQST)
#	$self evTrace P NACK dup
}

SRM/repair instproc recv-repair {} {
	$self instvar ns_ agent_ round_ sender_ msgid_ eventID_ requestor_
	if [info exists eventID_] {
		#
		# holdDown is identical to the ignore_ parameter in
		# SRM/request::recv-repair.  However, since recv-request
		# for this class is very simple, we do not need an
		# instance variable to record the current state, and
		# hence only require a simple variable.
		#
		set holdDown [expr [$ns_ now] +		\
				3 * [$self distance? $requestor_]]
		$ns_ at $holdDown "$agent_ clear $self $sender_ $msgid_"
		$self cancel REPAIR
		$self evTrace P REPAIR IGNORES $holdDown
	} else {		;# we must in the 3dS,B holdDown interval
		$self instvar statistics_
		incr statistics_(dupREPR)
#		$self evTrace P REPAIR dup
	}
}

#
SRM/session instproc init args {
	eval $self next $args
	$self instvar agent_ sessionDelay_ round_
	set sessionDelay_ [$agent_ set sessionDelay_]
	set round_ 1
	$self array set statistics_ "#sent 0"

	$self set sender_ 0
	$self set msgid_  0
}

SRM/session instproc delete {} {
	$self instvar $ns_ eventID_
	$ns_ cancel $eventID_
	$self next
}

SRM/session instproc schedule {} {
	$self instvar ns_ agent_ sessionDelay_ eventID_

	$self next

	# What is a reasonable interval to schedule session messages?
	set fireTime [expr $sessionDelay_ * [uniform 0.9 1.1]]
	# set fireTime [expr $sessionDelay_ * [uniform 0.9 1.1] * \
			(1 + log([$agent_ set groupSize_])) ]

	set eventID_ [$ns_ at [expr [$ns_ now] + $fireTime]		\
			"$self send-session"]
}

SRM/session instproc send-session {} {
	$self instvar agent_ statistics_
	$agent_ send session
	$self evTrace S SESSION
	incr statistics_(#sent)
	$self schedule
}

SRM/session instproc evTrace args {}	;# because I don't want to trace
					 # session messages.
