// $Id: SteppingStone.cc,v 1.13 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1999, 2000, 2002
//      The 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: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// 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.

#include "config.h"

#include <stdlib.h>

#include "Event.h"
#include "Net.h"
#include "NetVar.h"
#include "TCP.h"
#include "SteppingStone.h"
#include "util.h"

SteppingStoneEndpoint::SteppingStoneEndpoint(TCP_Endpoint* e, SteppingStoneManager* m)
: TCP_EndpointAnalyzer(e)
	{
	stp_max_top_seq = 0;
	stp_last_time = stp_resume_time = 0.0;
	stp_manager = m;
	stp_id = stp_manager->NextID();
	stp_key = new HashKey(stp_id);

	CreateEndpEvent(e->IsOrig());
	}

SteppingStoneEndpoint::~SteppingStoneEndpoint()
	{
	delete stp_key;
	}

void SteppingStoneEndpoint::Done()
	{
	if ( RefCnt() > 1 )
		return;

	SteppingStoneEndpoint* ep;
	IterCookie* cookie;

	cookie = stp_inbound_endps.InitForIteration();
	while ( (ep = stp_inbound_endps.NextEntry(cookie)) )
		{
		ep->stp_outbound_endps.Remove(stp_key);
		Event(stp_remove_pair, ep->stp_id, stp_id);
		Unref(ep);
		}

	cookie = stp_outbound_endps.InitForIteration();
	while ( (ep = stp_outbound_endps.NextEntry(cookie)) )
		{
		ep->stp_inbound_endps.Remove(stp_key);
		Event(stp_remove_pair, stp_id, ep->stp_id);
		Unref(ep);
		}

	Event(stp_remove_endp, stp_id);
	}

int SteppingStoneEndpoint::DataSent(double t, int seq, int len,
		const u_char* data, const IP_Hdr* /* ip */,
		const struct tcphdr* tp)
	{
	double tmin = t - stp_delta;

	while ( stp_manager->OrderedEndpoints().length() > 0 )
		{
		int f = stp_manager->OrderedEndpoints().front();

		if ( stp_manager->OrderedEndpoints()[f]->stp_resume_time < tmin )
			{
			SteppingStoneEndpoint* endp =
				stp_manager->OrderedEndpoints().pop_front();
			endp->Done();
			Unref(endp);
			}
		else
			break;
		}

	int ack = endp->AckSeq() - endp->StartSeq();
	int top_seq = seq + len;

	if ( top_seq <= ack || top_seq <= stp_max_top_seq )
		// There is no new data in this packet
		return 0;

	stp_max_top_seq = top_seq;

	if ( stp_last_time && t <= stp_last_time + stp_idle_min )
		{
		stp_last_time = t;
		return 1;
		}

	// Either just starts, or resumes from an idle period.
	stp_last_time = stp_resume_time = t;

	Event(stp_resume_endp, stp_id);
	loop_over_queue(stp_manager->OrderedEndpoints(), i)
		{
		SteppingStoneEndpoint* ep = stp_manager->OrderedEndpoints()[i];
		if ( ep->Conn() != Conn() )
			{
			Ref(ep);
			Ref(this);

			stp_inbound_endps.Insert(ep->stp_key, ep);
			ep->stp_outbound_endps.Insert(stp_key, this);

			Event(stp_correlate_pair, ep->stp_id, stp_id);
			}

		else
			{ // ep and this belong to same connection
			}
		}

	stp_manager->OrderedEndpoints().push_back(this);
	Ref(this);

	return 1;
	}

void SteppingStoneEndpoint::Event(EventHandlerPtr f, int id1, int id2)
	{
	if ( ! f )
		return;

	val_list* vl = new val_list;

	vl->append(new Val(id1, TYPE_INT));

	if ( id2 >= 0 )
		vl->append(new Val(id2, TYPE_INT));

	Conn()->ConnectionEvent(f, vl);
	}

void SteppingStoneEndpoint::CreateEndpEvent(int is_orig)
	{
	val_list* vl = new val_list;

	vl->append(Conn()->BuildConnVal());
	vl->append(new Val(stp_id, TYPE_INT));
	vl->append(new Val(is_orig, TYPE_BOOL));

	Conn()->ConnectionEvent(stp_create_endp, vl);
	}


SteppingStoneAnalyzer::SteppingStoneAnalyzer(TCP_Connection* c,
						SteppingStoneManager* m)
: TCP_Analyzer(c)
	{
	stp_manager = m;

	orig_endp = new SteppingStoneEndpoint(c->Orig(), stp_manager);
	resp_endp = new SteppingStoneEndpoint(c->Resp(), stp_manager);
	}

void SteppingStoneAnalyzer::Done()
	{
	orig_endp->Done();
	resp_endp->Done();

	Unref(orig_endp);
	Unref(resp_endp);
	}
