// $Id: CompHash.cc,v 1.20 2003/07/13 19:23:40 vern Exp $
//
// Copyright (c) 1995, 1997, 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 "CompHash.h"
#include "Val.h"

CompositeHash::CompositeHash(TypeList* composite_type)
	{
	type = composite_type;
	Ref(type);
	is_singleton = type->Types()->length() == 1;

	if ( is_singleton )
		{
		// Don't do any further key computations - we'll do them
		// via the singleton later.
		singleton_tag = (*type->Types())[0]->InternalType();
		size = 0;
		key = 0;
		}

	else
		{
		size = ComputeKeySize();

		if ( size > 0 )
			// Fixed size.  Make sure what we get is fully aligned.
			key = (char*) new double[size/sizeof(double) + 1];
		else
			key = 0;
		}
	}

CompositeHash::~CompositeHash()
	{
	Unref(type);
	delete [] key;
	}

HashKey* CompositeHash::ComputeHash(const Val* v, int type_check) const
	{
	if ( is_singleton )
		return ComputeSingletonHash(v, type_check);

	char* k = key;

	if ( ! k )
		{
		int sz = ComputeKeySize(v, type_check);
		if ( sz == 0 )
			return 0;

		k = (char*) new double[sz/sizeof(double) + 1];
		type_check = 0;	// no need to type-check again.
		}

	const type_list* tl = type->Types();

	if ( type_check && v->Type()->Tag() != TYPE_LIST )
		return 0;
	const val_list* vl = v->AsListVal()->Vals();
	if ( type_check && vl->length() != tl->length() )
		return 0;

	char* kp = k;
	loop_over_list(*tl, i)
		{
		InternalTypeTag t = (*tl)[i]->InternalType();

		if ( type_check )
			{
			InternalTypeTag vt = (*vl)[i]->Type()->InternalType();
			if ( vt != t )
				return 0;
			}

		switch ( t ) {
		case TYPE_INTERNAL_INT:
		case TYPE_INTERNAL_UNSIGNED:
			{
			kp = AlignAndPad(kp, sizeof(int));
			int ival = (*vl)[i]->ForceAsInt();
			*((int*) kp) = ival;
			kp += sizeof(int);
			}
			break;

		case TYPE_INTERNAL_ADDR:
			{
			kp = AlignAndPad(kp, sizeof(int));
#ifdef BROv6
			const addr_type av = (*vl)[i]->AsAddr();
			((int*) kp)[0] = av[0];
			((int*) kp)[1] = av[1];
			((int*) kp)[2] = av[2];
			((int*) kp)[3] = av[3];
			kp += 4 * sizeof(int);
#else
			int ival = (*vl)[i]->ForceAsInt();
			*((int*) kp) = ival;
			kp += sizeof(int);
#endif
			}
			break;

		case TYPE_INTERNAL_SUBNET:
			{
			kp = AlignAndPad(kp, sizeof(subnet_type));
#ifdef BROv6
			const subnet_type* sv = (*vl)[i]->AsSubNet();
			((int*) kp)[0] = sv->net[0];
			((int*) kp)[1] = sv->net[1];
			((int*) kp)[2] = sv->net[2];
			((int*) kp)[3] = sv->net[3];
			((int*) kp)[4] = sv->width;
			kp += 5 * sizeof(int);
#else
			const subnet_type* sv = (*vl)[i]->AsSubNet();
			((int*) kp)[0] = sv->net;
			((int*) kp)[1] = sv->width;
			kp += 2 * sizeof(int);
#endif
			}
			break;

		case TYPE_INTERNAL_DOUBLE:
			{
			kp = AlignAndPad(kp, sizeof(double));
			double dval = (*vl)[i]->InternalDouble();
			*((double*) kp) = dval;
			kp += sizeof(double);
			}
			break;

		case TYPE_INTERNAL_VOID:
		case TYPE_INTERNAL_OTHER:
			internal_error("bad index type in CompositeHash::ComputeHash");
			return 0;

		case TYPE_INTERNAL_STRING:
			{
			// Align to int for the length field.
			kp = AlignAndPad(kp, sizeof(int));
			const BroString* sval = (*vl)[i]->AsString();

			int n = sval->Len();
			*((int*) kp) = n;	// so we can recover the value
			kp += sizeof(int);

			memcpy((void*) kp, sval->Bytes(), sval->Len());
			kp += sval->Len();
			}
			break;

		case TYPE_INTERNAL_ERROR:
			return 0;
		}
		}

	return new HashKey((k == key), (void*) k, kp - k);
	}

HashKey* CompositeHash::ComputeSingletonHash(const Val* v, int type_check) const
	{
	if ( v->Type()->Tag() == TYPE_LIST )
		{
		const val_list* vl = v->AsListVal()->Vals();
		if ( type_check && vl->length() != 1 )
			return 0;

		v = (*vl)[0];
		}

	if ( type_check && v->Type()->InternalType() != singleton_tag )
		return 0;

	switch ( singleton_tag ) {
	case TYPE_INTERNAL_INT:
	case TYPE_INTERNAL_UNSIGNED:
		return new HashKey(v->ForceAsInt());

	case TYPE_INTERNAL_ADDR:
#ifdef BROv6
		return new HashKey(v->AsAddr(), 4);
#else
		return new HashKey(v->ForceAsInt());
#endif

	case TYPE_INTERNAL_SUBNET:
#ifdef BROv6
		return new HashKey((const uint32*) v->AsSubNet(), 5);
#else
		return new HashKey((const uint32*) v->AsSubNet(), 2);

#endif

	case TYPE_INTERNAL_DOUBLE:
		return new HashKey(v->AsDouble());

	case TYPE_INTERNAL_VOID:
	case TYPE_INTERNAL_OTHER:
		internal_error("bad index type in CompositeHash::ComputeSingletonHash");
		return 0;

	case TYPE_INTERNAL_STRING:
		return new HashKey(v->AsString());

	case TYPE_INTERNAL_ERROR:
		return 0;

	default:
		internal_error("bad internal type in CompositeHash::ComputeSingletonHash");
		return 0;
	}
	}

int CompositeHash::ComputeKeySize(const Val* v, int type_check) const
	{
	const type_list* tl = type->Types();
	const val_list* vl = 0;
	if ( v )
		{
		if ( type_check && v->Type()->Tag() != TYPE_LIST )
			return 0;

		vl = v->AsListVal()->Vals();
		if ( type_check && vl->length() != tl->length() )
			return 0;
		}

	int sz = 0;
	loop_over_list(*tl, i)
		{
		InternalTypeTag t = (*tl)[i]->InternalType();

		if ( type_check && vl )
			{
			InternalTypeTag vt = (*vl)[i]->Type()->InternalType();
			if ( vt != t )
				return 0;
			}

		switch ( t ) {
		case TYPE_INTERNAL_INT:
		case TYPE_INTERNAL_UNSIGNED:
			sz = SizeAlign(sz, sizeof(int));
			break;

		case TYPE_INTERNAL_ADDR:
#ifdef BROv6
			sz = SizeAlign(sz, sizeof(int));
			sz += sizeof(int) * 3;	// to make a total of 4 words
#else
			sz = SizeAlign(sz, sizeof(int));
#endif
			break;

		case TYPE_INTERNAL_SUBNET:
#ifdef BROv6
			sz = SizeAlign(sz, sizeof(int));
			sz += sizeof(int) * 4;	// to make a total of 5 words
#else
			sz = SizeAlign(sz, sizeof(int));
			sz += sizeof(int);	// make room for width
#endif
			break;

		case TYPE_INTERNAL_DOUBLE:
			sz = SizeAlign(sz, sizeof(double));
			break;

		case TYPE_INTERNAL_VOID:
		case TYPE_INTERNAL_OTHER:
			internal_error("bad index type in CompositeHash::CompositeHash");
			return 0;

		case TYPE_INTERNAL_STRING:
			if ( ! vl )
				return 0;

			// Factor in length field.
			sz = SizeAlign(sz, sizeof(int));
			sz += (*vl)[i]->AsString()->Len();
			break;

		case TYPE_INTERNAL_ERROR:
			return 0;
		}
		}

	return sz;
	}

const char* CompositeHash::Align(const char* ptr, unsigned int size) const
	{
	unsigned int mask = size - 1;	// Assume size is a power of 2.
	unsigned long l_ptr = (unsigned long) ptr;
	unsigned long offset = l_ptr & mask;

	if ( offset > 0 )
		return ptr - offset + size;
	else
		return ptr;
	}

char* CompositeHash::AlignAndPad(char* ptr, unsigned int size) const
	{
	unsigned int mask = size - 1;	// Assume size is a power of 2.
	while ( (((unsigned long) ptr) & mask) != 0 )
		// Not aligned - zero pad.
		*ptr++ = '\0';

	return ptr;
	}

int CompositeHash::SizeAlign(int offset, unsigned int size) const
	{
	unsigned int mask = size - 1;	// Assume size is a power of 2.
	offset &= ~mask;	// Round down.
	offset += size;		// Round up.
	offset += size;		// Add in size.

	return offset;
	}

ListVal* CompositeHash::RecoverVals(const HashKey* k) const
	{
	ListVal* l = new ListVal(TYPE_ANY);
	const type_list* tl = type->Types();
	const char* kp = (const char*) k->Key();
	const char* k_end = kp + k->Size();

	loop_over_list(*tl, i)
		{
		// k->Size() == 0 for a single empty string.
		if ( kp >= k_end && k->Size() > 0 )
			internal_error("over-ran key in CompositeHash::RecoverVals");

		const BroType* t = (*tl)[i];
		InternalTypeTag it = t->InternalType();
		int n = k->Size();

		switch ( it ) {
		case TYPE_INTERNAL_INT:
			{
			kp = Align(kp, sizeof(int));
			l->Append(recover_val((void*) kp, t, n));
			kp += n;
			}
			break;

		case TYPE_INTERNAL_UNSIGNED:
		case TYPE_INTERNAL_ADDR:
			{
			kp = Align(kp, sizeof(unsigned int));
			l->Append(recover_val((void*) kp, t, n));
			kp += n;
			}
			break;

		case TYPE_INTERNAL_SUBNET:
			{
			kp = Align(kp, sizeof(unsigned int));
			l->Append(recover_val((void*) kp, t, n));
			kp += n;
			}
			break;

		case TYPE_INTERNAL_DOUBLE:
			{
			kp = Align(kp, sizeof(double));
			l->Append(recover_val((void*) kp, t, n));
			kp += n;
			}
			break;

		case TYPE_INTERNAL_VOID:
		case TYPE_INTERNAL_OTHER:
			internal_error("bad index type in CompositeHash::DescribeKey");
			break;

		case TYPE_INTERNAL_STRING:
			{
			kp = Align(kp, sizeof(int));

			if ( is_singleton )
				n = k->Size();
			else
				{
				n = *(int*) kp;
				kp += sizeof(int);
				}

			l->Append(recover_val((void*) kp, t, n));
			kp += n;
			}
			break;

		case TYPE_INTERNAL_ERROR:
			break;
		}
		}

	if ( kp != k_end )
		internal_error("under-ran key in CompositeHash::DescribeKey");

	return l;
	}
