/*
 * Copyright (c) 1997-98 Stanford University.
 * All rights reserved.
 *
 * TCP-BFA: Buffer Fill Avoidance (http://klamath.stanford.edu/~aaa/tcp-bfa)
 * By: Amr A. Awadallah (aaa@stanford.edu) and Chetan Rai (crai@stanford.edu)
 *
 * You are allowed to do whatever you want with this file as long as copyright
 * above is always reproduced whether in text or binary format. We hereby
 * disclaim any misfortunes that might happen to you due to the use of this
 * code.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <math.h>

#include "ip.h"
#include "tcp-bfa.h"
#include "flags.h"
#include "random.h"

static class BfaRenoTcpClass : public TclClass {
public:
	BfaRenoTcpClass() : TclClass("Agent/TCP/Reno/Bfa") {}
	TclObject* create(int, const char*const*) {
		return (new BfaRenoTcpAgent());
	}
} class_bfa_reno;

BfaRenoTcpAgent::BfaRenoTcpAgent() : RenoTcpAgent(), bfa_flag_(0), 
  bfa_off_thresh_(-4), bfa_on_thresh_(4), bfa_signed_rttvar_(0), bfa_enable_(1)
{
	/* init BFA vars */
  
    	bind("bfa_flag_", &bfa_flag_);
	bind_bool("bfa_enable_", &bfa_enable_);
	bind("bfa_off_thresh_", &bfa_off_thresh_);
	bind("bfa_on_thresh_", &bfa_on_thresh_);
	bind("bfa_signed_rttvar_", &bfa_signed_rttvar_ );
	
	reset();
}

void BfaRenoTcpAgent::reset()
{
	/* AMR: Reset the BFA flag */
	bfa_flag_ = 0;
	RenoTcpAgent::reset();
}

/*
 * Initialize variables for the retransmit timer.
 */
void BfaRenoTcpAgent::rtt_init()
{
	/* AMR: Initialize our rtt values */
	bfa_signed_rttvar_ = 0;
	bfa_flag_ = 0;
	RenoTcpAgent::rtt_init();
}

/* This has been modified to use the tahoe code. */
double BfaRenoTcpAgent::rtt_timeout()
{
	double timeout;

	/* AMR: I changed following code so that timeout always has
	        a granuality of 0.5 sec */

	/* I removed this line: timeout = t_rtxcur_ * t_backoff_;
	   and added the couple of following lines: */
	
	double rtxcur = int(2 * t_rtxcur_ + 1) / 2.0;
	timeout = rtxcur * t_backoff_;

	if (timeout > maxrto_)
		timeout = maxrto_;

        if (timeout < 2 * tcp_tick_) {
		if (timeout < 0) {
			fprintf(stderr, "BfaRenoTcpAgent: negative RTO! (%f)\n",
				timeout);
			exit(1);
		}
		timeout = 2 * tcp_tick_;
	}
	return (timeout);
}

/* This has been modified to use the tahoe code. */
void BfaRenoTcpAgent::rtt_update(double tao)
{
	if (ts_option_)
		t_rtt_ = int(tao /tcp_tick_ + 0.5);
	else {
		double now = Scheduler::instance().clock();
		double sendtime = now - tao;
		sendtime += boot_time_;
		double tickoff = fmod(sendtime, tcp_tick_);
		t_rtt_ = int((tao + tickoff) / tcp_tick_);
	}
	if (t_rtt_ < 1)
		t_rtt_ = 1;

	//
	// srtt has 3 bits to the right of the binary point
	// rttvar has 2
	//
        if (t_srtt_ != 0) {
		register short delta;
		delta = t_rtt_ - (t_srtt_ >> T_SRTT_BITS);	// d = (m - a0)
		if ((t_srtt_ += delta) <= 0)	// a1 = 7/8 a0 + 1/8 m
			t_srtt_ = 1;
		
		// AMR: Code to maintain the signed variance 
	        bfa_signed_rttvar_
		 += (delta - bfa_signed_rttvar_ >> T_RTTVAR_BITS );
		// CRAI: changing to 1/2 + 1/2 instead of 3/4 + 1/4
		// (to see if steady-state queue size is smaller)
		// bfa_signed_rttvar_
		//  += ((delta << 1) - (bfa_signed_rttvar_ >> (T_RTTVAR_BITS - 1)));

		// AMR: Set the BFA flag using hysterisis between our
		// bfa off/on thresholds.
		if( bfa_flag_ == 1 )
		  {
		    if( bfa_signed_rttvar_ < bfa_off_thresh_ ) {
		      bfa_flag_ = 0;
		      cwnd_ += 0.001; // CRAI: bump cwnd up and down on flag
		      cwnd_ -= 0.001; // CRAI: switchoff for staircase in graph
		                      // CRAI: This is a hack to get this point
		                      // in the log for smooth plots.
		    }
		  }
		else
		  {
		    if( bfa_signed_rttvar_ > bfa_on_thresh_ ) {
		      bfa_flag_ = 1;
                      // ssthresh_ = cwnd_;
                    }
		  }
		
		if (delta < 0)
			delta = -delta;
		delta -= (t_rttvar_ >> T_RTTVAR_BITS);
		if ((t_rttvar_ += delta) <= 0)	// var1 = 3/4 var0 + 1/4 |d|
			t_rttvar_ = 1;
	} else {
		t_srtt_ = t_rtt_ << T_SRTT_BITS;		// srtt = rtt
		t_rttvar_ = t_rtt_ << (T_RTTVAR_BITS-1);	// rttvar = rtt / 2
		// AMR: signed variance should be set to zero initially */
		bfa_signed_rttvar_ = 0;
		// XXX AMR: We might want to reset the BFA flag here.
	}
	//
	// Current retransmit value is 
	//    (unscaled) smoothed round trip estimate
	//    plus 4 times (unscaled) rttvar. 
	//
	t_rtxcur_ = (((t_rttvar_ << (2 + (T_SRTT_BITS - T_RTTVAR_BITS))) + t_srtt_)  >> T_SRTT_BITS ) * tcp_tick_;
}

void BfaRenoTcpAgent::rtt_backoff()
{
	if (t_backoff_ < 64)
		t_backoff_ <<= 1;

	if (t_backoff_ > 8) {
		/*
		 * If backed off this far, clobber the srtt
		 * value, storing it in the mean deviation
		 * instead.
		 */
		t_rttvar_ += (t_srtt_ >> T_SRTT_BITS);
		t_srtt_ = 0;

		// AMR: initial signed variance = 0
                bfa_signed_rttvar_ = 0;
		// XXX AMR: We may want to set bfa_flag_ to zero here 		
	}
}

/*
 * open up the congestion window
 */
void BfaRenoTcpAgent::opencwnd()
{
	if (cwnd_ < ssthresh_) {
		/* slow-start (exponential) */

	  // XXX AMR: We might want to *not* apply BFA rules during slow-start.
	  if( !bfa_flag_ || !bfa_enable_ )   // AMR: Check the bfa_flag_ first. 
		  cwnd_ += 1;
	} else {
		/* linear */
		double f;
		switch (wnd_option_) {
		case 0:
			if (++count_ >= cwnd_) {
				count_ = 0;
				++cwnd_;
			}
			break;

		case 1:
			/* This is the standard algorithm. */
		  
		  // AMR: Check the bfa_flag_ first. 
		  if( !bfa_flag_ || !bfa_enable_ )   
			cwnd_ += 1 / cwnd_;

		  break;

		case 2:
			/* These are window increase algorithms
			 * for experimental purposes only. */
			f = (t_srtt_ >> T_SRTT_BITS) * tcp_tick_;
			f *= f;
			f *= wnd_const_;
			f += fcnt_;
			if (f > cwnd_) {
				fcnt_ = 0;
				++cwnd_;
			} else
				fcnt_ = f;
			break;

		case 3:
			f = awnd_;
			f *= f;
			f *= wnd_const_;
			f += fcnt_;
			if (f > cwnd_) {
				fcnt_ = 0;
				++cwnd_;
			} else
				fcnt_ = f;
			break;

                case 4:
                        f = awnd_;
                        f *= wnd_const_;
                        f += fcnt_;
                        if (f > cwnd_) {
                                fcnt_ = 0;
                                ++cwnd_;
                        } else
                                fcnt_ = f;
                        break;
		case 5:
                        f = (t_srtt_ >> T_SRTT_BITS) * tcp_tick_;
                        f *= wnd_const_;
                        f += fcnt_;
                        if (f > cwnd_) {
                                fcnt_ = 0;
                                ++cwnd_;
                        } else
                                fcnt_ = f;
                        break;

		default:
#ifdef notdef
			/*XXX*/
			error("illegal window option %d", wnd_option_);
#endif
			abort();
		}
	}
	// if maxcwnd_ is set (nonzero), make it the cwnd limit
	if (maxcwnd_ && (int(cwnd_) > maxcwnd_))
		cwnd_ = maxcwnd_;

	return;
}

/*
 * close down the congestion window
 */
void BfaRenoTcpAgent::closecwnd(int how)
{   
	// AMR: Whenever cwnd is closed we reset the bfa_flag_
	bfa_flag_ = 0;
	RenoTcpAgent::closecwnd( how );
}

void BfaRenoTcpAgent::plot()
{
#ifdef notyet
	double t = Scheduler::instance().clock();
	sprintf(trace_->buffer(), "t %g %d var %g\n", t, class_, bfa_signed_rttvar_ * tcp_tick_); // AMR: Added line for bfa_signed_rttvar_
	trace_->dump();
	RenoTcpAgent::plot();
#endif
}

/* Print out all the traced variables whenever any one is changed */
void
BfaRenoTcpAgent::traceAll() {
	double curtime;
	Scheduler& s = Scheduler::instance();
	char wrk[500];
	int n;

	curtime = &s ? s.clock() : 0;
	sprintf(wrk,"time: %-8.5f saddr: %-2d sport: %-2d daddr: %-2d dport: %-2d maxseq: %-4d hiack: %-4d seqno: %-4d cwnd: %-6.3f ssthresh: %-3d dupacks: %-2d rtt: %-6.3f srtt: %-6.3f rttvar: %-6.3f bkoff: %-d signed_rttvar: %-6.3f bfa_flag: %-2d", curtime, addr_/256, addr_%256, dst_/256, dst_%256, int(maxseq_), int(highest_ack_), int(t_seqno_), double(cwnd_), int(ssthresh_), int(dupacks_), int(t_rtt_)*tcp_tick_, (int(t_srtt_) >> T_SRTT_BITS)*tcp_tick_, int(t_rttvar_)*tcp_tick_/4.0, int(t_backoff_), int(bfa_signed_rttvar_)*tcp_tick_/4.0, int(bfa_flag_));
	// AMR: Added bfa_signed_rttvar_ and bfa_flag_ above
	n = strlen(wrk);
	wrk[n] = '\n';
	wrk[n+1] = 0;
	if (channel_)
		(void)Tcl_Write(channel_, wrk, n+1);
	wrk[n] = 0;
	return;
}

/* Print out just the variable that is modified */
void
BfaRenoTcpAgent::traceVar(TracedVar* v) 
{
	double curtime;
	Scheduler& s = Scheduler::instance();
	char wrk[500];
	int n;

	curtime = &s ? s.clock() : 0;
	if (!strcmp(v->name(), "cwnd_") || !strcmp(v->name(), "maxrto_"))
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %-6.3f", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), double(*((TracedDouble*) v)));
	else if (!strcmp(v->name(), "rtt_"))
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %-6.3f", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), int(*((TracedInt*) v))*tcp_tick_);
	else if (!strcmp(v->name(), "srtt_"))
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %-6.3f", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), (int(*((TracedInt*) v)) >> T_SRTT_BITS)*tcp_tick_);
	else if (!strcmp(v->name(), "rttvar_"))
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %-6.3f", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), int(*((TracedInt*) v))*tcp_tick_/4.0);
	else if (!strcmp(v->name(), "bfa_signed_rttvar_"))   // AMR: Added trace
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %-6.3f", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), int(*((TracedInt*) v))*tcp_tick_/4.0);
	else if (!strcmp(v->name(), "bfa_flag_"))   // CRAI: Added trace
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %d", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), int(*((TracedInt*) v)));
	else
		sprintf(wrk,"%-8.5f %-2d %-2d %-2d %-2d %s %d", curtime, addr_/256, addr_%256, dst_/256, dst_%256, v->name(), int(*((TracedInt*) v)));
	n = strlen(wrk);
	wrk[n] = '\n';
	wrk[n+1] = 0;
	if (channel_)
		(void)Tcl_Write(channel_, wrk, n+1);
	wrk[n] = 0;
	return;
}







