// $Id: InterConn.cc,v 1.11 2002/10/26 04:15:44 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 "InterConn.h"
#include "Event.h"
#include "Net.h"
#include "TCP.h"

InterConnEndpoint::InterConnEndpoint(TCP_Endpoint* e)
: TCP_EndpointAnalyzer(e)
	{
	max_top_seq = 0;
	num_pkts = num_keystrokes_two_in_a_row = num_normal_interarrivals =
		num_8k0_pkts = num_8k4_pkts = num_bytes = num_7bit_ascii =
			num_lines = num_normal_lines = 0;
	is_partial = keystroke_just_seen = 0;
	last_keystroke_time = 0.0;
	}

#define NORMAL_LINE_LENGTH 80

int InterConnEndpoint::DataSent(double t, int seq, int len,
		const u_char* data, const IP_Hdr* /* ip */,
		const struct tcphdr* /* tp */)
	{
	if ( endp->state == TCP_PARTIAL )
		is_partial = 1;

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

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

	if ( seq < max_top_seq )
		{ // Only consider new data
		int amount_seen = max_top_seq - seq;
		seq += amount_seen;
		data += amount_seen;
		len -= amount_seen;
		}

	if ( max_top_seq && seq > max_top_seq )
		// We've got a pkt above a hole
		num_pkts += EstimateGapPacketNum(seq - max_top_seq);

	++num_pkts;
	max_top_seq = top_seq;

	// Count the bytes.
	num_bytes += len;

	int last_char = 0;
	int offset = 0;	// where we consider the latest line to have begun

	for ( int i = 0; i < len; ++i )
		{
		unsigned int c = data[i];

		if ( c == '\n' && last_char == '\r' )
			{
			// Compress CRLF to just one line termination.
			last_char = c;
			continue;
			}

		if ( c == '\n' || c == '\r' )
			{
			++num_lines;
			if ( i - offset <= NORMAL_LINE_LENGTH )
				++num_normal_lines;
			offset = i;
			}

		else if ( c != 0 && c < 128 )
			++num_7bit_ascii;

		last_char = c;
		}

	if ( IsPotentialKeystrokePacket(len) )
		{
		if ( keystroke_just_seen )
			{
			++num_keystrokes_two_in_a_row;

			if ( IsNormalKeystrokeInterarrival(t - last_keystroke_time) )
				++num_normal_interarrivals;
			}
		else
			keystroke_just_seen = 1;

		// Look for packets matching the SSH signature of
		// being either 0 or 4 modulo 8.
		switch ( len & 7 ) {
		case 0:
			if ( len >= 16 )
				++num_8k0_pkts;
			break;

		case 4:
			++num_8k4_pkts;
			break;
		}

		last_keystroke_time = t;
		}
	else
		keystroke_just_seen = 0;

	return 1;
	}

RecordVal* InterConnEndpoint::BuildStats()
	{
	RecordVal* stats = new RecordVal(interconn_endp_stats);

	stats->Assign(0, new Val(num_pkts, TYPE_COUNT));
	stats->Assign(1, new Val(num_keystrokes_two_in_a_row, TYPE_COUNT));
	stats->Assign(2, new Val(num_normal_interarrivals, TYPE_COUNT));
	stats->Assign(3, new Val(num_8k0_pkts, TYPE_COUNT));
	stats->Assign(4, new Val(num_8k4_pkts, TYPE_COUNT));
	stats->Assign(5, new Val(is_partial, TYPE_BOOL));
	stats->Assign(6, new Val(num_bytes, TYPE_COUNT));
	stats->Assign(7, new Val(num_7bit_ascii, TYPE_COUNT));
	stats->Assign(8, new Val(num_lines, TYPE_COUNT));
	stats->Assign(9, new Val(num_normal_lines, TYPE_COUNT));

	return stats;
	}

int InterConnEndpoint::EstimateGapPacketNum(int gap) const
	{
	return (gap + interconn_default_pkt_size - 1) / interconn_default_pkt_size;
	}

int InterConnEndpoint::IsPotentialKeystrokePacket(int len) const
	{
	return len <= interconn_max_keystroke_pkt_size;
	}

int InterConnEndpoint::IsNormalKeystrokeInterarrival(double t) const
	{
	return interconn_min_interarrival <= t && t <= interconn_max_interarrival;
	}


InterConnAnalyzer::InterConnAnalyzer(TCP_Connection* c)
: TCP_Analyzer(c)
	{
	orig_endp = new InterConnEndpoint(c->Orig());
	resp_endp = new InterConnEndpoint(c->Resp());

	timeout = interconn_stat_period;
	backoff = interconn_stat_backoff;
	timer_mgr->Add(new InterConnTimer(network_time + timeout, this));
	}

InterConnAnalyzer::~InterConnAnalyzer()
	{
	Done();
	}

void InterConnAnalyzer::StatTimer(double t, int is_expire)
	{
	if ( done || Conn()->Skipping() )
		return;

	StatEvent();

	if ( ! is_expire )
		{
		timeout *= backoff;
		timer_mgr->Add(new InterConnTimer(t + timeout, this));
		}
	}

void InterConnAnalyzer::StatEvent()
	{
	val_list* vl = new val_list;
	vl->append(Conn()->BuildConnVal());
	vl->append(orig_endp->BuildStats());
	vl->append(resp_endp->BuildStats());

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

void InterConnAnalyzer::RemoveEvent()
	{
	val_list* vl = new val_list;
	vl->append(Conn()->BuildConnVal());

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

void InterConnAnalyzer::Done()
	{
	if ( ! done )
		{
		if ( ! Conn()->Skipping() )
			StatEvent();
		RemoveEvent();
		done = 1;
		}
	}

InterConnTimer::InterConnTimer(double t, InterConnAnalyzer* a)
: Timer(t)
	{
	analyzer = a;
	Ref(a);
	}

InterConnTimer::~InterConnTimer()
	{
	Unref(analyzer);
	}

void InterConnTimer::Dispatch(double t, int is_expire)
	{
	analyzer->StatTimer(t, is_expire);
	}
