/*
 * Copyright (c) 2004-2007 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: filter_loader.c 6998 2007-06-18 04:22:11Z vladimir $
 */


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

/* CVS header. */
static const char* const kMainCvsHeader __attribute__ ((unused)) = "$Id: filter_loader.c 6998 2007-06-18 04:22:11Z vladimir $";
static const char* const kRevisionString = "$Revision: 6998 $";

/* If 1, the index of the entry is written to the snaplength instead of the actual snaplength. */
#define DEBUG_SNAPLENGTH 0

/* Unit variables. */
static char uDeviceName[DAGNAME_BUFSIZE] = "";
static char uDeviceNameBuf[DAGNAME_BUFSIZE] = "dag0";

#define FILENAME_BYTES 256
static char uInfileBuf[FILENAME_BYTES] = "";

#define SMALLBUF_BYTES 64
static char uLinktypeBuf[SMALLBUF_BYTES] = "";
static char uMappingBuf[SMALLBUF_BYTES] = "";


const char* gDeviceName = NULL;
const char* gProgramName = NULL;
const char* gInfileName = NULL;

int gDagfd = -1;
FILE* gInfile = NULL;

/* Internal routines. */
static void print_version(void);
static void print_usage(ClArgPtr clarg);
static linktype_t get_linktype(const char* argument);
static mapping_t get_mapping(const char* argument);
static void configure(int argc, const char* argv[], filter_param_t* param);



int
main(int argc, const char* argv[])
{
    filter_param_t* param;
    filter_list_t* filterlist_array;
    int errors = 0;

    /* initialize the filter loader library */
    if (dagpf_init(&filterlist_array, &param)) {
        dagutil_panic("Could not initialize data structures");
    }

	/* Read command-line arguments. */
    /* It fills up 'param' and sets gInfile and gDagfd */
	configure(argc, argv, param);

	assert(gInfile);

    /* check for coprocessor and its image */
    errors += dagpf_check_copro(gDagfd, param);

    /* process the input file */
	errors += dagpf_read_filters(gInfile, filterlist_array, param);

	/* Program the DAG card. */
	errors += dagpf_load_filters(gDagfd, filterlist_array, param);
	dagutil_verbose("finished loading filters.\n");

    /* free the allocated resources */
    dagpf_cleanup(filterlist_array, param);

    fclose(gInfile);
    dag_close(gDagfd);
    if (errors) {
        return EXIT_FAILURE;
    }
	return EXIT_SUCCESS;

}


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


static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("filter_loader - load filters into a Coprocessor-equipped Endace DAG card.\n");
	printf("Usage: filter_loader [options] -d <device> -l <linktype> -m <mapping> \n");
	dagclarg_display_usage(clarg, stdout);
}


static linktype_t
get_linktype(const char* argument)
{
	if (0 == strcmp(argument, "ethernet"))
	{
		return DF_LINK_ETHERNET;
	}
	else if (0 == strcmp(argument, "pos4"))
	{
		return DF_LINK_POS4;
	}
	else if (0 == strcmp(argument, "pos4chdlc"))
	{
		return DF_LINK_POS4_CHDLC;
	}
	else if (0 == strcmp(argument, "pos4ppp"))
	{
		return DF_LINK_POS4_PPP;
	}
	else if (0 == strcmp(argument, "pos6"))
	{
		return DF_LINK_POS6;
	}
	
	assert(0);
	return DF_LINK_UNKNOWN;
}


static mapping_t
get_mapping(const char* argument)
{
	if (0 == strcmp(argument, "rxerror"))
	{
		return DF_MAPPING_RXERROR;
	}
	else if ((0 == strcmp(argument, "lcntr")) || (0 == strcmp(argument, "colour")) || (0 == strcmp(argument, "color")))
	{
		return DF_MAPPING_COLOURED_ERF;
	}
	else if ((0 == strcmp(argument, "colour0")) || (0 == strcmp(argument, "color0")))
	{
		return DF_MAPPING_COLOURED_ERF_STREAM0;
	}
	else if (0 == strcmp(argument, "padoffset"))
	{
		return DF_MAPPING_PADOFFSET;
	}
	else if (0 == strcmp(argument, "padoffset0"))
	{
		return DF_MAPPING_PADOFFSET_STREAM0;
	}
	else if (0 == strcmp(argument, "none"))
	{
		return DF_MAPPING_NONE;
	}
	else if (0 == strcmp(argument, "entire"))
	{
		return DF_MAPPING_ENTIRE_REGISTER;
	}
	else if (0 == strcmp(argument, "hdlcheader"))
	{
		return DF_MAPPING_HDLC_HEADER;
	}
	else if (0 == strcmp(argument, "hdlcheader0"))
	{
		return DF_MAPPING_HDLC_HEADER_STREAM0;
	}
	
	assert(0);
	return DF_MAPPING_UNKNOWN;
}


/* Commandline argument codes. */
enum
{
	CLA_DEVICE,
	CLA_DROP,
	CLA_HELP,
	CLA_INITIALISE,
	CLA_INPUT_FILE,
	CLA_INTERFACE_COUNT,
	CLA_INTERFACE,
	CLA_LINKTYPE,
	CLA_NO_DROP,
	CLA_OUTPUT_MAPPING,
	CLA_PORT,
	CLA_RULESET_COUNT,
	CLA_SNAPLENGTH,
	CLA_VERIFY,
	CLA_VERBOSE,
	CLA_VERSION
};


static void
configure(int argc, const char* argv[], filter_param_t* param)
{
	ClArgPtr clarg = NULL;
	FILE* errorfile = NULL;
	unsigned int errorflag = 0;
	unsigned int index;
	int dagstream = 0;
	int snaplength;
	int iface_count;
	int ruleset_count;
	int iface;
	uint8_t port;
	int code;
	int result;
	int argindex;

	gInfile = stdin;
	gProgramName = "filter_loader";
	dagutil_set_progname("filter_loader");

	/* Set default device. */
	if (-1 == dag_parse_name(uDeviceNameBuf, uDeviceName, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_error("could not set default device name.\n");
		exit(EXIT_FAILURE);
	}

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, argv);
	dagclarg_add_string(clarg, "Name of the DAG device to program.  (Default is 'dag0'.)", "--device", 'd', "device", uDeviceNameBuf, DAGNAME_BUFSIZE, CLA_DEVICE);
	
	dagclarg_add(clarg, "This page.", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	
	dagclarg_add_string(clarg, "Name of a text file containing one filter per line.", "--infile", 'i', "filename", uInfileBuf, FILENAME_BYTES, CLA_INPUT_FILE);
	dagclarg_add_description(clarg, CLA_INPUT_FILE, "Filters will be read from standard input if this option is not present.");
	
	dagclarg_add(clarg, "Reset the coprocessor (as opposed to hot-swapping the rulesets).", "--initialise", 0, CLA_INITIALISE);
	dagclarg_add_long_option(clarg, CLA_INITIALISE, "--initialize");

	dagclarg_add_int(clarg, "Number of interfaces in the Coprocessor configuration.  (Only valid with the --initialise option.)", "--init-ifaces", 0, "int", &iface_count, CLA_INTERFACE_COUNT);
	dagclarg_add_long_option(clarg, CLA_INTERFACE_COUNT, "--init-ports");

	dagclarg_add_int(clarg, "Number of rulesets (1 or 2) in the coprocessor configuration.  (Only valid with the --initialise option.)", "--init-rulesets", 0, "int", &ruleset_count, CLA_RULESET_COUNT);

	dagclarg_add_int(clarg, "Which interface is to be filtered.  (Default is interface 0.)", "--iface", 0, "0/1", &iface, CLA_INTERFACE);
	dagclarg_add_char(clarg, "Synonym for --iface.  (Port A is interface 0, port B is interface 1.)", "--port", 'p', "a/b", &port, CLA_PORT);

	dagclarg_add_string(clarg, "Link type: valid values are 'ethernet', 'pos4chdlc', and 'pos4ppp'.", "--link", 'l', "linktype", uLinktypeBuf, SMALLBUF_BYTES, CLA_LINKTYPE);
	dagclarg_add(clarg, "Drop packets routed to a full receive stream.  (Only honoured with the --initialise option.)", "--drop", 0, CLA_DROP);
	dagclarg_add(clarg, "Don't drop packets routed to a full receive stream.  (Only honoured with the --initialise option.)", "--no-drop", 0, CLA_NO_DROP);
	dagclarg_add_description(clarg, CLA_NO_DROP, "This may cause a fast-reading process to block waiting for a slow-reading process.");
	
	dagclarg_add_string(clarg, "Where to map the packet classification.", "--mapping", 'm', "mapping", uMappingBuf, SMALLBUF_BYTES, CLA_OUTPUT_MAPPING);
	dagclarg_add_description(clarg, CLA_OUTPUT_MAPPING, "Valid values are 'rxerror', 'color' (equivalent to 'lcntr' and 'colour'), 'hdlcheader' (PoS4 links only) and 'padoffset' (Ethernet links only).");
	dagclarg_add_description(clarg, CLA_OUTPUT_MAPPING, "To force all packets to receive stream 0 use 'color0' (or 'colour0'), 'hdlcheader0' (PoS4 links only) or 'padoffset0' (Ethernet links only).");
	dagclarg_add_description(clarg, CLA_OUTPUT_MAPPING, "For testing purposes only, 'entire' will put the entire results register into the packet.");
	
	dagclarg_add_int(clarg, "Snaplength added to filters that do not have an explicit snaplength.", "--snap", 's', "bytes", &snaplength, CLA_SNAPLENGTH);
	dagclarg_add(clarg, "Verify (Read back) rules after loading. (Default is no verification.)", "--verify", 'r', CLA_VERIFY);
	dagclarg_add(clarg, "Increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "Display version information.", "--version", 'V', CLA_VERSION);

	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
		case CLA_DEVICE:
			if (-1 == dag_parse_name(uDeviceNameBuf, uDeviceName, DAGNAME_BUFSIZE, &dagstream))
			{
				dagutil_error("failed to set device name to '%s'.\n", uDeviceNameBuf);
				errorflag++;
			}
			break;

		case CLA_DROP:
			param->drop = true;
			break;

		case CLA_NO_DROP:
			param->drop = false;
			break;

		case CLA_HELP:
			print_usage(clarg);
			exit(EXIT_SUCCESS);
			break;

		case CLA_INITIALISE:
			param->initialise = true;
			break;

		case CLA_INPUT_FILE:
			gInfileName = (const char*) uInfileBuf;
			break;

		case CLA_INTERFACE:
			param->_port = iface;
			break;

		case CLA_PORT:
			param->_port = (int) (tolower(port) - 'a');
			break;

		case CLA_INTERFACE_COUNT:
			param->init_interfaces = iface_count;
			break;

		case CLA_LINKTYPE:
			param->linktype = get_linktype(uLinktypeBuf);
			break;

		case CLA_OUTPUT_MAPPING:
			param->mapping = get_mapping(uMappingBuf);
			break;

		case CLA_RULESET_COUNT:
			param->init_rulesets = ruleset_count;
			break;

		case CLA_SNAPLENGTH:
			param->snaplength = snaplength;
			break;

		case CLA_VERIFY:
			param->verify = true;
			break;

		case CLA_VERBOSE:
			dagutil_inc_verbosity();
			errorfile = stderr;
			break;

		case CLA_VERSION:
			print_version();
			exit(EXIT_SUCCESS);
			break;

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

		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	if (-1 == result)
	{
		/* Error occurred. */
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]); 
		}
		dagclarg_display_usage(clarg, stderr);
		exit(EXIT_FAILURE);
	}
	
    /* Display unprocessed arguments if verbose flag was given. */
	argv = (const char**) dagclarg_get_unprocessed_args(clarg, &argc);
	if ((NULL != argv) && (0 < argc) && (0 < dagutil_get_verbosity()))
	{
		for (index = 0; index < argc; index++)
		{
			dagutil_verbose("unprocessed argument: '%s'\n", argv[index]);
		}
	}

	gDeviceName = (const char*) uDeviceName;

	if (gInfileName)
	{
		dagutil_verbose("reading filters from '%s'.\n", gInfileName);
		
		gInfile = fopen(gInfileName, "r");
		if (gInfile == NULL)
		{
			dagutil_error("could not open file '%s' for reading filters: %s\n",
                          gInfileName, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}
	else
	{
		dagutil_verbose("reading filters from standard input.\n");
	}

	/* Check that the link type has been set. */
	if (DF_LINK_UNKNOWN == param->linktype)
	{
		dagutil_error("no link type was specified.\n");
		errorflag++;
	}

	/* Check that the output mapping has been set. */
	if (DF_MAPPING_UNKNOWN == param->mapping)
	{
		dagutil_error("no mapping was specified.\n");
		errorflag++;
	}
	
	/* The padoffset mapping can only be used if the link is Ethernet. */
	if (((DF_MAPPING_PADOFFSET == param->mapping) ||
         (DF_MAPPING_PADOFFSET_STREAM0 == param->mapping)) &&
        (DF_LINK_ETHERNET != param->linktype))
	{
		dagutil_error("the 'padoffset' mapping can only be used on Ethernet links.\n");
		errorflag++;
	}
	
	/* The hdlc header mapping can only be used if the link is POS. */
	if (((DF_MAPPING_HDLC_HEADER == param->mapping) ||
         (DF_MAPPING_HDLC_HEADER_STREAM0 == param->mapping)) &&
        ((DF_LINK_POS4_CHDLC != param->linktype) &&
         (DF_LINK_POS4_PPP != param->linktype)))
	{
		dagutil_error("the 'hdlcheader' mapping can only be used on 4-byte PoS (CHDLC or PPP) links.\n");
		errorflag++;
	}

	if (errorflag)
	{
		print_usage(clarg);
		exit(EXIT_FAILURE);
	}

	if (param->initialise)
	{
		dagutil_verbose("will initialise the DAG card before loading new filters.\n");

		/* Ensure ports and rulesets options are valid. */
		if (param->init_interfaces == 0)
		{
			dagutil_verbose("--init-ifaces was not specified, assuming one interface.\n");
			param->init_interfaces = 1;
		}
		else if (param->init_interfaces > FILTER_LOADER_MAX_INTERFACES)
		{
			dagutil_verbose("argument to --init-ifaces was too large (must be in [1,%u]).\n",
                            FILTER_LOADER_MAX_INTERFACES);
			errorflag++;
		}
		else
		{
			dagutil_verbose("will configure the coprocessor to work with %u _port(s).\n",
                            param->init_interfaces);
		}

		if (param->init_rulesets == 0)
		{
			dagutil_verbose("--init-rulesets was not specified, assuming one ruleset.\n");
			param->init_rulesets = 1;
		}
		else
		{
			dagutil_verbose("will configure the coprocessor to work with %u ruleset(s) per _port.\n",
                            param->init_rulesets);
		}
	
		if (param->drop)
		{
			dagutil_verbose("WILL drop packets that are routed to a full receive stream.\n");
		}
		else
		{
			dagutil_verbose("will NOT drop packets that are routed to a full receive stream.\n");
		}
	}
	else
	{
		dagutil_verbose("will hot-swap the new filters with the existing ruleset (if the _ports were configured with more than one ruleset).\n");

		/* Ensure _ports and rulesets options have not been given. */
		if (param->init_interfaces != 0)
		{
			dagutil_error("--init-ifaces is only valid when initialising the coprocessor.\n");
			errorflag++;
		}

		if (param->init_rulesets != 0)
		{
			dagutil_error("--init-rulesets is only valid when initialising the coprocessor.\n");
			errorflag++;
		}
	}

	if (param->snaplength)
	{
		dagutil_verbose("default snaplength set to %u bytes.\n", param->snaplength);
	}

	if (-1 == param->_port)
	{
		dagutil_verbose("will apply the new filters to both interfaces.\n");
	}
	else
	{
		dagutil_verbose("will apply the new filters to interface %u.\n", param->_port);
	}

	if (gDeviceName)
	{
		dagutil_verbose("writing filters to '%s'.\n", gDeviceName);
		
		gDagfd = dag_open((char*) gDeviceName);
		if (-1 == gDagfd)
		{
			dagutil_error("could not open DAG card '%s': %s\n", gDeviceName, strerror(errno));
			exit(EXIT_FAILURE);
		}
	}
	else
	{
		dagutil_error("no DAG card specified to program.\n");
		errorflag++;
	}

    dagclarg_dispose(clarg);
}
