/* -*-	Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Header: /nfs/jade/vint/CVSROOT/ns-2/tcp/snoop.cc,v 1.25 2003/01/28 23:31:03 sfloyd Exp $ (UCB)";
#endif

#include "snoop.h"
#include "assert.h"

//del static Tcl & g_tcl = Tcl::instance();

int hdr_snoop::offset_;

class SnoopHeaderClass : public PacketHeaderClass {
public:
  SnoopHeaderClass() : PacketHeaderClass("PacketHeader/Snoop", sizeof(hdr_snoop))
  {
    bind_offset(&hdr_snoop::offset_);
	}
} class_snoophdr;

static class LLSnoopClass : public TclClass {
public:
	LLSnoopClass() : TclClass("LL/LLSnoop") {}
	TclObject* create(int, const char*const*)
	{
		return (new LLSnoop());
	}
} llsnoop_class;

static class SnoopClass : public TclClass {
public:
	SnoopClass() : TclClass("Snoop") {}
	TclObject* create(int, const char*const*) {
		return (new Snoop());
	}
} snoop_class;


/*****************************  class LLSnoop *****************************
*/
LLSnoop::LLSnoop() : LL(), ackControl_(0)
{
    bind("integrate_", &integrate_);
    bind("ackControl_", &ackControl_);
    //del bind("disableSnoop_", &disableSnoop);
	
	//print parameter values
	printf("--------------------- %x LLSnoop --------------------------\n", this);
	printf("integrated = %i\n", integrate_);
	printf("ack control  = %i\n", ackControl_);
	printf("------------  end!\n");
}

void LLSnoop::recv(Packet *p, Handler *h)
{
//del    printf("%s: LLSnoop::recv() called\n", name());

	Tcl &tcl = Tcl::instance();
	hdr_ip *iph = hdr_ip::access(p);

	/* get-snoop creates a snoop object if none currently exists */
	hdr_cmn *ch = HDR_CMN(p);
	if(ch->direction() == hdr_cmn::UP)
		/* get-snoop creates a snoop object if none currently exists */
		/* In ns, addresses have ports embedded in them. */
		tcl.evalf("%s get-snoop %d %d", name(), iph->daddr(),
			  iph->saddr());

	else
		tcl.evalf("%s get-snoop %d %d", name(), iph->saddr(),
			  iph->daddr());

	Snoop *snoop = (Snoop *) TclObject::lookup(tcl.result());

	snoop->recv(p, h);

	if (integrate_)
		tcl.evalf("%s integrate %d %d", name(), iph->saddr(),
			  iph->daddr());
	if (h)			/* resume higher layer (queue) */
		Scheduler::instance().schedule(h, &intr_, 0.000001);

	return;
}

/*
 * Calculate smoothed rtt estimate and linear deviation.
 */
void
LLSnoop::snoop_rtt(double sndTime)
{
//    printf("LLSnoop RTT\n");
	assert(false);  //since intergrated is always disabled for me!!
	
	double rtt = Scheduler::instance().clock() - sndTime;
	if (rtt > 0) {
		srtt_ = g_*srtt_ + (1-g_)*rtt;
		double delta = rtt - srtt_;
		if (delta < 0)
			delta = -delta;
		if (rttvar_ != 0)
			rttvar_ = g_*delta + (1-g_)*rttvar_;
		else
			rttvar_ = delta;
	}
}

/*****************************  class Snoop *****************************
*/
Snoop::Snoop() : NsObject(),
	fstate_(0), lastSeen_(-1), lastAck_(-1), 
	expNextAck_(0), expDupacks_(0), bufhead_(0), 
	toutPending_(0), buftail_(0),
	wl_state_(SNOOP_WLEMPTY), wl_lastSeen_(-1), wl_lastAck_(-1), 
	wl_bufhead_(0), wl_buftail_(0), lastRTT_(0), enableAckControl_(0)
{
	bind("snoopDisable_", &snoopDisable_);
	bind_time("srtt_", &srtt_);
	bind_time("rttvar_", &rttvar_);
	bind("maxbufs_", &maxbufs_);
	bind("snoopTick_", &snoopTick_);
	bind("g_", &g_);
	bind("tailTime_", &tailTime_);
	bind("rxmitStatus_", &rxmitStatus_);
	bind("lru_", &lru_); // lru_ is set to 0 by default in gen/ns_tcl.cc

	bind("enableAckControl_", &enableAckControl_);

	rxmitHandler_ = new SnoopRxmitHandler(this);

	int i;
	for (i = 0; i < SNOOP_MAXWIND; i++) /* data from wired->wireless */
		pkts_[i] = 0;
	for (i = 0; i < SNOOP_WLSEQS; i++) {/* data from wireless->wired */
		wlseqs_[i] = (hdr_seq *) malloc(sizeof(hdr_seq));
		wlseqs_[i]->seq = wlseqs_[i]->num = 0;
	}
	if (maxbufs_ == 0)
		maxbufs_ = SNOOP_MAXWIND;


	//print parameter values
	printf("--------------------- %x Snoop --------------------------\n", this);
	printf("snoopDisable_ = %i\n", snoopDisable_);
	printf("enableAckControl_ = %i\n", enableAckControl_);
	printf("------------  end!\n");
}

void
Snoop::reset()
{
    //wan??  Will this function really get called?! Ans: yes, it will get call when disconnection is long or packset drop is too high.
    assert(false);
  
	printf("%x resetting\n", this);
	fstate_ = 0;
	lastSeen_ = -1;
	lastAck_ = -1;
	expNextAck_ = 0;
	expDupacks_ = 0;
	bufhead_ = buftail_ = 0;
	if (toutPending_) {
		Scheduler::instance().cancel(toutPending_);
		// xxx: I think that toutPending_ doesn't need to be freed because snoop didn't allocate it (but I'm not sure).
        //wan: To answer the above comment, this is ok as a good programming practice to assignment unused pointer to null.
		toutPending_ = 0;
	};
	for (int i = 0; i < SNOOP_MAXWIND; i++) {
		if (pkts_[i]) {
			Packet::free(pkts_[i]);
			pkts_[i] = 0;
		}
	}
}

void 
Snoop::wlreset()
{
	wl_state_ = SNOOP_WLEMPTY;
	wl_bufhead_ = wl_buftail_ = 0;
	for (int i = 0; i < SNOOP_WLSEQS; i++) {
		wlseqs_[i]->seq = wlseqs_[i]->num = 0;
	}
}

int 
Snoop::command(int argc, const char*const* argv)
{
	//del Tcl& tcl = Tcl::instance();

	if (argc == 3) {
		if (strcmp(argv[1], "llsnoop") == 0) {
			parent_ = (LLSnoop *) TclObject::lookup(argv[2]);
			if (parent_)
				recvtarget_ = parent_->uptarget();
			return (TCL_OK);
		}
		
		if (strcmp(argv[1], "check-rxmit") == 0) {
			if (empty_()) {
				rxmitStatus_ = SNOOP_PROPAGATE;
				return (TCL_OK);
			}

			Packet *p = pkts_[buftail_];
			hdr_snoop *sh = hdr_snoop::access(p);

			if (sh->sndTime()!=-1 && sh->sndTime()<atoi(argv[2]) &&
			    sh->numRxmit() == 0)
				/* candidate for retransmission */
				rxmitStatus_ = snoop_rxmit(p);
			else
				rxmitStatus_ = SNOOP_PROPAGATE;
			return (TCL_OK);
		}
	}
	return NsObject::command(argc, argv);
}

/*
 * Receive a packet from higher layer or from the network.
 * Call snoop_data() if TCP packet and forward it on if it's an ack.
 */
void
Snoop::recv(Packet* p, Handler* h )
{	
	
	hdr_cmn *ch = HDR_CMN(p);	
	if(ch->direction() == hdr_cmn::UP)
	{
		handle((Event *) p);
		return;
	}

	packet_t type = hdr_cmn::access(p)->ptype();
	/* Put packet (if not ack) in cache after checking, and send it on */
	
	if (type == PT_TCP) 
		snoop_data(p);
	
	else if (type == PT_ACK)
		snoop_wired_ack(p);

	ch->direction() = hdr_cmn::DOWN;  // Ben added
	parent_->sendDown(p);	/* vector to LLSnoop's sendto() */
}

/*
 * Handle a packet received from peer across wireless link.  Check first
 * for packet errors, then call snoop_ack() or pass it up as necessary.
 */
void
Snoop::handle(Event *e)
{

	Packet *p = (Packet *) e;
	packet_t type = hdr_cmn::access(p)->ptype();
	//int seq = hdr_tcp::access(p)->seqno();
	int prop = SNOOP_PROPAGATE; // by default;  propagate ack or packet
	Scheduler& s = Scheduler::instance();

    //wan TODO: this could be the place to disable snoop.
    //
    
	//hdr_ll *llh = hdr_ll::access(p);
	if (hdr_cmn::access(p)->error()) {
		parent_->drop(p);	// drop packet if it's been corrupted
		return;
	}

	if (type == PT_ACK) 
		prop = snoop_ack(p); 

	else if (type == PT_TCP) /* XXX what about TELNET? */
		snoop_wless_data(p);

    //wan TODO: overload parent_->delay() to impliment the wired link delay!
    
	if (prop == SNOOP_PROPAGATE)
    {
//wan working
       	if (type == PT_ACK)
        {
            if (enableAckControl_ == 0) //not 0
            {
                s.schedule(recvtarget_, e, parent_->delay());
            }
            else //ack control enabled.
            {
                if (lastRTT_ < srtt_)
                {
                    //wan TODO: ack reveived too soon.  queue the ack, and to schedule deliver later.
                    double dAckDelay = srtt_ - lastRTT_;
                    printf("%s schedule ack at %f, with delay(%f)\n", name(), dAckDelay + parent_->delay(), dAckDelay);
        		    s.schedule(recvtarget_, e, dAckDelay + parent_->delay());
                }
                else
                {
                    printf("%s schedule ack at %f\n", name(), parent_->delay());
        	    	s.schedule(recvtarget_, e, parent_->delay());
                }
            }
        }
        else
        {
    		s.schedule(recvtarget_, e, parent_->delay());
        }
    }
    else {			// suppress ack
		/*		printf("---- %f suppressing ack %d\n", s.clock(), seq);*/
		Packet::free(p);
	}
}

/*
 * Data packet processing.  p is guaranteed to be of type PT_TCP when 
 * this function is called.
 */
void
Snoop::snoop_data(Packet *p)
{
	Scheduler &s = Scheduler::instance();
	int seq = hdr_tcp::access(p)->seqno();
	int resetPending = 0;  //wan??  what is this?
	
	//printf("%s [%f] snoop_data: seq(%d) ack(%d)\n", name(), s.clock(), seq, hdr_tcp::access(p)->ackno());
	if (fstate_ & SNOOP_ALIVE && seq == 0)
		reset();
    
	fstate_ |= SNOOP_ALIVE;
	if ((fstate_ & SNOOP_FULL) && !lru_)
    {
		printf("snoop full, fwding ... \n t %d h %d", buftail_, bufhead_);
		if (seq > lastSeen_)
			lastSeen_ = seq;
            
		return;
	}
    
	/* 
	 * Only if the ifq is NOT full do we insert, since otherwise we want
	 * congestion control to kick in.
	 */
	if (parent_->ifq()->length() < (parent_->ifq()->limit() - 1))
    {
        //wan: this packet will not be droped in parent_'s ifq.
        resetPending = snoop_insert(p);
        //del printf("queue tail = %i\n", buftail_);
    }
	if (toutPending_ && resetPending == SNOOP_TAIL) {
		s.cancel(toutPending_);
		// xxx: I think that toutPending_ doesn't need to be freed because snoop didn't allocate it (but I'm not sure).
        //wan: To answer the above comment, this is ok as a good programming practice to assignment unused pointer to null.
		toutPending_ = 0;
	}
    //wan: so there's only one packet timeed for retransmission at one time.
	if (!toutPending_ && !empty_()) {
		toutPending_ = (Event *) (pkts_[buftail_]);
		s.schedule(rxmitHandler_, toutPending_, timeout());
	}
    
	return;
}

/* 
 * snoop_insert() does all the hard work for snoop_data(). It traverses the 
 * snoop cache and looks for the right place to insert this packet (or
 * determines if its already been cached). It then decides whether
 * this is a packet in the normal increasing sequence, whether it
 * is a sender-rexmitted-but-lost-due-to-congestion (or network 
 * out-of-order) packet, or if it is a sender-rexmitted packet that
 * was buffered by us before.
 */
int
Snoop::snoop_insert(Packet *p)
{
	int i, seq = hdr_tcp::access(p)->seqno(), retval=0;

	if (seq <= lastAck_) 
		return retval;
	
	if (fstate_ & SNOOP_FULL) {
		/* free tail and go on */
        //wan??  why not to drop this new packet instead?
		printf("snoop full, making room\n");
		Packet::free(pkts_[buftail_]);
		pkts_[buftail_] = 0;
		buftail_ = next(buftail_);
		fstate_ |= ~SNOOP_FULL;
	}

	if (seq > lastSeen_ || pkts_[buftail_] == 0) { // in-seq or empty cache
		i = bufhead_;
		bufhead_ = next(bufhead_);
	} else if (seq < hdr_snoop::access(pkts_[buftail_])->seqno()) { //to insert before the buftail_.
		buftail_ = prev(buftail_);
		i = buftail_;
	} else { //to insert in the middle.
		for (i = buftail_; i != bufhead_; i = next(i)) {
			hdr_snoop *sh = hdr_snoop::access(pkts_[i]);
			if (sh->seqno() == seq) {  // cached before

				sh->numRxmit() = 0;
				sh->senderRxmit() = 1; //must be a sender retr
                
                //wan??  so, the sndTime is set to be the arrive time at the basestation from sender.
				sh->sndTime() = Scheduler::instance().clock();
				return SNOOP_TAIL;
			} else if (sh->seqno() > seq) { 

				//not cached before, should insert in the middle
				// find the position it should be: prev(i)
 
				Packet *temp = pkts_[prev(buftail_)];
				for (int j = buftail_; j != i; j = next(j)) 
					pkts_[prev(j)] = pkts_[j];
                    
				i = prev(i);
				pkts_[i] = temp;   // seems not necessary. Ben comments
				buftail_ = prev(buftail_);
				break;
			}
		}

		// This should not happen, since seq must be > lastSeen, which is 
		// handled before in the first if.   Ben comments
		if (i == bufhead_)
			bufhead_ = next(bufhead_);
	}
	
	// save in the buffer
	savepkt_(p, seq, i);
	
	if (bufhead_ == buftail_)
		fstate_ |= SNOOP_FULL;
	/* 
	 * If we have one of the following packets:
	 * 1. a network-out-of-order packet, or
	 * 2. a fast rxmit packet, or 3. a sender retransmission 
	 * AND it hasn't already been buffered, 
	 * then seq will be < lastSeen_. 
	 * We mark this packet as having been due to a sender rexmit 
	 * and use this information in snoop_ack(). We let the dupacks
	 * for this packet go through according to expDupacks_.
	 */
	if (seq < lastSeen_) { /* not in-order -- XXX should it be <= ? */
		if (buftail_ == i) {
			hdr_snoop *sh = hdr_snoop::access(pkts_[i]);
			sh->senderRxmit() = 1;
			sh->numRxmit() = 0;
		}
		expNextAck_ = buftail_;
		retval = SNOOP_TAIL;
	} else
		lastSeen_ = seq;

	//print curr queue
	/*Scheduler& s = Scheduler::instance();
	printf("%s [%f] currq(%i):", name(), s.clock(), (bufhead_ - buftail_ + SNOOP_MAXWIND) % SNOOP_MAXWIND);
	for (i = buftail_; i != bufhead_; i = next(i)) {
		hdr_snoop *sh = hdr_snoop::access(pkts_[i]);
		printf(" %i", sh->seqno());
	}
	printf("\n");
    */
	
	return retval;
}

/**
 * Inserts a *copy* of the new packet.
 * param[in] p: the packet to insert.
 * param[in] seq: the sequence number in the packet p.
 * param[in] i: the position where the pakcet will be inserted.
 */
void
Snoop::savepkt_(Packet *p, int seq, int i)
{
	pkts_[i] = p->copy();
	Packet *pkt = pkts_[i];
	hdr_snoop *sh = hdr_snoop::access(pkt);
	sh->seqno() = seq;
	sh->numRxmit() = 0;
	sh->senderRxmit() = 0;
	sh->sndTime() = Scheduler::instance().clock();
}

/*
 * Ack processing in snoop protocol.  We know for sure that this is an ack.
 * Return SNOOP_SUPPRESS if ack is to be suppressed and SNOOP_PROPAGATE o.w.
 */
int
Snoop::snoop_ack(Packet *p)
{
	Packet *pkt;
	Scheduler &s = Scheduler::instance();

    //wan: since this is a one-direction TCP only in ns-2, even this is a ack, ack no. is still stored in seqno field.
	int ack = hdr_tcp::access(p)->seqno();
	//printf("%s [%f] ack: seq(%d) ack(%d) flags(%i)\n", name(), s.clock(), hdr_tcp::access(p)->seqno(), hdr_tcp::access(p)->ackno(), hdr_tcp::access(p)->flags());	

	/*
	 * There are 3 cases:
	 * 1. lastAck_ > ack.  In this case what has happened is
	 *    that the acks have come out of order, so we don't
	 *    do any local processing but forward it on.
	 * 2. lastAck_ == ack.  This is a duplicate ack. If we have
	 *    the packet we resend it, and drop the dupack.
	 *    Otherwise we never got it from the fixed host, so we
	 *    need to let the dupack get through.
	 *    Set expDupacks_ to number of packets already sent
	 *    This is the number of dup acks to ignore.
	 * 3. lastAck_ < ack.  Set lastAck_ = ack, and update
	 *    the head of the buffer queue. Also clean up ack'd packets.
	 */
	if (fstate_ & SNOOP_CLOSED || lastAck_ > ack) 
		return SNOOP_PROPAGATE;	// send ack onward

	if (lastAck_ == ack) {	
		/* A duplicate ack; pure window updates don't occur in ns. */

		pkt = pkts_[buftail_];
		
		if (pkt == 0) //pkts queue is empty. 
			return SNOOP_PROPAGATE;
		
		hdr_snoop *sh = hdr_snoop::access(pkt);

        //wan:  again, this seqno() returns ack no. since this is a ack packet.
		if (pkt == 0 || sh->seqno() > ack + 1)
			/* don't have packet, letting thru' */
            return SNOOP_PROPAGATE;

		/* 
		 * We have the packet: one of 3 possibilities:
		 * 1. We are not expecting any dupacks (expDupacks_ == 0)
		 * 2. We are expecting dupacks (expDupacks_ > 0)
		 * 3. We are in an inconsistent state (expDupacks_ == -1)
		 */         			
		if (expDupacks_ == 0) {	// not expecting it
#define RTX_THRESH 1

            //wan???
			static int thresh = 0;
			if (thresh++ < RTX_THRESH) 
				/* no action if under RTX_THRESH */
				return SNOOP_PROPAGATE;
			
			thresh = 0;
			
			// if the packet is a sender retransmission, pass on
			if (sh->senderRxmit()) 
				return SNOOP_PROPAGATE;
			
			/*
			 * Otherwise, not triggered by sender.  If this is
			 * the first dupack recd., we must determine how many
			 * dupacks will arrive that must be ignored, and also
			 * rexmit the desired packet.  Note that expDupacks_
			 * will be -1 if we miscount for some reason.
			 */
			expDupacks_ = bufhead_ - expNextAck_;
			if (expDupacks_ < 0)
				expDupacks_ += SNOOP_MAXWIND;
			expDupacks_ -= RTX_THRESH + 1;
			expNextAck_ = next(buftail_);

			if (sh->numRxmit() == 0) 
				return snoop_rxmit(pkt);
		} else if (expDupacks_ > 0) {
			expDupacks_--;
			return SNOOP_SUPPRESS;
		} else if (expDupacks_ == -1) {
			if (sh->numRxmit() < 2) {
				return snoop_rxmit(pkt);
			}
		} else		// let sender deal with it
			return SNOOP_PROPAGATE;
            
	} else {		// a new ack

		fstate_ &= ~SNOOP_NOACK; // have seen at least 1 new ack

		/* free buffers */
		double sndTime = snoop_cleanbufs_(ack);

        //update srtt		
		if (sndTime != -1)
			snoop_rtt(sndTime);

		expDupacks_ = 0;
		expNextAck_ = buftail_;
		lastAck_ = ack;
	}
	return SNOOP_PROPAGATE;
}

/* 
 * Handle data packets that arrive from a wireless link, and we're not
 * the end recipient.  See if there are any holes in the transmission, and
 * if there are, mark them as candidates for wireless loss.  Then, when
 * (dup)acks troop back for this loss, set the ELN bit in their header, to
 * help the sender (or a snoop agent downstream) retransmit.
 */
void
Snoop::snoop_wless_data(Packet *p)
{
	hdr_tcp *th = hdr_tcp::access(p);
	int i, seq = th->seqno();

	if (wl_state_ & SNOOP_WLALIVE && seq == 0)
		wlreset();
	wl_state_ |= SNOOP_WLALIVE;

	if (wl_state_ & SNOOP_WLEMPTY && seq >= wl_lastAck_) {
		wlseqs_[wl_bufhead_]->seq = seq;
		wlseqs_[wl_bufhead_]->num = 1;
		wl_buftail_ = wl_bufhead_;
		wl_bufhead_ = wl_next(wl_bufhead_);
		wl_lastSeen_ = seq;
		wl_state_ &= ~SNOOP_WLEMPTY;
		return;
	}
	/* WL data list definitely not empty at this point. */
	if (seq >= wl_lastSeen_) {
		wl_lastSeen_ = seq;
		i = wl_prev(wl_bufhead_);
		if (wlseqs_[i]->seq + wlseqs_[i]->num == seq) {
			wlseqs_[i]->num++;
			return;
		}
		i = wl_bufhead_;
		wl_bufhead_ = wl_next(wl_bufhead_);
	} else if (seq == wlseqs_[i = wl_buftail_]->seq - 1) {
	} else
		return;

	wlseqs_[i]->seq = seq;
	wlseqs_[i]->num++;

	/* Ignore network out-of-ordering and retransmissions for now */
	return;
}

/*
 * Ack from wired side (for sender on "other" side of wireless link.
 */
void 
Snoop::snoop_wired_ack(Packet *p)
{
	hdr_tcp *th = hdr_tcp::access(p);
	int ack = th->seqno();
	int i;
	
	if (ack == wl_lastAck_ && snoop_wlessloss(ack)) {
		hdr_flags::access(p)->eln_ = 1;
	} else if (ack > wl_lastAck_) {
		/* update info about unack'd data */
		for (i = wl_buftail_; i != wl_bufhead_; i = wl_next(i)) {
			hdr_seq *t = wlseqs_[i];
			if (t->seq + t->num - 1 <= ack) {
				t->seq = t->num = 0;
			} else if (ack < t->seq) {
				break;
			} else if (ack < t->seq + t->num - 1) {
				/* ack for part of a block */
				t->num -= ack - t->seq +1;
				t->seq = ack + 1;
				break;
			}
		}
		wl_buftail_ = i;
		if (wl_buftail_ == wl_bufhead_)
			wl_state_ |= SNOOP_WLEMPTY;
		wl_lastAck_ = ack;
		/* Even a new ack could cause an ELN to be set. */
		if (wl_bufhead_ != wl_buftail_ && snoop_wlessloss(ack))
			hdr_flags::access(p)->eln_ = 1;
	}
}

/* 
 * Return 1 if we think this packet loss was not congestion-related, and 
 * 0 otherwise.  This function simply implements the lookup into the table
 * that maintains this info; most of the hard work is done in 
 * snoop_wless_data() and snoop_wired_ack().
 */
int
Snoop::snoop_wlessloss(int ack)
{
	if ((wl_bufhead_ == wl_buftail_) || wlseqs_[wl_buftail_]->seq > ack+1)
		return 1;
	return 0;
}

/*
 * clean snoop cache of packets that have been acked.
 */
double
Snoop::snoop_cleanbufs_(int ack)
{
	Scheduler &s = Scheduler::instance();
	double sndTime = -1;

	if (toutPending_) {
		s.cancel(toutPending_);
		// xxx: I think that toutPending_ doesn't need to be freed because snoop didn't allocate it (but I'm not sure).
        //wan: To answer the above comment, this is ok as a good programming practice to assignment unused pointer to null.
		toutPending_ = 0;
	};

	if (empty_())
		return sndTime;

	int i = buftail_;
	do {
		hdr_snoop *sh = hdr_snoop::access(pkts_[i]);
		int seq = hdr_tcp::access(pkts_[i])->seqno();

		if (seq <= ack) //wan??? this should be "<", since ack is next expected seqno? unless this is not true in ns!
        {
			sndTime = sh->sndTime();
			Packet::free(pkts_[i]);
			pkts_[i] = 0;
			fstate_ &= ~SNOOP_FULL;	/*del XXX redundant? */
		}
        else if (seq > ack)
			break;
            
		i = next(i);
        
	} while (i != bufhead_);

	if ((i != buftail_) || (bufhead_ != buftail_)) {
		fstate_ &= ~SNOOP_FULL;
		buftail_ = i;
	}
	if (!empty_()) {
		toutPending_ = (Event *) (pkts_[buftail_]);
		s.schedule(rxmitHandler_, toutPending_, timeout());
		hdr_snoop *sh = hdr_snoop::access(pkts_[buftail_]);
		tailTime_ = sh->sndTime();
	}

	return sndTime;
}

/* 
 * Calculate smoothed rtt estimate and linear deviation.
 */
void
Snoop::snoop_rtt(double sndTime)
{
//	double rtt = Scheduler::instance().clock() - sndTime;

    lastRTT_ = Scheduler::instance().clock() - sndTime;

	if (parent_->integrate()) {
		parent_->snoop_rtt(sndTime);
		return;
	}

//    printf("Snoop RTT\n");
	if (lastRTT_ > 0) {
		srtt_ = g_*srtt_ + (1-g_)*lastRTT_;
		double delta = lastRTT_ - srtt_;
		if (delta < 0)
			delta = -delta;
		if (rttvar_ != 0)
			rttvar_ = g_*delta + (1-g_)*rttvar_;
		else 
			rttvar_ = delta;
	}
}

/*
 * Returns 1 if recent queue length is <= half the maximum and 0 otherwise.
 */
int 
Snoop::snoop_qlong()
{
	/* For now only instantaneous lengths */
	//	if (parent_->ifq()->length() <= 3*parent_->ifq()->limit()/4)
	
	return 1;
		//	return 0;
}

/*
 * Ideally, would like to schedule snoop retransmissions at higher priority.
 */
int
Snoop::snoop_rxmit(Packet *pkt)
{
	Scheduler& s = Scheduler::instance();
	if (pkt != 0) {
		hdr_snoop *sh = hdr_snoop::access(pkt);
		if (sh->numRxmit() < SNOOP_MAX_RXMIT && snoop_qlong()) {
			/*			&& sh->seqno() == lastAck_+1)  */
			
#if 0
			printf("%f Rxmitting packet %d\n", s.clock(), 
			       hdr_tcp::access(pkt)->seqno());
#endif
			
			// need to specify direction, in this case, down
			hdr_cmn *ch = HDR_CMN(pkt);       
			ch->direction() = hdr_cmn::DOWN;  // Ben added

			sh->sndTime() = s.clock();
			sh->numRxmit() = sh->numRxmit() + 1;
			Packet *p = pkt->copy();
			parent_->sendDown(p);
		} else 
			return SNOOP_PROPAGATE;
	}
	/* Reset timeout for later time. */
	if (toutPending_) {
		s.cancel(toutPending_);
		// xxx: I think that toutPending_ doesn't need to be freed because snoop didn't allocate it (but I'm not sure).
	};
	toutPending_ = (Event *)pkt;
	s.schedule(rxmitHandler_, toutPending_, timeout());
	return SNOOP_SUPPRESS;
}

void 
Snoop::snoop_cleanup()
{
    //wan
    assert(false);
}

/*****************************  class SnoopRxmitHandler *****************************
*/
void
SnoopRxmitHandler::handle(Event *)
{
	Packet *p = snoop_->pkts_[snoop_->buftail_];
	snoop_->toutPending_ = 0;
	if (p == 0)
		return;
	hdr_snoop *sh = hdr_snoop::access(p);
	if (sh->seqno() != snoop_->lastAck_ + 1)
		return;
	if ((snoop_->bufhead_ != snoop_->buftail_) || 
	    (snoop_->fstate_ & SNOOP_FULL))
     {
		//		printf("%f Snoop timeout\n", Scheduler::instance().clock());
		if (snoop_->snoop_rxmit(p) == SNOOP_SUPPRESS)
			snoop_->expNextAck_ = snoop_->next(snoop_->buftail_);
	}
}
