// $Id: PktSrc.cc,v 1.10 2003/09/02 04:16:06 vern Exp $
//
// Copyright (c) 1999, 2000, 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 "util.h"
#include "PktSrc.h"
#include "Hash.h"

int snaplen = 8192;	// really want "capture entire packet"


PktSrc::PktSrc()
	{
	interface = readfile = 0;
	data = 0;
	hdr_size = 0;
	pd = 0;
	}

PktSrc::~PktSrc()
	{
	Close();

	bpf_program* code;
	IterCookie* cookie = filters.InitForIteration();
	while ( (code = filters.NextEntry(cookie)) )
		delete code;

	delete [] interface;
	delete [] readfile;
	}

pcap_dumper_t* PktSrc::OpenDumper(const char* writefile)
	{
	if ( pd )
		return pcap_dump_open(pd, (char*) writefile);
	else
		return 0;
	}

const u_char* PktSrc::NextPacket(struct pcap_pkthdr*& arg_hdr)
	{
	if ( ! data )
		{
		data = pcap_next(pd, &hdr);

		if ( ! data )
			return 0;
		}

	arg_hdr = &hdr;

	return data;
	}

double PktSrc::NextTimestamp()
	{
	if ( ! data )
		{
		data = pcap_next(pd, &hdr);

		if ( ! data )
			return -1.0;
		}

	return hdr.ts.tv_sec + hdr.ts.tv_usec / 1e6;
	}

int PktSrc::PrecompileFilter(int index, const char* filter)
	{
	// Compile filter.
	bpf_program* code = new bpf_program;
	if ( pcap_compile(pd, code, (char*) filter, 1, netmask) < 0 )
		{
		snprintf(errbuf, sizeof(errbuf),
			"pcap_compile(%s): %s", filter, pcap_geterr(pd));
		errbuf[PCAP_ERRBUF_SIZE + 255] = '\0';
		return 0;
		}

	// Store it in hash.
	HashKey* hash = new HashKey(HashKey(index));
	bpf_program* oldcode = (bpf_program*) filters.Lookup(hash);
	if ( oldcode )
		{
		pcap_freecode(oldcode);
		delete oldcode;
		}

	filters.Insert(hash, code);
	delete hash;

	return 1;
	}

int PktSrc::SetFilter(int index)
	{
	HashKey* hash = new HashKey(HashKey(index));
	bpf_program* code = (bpf_program*) filters.Lookup(hash);
	if ( ! code )
		{
		sprintf(errbuf, "No precompiled pcap filter for index %d",
				index);
		return 0;
		}

	if ( pcap_setfilter(pd, code) < 0 )
		{
		sprintf(errbuf, "pcap_setfilter(%d): %s",
				index, pcap_geterr(pd));
		return 0;
		}

	return 1;
	}

void PktSrc::SetHdrSize()
	{
	int dl = pcap_datalink(pd);

	switch ( dl )
		{
		case DLT_NULL:
			hdr_size = 4;
			break;

		case DLT_EN10MB:
			hdr_size = 14;
			break;

		case DLT_FDDI:
			hdr_size = 13 + 8;	// fddi_header + LLC
			break;

		default:
			sprintf(errbuf, "unknown data link type 0x%x", dl);
			Close();
		}
	}

void PktSrc::Close()
	{
	if ( pd )
		{
		pcap_close(pd);
		pd = 0;
		}
	}


PktInterfaceSrc::PktInterfaceSrc(const char* arg_interface, const char* filter)
: PktSrc()
	{
	char tmp_errbuf[PCAP_ERRBUF_SIZE];

	// Determine interface if not specified.
	if ( ! arg_interface && ! (arg_interface = pcap_lookupdev(tmp_errbuf)) )
		{
		sprintf(errbuf, "pcap_lookupdev: %s", tmp_errbuf);
		return;
		}

	interface = copy_string(arg_interface);

	// Determine network and netmask.
	uint32 net;
	if ( pcap_lookupnet(interface, &net, &netmask, tmp_errbuf) < 0 )
		{
		// ### The lookup can fail if no address is assigned to
		// the interface; and libpcap doesn't have any useful notion
		// of error codes, just error strings - how bogus - so we
		// just kludge around the error :-(.
		// sprintf(errbuf, "pcap_lookupnet %s", tmp_errbuf);
		// return;
		net = 0;
		netmask = 0xffffff00;
		}

	int timeout = 20;	// 20 msec; should be configurable ###
	pd = pcap_open_live(interface, snaplen, 1, timeout, tmp_errbuf);
	if ( ! pd )
		{
		sprintf(errbuf, "pcap_open_live: %s", tmp_errbuf);
		return;
		}

	if ( PrecompileFilter(0, filter) && SetFilter(0) )
		{
		SetHdrSize();
		fprintf(stderr, "listening on %s\n", interface);
		}
	}


PktFileSrc::PktFileSrc(const char* arg_readfile, const char* filter)
: PktSrc()
	{
	readfile = copy_string(arg_readfile);

	pd = pcap_open_offline((char*) readfile, errbuf);
	if ( pd && PrecompileFilter(0, filter) && SetFilter(0) )
		SetHdrSize();
	}
