/**********************************************************************
*
* Copyright (c) 2005-2006 Endace Technology Ltd, Hamilton, New Zealand.
* All rights reserved.
*
* This source code is proprietary to Endace Technology Limited and no part
* of it may be redistributed, published or disclosed except as outlined in
* the written contract supplied with this product.
*
* $Id: dsm_loader.c 9088 2008-04-16 04:58:56Z jomi $
*
**********************************************************************/

/**********************************************************************
* 
* Utility application for loading filters and output
*               expressions into a DSM equipt DAG card.
* 
*               Refer to the dsm_loader user manual for more information
*
**********************************************************************/


/* System headers */
#include "dag_platform.h"

/* LibXML2 header */
#include <libxml/parser.h>
#include <libxml/tree.h>


/* Endace headers */
#include "dagapi.h"
#include "dagutil.h"
#include "dagclarg.h"

/* DSM headers */
#include "dagdsm.h"


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused))  = "$Id: dsm_loader.c 9088 2008-04-16 04:58:56Z jomi $";
static const char* const kRevisionString = "$Revision: 9088 $";



/* CONSTANTS */

#ifndef _WIN32
#define DEFAULT_DEVICE  "/dev/dag0"     /* default device */
#else /* _WIN32 */
#define DEFAULT_DEVICE  "dag0"          /* default device */
#endif /* _WIN32 */


#if !defined(MAX_PATH)
	#define MAX_PATH 260
#endif



/* error codes past to the xml_warning() function */
#define EUNKNOWNELEM 0x10001




/* XMLTAG HASH VALUES */

#define XMLHASH_FILTER            652
#define XMLHASH_INTERFACE         954
#define XMLHASH_STEERING          873
#define XMLHASH_PARTIAL           756
#define XMLHASH_STREAM            658

#define XMLHASH_EARLYTERM         1036
#define XMLHASH_RAW               333
#define XMLHASH_WORD              448
#define XMLHASH_SONET             558
#define XMLHASH_HDLCTYPE          915
#define XMLHASH_IPV4              391
#define XMLHASH_FRAGMENT          1070
#define XMLHASH_IHL               320
#define XMLHASH_IPSOURCE          928
#define XMLHASH_IPDEST            701
#define XMLHASH_ADDR              415
#define XMLHASH_MASK              432
#define XMLHASH_TCP               330
#define XMLHASH_UDP               332
#define XMLHASH_SOURCEPORT        1166
#define XMLHASH_DESTPORT          939
#define XMLHASH_PORT              457
#define XMLHASH_TCPFLAGS          906
#define XMLHASH_FLAGS             530
#define XMLHASH_ETHERNET          871
#define XMLHASH_ETHERNET_VLAN     1354
#define XMLHASH_ETHERTYPE         995
#define XMLHASH_MACSOURCE         1017
#define XMLHASH_MACDEST           790
#define XMLHASH_ICMP              429
#define XMLHASH_ICMPCODE          890
#define XMLHASH_CODE              415
#define XMLHASH_ICMPTYPE          929
#define XMLHASH_TYPE              454
#define XMLHASH_VLANID            690

#define XMLHASH_NAME              421
#define XMLHASH_NUMBER            655
#define XMLHASH_ALGORITHM         976
#define XMLHASH_PARTIAL_COMPONENT 1790
#define XMLHASH_STREAM_COMPONENT  1692
#define XMLHASH_HASH_VALUE        1016  /*hash-value*/



/* TYPES */

typedef struct component_s
{
	enum { kFilter, kInterface, kSteering, kSteeringHash } type;
	
	union
	{
		uint32_t filter_num;
		uint32_t iface_num;
		uint32_t hlb_num;
		uint32_t hlb_hash_value;
	} data;
	
} component_t;



/* COMMANDLINE OPTIONS */

enum
{
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_DEVICE,
	CLA_STATS,
	CLA_DONTDOWNLOAD,
	CLA_INPUTFILE,
	CLA_OUTPUTFILE,
	CLA_VALIDATEFILE,
	CLA_VALIDATESTREAM,
	CLA_VALIDATEAUTOSTREAM,
	CLA_BYPASS
};



/* Internal routines. */
static int
parse_rules_file (int dagfd, DsmConfigH config_h, const char * filename);

static void
validate_erf_file (DsmConfigH config_h, const char * filename, int const erf_stream);

static void
display_stats (int dagfd, DsmConfigH config_h);



/* Implementation of internal routines. */
static void
print_version(void)
{
	printf("dsm_loader (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}


static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dsm_loader - loads filters and output expressions into DSM equipped DAG cards\n"
		"Usage : dsm_loader -d <device> [options] -f <config file>\n");
	dagclarg_display_usage(clarg, stdout);
}

uint32_t g_dsm_version;


int
main(int argc, char *argv[])
{ 
	ClArgPtr   clarg = NULL;
	char       dagname_buf[DAGNAME_BUFSIZE];
	char       dagname[DAGNAME_BUFSIZE];
	char       in_filename[MAX_PATH];
	char       out_filename[MAX_PATH];
	char       erf_filename[MAX_PATH];
	int        argindex = 0;
	int        clarg_result = 0;
	int        code = 0;
	int        dagstream;
	DsmConfigH config_h;
	int        dagfd;
	int        dump_output = 0;
	int        use_rec_stream = 0;
	FILE*      outputfile = NULL;
	int        i;
	int        validate_stream = 0;
	int        stats_interval = -1;
	int        download_to_card = 1;
	int        enable_bypass = 0;
	int        have_src_file = 0;
	
	dagutil_set_progname("dsm_loader");

	
	printf("\nEndace DSM Filter and Expression Loader\n");
	printf("(c) 2006 Endace Technology Ltd.\n\n");

	
	
	/* set the default dag card */
	strcpy(dagname, DEFAULT_DEVICE);
	
	
	/* clean the buffers */
	memset (out_filename, 0, MAX_PATH);
	memset (in_filename,  0, MAX_PATH);
	memset (erf_filename, 0, MAX_PATH);


	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char* const *) argv);


	/* General options. */
	dagclarg_add(clarg, "display help (this page)", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	
	dagclarg_add(clarg, "increase verbosity", "--verbose", 'v', CLA_VERBOSE);

	dagclarg_add_string(clarg, "DAG device to use", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add       (clarg, "enables bypass mode (which disables DSM), if not specified bypass mode is disabled automatically", "--bypass", 'b', CLA_BYPASS);
	dagclarg_add_string(clarg, "configuration file for the filters and expressions", "--config_file", 'f', "filename", in_filename, MAX_PATH, CLA_INPUTFILE);
	dagclarg_add       (clarg, "don't download the configuration to the card, if not specified the configuration is always downloaded to the card", "--dont_download", 'y', CLA_DONTDOWNLOAD);
	dagclarg_add_int   (clarg, "display DSM statistics every <seconds>", "--stats", 's', "seconds", &stats_interval, CLA_STATS);
	dagclarg_add_string(clarg, "output file to store the raw filters and lookup table", "--output_file", 'o', "filename", out_filename, MAX_PATH, CLA_OUTPUTFILE);
	dagclarg_add       (clarg, "Display version information.", "--version", 'V', CLA_VERSION);
	
	/* Deliberately undocumented. */
	dagclarg_add_string(clarg, "ERF record file that is to be validated", "--validate", 'e', "filename", erf_filename, MAX_PATH, CLA_VALIDATEFILE);
	dagclarg_suppress_display(clarg, CLA_VALIDATEFILE);
	dagclarg_add_int(clarg, "receive stream of the validation file, only valid if -e is also set", "--stream", 'q', "stream", &validate_stream, CLA_VALIDATESTREAM);
	dagclarg_suppress_display(clarg, CLA_VALIDATESTREAM);
	dagclarg_add(clarg, "use the stream value in the lctr/color field of the record to validate records", "--rec_stream", 'r', CLA_VALIDATEAUTOSTREAM);
	dagclarg_suppress_display(clarg, CLA_VALIDATEAUTOSTREAM);


	/* Parse the command line options. */
	clarg_result = dagclarg_parse(clarg, stderr, &argindex, &code);
	while (1 == clarg_result)
	{
		switch (code)
		{
			case CLA_HELP:
				print_usage(clarg); 
				return EXIT_SUCCESS;
				break;
			
			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				break;

			case CLA_DEVICE:
				if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
				{
					dagutil_verbose("device=%s\n", dagname);
					dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
				}
				break;

			case CLA_STATS:
				break;
			
			case CLA_VALIDATEFILE:
			case CLA_VALIDATESTREAM:
				break;
			
			case CLA_VALIDATEAUTOSTREAM:
				use_rec_stream = 1;
				break;
			
			case CLA_DONTDOWNLOAD:
				download_to_card = 0;
				break;
			
			case CLA_BYPASS:
				enable_bypass = 1;
				break;
				
			case CLA_INPUTFILE:
				have_src_file = 1;
				break;

			case CLA_OUTPUTFILE:
				dump_output = 1;
				break;
			
			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;
				break;

			default:
				if (argv[argindex][0] == '-')
				{
					/* Unknown option. */
					dagutil_error("unknown option %s\n", argv[argindex]);
					print_usage(clarg);
					return EXIT_FAILURE;
				}
				break;
		}
		
		clarg_result = dagclarg_parse(clarg, stderr, &argindex, &code);
	}

	if (-1 == clarg_result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}

	/* ClargPtr should no longer be necessary. */
	dagclarg_dispose(clarg);

	/* Open the DAG card */
	if ( (dagfd = dag_open(dagname)) == -1 )
	{
		dagutil_error ("dag_open(\"%s\") failed\n", dagname);
		return EXIT_FAILURE;
	}
		
	
	/* Initialise a configuration */
	if ( (config_h = dagdsm_create_configuration(dagfd)) == NULL )
	{
		if ( dagdsm_get_last_error() == ENOENT )
			dagutil_error ("failed to create a DSM configuration for the DAG card, check the correct DSM firmware is installed. Refer to the DSM user manual for more information.\n");
		else
			dagutil_error ("failed to create a DSM configuration for the DAG card, check the card supports DSM.\n");
		
		dag_close (dagfd);
		return EXIT_FAILURE;
	}

	g_dsm_version = dagdsm_get_dsm_version(dagfd);

	/* If we have a source file we parse it and optionally load it into the card */
	if ( have_src_file )
	{
		
		/* Parse the input file and load into the card */
		if ( parse_rules_file(dagfd, config_h, in_filename) == -1 )
		{
			dagutil_error ("Failed to parse the rules file.\n");
	
			dagdsm_destroy_configuration (config_h);
			dag_close (dagfd);
			return EXIT_FAILURE;
		}
	
		/* Download the fiel to the card */
		if ( download_to_card == 1 )
		{
			
			/* IMPORTANT: Prepare the card for DSM, this is required */
			if ( dagdsm_prepare_card(dagfd) == -1 )
			{
				if ( dagdsm_get_last_error() == ENOENT )
					dagutil_error ("failed to prepare the card for DSM, check the correct DSM firmware is installed. Refer to the DSM user manual for more information.\n");
				else
					dagutil_error ("failed to prepare the card for DSM, check the card supports DSM.\n");
		
				dagdsm_destroy_configuration (config_h);
				dag_close (dagfd);
				return EXIT_FAILURE;
			}
	
			/* put the DSM in bypass mode */
			dagdsm_bypass_dsm(dagfd, 1);
		
		
			/* attempt to download the confiuration to the card */
			if ( dagdsm_load_configuration(config_h) == -1 )
			{
				dagutil_error ("Failed to load the configuration into the card, errorcode %d\n", dagdsm_get_last_error());
				
				dagdsm_destroy_configuration (config_h);
				dag_close (dagfd);
				return EXIT_FAILURE;
			}
	
			/* enable the DSM module */
			if ( enable_bypass == 0 )
				dagdsm_bypass_dsm(dagfd, 0);

			/* acknowledgement */
			printf ("The configuration has been loaded into the card successfully.\n\n");

		}
	
	
		/* Dump the filters and COLUR to the output file or stdout */
		if ( dump_output )
		{
			if ( out_filename[0] == '\0' )
				outputfile = stdout;
			else if ( (outputfile = fopen(out_filename, "wt")) == NULL )
				dagutil_warning ("failed to create output file.\n");
			
		
			if ( outputfile )
			{
				for (i=0; i<7; i++)
				{
					dagdsm_display_filter (config_h, outputfile, i, DSM_DBG_INTERNAL_FILTER);
					fprintf (outputfile, "\n\n");
				}
				
				dagdsm_display_colur (config_h, outputfile, 0, DSM_DBG_INTERNAL_COLUR);
				
				if ( out_filename[0] != '\0' )
				{
					fflush (outputfile);
					fclose (outputfile);
				}
			}
		}
	
	}
	
	
	/* Validate the supplied file (if given) */
	if ( erf_filename[0] != '\0' )
	{
		if ( use_rec_stream )
			validate_stream = -1;
		
		validate_erf_file (config_h, erf_filename, validate_stream);
	}
	
	
	/* Display the stats every so many seconds */
	for (;stats_interval > 0;)
	{
		display_stats (dagfd, config_h);
		fflush(stdout);
		sleep(stats_interval);
	}

	
	/* Enable bypass mode if asked to */
	if ( enable_bypass == 1 )
	{
		dagutil_verbose ("enabling DSM bypass mode (disables DSM functionality).\n");
		dagdsm_bypass_dsm(dagfd, 1);
        /* and make sure that the  IPF steering control is in Stream0 mode . We change it to Dsm/Color mode in dagdsm_prepare_card*/
        dagutil_verbose ("Changing Steering to stream0 .\n");
        if ( dsm_set_ipf_steering_control(dagfd, 0) == -1 )
		      dagutil_warning ("failed to set IPF steering to Stream0\n");
	}
	
	
	/* Clean up */
	dagdsm_destroy_configuration (config_h);
	
	dag_close (dagfd);
	
	return EXIT_SUCCESS;
}


/**
 Displays the DSM counter values on stdout.
 
 @param dagfd File handle to the dag card
 @param config_h Handle to the virtual configuration created for the card.
 */
static void
display_stats (int dagfd, DsmConfigH config_h)
{
	static uint64_t filters[7] = { 0,0,0,0,0,0,0 };
	static uint64_t streams[8] = { 0,0,0,0,0,0,0,0 }; 
	static uint64_t drop       = 0;
	static uint64_t hlbs[2]    = { 0,0 };
	uint32_t        value0, value1;
	int             num_streams;
	DsmFilterH      filter_h;
	int             i;


	/* latch and clear the counters */
	if ( dagdsm_latch_and_clear_counters(config_h) == -1 )
	{
		dagutil_warning ("failed to latch and clear the counters, error code %d\n", dagdsm_get_last_error());
		return;
	}


	printf ("--------------------------------------------------------------------------------\n");
	
	/* read the counter values */
	for (i=0; i<7; i++)
	{
		filter_h = dagdsm_get_filter (config_h, i);
		if ( dagdsm_read_filter_counter(config_h, filter_h, &value0) == -1 )
		{
			dagutil_warning ("failed to read value in filter counter, error code %d\n", dagdsm_get_last_error());
			continue;
		}
		
		filters[i] += value0;
		printf ("Filter%d          : %"PRIu64"\n", i, filters[i]);
	}


	/* read the hlb values */
	if(g_dsm_version==DSM_VERSION_0)
	{
		if ( dagdsm_read_hlb_counter(config_h, &value0, &value1) == -1 )
			dagutil_warning ("failed to read value in HLB counter, error code %d\n", dagdsm_get_last_error());
	
		hlbs[0] += value0;
		hlbs[1] += value1;
		
		printf ("CRC Algorithm    : %"PRIu64"\n", hlbs[0]);
		printf ("Parity Algorithm : %"PRIu64"\n", hlbs[1]);
	}


	/* read the drop counter */
	if ( dagdsm_read_drop_counter(config_h, &value0) == -1 )
		dagutil_warning ("failed to read value in drop counter, error code %d\n", dagdsm_get_last_error());
	
	drop += value0;
	
	printf ("Drop counter     : %"PRIu64"\n", drop);


	
	/* read the stream values */
	if(g_dsm_version==DSM_VERSION_0)
	{
		num_streams = dagdsm_get_filter_stream_count (dagfd);
		num_streams = (num_streams > 8) ? 8 : num_streams;
		for (i=0; i<num_streams; i++)
		{
			if ( dagdsm_read_stream_counter(config_h, (i*2), &value0) == -1 )
			{
				dagutil_warning ("failed to read value in filter counter, error code %d\n", dagdsm_get_last_error());
				continue;
			}
			
			streams[i] += value0;
			printf ("Stream %d         : %"PRIu64"\n", (i*2), streams[i]);
		}
	}
}


/**
 Very simple hash function, used for transversing the XML document.
 @param str String to create a hash for.
 @return The hash of the string.
 */
static int
simple_str_hash (const xmlChar * str)
{
	int len;
	int hash;
	
	assert(str);
	
	hash = xmlStrlen(str);
	len = hash;
	
	if ( len > 0 )
	{
		while (len-- > 0)
		{
			hash += (int) str[len];
		}
	}
	
	return hash;
}


/**
 Displays an error message corresponding to the supplied error code and element.
 @param errorcode Error code that indicates what type of message to display.
 @param node Pointer to the offending node.
 @return The hash of the string.
 */
static void
xml_warning (int errorcode, const xmlNodePtr node)
{
	switch (errorcode)
	{
		case EUNKNOWNELEM:
			dagutil_warning ("Unknown or unexpected element \'<%s>\' on line %d.\n", node->name, node->line);
			break;

		default:
			assert(0);
			break;
	}
}


/**
 @param doc Handle the configuration to modify.
 @param filter_h Filter hash table that the filter handle is added to.
 @param cur_node Pointer to the filter element (node).
 */
static void
process_udp_tcp_node (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr child_node;
	xmlNodePtr sub_child_node;
	xmlChar *  attr;
	char       fmt_str[32];
	int        hash;
	uint16_t   port;
	uint16_t   mask;
	uint16_t   flags;
	int        res;
	xmlChar *  key;


	/* set the protocol as either UDP or TCP */
	if ( xmlStrEqual(cur_node->name, BAD_CAST"tcp") )
		dagdsm_filter_set_ip_protocol (filter_h, IPPROTO_TCP);
	else
		dagdsm_filter_set_ip_protocol (filter_h, IPPROTO_UDP);
		

	
	/* loop through the child elements */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
		
		
		hash = simple_str_hash (child_node->name);
		switch (hash)
		{
			case XMLHASH_SOURCEPORT:
			case XMLHASH_DESTPORT:
			case XMLHASH_TCPFLAGS:
				port  = 0;
				flags = 0;
				mask  = 0xFFFF;
			
				for (sub_child_node=child_node->children; sub_child_node; sub_child_node = sub_child_node->next)
				{
					if (sub_child_node->type != XML_ELEMENT_NODE)
						continue;
					
					
					/* get the text contents */
					key = xmlNodeListGetString(doc, sub_child_node->xmlChildrenNode, 1);
					if ( key == NULL )
						break;
					
					
					/* get the 'hex' attribute value */		
					attr = xmlGetProp (sub_child_node, BAD_CAST"hex");
					if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
						strcpy (fmt_str, "%hd");
					else
						strcpy (fmt_str, "%hx");
					
					if (attr)
						xmlFree (attr);
					
					
					/* process the sub-components */
					if ( xmlStrEqual(sub_child_node->name, BAD_CAST"port") )
						res = sscanf ((char*)key, fmt_str, &port);
					else if ( xmlStrEqual(sub_child_node->name, BAD_CAST"flags") )
						res = sscanf ((char*)key, fmt_str, &flags);
					else if ( xmlStrEqual(sub_child_node->name, BAD_CAST"mask") )
						res = sscanf ((char*)key, fmt_str, &mask);
					else
					{
						xmlFree(key);
						xml_warning (EUNKNOWNELEM, sub_child_node);
						break;
					}
					
					xmlFree(key);
					
					
					
					/* sanity check */
					if ( res != 1 )
					{
						dagutil_warning ("invalid format of the contents of element \'%s\' on line %d.\n", sub_child_node->name, sub_child_node->line);
						break;
					}
				}
				
				/* add the port/tcp_flags settings to the filter */
				if ( hash == XMLHASH_SOURCEPORT )
					dagdsm_filter_set_src_port (filter_h, port, mask);
				else if ( hash == XMLHASH_DESTPORT )
					dagdsm_filter_set_dst_port (filter_h, port, mask);
				else 
					dagdsm_filter_set_tcp_flags (filter_h, (uint8_t)flags, (uint8_t)mask);
				break;
				
			default:
				xml_warning (EUNKNOWNELEM, child_node);
				break;
		} /* switch (hash) */
	} /* for (...  */
}


/**
 Parses the ICMP node elements and adds the details to the filter.
 @param doc Pointer to the XML configuration document.
 @param cur_node Pointer to the icmp element (node)
 @param filter_h Handle to the filter to modify.
 */
static void
process_icmp_node (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr child_node;
	xmlNodePtr sub_child_node;
	xmlChar *  attr;
	char       fmt_str[32];
	int        hash;
	uint16_t   code;
	uint16_t   type;
	uint16_t   mask;
	int        res;
	xmlChar *  key;
	

	/* set the protocol as ICMP */
	dagdsm_filter_set_ip_protocol (filter_h, IPPROTO_ICMP);
	
	
	/* loop through the child elements */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
		
		
		hash = simple_str_hash (child_node->name);
		switch (hash)
		{
			case XMLHASH_ICMPCODE:
			case XMLHASH_ICMPTYPE:
				
				type = 0;
				code = 0;
				mask = 0xFFFF;
			
				for (sub_child_node=child_node->children; sub_child_node; sub_child_node = sub_child_node->next)
				{
					if (sub_child_node->type != XML_ELEMENT_NODE)
						continue;
					
					/* get the text contents */
					key = xmlNodeListGetString(doc, sub_child_node->xmlChildrenNode, 1);
					if ( key == NULL )
						break;
				
					
					/* get the 'hex' attribute value */		
					attr = xmlGetProp (sub_child_node, BAD_CAST"hex");
					if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
						strcpy (fmt_str, "%hd");
					else
						strcpy (fmt_str, "%hx");

					if (attr)
						xmlFree (attr);

					
					/* process the sub-components */
					if ( xmlStrEqual(sub_child_node->name, BAD_CAST"type") )
						res = sscanf ((char*)key, fmt_str, &type);
					else if ( xmlStrEqual(sub_child_node->name, BAD_CAST"code") )
						res = sscanf ((char*)key, fmt_str, &code);
					else if ( xmlStrEqual(sub_child_node->name, BAD_CAST"mask") )
						res = sscanf ((char*)key, fmt_str, &mask);
					else
					{
						xmlFree(key);
						xml_warning (EUNKNOWNELEM, sub_child_node);
						break;
					}
					
					xmlFree(key);

					
					/* sanity check */
					if ( res != 1 )
					{
						dagutil_warning ("invalid format for contents of element \'%s\' on line %d.\n", sub_child_node->name, sub_child_node->line);
						break;
					}
				}
				
				/* add the port settings to the filter */
				if ( hash == XMLHASH_ICMPCODE )
					dagdsm_filter_set_icmp_code (filter_h, (uint8_t)code, (uint8_t)mask);
				else
					dagdsm_filter_set_icmp_type (filter_h, (uint8_t)type, (uint8_t)mask);
				
				break;
			
				
			default:
				xml_warning (EUNKNOWNELEM, child_node);
				break;
				
		} /* switch (hash) */
	} /* for (...  */
}


/**
 Processes either an <ip-source> or <ip-dest> element and returns the parsed address and mask.
 @param doc  Handle the configuration to modify
 @param cur_node  Pointer to the filter element (node)
 @param[OUT] addr Pointer to an IPv4 address structure that is populated with the contents if the <addr> child element.
 @param[OUT] mask Pointer to an IPv4 address structure that is populated with the <mask> child element.
*/
static int
process_ipv4_address (xmlDocPtr doc, xmlNodePtr cur_node, struct in_addr * addr, struct in_addr * mask)
{
	xmlNodePtr child_node;
	xmlChar *  attr;
	char       fmt_str[64];
	int        res;
	uint16_t   tmp_addr[4];
	uint16_t   tmp_mask[4];
	xmlChar *  key;
	
	
	
	/* sanity checking */
	assert(addr);
	assert(mask);
	if ( addr == NULL || mask == NULL )
		return -1;
	
	
	/* clear the address and mask */
	memset (tmp_addr, 0,    sizeof(tmp_addr));
	memset (tmp_mask, 0xFF, sizeof(tmp_mask));


	
	/* loop through the child elements, which should only be <addr> or <mask> */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;

		
		/* get the 'hex' attribute value */
		attr = xmlGetProp (child_node, BAD_CAST"hex");
		if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
			strcpy (fmt_str, "%hd.%hd.%hd.%hd");
		else
			strcpy (fmt_str, "%hX.%hX.%hX.%hX");
	
		if (attr)
			xmlFree (attr);


		/* get the text contents */
		key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
		if ( key == NULL )
			break;
		
		
		/* parse the address */
		if ( xmlStrEqual(child_node->name, BAD_CAST"addr") )
			res = sscanf ((char*)key, fmt_str, &tmp_addr[0], &tmp_addr[1], &tmp_addr[2], &tmp_addr[3]);
		
		else if ( xmlStrEqual(child_node->name, BAD_CAST"mask") )
			res = sscanf ((char*)key, fmt_str, &tmp_mask[0], &tmp_mask[1], &tmp_mask[2], &tmp_mask[3]);

		else
		{
			xmlFree(key);
			xml_warning (EUNKNOWNELEM, child_node);
			continue;
		}
		
		xmlFree(key);

		
		/* check the address was parsed correctly */
		if ( res != 4 )
		{
			dagutil_warning ("failed to parse the IPv4 address on line %d, should be formated like xxx.xxx.xxx.xxx\n", child_node->line);
			return -1;
		}
	}
	
	
	
	/* copy over the temporary values and truncate */
#if defined(WIN32)
	mask->S_un.S_un_b.s_b1 = (uint8_t) (tmp_mask[0] & 0x00FF);
	mask->S_un.S_un_b.s_b2 = (uint8_t) (tmp_mask[1] & 0x00FF);
	mask->S_un.S_un_b.s_b3 = (uint8_t) (tmp_mask[2] & 0x00FF);
	mask->S_un.S_un_b.s_b4 = (uint8_t) (tmp_mask[3] & 0x00FF);

	addr->S_un.S_un_b.s_b1 = (uint8_t) (tmp_addr[0] & mask->S_un.S_un_b.s_b1);
	addr->S_un.S_un_b.s_b2 = (uint8_t) (tmp_addr[1] & mask->S_un.S_un_b.s_b2);
	addr->S_un.S_un_b.s_b3 = (uint8_t) (tmp_addr[2] & mask->S_un.S_un_b.s_b3);
	addr->S_un.S_un_b.s_b4 = (uint8_t) (tmp_addr[3] & mask->S_un.S_un_b.s_b4);

#elif defined (__linux__) || defined(__FreeBSD__) || (defined(__APPLE__) && defined(__ppc__))
	mask->s_addr = ((uint32_t)(tmp_mask[0] & 0x00FF) << 24) |
	               ((uint32_t)(tmp_mask[1] & 0x00FF) << 16) |
	               ((uint32_t)(tmp_mask[2] & 0x00FF) <<  8) |
	               ((uint32_t)(tmp_mask[3] & 0x00FF)      );
	
	addr->s_addr = ((uint32_t)(tmp_addr[0] & 0x00FF) << 24) |
	               ((uint32_t)(tmp_addr[1] & 0x00FF) << 16) |
	               ((uint32_t)(tmp_addr[2] & 0x00FF) <<  8) |
	               ((uint32_t)(tmp_addr[3] & 0x00FF)      );
	
	addr->s_addr &= mask->s_addr;
	
	addr->s_addr = htonl (addr->s_addr);
	mask->s_addr = htonl (mask->s_addr);
	
#endif

	return 0;
}


/**
 Processes an <ipv4> element.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <ipv4> element (node)
 @param filter_h Handle to the filter that is being updated.
*/
static void
process_ipv4_node (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr     child_node;
	int            hash;
	struct in_addr addr;
	struct in_addr mask;
	xmlChar *      key;
	int            ihl;

	/* set the filter as an IPv4 one */
	dagdsm_filter_set_layer3_type (filter_h, kIPv4);

	
	/* loop through the child elements */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;

		/* determine which node we are looking at */
		hash = simple_str_hash (child_node->name);
		switch (hash)
		{
			case XMLHASH_IPSOURCE:
				if ( process_ipv4_address(doc, child_node, &addr, &mask) != -1 )
					dagdsm_filter_set_ip_source (filter_h, &addr, &mask);
				break;
				
			case XMLHASH_IPDEST:
				if ( process_ipv4_address(doc, child_node, &addr, &mask) != -1 )
					dagdsm_filter_set_ip_dest (filter_h, &addr, &mask);
				break;
				
			case XMLHASH_ICMP:
				process_icmp_node (doc, child_node, filter_h);
				break;
			
			case XMLHASH_TCP:
			case XMLHASH_UDP:
				process_udp_tcp_node (doc, child_node, filter_h);
				break;

			case XMLHASH_FRAGMENT:
				dagdsm_filter_ip_fragment (filter_h, 1);
				break;
				
			case XMLHASH_IHL:
				key = xmlNodeListGetString (doc, child_node->xmlChildrenNode, 1);
				if ( key == NULL )
					break;
				ihl = atoi ((char*)key);
				xmlFree (key);
				
				dagdsm_filter_set_ip_hdr_length (filter_h, ihl);
				break;
			
			
			default:
				xml_warning (EUNKNOWNELEM, child_node);
				break;
		}
	}
}


/**
 Processes an <ethertype> element.
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <ethertype> element (node)
 @param filter_h Handle to the filter that is being updated.
*/
static void
process_ether_type (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	uint16_t   ethertype;
	xmlChar *  attr;
	int        res;
	xmlChar *  key;
	

	/* get the elements TEXT */
	key = xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1);
	if ( key == NULL )
		return;


	/* determine if hex and parse the input */
	attr = xmlGetProp (cur_node, BAD_CAST"hex");
	if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
		res = sscanf ((char*)key, "%hd", &ethertype);
	else
		res = sscanf ((char*)key, "%hx", &ethertype);
	
	if (attr)
		xmlFree (attr);


	xmlFree(key);

	if ( res == 1 )
		dagdsm_filter_set_ethertype (filter_h, ethertype, 0xFFFF);
	else
		dagutil_warning ("invalid contents of element \'ethertype\' on line %d.\n", cur_node->line);
}


/**
 Processes either a <mac-source> or <mac-dest> element.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <mac-source> or <mac-dest> element (node)
 @param filter_h  Handle to the filter that is being updated.
*/
static int
process_mac_address (xmlDocPtr doc, xmlNodePtr cur_node, uint8_t * addr, uint8_t * mask)
{
	xmlNodePtr child_node;
	xmlChar *  attr;
	char       fmt_str[64];
	int        res;
	uint16_t   tmp_addr[6];
	uint16_t   tmp_mask[6];
	uint32_t   i;
	xmlChar *  key;
	
	
	/* sanity checking */
	assert(addr);
	assert(mask);
	if ( addr == NULL || mask == NULL )
		return -1;
	
	
	/* clear the address and mask */
	memset (tmp_addr, 0,    sizeof(tmp_addr));
	memset (tmp_mask, 0xFF, sizeof(tmp_mask));


	/* loop through the child elements, which should only be <addr> or <mask> */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;

		
		/* get the elements TEXT */
		key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
		if ( key == NULL )
			continue;
		
		
		/* get the 'hex' attribute value */
		attr = xmlGetProp (child_node, BAD_CAST"hex");
		if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
			strcpy (fmt_str, "%hd:%hd:%hd:%hd:%hd:%hd");
		else
			strcpy (fmt_str, "%hX:%hX:%hX:%hX:%hX:%hX");

		if (attr)
			xmlFree (attr);

		
		/* parse the address */
		if ( xmlStrEqual(child_node->name, BAD_CAST"addr") )
			res = sscanf ((char*)key, fmt_str, &tmp_addr[0], &tmp_addr[1], &tmp_addr[2], &tmp_addr[3], &tmp_addr[4], &tmp_addr[5]);
		
		else if ( xmlStrEqual(child_node->name, BAD_CAST"mask") )
			res = sscanf ((char*)key, fmt_str, &tmp_mask[0], &tmp_mask[1], &tmp_mask[2], &tmp_mask[3], &tmp_mask[4], &tmp_mask[5]);

		else
		{
			xmlFree(key);
			xml_warning (EUNKNOWNELEM, child_node);
			continue;
		}
		

		/* check the address was parsed correctly */
		if ( res != 6 )
		{
			dagutil_warning ("failed to parse the mac address on line %d, should be formated like XX:XX:XX:XX:XX:XX.\n", child_node->line);
			return -1;
		}

		xmlFree(key);
	}
	
	
	/* copy over the temporary values and truncate */
	for (i = 0; i < 6; i++)
	{
		mask[i] = (uint8_t) (tmp_mask[i] & 0x00FF);
		addr[i] = (uint8_t) (tmp_addr[i] & mask[i]);
	}
	
	return 0;
}


/**
 Processes a <vlan-id> element.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <vlan-id> element (node)
 @param filter_h Handle to the filter that is being updated.
*/
static void
process_vlan_id (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr child_node;
	xmlChar *  attr;
	uint16_t   id;
	uint16_t   mask;
	char       fmt_str[32];
	int        res;
	xmlChar *  key;
	
	
	/* initialise */
	id = 0;
	mask = 0xFFF;
	
	
	/* loop through the child elements, which should only be <addr> or <mask> */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;

		
		/* get the elements TEXT */
		key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
		if ( key == NULL )
			continue;
		
		
		/* get the 'hex' attribute value */
		attr = xmlGetProp (child_node, BAD_CAST"hex");
		if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
			strcpy (fmt_str, "%hd");
		else
			strcpy (fmt_str, "%hX");

		if (attr)
			xmlFree (attr);

		
		/* parse the address */
		if ( xmlStrEqual(child_node->name, BAD_CAST"id") )
			res = sscanf ((char*)key, fmt_str, &id);
		
		else if ( xmlStrEqual(child_node->name, BAD_CAST"mask") )
			res = sscanf ((char*)key, fmt_str, &mask);

		else
		{
			xmlFree(key);
			xml_warning (EUNKNOWNELEM, child_node);
			continue;
		}
		
	
		/* check the id or mask was parsed correctly */
		if ( res != 1 )
		{
			dagutil_warning ("failed to parse the mask or id of the vlan-id element on line %d.\n", cur_node->line);
			return;
		}

		xmlFree(key);
	}
	
	
	/* set the vlan id */
	dagdsm_filter_set_vlan_id (filter_h, id, mask);
}


/**
 Processes an <ethernet> element.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <ethernet> element (node).
 @param filter_h Handle to the filter that is being updated.
*/
static void
process_ethernet_filter (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr     child_node;
	int            hash;
	uint8_t        addr[6];
	uint8_t        mask[6];

	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
		
		/*  */
		hash = simple_str_hash (child_node->name);
		switch (hash)
		{
			case XMLHASH_MACSOURCE:
				if ( process_mac_address(doc, child_node, addr, mask) != -1 )
					dagdsm_filter_set_mac_src_address (filter_h, addr, mask);
				break;
				
			case XMLHASH_MACDEST:
				if ( process_mac_address(doc, child_node, addr, mask) != -1 )
					dagdsm_filter_set_mac_dst_address (filter_h, addr, mask);
				break;
				
			case XMLHASH_ETHERTYPE:
				process_ether_type (doc, child_node, filter_h);
				break;
			
			case XMLHASH_VLANID:
				process_vlan_id (doc, child_node, filter_h);
				break;
			
			case XMLHASH_IPV4:
				process_ipv4_node (doc, child_node, filter_h);
				break;

			default:
				xml_warning (EUNKNOWNELEM, child_node);
		}
	}
}


/**
 Processes an <hdlc-type> element.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <hdlc-type> element (node).
 @param filter_h Handle to the filter that is being updated.
*/
static void
process_hdlc_header (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	uint32_t   hdlc_hdr;
	xmlChar *  attr;
	int        res;
	xmlChar *  key;
	
	
	/* get the elements TEXT */
	key = xmlNodeListGetString(doc, cur_node->xmlChildrenNode, 1);
	if ( key == NULL )
		return;
	
	
	/* determine if hex and parse the input */
	attr = xmlGetProp (cur_node, BAD_CAST"hex");
	if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
		res = sscanf ((char*)key, "%d", &hdlc_hdr);
	else
		res = sscanf ((char*)key, "%x", &hdlc_hdr);
	
	if (attr)
		xmlFree (attr);

	xmlFree(key);
	
	if ( res == 1 )
		dagdsm_filter_set_hdlc_header (filter_h, hdlc_hdr, 0xFFFFFFFF);
	else
		dagutil_warning ("invalid contents of element \'hdlctype\' on line %d.\n", cur_node->line);
}


/**
 Processes an <sonet> element.
 
 @param doc Pointer to the parsed XML configuration
                                         file.
 @param cur_node Pointer to the <sonet-type> element (node)
 @param filter_h  Handle to the filter that is being
                                         updated.
*/
static void
process_sonet_filter (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr child_node;
	int        hash;


	/* process the child elements */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
		
		hash = simple_str_hash (child_node->name);
		switch (hash)
		{
			case XMLHASH_HDLCTYPE:
				process_hdlc_header (doc, child_node, filter_h);
				break;
			
			case XMLHASH_IPV4:
				process_ipv4_node (doc, child_node, filter_h);
				break;

			default:
				xml_warning (EUNKNOWNELEM, child_node);
				break;
		}
	}
}


/**
 Processes a <raw> element.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <raw> element (node).
 @param filter_h Handle to the filter that is being updated.
*/
static void
process_raw_filter (xmlDocPtr doc, xmlNodePtr cur_node, DsmFilterH filter_h)
{
	xmlNodePtr  child_node;
	uint32_t    word;
	uint8_t     values[64];
	uint8_t     masks[64];
	uint32_t    value32;
	uint32_t    mask32;
	int         strlen;
	int         hash;
	xmlChar *   key;
	

	word = 0;
	memset (values, 0, 64);
	memset (masks,  0, 64);

	
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
		
		hash = simple_str_hash(child_node->name);
		if ( hash == XMLHASH_WORD )
		{
			/* check we haven't run of the end */
			if ( word >= 16 )
			{
				dagutil_warning ("too many <word> elements in raw filter on line %d\n", cur_node->line);
				break;
			}
			
			
			/* get the elements TEXT */
			key = xmlNodeListGetString(doc,child_node->xmlChildrenNode, 1);
			if ( key == NULL )
				continue;
			
			
			/* get the length of the child contents */
			strlen = xmlStrlen (key);
			if ( strlen > 32 )
			{
				dagutil_warning ("the contents of the <word> tag on line %d exceeds 32 characters,\n"
			                     "only the first 32 will be used, the rest are ignored.\n", child_node->line);
			}
			
			
			/* convert the string to a bitmasked value */
			mask32 = 0;
			value32 = 0;
			
			if ( strlen > 0 )
				while (strlen-- > 0)
				{
					mask32 >>= 1;
					value32 >>= 1;

					if ( key[strlen] == '1' )
					{
						value32 |= BIT31;
						mask32  |= BIT31;
					}
					else if ( key[strlen] == '0' )
					{
						mask32  |= BIT31;
					}

					else if ( key[strlen] != '-' )
					{
						dagutil_warning ("invalid character in the contents of <word> element on line %d\n", child_node->line);
						break;
					}
					
				}

			xmlFree(key);
			
			
			/* copy over the values and masks */
			values[(word * 4) + 0] = (uint8_t) ((value32 >> 24) & 0xFF);
			values[(word * 4) + 1] = (uint8_t) ((value32 >> 16) & 0xFF);
			values[(word * 4) + 2] = (uint8_t) ((value32 >>  8) & 0xFF);
			values[(word * 4) + 3] = (uint8_t) ((value32      ) & 0xFF);
			
			masks[(word * 4) + 0]  = (uint8_t) ((mask32 >> 24) & 0xFF);
			masks[(word * 4) + 1]  = (uint8_t) ((mask32 >> 16) & 0xFF);
			masks[(word * 4) + 2]  = (uint8_t) ((mask32 >>  8) & 0xFF);
			masks[(word * 4) + 3]  = (uint8_t) ((mask32      ) & 0xFF);

			/* move to the next word */
			word++;
		}
		
	}

	
	/* setup the raw filter */
	dagdsm_filter_set_raw_filter (filter_h, values, masks, 64);
}


/**
Processes a <filter> element. If the filter is valid a
 new component is added to the hash table that matches
 names to filter numbers.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <filter> element (node)
 @param config_h Handle to the virtual configuration to be updated.
 @param components_tbl Handle to the hash table that maps the names of the filters to virtual filter handles.
 @param filters_p Bit masked value that contains the filters that have already been processed.  This value is used to check the user hasn't defined 2 filters with the same number.

 @return -1 if the filter element was not valid, 0 if valid and an entry was added to the component table.
*/
static int
process_filter_element (xmlDocPtr doc, xmlNodePtr cur_node, DsmConfigH config_h, xmlHashTablePtr components_tbl, uint32_t * filters_p)
{
	DsmFilterH    filter_h;
	xmlNodePtr    child_node;
	xmlChar *     name = NULL;
	xmlChar *     number = NULL;
	component_t * component_p;
	xmlChar *     key;
	xmlChar *     attr;
	uint32_t      enabled;
	uint32_t      filter_num;


	/* first time through get the name and number of the filter */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
	
		if ( xmlStrEqual(child_node->name, BAD_CAST"name") )
			name = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);

		else if ( xmlStrEqual(child_node->name, BAD_CAST"number") )
			number = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
	}
	
	
	/* check the name and number where specified */
	if ( name == NULL || number == NULL )
	{
		dagutil_warning ("missing <name> or <number> child elements for filter on line %d, filter ignored\n", cur_node->line);
		return -1;
	}
	
	
	/* convert the number and verify that the filter hasn't already been defined */
	filter_num = atoi ((char*)number);
	if ( (filter_num < 0) || (filter_num > 6) )
	{
		dagutil_warning ("invalid filter number for filter on line %d, filter ignored\n", cur_node->line);
		return -1;
	}
	if ( ((*filters_p >> filter_num) & 0x01) == 0x01 )
	{
		dagutil_warning ("the number of the filter on line %d has already been used, filter ignored\n", cur_node->line);
		return -1;
	}
	*filters_p |= (1 << filter_num);


	/* check if the filter should be disabled */
	attr = xmlGetProp (cur_node, BAD_CAST"enabled");
	if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"true") )
		enabled = 1;
	else
		enabled = 0;
	
	if (attr)
		xmlFree (attr);


	/* get a handle to the filter */
	filter_h = dagdsm_get_filter (config_h, filter_num);
	if ( filter_h == NULL )
	{
		xmlFree (name);
		dagutil_warning ("failed to get a handle to filter number %d\n", filter_num);
		return -1;
	}
	
	/* clear the filter back to a raw state */
	dagdsm_filter_clear (filter_h);


	/* add to the component hash table */
	component_p = (component_t*) dagutil_malloc (sizeof(component_t));
	component_p->type = kFilter;
	component_p->data.filter_num = filter_num;
	
	if ( xmlHashAddEntry(components_tbl, name, component_p) == -1 )
	{
		dagutil_warning ("failed to add the filter named \'%s\' on line %d, this is usually caused by a duplicate name.\n", name, cur_node->line);
		xmlFree (name);
		dagutil_free (component_p);
		return -1;
	}
	xmlFree (name);


	/* find the layer2 type (Ethernet, Sonet, Raw) */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;

		switch ( simple_str_hash(child_node->name) )
		{
			case XMLHASH_NAME:
			case XMLHASH_NUMBER:
				continue;

			case XMLHASH_EARLYTERM:
				key = xmlNodeListGetString (doc, child_node->xmlChildrenNode, 1);
				if ( key )
				{
					dagdsm_filter_set_early_term_depth (filter_h, atoi((char*)key));
					xmlFree (key);
				}
				break;
			
			case XMLHASH_RAW:
				dagdsm_filter_set_raw_mode (filter_h, 1);
				process_raw_filter (doc, child_node, filter_h);
				break;
				
			case XMLHASH_ETHERNET_VLAN:
				dagdsm_filter_set_raw_mode (filter_h, 0);
				dagdsm_filter_enable_vlan (filter_h, 1);
			case XMLHASH_ETHERNET:
				if ( dagdsm_is_ethernet(config_h) != 1 )
					dagutil_warning ("current card configuration doesn't support ethernet frames, etherent specific elements will be ignored.\n");
				dagdsm_filter_set_raw_mode (filter_h, 0);
				process_ethernet_filter (doc, child_node, filter_h);
				break;
			
			case XMLHASH_SONET:
				if ( dagdsm_is_sonet(config_h) != 1 )
					dagutil_warning ("current card configuration doesn't support Sonet/PoS packets, Sonet/PoS specific elements will be ignored.\n");
				dagdsm_filter_set_raw_mode (filter_h, 0);
				process_sonet_filter (doc, child_node, filter_h);
				break;
			
			default:
				xml_warning (EUNKNOWNELEM, child_node);
		}
	}

	/* enable the filter */
	dagdsm_filter_enable (filter_h, enabled);
	
	
	return 0;
}


/**
 Process an interface element, all this does is sanity check the interface number and name and then adds a component to the hash table.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <interface> element (node).
 @param components_tbl Handle to the hash table that maps the names of the interfaces to the actual interface numbers.
 @param num_ifaces The number of interfaces present on the card.
*/
static void
process_interface_element (xmlDocPtr doc, xmlNodePtr cur_node, xmlHashTablePtr components_tbl, const int num_ifaces)
{
	xmlNodePtr    child_node;
	xmlChar *     name = NULL;
	int			  number = -1;
	component_t * component_p;
	xmlChar *     key;

	/* first time through get the name and number of the filter */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
	
		switch ( simple_str_hash(child_node->name) )
		{
			case XMLHASH_NAME:
				name = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
				break;
			
			case XMLHASH_NUMBER:
				key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
				if (key)
				{
					number = atoi((char*)key);
					xmlFree (key);
				}
				break;

			default:
				xml_warning (EUNKNOWNELEM, child_node);
				break;
		}
	}


	/* check the name and number where specified */
	if ( name == NULL || number < 0 )
	{
		if (name)
			xmlFree (name);
		dagutil_warning ("invalid interface name or number on line %d, interface ignored.\n", cur_node->line);
		return;
	}
	
	/* check if the interface is available on the card */
	if ( number >= num_ifaces )
	{
		dagutil_error ("The number of the interface element on line %d, exceeds the number of interfaces present on the DAG card.\n", cur_node->line);
		return;
	}
	
	
	/* add to the component hash table */
	component_p = (component_t*) dagutil_malloc (sizeof(component_t));
	component_p->type = kInterface;
	component_p->data.iface_num = number;
	
	if ( xmlHashAddEntry(components_tbl, name, component_p) == -1 )
	{
		dagutil_warning ("failed to add the interface named \'%s\', this is usually caused by a duplicate name.\n", name);
		dagutil_free (component_p);
	}
	
	xmlFree (name);
}


/**
 Process an <steering> element, all this does is sanity check the steering algorithm and name, then adds a component to the hash table.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <steering> element (node)
 @param components_tbl Handle to the hash table that maps the names of the steering algorithm to the actual HLB number.
*/
static void
process_steering_element (xmlDocPtr doc, xmlNodePtr cur_node, xmlHashTablePtr components_tbl)
{
	xmlNodePtr    child_node;
	xmlChar *     name = NULL;
	uint32_t      number = -1;
	component_t * component_p;
	xmlChar *     key;
	uint32_t	  hash_value;

	/* add to the component hash table */
	component_p = (component_t*) dagutil_malloc (sizeof(component_t));


	/* first time through get the name and number of the filter */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
	
		switch ( simple_str_hash(child_node->name) )
		{
			case XMLHASH_NAME:
				name = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
				break;
			
			case XMLHASH_ALGORITHM:
				key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
				if (key)
				{
					if ( xmlStrEqual(key, BAD_CAST"crc32") )
					{
						if(g_dsm_version!=DSM_VERSION_0)
							dagutil_warning("crc32  is not available in this version of dsm, element on line %d will not take effect\n", cur_node->line);
						component_p->type = kSteering;
						number = 0;
						component_p->data.hlb_num = number;
					}
					else if ( xmlStrEqual(key, BAD_CAST"parity") )
					{
						if(g_dsm_version!=DSM_VERSION_0)
							dagutil_warning("parity is not available in this version of dsm, element on line %d will not take effect\n", cur_node->line);
						component_p->type = kSteering;
						number = 1;
						component_p->data.hlb_num = number;
					}
					else if ( xmlStrEqual(key, BAD_CAST"crc32-hlb"))
					{
						if(g_dsm_version!=DSM_VERSION_1)
							dagutil_warning("crc32-hlb is not available in this version of dsm, this element will not take effect\n");
						component_p->type = kSteeringHash;
					}
					else
					{
						xmlFree (key);
						dagutil_warning ("invalid algorithm for steering on line %d, must be either \'crc32\' or \'parity\'.\n", child_node->line);
						return;
					}
					
					xmlFree (key);
				}
				break;
			case XMLHASH_HASH_VALUE:
				key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
				if(key)
				{
					hash_value = strtoul((char*)key,NULL,10);
					component_p->data.hlb_hash_value = hash_value;
				}
				break;
			default:
				xml_warning (EUNKNOWNELEM, child_node);
				continue;
		}
	}

	/* check the name and number where specified */
	if ( name == NULL )
	{
		dagutil_warning ("invalid steering name or algorithum on line %d, steering ignored.\n", cur_node->line);
		dagutil_free(component_p);
		return;
	}
	
	if ( xmlHashAddEntry(components_tbl, name, component_p) == -1 )
	{
		dagutil_warning ("failed to add the interface named \'%s\', this is usually caused by a duplicate name.\n", name);
		dagutil_free (component_p);
	}
	xmlFree (name);
}


/**
 Process an <partial> element, by looping through the child elements and adding the interface setting to the configuration.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <partial> element (node)
 @param config_h Handle to the virtual configuration that is updated with then new partial expression.
 @param partial_tbl Hash table that matches the name of a partial expression with its handle.
 @param components_tbl Handle to the hash table that maps the names of the components to actual implementation details, used to create the partial expression.
*/
static void
process_partial_element (xmlDocPtr doc, xmlNodePtr cur_node, DsmConfigH config_h, xmlHashTablePtr partial_tbl, xmlHashTablePtr components_tbl)
{
	xmlNodePtr     child_node;
	xmlChar *      name = NULL;
	xmlChar *      attr;
	DsmPartialExpH partial_h;
	component_t *  component_p;
	uint32_t       invert;
	xmlChar *      key;


	/* first time through get the name of the partial expression */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
	
		if ( xmlStrEqual(child_node->name, BAD_CAST"name") )
		{
			name = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
			break;
		}
	}
	
	if ( name == NULL )
	{
		dagutil_warning ("partial expression element on line %d doesn't have a required <name> element.\n", cur_node->line);
		return;
	}
	
	
	
	/* check if a partial expression with the same name already exists */
	if ( xmlHashLookup(partial_tbl, name) != NULL )
	{
		dagutil_warning ("failed to add the partial expression \'%s\' because the name is not unique.\n", name);
		xmlFree(name);
		return;
	}
	
	
	/* create a blank partial expression */
	partial_h = dagdsm_create_partial_expr (config_h);
	if ( partial_h == NULL )
	{
		xmlFree (name);
		dagutil_warning ("failed to create partial expression \'%s\', errorcode %d.\n", name, dagdsm_get_last_error());
		return;
	}
	
	
	
	/* add the components to the partial expression */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
	
		if ( xmlStrEqual(child_node->name, BAD_CAST"partial-component") )
		{

			/* get the TEXT of the partial component */
			key = xmlNodeListGetString (doc, child_node->xmlChildrenNode, 1);
			if ( key == NULL )
				continue;


			/* find the component that matches the contents */
			component_p = (component_t*) xmlHashLookup (components_tbl, key);
			if ( component_p == NULL )
			{
				dagutil_warning ("failed to find the partial component \'%s\' on line %d\n", key, child_node->line);
				xmlFree (key);
				continue;
			}

			xmlFree (key);

			/* determine if the value should be inverted or not */
			attr = xmlGetProp (child_node, BAD_CAST"invert");
			if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
				invert = 0;
			else
				invert = 1;
			
			if (attr)
				xmlFree(attr);

			/* add to the partial expression */
			switch (component_p->type)
			{
				case kFilter:
					dagdsm_expr_set_filter (partial_h, component_p->data.filter_num, invert);
					break;
				
				case kInterface:
					dagdsm_expr_set_interface (partial_h, component_p->data.iface_num, invert);
					break;
					
				case kSteering:
					dagdsm_expr_set_hlb (partial_h, component_p->data.hlb_num, invert);
					break;
				case kSteeringHash:
					dagdsm_expr_set_hlb_hash(partial_h, component_p->data.hlb_hash_value, invert);
					break;
				default:
					assert(0);
					break;
			}
		}
	}


	/* add the paritial expression to the table of partial expressions */
	if ( xmlHashAddEntry (partial_tbl, name, partial_h) == -1 )
		dagutil_warning ("failed to add the partial expression named \'%s\'.\n", name);

	xmlFree (name);
}


/**
 Processes a <stream> element. Gets a handle to the stream output expression from the virtual configuration, then iterates over the child partial expressions to add to the expression parameters.
 
 @param doc Pointer to the parsed XML configuration file.
 @param cur_node Pointer to the <stream> element (node).
 @param config_h Handle to the virtual configuration.
 @param partial_tbl Hash table that maps the names of the partial expressions with their actual handles.
*/
static void
process_stream_element (xmlDocPtr doc, xmlNodePtr cur_node, DsmConfigH config_h, xmlHashTablePtr partial_tbl, int num_streams)
{
	xmlNodePtr     child_node;
	int       rx_stream = -1;
	xmlChar *      key;
	DsmOutputExpH  output_h;
	DsmPartialExpH partial_h;
	uint32_t       invert;
	xmlChar *      attr;


	/* first time through get the receive stream number */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
	
		if ( xmlStrEqual(child_node->name, BAD_CAST"number") )
		{
			key = xmlNodeListGetString(doc, child_node->xmlChildrenNode, 1);
			if ( key == NULL )
			{
				dagutil_warning ("invalid stream number on line %d.\n", child_node->line);
				return;
			}
			
			rx_stream = atoi ((char*)key);
			xmlFree (key);
			
			break;
		}
	}

	/* sanity check the rx_stream */
	if ( (rx_stream < 0) || (rx_stream & 1) || (rx_stream >= (num_streams*2)) )
	{
		dagutil_warning ("invalid stream number %d on line %d (Note: streams should have even numbers).\n", rx_stream, cur_node->line);
		return;
	}
	
	
	/* get a handle to the output expression */
	output_h = dagdsm_get_output_expr (config_h, rx_stream);
	if ( output_h == NULL )
	{
		dagutil_warning ("failed to get the output expression for receive stream %d, errorcode %d.\n", rx_stream, dagdsm_get_last_error());
		return;
	}


	/* add the partial expressions to the output expression */
	for (child_node = cur_node->children; child_node; child_node = child_node->next)
	{
		if (child_node->type != XML_ELEMENT_NODE)
			continue;
		
		if ( xmlStrEqual(child_node->name, BAD_CAST"stream-component") )
		{
			
			/* get the name of the partial expression */
			key = xmlNodeListGetString (doc, child_node->xmlChildrenNode, 1);
			if ( key == NULL )
				continue;
			
			
			/* determine if the value should be inverted or not */
			attr = xmlGetProp (child_node, BAD_CAST"invert");
			if ( attr == NULL || xmlStrEqual(attr, BAD_CAST"false") )
				invert = 0;
			else
				invert = 1;
			
			if (attr)
				xmlFree(attr);
			
			
			/* find the partial component in the hash table */
			partial_h = xmlHashLookup (partial_tbl, key);
			if ( partial_h == NULL )
			{
				dagutil_warning ("failed to find the partial expression with the name \'%s\' for the stream component on line %d.\n", key, child_node->line);
				xmlFree (key);
				continue;
			}
			
			xmlFree (key);

			/* add the partial expression to the output expression */
			dagdsm_expr_add_partial_expr (output_h, partial_h, invert);
		}
	}
}


/**
 Callback function to deallocate hash table entries created when parsing the xml rules document.
 
 @param payload The memory allocated.
 @param name The name used for lookups.
*/
void
dealloc_components(void * payload, xmlChar * name)
{
	assert(payload);

	if (payload)
		dagutil_free (payload);
}


/**
 Parses the given XML configuration file and loads all the details into the given virtual configuration.
 
 @param config_h Handle to the virtual configuration to load the details into.
 @param filename The name of the file to parse.
*/
static int
parse_rules_file (int dagfd, DsmConfigH config_h, const char * filename)
{
	xmlDocPtr       doc;
	xmlNodePtr      root_element;
	xmlNodePtr      cur_node;
	xmlChar *       attr;
	uint32_t        filters = 0;
	int             retval = -1;
	int             num_ifaces;
	int             num_streams;
	
	/* hash tables that match the names of filters/partial expressions to DSM handles */
	xmlHashTablePtr components_tbl;
	xmlHashTablePtr partial_tbl;
	
	
	
	/* sanity checking */
	if ( filename == NULL || filename[0] == '\0' )
	{
		dagutil_error ("Invalid input filename\n");
		return -1;
	}
	
	
	/* get the number of interfaces present on the card */
	num_ifaces = dagdsm_get_port_count (dagfd);
	if ( num_ifaces == -1 )
	{
		dagutil_error ("Failed to get the number of interfaces on the card (errorcode %d)\n", dagdsm_get_last_error());
		return -1;
	}
	
	num_streams = dagdsm_get_filter_stream_count(dagfd);


	/*
	 * this initialize the library and check potential ABI mismatches
	 * between the version it was compiled for and the actual shared
	 * library used.
	 */
	LIBXML_TEST_VERSION

	
	/* parse the file */
	doc = xmlReadFile (filename, NULL, 0);
	if ( doc == NULL )
	{
		dagutil_error ("Failed to parse %s\n", filename);
		return -1;
	}


	/* get the root element and confirm it is 'dsm-rules' */
	root_element = xmlDocGetRootElement(doc);
	if ( root_element == NULL )
	{
		dagutil_error ("Failed to get the document root element\n");
		goto clean_up;
	}

	
	/* validate the root element and the version */
	if ( !xmlStrEqual(root_element->name, BAD_CAST"dsm-config") )
	{
		dagutil_error ("The document root element is not <dsm-config>.\n");
		goto clean_up;
	}
	
	/* check the version */
	attr = xmlGetProp (root_element, BAD_CAST"version");
	if ( attr == NULL || !xmlStrEqual(attr, BAD_CAST"1.0") )
	{
		if (attr)
			xmlFree (attr);
		dagutil_error ("Invalid or missing version attribute in the <dsm-config> root element.\n");
		goto clean_up;
	}
	
	dagutil_verbose ("Starting parsing the configuration file.\n");
	
	xmlFree (attr);


	/* create the hash tables for both the filters and partial epxressions */
	components_tbl = xmlHashCreate (64);
	partial_tbl    = xmlHashCreate (64);


	/* loop through the child nodes (filter, interface, steering, partial and stream) */
	
	/* the filter, interface and steering need to be processed first */
	for (cur_node=root_element->children; cur_node; cur_node = cur_node->next)
	{
		if (cur_node->type != XML_ELEMENT_NODE)
			continue;

		switch ( simple_str_hash(cur_node->name) )
		{
			case XMLHASH_FILTER:
				process_filter_element(doc, cur_node, config_h, components_tbl, &filters);
				break;

			case XMLHASH_INTERFACE:
				process_interface_element (doc, cur_node, components_tbl, num_ifaces);
				break;

			case XMLHASH_STEERING:
				process_steering_element (doc, cur_node, components_tbl);
				break;

			default:
				continue;
		}
	}

	/* next the partial expressions need to be parsed */
	for (cur_node = root_element->children; cur_node; cur_node = cur_node->next)
	{
		if (cur_node->type != XML_ELEMENT_NODE)
			continue;

		switch ( simple_str_hash(cur_node->name) )
		{
			case XMLHASH_PARTIAL:
				process_partial_element (doc, cur_node, config_h, partial_tbl, components_tbl);
				break;
			
			default:
				continue;
		}
	}
	
	
	/* finally the output expressions */
	for (cur_node=root_element->children; cur_node; cur_node = cur_node->next)
	{
		if (cur_node->type != XML_ELEMENT_NODE)
			continue;

		switch ( simple_str_hash(cur_node->name) )
		{
			case XMLHASH_STREAM:
				process_stream_element (doc, cur_node, config_h, partial_tbl, num_streams);
				break;
			
			default:
				continue;
		}
	}
	
	
	/* clean-up the hash tables */
	xmlHashFree (components_tbl, dealloc_components);
	xmlHashFree (partial_tbl, NULL);
	

	dagutil_verbose ("Finished parsing the configuration file.\n");

	
	retval = 0;

clean_up:
	/* clean up */
	xmlFreeDoc (doc);
	xmlCleanupParser();
	
	return retval;
}


/**
	Accepts an ERF record file that should contain the records
	processed by the DSM module. The records are validated against
	the virtual configuration, the following types of invalid
	packets are checked for:
		- Packet received the should have been dropped
		- Invalid HLB value (in the color field)
		- Invalid color field (HLB, filter or stream bits) 
		- Invalid stream
 
 @param config_h Handle for the virtual configuration to validate against.
 @param filename The name of the ERF file to validate.
 @param erf_stream The stream number that the ERF records arrived on.  Set this value to -1 if you want to use the bits in the color field to define the stream.
 */
static void
validate_erf_file (DsmConfigH config_h, const char * filename, int const erf_stream)
{
	FILE *       file;
	dag_record_t drec;
	uint32_t     rlen;
	uint16_t     color;
	int          exp_stream;
	uint32_t     hlb0_hits, hlb1_hits;
	uint32_t     lookup;
	uint32_t     count;
	uint32_t     failures;
	int          num_streams;
	uint8_t      rec_buf[2048];
	uint32_t     stream;
	uint16_t     iface;
	
	
	/* open the ERF file to parse */
	file = fopen(filename, "rb");
	if (file == NULL)
	{
		dagutil_error ("failed to open the ERF file to validate.\n");
		return;
	}
	
	
	/* get the number of possible receive streams */
	num_streams = dagdsm_get_output_expr_count(config_h);
	if ( num_streams == -1 )
	{
		dagutil_error ("failed to get the number of possible receive streams, errorcode %d.\n", dagdsm_get_last_error());
		return;
	}

	dagutil_verbose ("Validating ERF record file ...\n");
	count = 0;
	failures = 0;
	hlb0_hits = 0;
	hlb1_hits = 0;
	
	
	/* loop through all the records in the file */
	while ( fread(&drec, 1, 16, file) == 16 )
	{
		rlen = ntohs (drec.rlen);
		fseek (file, -16, SEEK_CUR);
		
		dagutil_verbose_level (2, "\n\n");
		dagutil_verbose_level (2, "type=%02X iface=%d color=%04X rlen=%d\n", drec.type, drec.flags.iface, ntohs(drec.lctr), rlen);

		/* read the packet body */
		if ( rlen > 2048 )
		{
			dagutil_warning ("ERF record bigger than 2048 bytes ignored.\n");
			fseek (file, rlen, SEEK_CUR);
			continue;
		}
		
		if ( fread(rec_buf, 1, rlen, file) != rlen )
		{
			dagutil_panic ("failed to read the complete ERF record (rlen:%d)\n", rlen);
			return;
		}
	
		
		/* check the stream in the color field of the packet and determine if its matches the specified stream */
		if ( erf_stream >= 0 )
		{
			if ( (((drec.lctr >> 8) & 0x3F) % num_streams) != erf_stream )
			{
				dagutil_verbose_level (2, "Error: Stream field in the color doesn't match the -s argument.\n");
				failures++;
			}
			stream = erf_stream;
		}
		else
		{
			stream = (((drec.lctr >> 8) & 0x3F) % num_streams);
		}
		
		
		/* compute how the packet should have been filtered and hence determine if the colour field is correct */
		exp_stream = dagdsm_compute_packet(config_h, rec_buf, (uint16_t)rlen, &lookup);
		if (exp_stream != stream)
		{
			dagutil_verbose_level (2, "Error: Received stream doesn't match the expected stream (expected: %d, actual: %d).\n", exp_stream, stream);
			failures++;
		}
		
		
		/* check if the lookup value matches the colour */
		iface = drec.flags.iface;
		color = ntohs (drec.lctr);
		color = ((color >> 6) & 0x00FF) | ((iface << 8) & 0x0300) | ((color >> 4) & 0x0C00);
		
		if ( (lookup & 0x0FFF) != color )
		{
			dagutil_verbose_level (2, "Error: Packet lookup and colour don't match (expected: %04X, actual: %04X).\n", lookup, color);
			failures++;
		}
		

		/* total the expected hlb hits */
		if ( lookup & BIT10 )
			hlb0_hits++;
		
		if ( lookup & BIT11 )
			hlb1_hits++;
		
		/* total the expected filter hits */
		count++;
		if ( count % 64 )
			dagutil_verbose ("compares %8d, failures %d\r", count, failures);
	}

	dagutil_verbose ("\n");
	dagutil_verbose ("\n");
	dagutil_verbose ("----------------------------------------------------------------------\n");
	dagutil_verbose ("Packets:         %d\n", count);
	dagutil_verbose ("Failures:        %d\n", failures);
	dagutil_verbose ("HLB CRC hits:    %d\n", hlb0_hits);
	dagutil_verbose ("HLB Parity hits: %d\n", hlb1_hits);
}
