/* -*-	Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */
/*
 * Copyright (c) 1997 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Daedalus Research
 *	Group at the University of California Berkeley.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Contributed by the Daedalus Research Group, U.C.Berkeley
 * http://daedalus.cs.berkeley.edu
 *
 * @(#) $Header: 
 */

/*
 * tcp-asym includes modifications to several flavors of TCP to enhance
 * performance over asymmetric networks, where the ack channel is
 * constrained.  Types of asymmetry we have studied and used these mods
 * include bandwidth asymmetry and latency asymmetry (where  variable 
 * latencies cause problems to TCP, e.g., in packet radio networks.
 * The receiver-side code in this file is derived from the regular
 * TCP sink code. The main additional functionality is that the sink responds
 * to ECN by performing ack congestion control, i.e. it multiplicatively backs
 * off the frequency with which it sends acks (up to a limit). For each 
 * subsequent round-trip period during which it does not receive an ECN, 
 * it gradually increases the frequency of acks (up to a maximum of 1 
 * per data packet).
 *
 * For questions/comments, please contact:
 *   Venkata N. Padmanabhan (padmanab@cs.berkeley.edu)
 *   http://www.cs.berkeley.edu/~padmanab
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Header: /usr/src/mash/repository/vint/ns-2/tcp-asym-sink.cc,v 1.15 1998/08/27 16:38:58 tomh Exp $ (UCB)";
#endif

#include "template.h"
#include "flags.h"
#include "tcp-sink.h"
#include "tcp-asym.h"


class TcpAsymSink : public DelAckSink {
public:
	TcpAsymSink(Acker*);
	virtual void recv(Packet* pkt, Handler* h);
	virtual void timeout(int tno);
protected:
	virtual void add_to_ack(Packet* pkt);
	int delackcount_;	/* the number of consecutive packets that have
				   not been acked yet */
	int maxdelack_;		/* the maximum extent to which acks can be
				   delayed */
	int delackfactor_;	/* the dynamically varying limit on the extent
				   to which acks can be delayed */
	int delacklim_;		/* limit on the extent of del ack based on the
				   sender's window */
	double ts_ecn_;		/* the time when an ECN was received last */
	double ts_decrease_;    /* the time when delackfactor_ was decreased last */
	double highest_ts_echo_;/* the highest timestamp echoed by the peer */
};	

static class TcpAsymSinkClass : public TclClass {
public:
	TcpAsymSinkClass() : TclClass("Agent/TCPSink/Asym") {}
	TclObject* create(int, const char*const*) {
		return (new TcpAsymSink(new Acker));
	}
} class_tcpasymsink;

TcpAsymSink::TcpAsymSink(Acker* acker) : DelAckSink(acker), delackcount_(0), 
    delackfactor_(1), delacklim_(0), ts_ecn_(0), ts_decrease_(0)
{
	bind("maxdelack_", &maxdelack_);
}

/* Add fields to the ack. Not needed? */
void TcpAsymSink::add_to_ack(Packet* pkt) 
{
	hdr_tcpasym *tha = hdr_tcpasym::access(pkt);
	tha->ackcount() = delackcount_;
}

void TcpAsymSink::recv(Packet* pkt, Handler*) 
{
	int olddelackfactor = delackfactor_;
	int olddelacklim = delacklim_; 
	int max_sender_can_send = 0;
	hdr_flags *fh = hdr_flags::access(pkt);
	hdr_tcp *th = hdr_tcp::access(pkt);
	hdr_tcpasym *tha = hdr_tcpasym::access(pkt);
	double now = Scheduler::instance().clock();
	int numBytes = hdr_cmn::access(pkt)->size();


	acker_->update_ts(th->seqno(),th->ts());
	acker_->update(th->seqno(), numBytes);

#if 0  // johnh
	int numToDeliver;
	/* XXX if the #if 0 is removed, delete the call to acker_->update() above */
	numToDeliver = acker_->update(th->seqno(), numBytes);
	if (numToDeliver)
		recvBytes(numToDeliver);
#endif /* 0 */

	/* determine the highest timestamp the sender has echoed */
	highest_ts_echo_ = max(highest_ts_echo_, th->ts_echo());
	/* 
	 * if we receive an ECN and haven't received one in the past
	 * round-trip, double delackfactor_ (and consequently halve
	 * the frequency of acks) subject to a maximum
	 */
	if (fh->ecnecho() && highest_ts_echo_ >= ts_ecn_) {
		delackfactor_ = min(2*delackfactor_, maxdelack_);
		ts_ecn_ = now;
	}
	/*
	 * else if we haven't received an ECN in the past round trip and
	 * haven't (linearly) decreased delackfactor_ in the past round
	 * trip, we decrease delackfactor_ by 1 (and consequently increase
	 * the frequency of acks) subject to a minimum
	 */
	else if (highest_ts_echo_ >= ts_ecn_ && highest_ts_echo_ >= ts_decrease_) {
		delackfactor_ = max(delackfactor_ - 1, 1);
		ts_decrease_ = now;
	}

	/*
         * if this is the next packet in sequence, we can consider delaying the ack. 
	 * Set delacklim_ based on how much data the sender can send if we don't
	 * send back any more acks. The idea is to avoid stalling the sender because
	 * of a lack of acks.
         */
        if (th->seqno() == acker_->Seqno()) {
		max_sender_can_send = (int) min(tha->win()+acker_->Seqno()-tha->highest_ack(), tha->max_left_to_send());
		/* XXXX we use a safety factor 2 */
		delacklim_ = min(maxdelack_, max_sender_can_send/2); 
	}
	else
		delacklim_ = 0;

	if (delackfactor_ < delacklim_) 
		delacklim_ = delackfactor_;

	/* 
	 * Log values of variables of interest. Since this is the only place
	 * where this is done, we decided against using a more general method
	 * as used for logging TCP sender state variables.
	 */
	if (channel_ && (olddelackfactor != delackfactor_ || olddelacklim != delacklim_)) {
		char wrk[500];
		int n;

		/* we print src and dst in reverse order to conform to sender side */
		sprintf(wrk, "time: %-6.3f saddr: %-2d sport: %-2d daddr: %-2d dport: %-2d dafactor: %2d dalim: %2d max_scs: %4d win: %4d\n", now, dst_/256, dst_%256, addr_/256, addr_%256, delackfactor_, delacklim_,max_sender_can_send, tha->win());
		n = strlen(wrk);
		wrk[n] = '\n';
		wrk[n+1] = 0;
		(void)Tcl_Write(channel_, wrk, n+1);
		wrk[n] = 0;
	}
		
	delackcount_++;
	/* check if we have waited long enough that we should send an ack */
	if (delackcount_ < delacklim_) { /* it is not yet time to send an ack */
		/* if the delayed ack timer is not set, set it now */
		if (!(delay_timer_.status() == TIMER_PENDING)) {
			save_ = pkt;
			delay_timer_.resched(interval_);
		}
		else {
			hdr_tcp *sth = hdr_tcp::access(save_);
			/* save the pkt with the more recent timestamp */
			if (th->ts() > sth->ts()) {
				Packet::free(save_);
				save_ = pkt;
			}
		}
		return;
	}
	else { /* send back an ack now */
		if (delay_timer_.status() == TIMER_PENDING) {
			delay_timer_.cancel();
			Packet::free(save_);
			save_ = 0;
		}
		hdr_flags* hf = hdr_flags::access(pkt);
		hf->ect() = 1;
		ack(pkt);
		delackcount_ = 0;
		Packet::free(pkt);
	}
}


void TcpAsymSink::timeout(int /*tno*/)
{
	/*
	 * The timer expired so we ACK the last packet seen.
	 */
	Packet* pkt = save_;
	delackcount_ = 0;
	ack(pkt);
	save_ = 0;
	Packet::free(pkt);
}

