// $Id: Reassem.cc,v 1.16 2002/10/06 04:21:49 leres Exp $
//
// Copyright (c) 1998, 1999, 2001, 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 "Reassem.h"

DataBlock::DataBlock(const u_char* data, int size, int arg_seq,
			DataBlock* arg_prev, DataBlock* arg_next)
	{
	seq = arg_seq;
	upper = seq + size;

	block = new u_char[size];
	if ( ! block )
		internal_error("out of memory");

	memcpy((void*) block, (const void*) data, size);

	prev = arg_prev;
	next = arg_next;

	if ( prev )
		prev->next = this;
	if ( next )
		next->prev = this;
	}


Reassembler::Reassembler(int init_seq, const uint32* ip_addr,
			ReassemblerType arg_type)
	{ 
	blocks = 0; 
	trim_seq = last_reassem_seq = init_seq; 

	const NumericData* host_profile;
	get_map_result(ip_addr[0], host_profile);	// breaks for IPv6

	policy = (arg_type == REASSEM_TCP) ?
			host_profile->tcp_reassem : host_profile->ip_reassem;
	}


Reassembler::~Reassembler()
	{
	while ( blocks )
		{
		DataBlock* b = blocks->next;
		delete blocks;
		blocks = b;
		}
	}

void Reassembler::NewBlock(double t, int seq, int len, const u_char* data)
	{
	if ( len == 0 ) 
		return;

	int upper_seq = seq + len;

	if ( upper_seq <= trim_seq )
		// Old data, don't do any work for it.
		return;

	if ( seq < trim_seq )
		{ // Partially old data, just keep the good stuff.
		int amount_old = trim_seq - seq;

		data += amount_old;
		seq += amount_old;
		len -= amount_old;
		}

	DataBlock* start_block;

	if ( ! blocks )
		blocks = last_block = start_block =
			new DataBlock(data, len, seq, 0, 0);
	else
		start_block = AddAndCheck(blocks, seq, upper_seq, data);

	BlockInserted(t, start_block);
	}

int Reassembler::TrimToSeq(int seq)
	{
	if ( seq > last_reassem_seq )
		// We're trimming data we never delivered.
		Undelivered(seq);

	int all_in_seq = 1;
	while ( blocks && blocks->upper <= seq )
		{
		DataBlock* b = blocks->next;

		if ( b && b->seq <= seq )
			{
			if ( blocks->upper != b->seq )
				all_in_seq = 0;
			}
		else
			{
			// No more blocks - did this one make it to seq?
			// Second half of test is for acks of FINs, which
			// don't get entered into the sequence space.
			if ( blocks->upper != seq && blocks->upper != seq - 1 )
				all_in_seq = 0;
			}

		delete blocks;

		blocks = b;
		}

	if ( blocks )
		blocks->prev = 0;
	else
		last_block = 0;

	trim_seq = max(trim_seq, seq);

	return all_in_seq;
	}

int Reassembler::TotalSize() const
	{
	int size = 0;

	for ( DataBlock* b = blocks; b; b = b->next )
		size += b->upper - b->seq;

	return size;
	}

void Reassembler::Describe(ODesc* d) const
	{
	d->Add("reassembler");
	}

void Reassembler::Undelivered(int /* up_to_seq */)
	{
	}

DataBlock* Reassembler::AddAndCheck(DataBlock* b, int seq, int upper,
					const u_char* data)
	{
	// Special check for the common case of appending to the end.
	if ( last_block && seq == last_block->upper )
		{
		last_block = new DataBlock(data, upper - seq, seq,
						last_block, 0);
		return last_block;
		}

	// Find the first block that doesn't come completely before the
	// new data.
	while ( b->next && b->upper <= seq )
		b = b->next;

	if ( b->upper <= seq )
		{
		// b is the last block, and it comes completely before
		// the new block.
		last_block = new DataBlock(data, upper - seq, seq, b, 0);
		return last_block;
		}

	DataBlock* new_b = 0;

	if ( upper <= b->seq )
		{
		// The new block comes completely before b.
		new_b = new DataBlock(data, upper - seq, seq, b->prev, b);
		if ( b == blocks )
			blocks = new_b;
		return new_b;
		}

	// The blocks overlap.

#ifdef ACTIVE_MAPPING
	if ( policy != RP_UNKNOWN )
		{
		if ( seq < b->seq )
			{ // The new block has a prefix that comes before b.
			int prefix_len = b->seq - seq;
			new_b = new DataBlock(data, prefix_len, seq, b->prev, b);
			if ( b == blocks )
				blocks = new_b;

			data += prefix_len;
			seq += prefix_len;
			}

		if ( policy == RP_LAST ||
			     // After handling the prefix block, BSD takes the rest
		     (policy == RP_BSD && new_b) ||
			     // Similar, but overwrite for same seq number
		     (policy == RP_LINUX && (new_b || seq == b->seq)) )
			{
			DataBlock* bprev = b->prev;
			bool b_was_first = b == blocks;
			while ( b && b->upper <= upper )
				{
				DataBlock* next = b->next;
				delete b;
				b = next;
				}

			new_b = new DataBlock(data, upper - seq, seq, bprev, b);
			if ( b_was_first )
				blocks = new_b;

			// Trim the next block as needed.
			if ( b && new_b->upper > b->seq )
				{
				DataBlock* next_b =
					new DataBlock(&b->block[upper - b->seq],
							b->upper - upper, upper,
							new_b, b->next);
				if ( b == last_block )
					last_block = next_b;

				delete b;
				}
			}

		else
			{ // handle the piece that sticks out past b
			new_b = b;
			int len = upper - b->upper;
			if ( len > 0 )
				new_b = AddAndCheck(b, b->upper, upper, &data[b->upper - seq]);
			}
		}
	else
		{
#endif
	// Default behavior - complain about overlaps.
	if ( seq < b->seq )
		{
		// The new block has a prefix that comes before b.
		int prefix_len = b->seq - seq;
		new_b = new DataBlock(data, prefix_len, seq, b->prev, b);
		if ( b == blocks )
			blocks = new_b;

		data += prefix_len;
		seq += prefix_len;
		}
	else
		new_b = b;

	int overlap_start = seq;
	int overlap_offset = overlap_start - b->seq;
	int new_b_len = upper - seq;
	int b_len = b->upper - overlap_start;
	int overlap_len = min(new_b_len, b_len);

	Overlap(&b->block[overlap_offset], data, overlap_len);

	if ( overlap_len < new_b_len )
		{
		// Recurse to resolve remainder of the new data.
		data += overlap_len;
		seq += overlap_len;

		if ( new_b == b )
			new_b = AddAndCheck(b, seq, upper, data);
		else
			(void) AddAndCheck(b, seq, upper, data);
		}

#ifdef ACTIVE_MAPPING
		}	// else branch, for RP_UNKNOWN behavior
#endif

	if ( new_b->prev == last_block )
		last_block = new_b;

	return new_b;
	}
