/*
	**
	** packet-summary.c
	**
	** Stores and retrieves information about collections of packets
	**
	** Copyright 1998 Damien Miller <dmiller@ilogic.com.au>
	**
	** This software is licensed under the terms of the GNU General 
	** Public License (GPL). Please see the file COPYING for details.
	** 
	** $Id: packet-summary.c,v 1.9 1998/12/01 13:56:31 dmiller Exp $
	**
 */

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "summary-structures.h"
#include "packet-summary.h"
#include "util.h"

static char rcsid[]="$Id: packet-summary.c,v 1.9 1998/12/01 13:56:31 dmiller Exp $";

/* Prototypes */
int				add_host_details(GHashTable *hosts, GHashTable *peers, pinfo_t *pinfo);
u_int64_t		make_peer_key(u_int32_t ip_addr1, u_int32_t ip_addr2);
unsigned int 	peer_hash(u_int64_t *peer_key);
unsigned int 	peer_compare(u_int64_t *peer_key1, u_int64_t *peer_key2);
void				add_peer_details(host_t *src, host_t *dst, GHashTable *peers, pinfo_t *pinfo);
void				free_host(u_int8_t *dummy, host_t *host, gpointer dummy2);
void				free_peer(u_int8_t *dummy, peer_t *peer, gpointer dummy2);
void				resolve_host(u_int8_t *dummy, host_t *host, gpointer dummy2);

/* Adds a packet's information to the summary */
void store_packet_info(summary_t *ps, pinfo_t *pinfo)
{
	/* Update global counters */
	if (pinfo->is_fragment)
		ps->total_bytes += pinfo->header_length;
	else
		ps->total_bytes += pinfo->total_length;
	ps->total_packets++;
	if (pinfo->is_connection_request)
		ps->total_connections++;
		
	/* Update last-used time */
	ps->summary_finish = time(NULL);
	
	/* Add source and destination hosts to hash table and update */
	/* number of total active hosts if they are new */
	ps->active_hosts += add_host_details(ps->hosts, ps->peers, pinfo);
}

/* Adds a packets information to hosts table */
int add_host_details(GHashTable *hosts, GHashTable *peers, pinfo_t *pinfo)
{
	int 		new_hosts = 0;
	host_t	*src;	
	host_t	*dst;	
	
	/* If source host has not been seen before, create a new entry */
	src = g_hash_table_lookup(hosts, (gpointer)&(pinfo->src_addr));
	if (src == NULL)
	{
#ifdef DEBUG
		fprintf(stderr, "Added host %lu.%lu.%lu.%lu\n", (pinfo->src_addr >> 24) & 0xFF, (pinfo->src_addr >> 16) & 0xFF, (pinfo->src_addr >> 8) & 0xFF, (pinfo->src_addr) & 0xFF);
#endif
		new_hosts++;
		src = util_zmalloc(sizeof(*src));
		src->ip_addr = pinfo->src_addr;
		src->first_seen = time(NULL);
	}
	
	/* Update source host information with packet contents */
	src->last_seen = time(NULL);
	src->packets_sent++;
	if (pinfo->is_connection_request)
		src->connections_sent++;
	if (pinfo->is_fragment)
		src->bytes_sent += pinfo->header_length;
	else
		src->bytes_sent += pinfo->total_length;
	
	/* If destination host has not been seen before, create a new entry */
	dst = g_hash_table_lookup(hosts, (gpointer)&(pinfo->dest_addr));
	if (dst == NULL)
	{
#ifdef DEBUG
		fprintf(stderr, "Added host %lu.%lu.%lu.%lu\n", (pinfo->dest_addr >> 24) & 0xFF, (pinfo->dest_addr >> 16) & 0xFF, (pinfo->dest_addr >> 8) & 0xFF, (pinfo->dest_addr) & 0xFF);
#endif
		new_hosts++;
		dst = util_zmalloc(sizeof(*dst));
		dst->ip_addr = pinfo->dest_addr;
		dst->first_seen = time(NULL);
	}
	
	/* Update source host information with packet contents */
	dst->last_seen = time(NULL);
	dst->packets_received++;
	if (pinfo->is_connection_request)
		dst->connections_received++;
	if (pinfo->is_fragment)
		dst->bytes_received += pinfo->header_length;
	else
		dst->bytes_received += pinfo->total_length;

	/* Store updated/new host entries in hash table */
	g_hash_table_insert(hosts, (gpointer)&(src->ip_addr), (gpointer)src);
	g_hash_table_insert(hosts, (gpointer)&(dst->ip_addr), (gpointer)dst);
	
	/* Store peer information in peers arrays */
	add_peer_details(src, dst, peers, pinfo);

	return(new_hosts);
}

void add_peer_details(host_t *src, host_t *dst, GHashTable *peers, pinfo_t *pinfo)
{
	peer_t		*peer;
	u_int64_t	peer_key;

	peer_key = make_peer_key(src->ip_addr, dst->ip_addr);

	/* Try to find peer record */
	peer = g_hash_table_lookup(peers, (gpointer)&(peer_key));

	/* If peer not found, allocate a new one */
	if (peer == NULL)
	{
		/* Add peer key to source and destination host's peer lists */
		src->peer_keys = util_realloc(src->peer_keys, (src->n_peers + 1) * sizeof(*(src->peer_keys)));
		src->peer_keys[src->n_peers] = peer_key;
		src->n_peers++;
		dst->peer_keys = util_realloc(dst->peer_keys, (dst->n_peers + 1) * sizeof(*(dst->peer_keys)));
		dst->peer_keys[dst->n_peers] = peer_key;
		dst->n_peers++;
		
		/* Create the peer record */
		peer = util_zmalloc(sizeof(*peer));
		peer->key = peer_key;
		
		if (src->ip_addr > dst->ip_addr)
		{
			peer->host1 = src;
			peer->host2 = dst;
		}
		else
		{
			peer->host2 = src;
			peer->host1 = dst;
		}
		peer->first_seen = time(NULL);
	}
	
	peer->last_seen = time(NULL);

	/* Update peer record */
	if (src->ip_addr > dst->ip_addr)
	{
		peer->packets_sent_1_to_2++;
		if (pinfo->is_fragment)
			peer->bytes_sent_1_to_2 += pinfo->header_length;
		else
			peer->bytes_sent_1_to_2 += pinfo->total_length;
		if (pinfo->is_connection_request)
			peer->connections_sent_1_to_2++;
	} else
	{
		peer->packets_sent_2_to_1++;
		if (pinfo->is_fragment)
			peer->bytes_sent_2_to_1 += pinfo->header_length;
		else
			peer->bytes_sent_2_to_1 += pinfo->total_length;
		if (pinfo->is_connection_request)
			peer->connections_sent_2_to_1++;
	}

	/* Store peer record */
	g_hash_table_insert(peers, (gpointer)&(peer->key), (gpointer)peer);
}

u_int64_t make_peer_key(u_int32_t ip_addr1, u_int32_t ip_addr2)
{
	if (ip_addr1 > ip_addr2)
		return(((u_int64_t)ip_addr1 << 32) | (u_int64_t)ip_addr2);
	else
		return(((u_int64_t)ip_addr2 << 32) | (u_int64_t)ip_addr1);
}

/* Sets up packet summary data structures */
void packet_summary_init(summary_t **ps)
{
	*ps = util_zmalloc(sizeof(**ps));
	(*ps)->summary_finish = (*ps)->summary_start = time(NULL);
	(*ps)->hosts = g_hash_table_new((GHashFunc)host_hash, (GCompareFunc)host_compare);
	(*ps)->peers = g_hash_table_new((GHashFunc)peer_hash, (GCompareFunc)peer_compare);
}

/* Deallocates packet summary data structures */
void packet_summary_free(summary_t *ps)
{
	g_hash_table_foreach(ps->hosts, (GHFunc)free_host, (gpointer)NULL);
	g_hash_table_foreach(ps->peers, (GHFunc)free_peer, (gpointer)NULL);
	free(ps);
}

/* Frees a host */
void free_host(u_int8_t *dummy, host_t *host, gpointer dummy2)
{
	if (host->hostname != NULL)
		free(host->hostname);

	if (host->n_peers != 0)
		free(host->peer_keys);
		
	free(host);
}

/* Frees a peer description */
void free_peer(u_int8_t *dummy, peer_t *peer, gpointer dummy2)
{
	free(peer);
}

/* Hashes a host's ip address */
unsigned int host_hash(u_int32_t *ip_addr)
{
	return((unsigned int)(*ip_addr));
}

/* Compares two hosts */
unsigned int host_compare(u_int32_t *ip_addr1, u_int32_t *ip_addr2)
{
	return((*ip_addr1) == (*ip_addr2));
}

/* Compares two peers */
unsigned int peer_compare(u_int64_t *peer_key1, u_int64_t *peer_key2)
{
	return((*peer_key1) == (*peer_key2));
}

/* Hashes a peer  */
unsigned int peer_hash(u_int64_t *peer_key)
{
	return((*peer_key) ^ (*peer_key >> 32));
}

/* Return RCS version */
const char *ps_version(void)
{
	return(rcsid);
}

void resolve_host_names(summary_t *ps)
{
	g_hash_table_foreach(ps->hosts, (GHFunc)resolve_host, (gpointer)NULL);
}

void resolve_host(u_int8_t *dummy, host_t *host, gpointer dummy2)
{
	struct hostent	*h;
	unsigned long	addr;
	
	addr = htonl(host->ip_addr);
	h = gethostbyaddr((char *)&addr, 4, AF_INET);
	
	if (h != NULL)
		host->hostname = util_strdup(h->h_name);
	else
		host->hostname = NULL;
}
