// See the file "COPYING" in the main distribution directory for copyright.

#include "config.h"

#include "util.h"
#include "BPF_Program.h"

#ifdef DONT_HAVE_LIBPCAP_PCAP_FREECODE
extern "C" {
#include "pcap-int.h"

int pcap_freecode(pcap_t* unused, struct bpf_program* program)
	{
	program->bf_len = 0;

	if ( program->bf_insns )
		{
		free((char*) program->bf_insns);	// copied from libpcap
		program->bf_insns = 0;
		}

	return 0;
	}

pcap_t* pcap_open_dead(int linktype, int snaplen)
	{
	pcap_t* p;

	p = (pcap_t*) malloc(sizeof(*p));
	if ( ! p )
		return 0;

	memset(p, 0, sizeof(*p));

	p->fd = -1;
	p->snapshot = snaplen;
	p->linktype = linktype;

	return p;
	}

int pcap_compile_nopcap(int snaplen_arg, int linktype_arg,
			struct bpf_program* program, char* buf,
			int optimize, bpf_u_int32 mask)
	{
	pcap_t* p;
	int ret;

	p = pcap_open_dead(linktype_arg, snaplen_arg);
	if ( ! p )
		return -1;

	ret = pcap_compile(p, program, buf, optimize, mask);
	pcap_close(p);

	return ret;
	}
}
#endif

// Simple heuristic to identify filters that always match, so that we can
// skip the filtering in that case. "ip or not ip" is Bro's default filter.
static bool filter_matches_anything(const char *filter)
	{
	return (! filter) || strlen(filter) == 0 || strcmp(filter, "ip or not ip") == 0;
	}

BPF_Program::BPF_Program() : m_compiled(), m_matches_anything(false), m_program()
	{
	}

BPF_Program::~BPF_Program()
	{
	FreeCode();
	}

bool BPF_Program::Compile(pcap_t* pcap, const char* filter, uint32 netmask,
			  char* errbuf, unsigned int errbuf_len, bool optimize)
	{
	if ( ! pcap )
		return false;

	FreeCode();

	if ( pcap_compile(pcap, &m_program, (char *) filter, optimize, netmask) < 0 )
		{
		if ( errbuf )
			safe_snprintf(errbuf, errbuf_len,
				      "pcap_compile(%s): %s", filter,
				      pcap_geterr(pcap));

		return false;
		}

	m_compiled = true;
	m_matches_anything = filter_matches_anything(filter);

	return true;
	}

bool BPF_Program::Compile(int snaplen, int linktype, const char* filter,
			  uint32 netmask, char* errbuf, unsigned int errbuf_len,
			  bool optimize)
	{
	FreeCode();

#ifdef LIBPCAP_PCAP_COMPILE_NOPCAP_HAS_ERROR_PARAMETER
	char my_error[PCAP_ERRBUF_SIZE];

	int err = pcap_compile_nopcap(snaplen, linktype, &m_program,
				     (char *) filter, optimize, netmask, my_error);
	if ( err < 0 && errbuf )
		safe_strncpy(errbuf, my_error, errbuf_len);
		*errbuf = '\0';
#else
	int err = pcap_compile_nopcap(snaplen, linktype, &m_program,
				     (char*) filter, optimize, netmask);

	if ( err < 0 && errbuf && errbuf_len )
		*errbuf = '\0';
#endif

	if ( err == 0 )
		{
		m_compiled = true;
		m_matches_anything = filter_matches_anything(filter);
		}

	return err == 0;
	}

bpf_program* BPF_Program::GetProgram()
	{
	return m_compiled ? &m_program : 0;
	}

void BPF_Program::FreeCode()
	{
	if ( m_compiled )
		{
#ifdef DONT_HAVE_LIBPCAP_PCAP_FREECODE
		pcap_freecode(NULL, &m_program);
#else
		pcap_freecode(&m_program);
#endif
		m_compiled = false;
		}
	}
