/*
 * $Id: dfilter.c,v 1.5 2001/07/13 00:55:54 guy Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@zing.org>
 * Copyright 2001 Gerald Combs
 *
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <string.h>

#ifdef NEED_SNPRINTF_H
#include "snprintf.h"
#endif

#include "dfilter-int.h"
#include "syntax-tree.h"
#include "gencode.h"
#include "semcheck.h"
#include "dfvm.h"


/* Balanced tree of abbreviations and IDs */
GTree *dfilter_tokens = NULL;

#define DFILTER_TOKEN_ID_OFFSET	1

/* Comparision function for tree insertion. A wrapper around strcmp() */
static int g_strcmp(gconstpointer a, gconstpointer b);

/* Global error message space for dfilter_compile errors */
gchar dfilter_error_msg_buf[1024];
gchar *dfilter_error_msg;	/* NULL when no error resulted */

/* In proto.c */
extern int hf_text_only;

/* From scanner.c */
void    df_scanner_text(const char *text);
void    df_scanner_file(FILE *fh);
void    df_scanner_cleanup(void);
int     df_lex(void);

/* Holds the singular instance of our Lemon parser object */
void*		ParserObj = NULL;

void
dfilter_fail(char *format, ...)
{
	va_list	args;

	/* If we've already reported one error, don't overwite it */
	if (dfilter_error_msg != NULL)
		return;

	va_start(args, format);

	vsnprintf(dfilter_error_msg_buf, sizeof(dfilter_error_msg_buf),
			format, args);
	dfilter_error_msg = dfilter_error_msg_buf;
	va_end(args);
}


/* Initialize the dfilter module */
void
dfilter_init(void)
{
	int 			id, num_symbols;
	char 			*abbrev;
	header_field_info	*hfinfo, *same_name_hfinfo, *same_name_next_hfinfo;

	num_symbols = proto_registrar_n();

	if (dfilter_tokens) {
		/* XXX - needed? */
		g_message("I expected hf_ids to be NULL\n");
		g_tree_destroy(dfilter_tokens);

		/* Make sure the hfinfo->same_name links are broken */
		for (id = 0; id < num_symbols; id++) {
			hfinfo = proto_registrar_get_nth(id);
			hfinfo->same_name_next = NULL;
			hfinfo->same_name_prev = NULL;
		}
	}
	dfilter_tokens = g_tree_new(g_strcmp);

	/* Populate the abbrev/ID GTree (header-field symbol table) */

	
	for (id = 0; id < num_symbols; id++) {
		if (id == hf_text_only) {
			continue;
		}
		abbrev = proto_registrar_get_abbrev(id);
		hfinfo = proto_registrar_get_nth(id);

		g_assert(abbrev);		/* Not Null */
		g_assert(abbrev[0] != 0);	/* Not empty string */

		/* We allow multiple hfinfo's to be registered under the same
		 * abbreviation. This was done for X.25, as, depending
		 * on whether it's modulo-8 or modulo-128 operation,
		 * some bitfield fields may be in different bits of
		 * a byte, and we want to be able to refer to that field
		 * with one name regardless of whether the packets
		 * are modulo-8 or modulo-128 packets. */
		same_name_hfinfo = g_tree_lookup(dfilter_tokens, abbrev);
		if (same_name_hfinfo) {
			/* There's already a field with this name.
			 * Put it after that field in the list of
			 * fields with this name, then allow the code
			 * after this if{} block to replace the old
			 * hfinfo with the new hfinfo in the GTree. Thus,
			 * we end up with a linked-list of same-named hfinfo's,
			 * with the root of the list being the hfinfo in the GTree */
			same_name_next_hfinfo =
			    same_name_hfinfo->same_name_next;

			hfinfo->same_name_next = same_name_next_hfinfo;
			if (same_name_next_hfinfo)
				same_name_next_hfinfo->same_name_prev = hfinfo;

			same_name_hfinfo->same_name_next = hfinfo;
			hfinfo->same_name_prev = same_name_hfinfo;
		}
		g_tree_insert(dfilter_tokens, abbrev, hfinfo);
	}

	if (ParserObj) {
		g_message("I expected ParserObj to be NULL\n");
		/* Free the Lemon Parser object */
		DfilterFree(ParserObj, g_free);
	}
	/* Allocate an instance of our Lemon-based parser */
	ParserObj = DfilterAlloc(g_malloc);

	/* Initialize the syntax-tree sub-sub-system */
	sttype_init();
}

/* Clean-up the dfilter module */
void
dfilter_cleanup(void)
{
	/* Free the abbrev/ID GTree */
	if (dfilter_tokens) {
		g_tree_destroy(dfilter_tokens);
		dfilter_tokens = NULL;
	}

	/* Free the Lemon Parser object */
	if (ParserObj) {
		DfilterFree(ParserObj, g_free);
	}

	/* Clean up the syntax-tree sub-sub-system */
	sttype_cleanup();
}



/* Lookup an abbreviation in our token tree, returing the ID #
 * If the abbreviation doesn't exit, returns -1 */
header_field_info*
dfilter_lookup_token(char *abbrev)
{
	g_assert(abbrev != NULL);
	return g_tree_lookup(dfilter_tokens, abbrev);
}

/* String comparison func for dfilter_token GTree */
static int
g_strcmp(gconstpointer a, gconstpointer b)
{
	return strcmp((const char*)a, (const char*)b);
}

static dfilter_t*
dfilter_new(void)
{
	dfilter_t	*df;

	df = g_new(dfilter_t, 1);
	df->insns = NULL;

	return df;
}

/* Given a GPtrArray of instructions (dfvm_insn_t),
 * free them. */
static void
free_insns(GPtrArray *insns)
{
	unsigned int	i;
	dfvm_insn_t	*insn;

	for (i = 0; i < insns->len; i++) {
		insn = g_ptr_array_index(insns, i);
		dfvm_insn_free(insn);
	}
}

void
dfilter_free(dfilter_t *df)
{
	if (df->insns) {
		free_insns(df->insns);
	}

	g_free(df->registers);
	g_free(df->attempted_load);
	g_free(df);
}


static dfwork_t*
dfwork_new(void)
{
	dfwork_t	*dfw;

	dfw = g_new(dfwork_t, 1);

	dfw->st_root = NULL;
	dfw->syntax_error = FALSE;
	dfw->insns = NULL;
	dfw->loaded_fields = NULL;
	dfw->next_insn_id = 0;
	dfw->next_register = 0;

	return dfw;
}

static void
dfwork_free(dfwork_t *dfw)
{
	if (dfw->st_root) {
		stnode_free(dfw->st_root);
	}

	if (dfw->loaded_fields) {
		g_hash_table_destroy(dfw->loaded_fields);
	}

	if (dfw->insns) {
		free_insns(dfw->insns);
	}

	g_free(dfw);
}


gboolean
dfilter_compile(gchar *text, dfilter_t **dfp)
{
	int		token;
	dfilter_t	*dfilter;
	dfwork_t	*dfw;

	dfilter_error_msg = NULL;

	dfw = dfwork_new();

	df_scanner_text(text);

	while (1) {
		df_lval = stnode_new(STTYPE_UNINITIALIZED, NULL);
		token = df_lex();

		/* Check for end-of-input */
		if (token == 0) {
			/* Tell the parser that we have reached the end of input */
			Dfilter(ParserObj, 0, NULL, dfw);

			/* Free the stnode_t that we just generated, since
			 * the parser doesn't know about it and won't free it
			 * for us. */
			stnode_free(df_lval);
			df_lval = NULL;
			break;
		}

		/* Give the token to the parser */
		Dfilter(ParserObj, token, df_lval, dfw);

		if (dfw->syntax_error) {
			break;
		}
	}

	/* One last check for syntax error (after EOF) */
	if (dfw->syntax_error) {
		goto FAILURE;
	}


	/* Success, but was it an empty filter? If so, discard
	 * it and set *dfp to NULL */
	if (dfw->st_root == NULL) {
		*dfp = NULL;
	}
	else {

		/* Check semantics and do necessary type conversion*/
		if (!dfw_semcheck(dfw)) {
			goto FAILURE;
		}

		/* Create bytecode */
		dfw_gencode(dfw);
	
		/* Tuck away the bytecode in the dfilter_t */
		dfilter = dfilter_new();
		dfilter->insns = dfw->insns;
		dfw->insns = NULL;

		/* Initialize run-time space */
		dfilter->num_registers = dfw->next_register;
		dfilter->registers = g_new0(GList*, dfilter->num_registers);
		dfilter->attempted_load = g_new0(gboolean, dfilter->num_registers);

		/* And give it to the user. */
		*dfp = dfilter;
	}
	/* SUCCESS */
	dfwork_free(dfw);

	/* Reset flex */
	df_scanner_cleanup();

	return TRUE;

FAILURE:
	if (dfw) {
		dfwork_free(dfw);
	}
	dfilter_fail("Unable to parse filter string \"%s\".", text);
	*dfp = NULL;

	/* Reset flex */
	df_scanner_cleanup();
	return FALSE;

}


gboolean
dfilter_apply(dfilter_t *df, tvbuff_t *tvb, proto_tree *tree)
{
	return dfvm_apply(df, tvb, tree);
}

gboolean
dfilter_apply_edt(dfilter_t *df, epan_dissect_t* edt)
{
	return dfvm_apply(df, edt->tvb, edt->tree);
}


void
dfilter_dump(dfilter_t *df)
{
	dfvm_dump(stdout, df->insns);
}
