/*
 * Copyright (c) 2002-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: dagconvert.c 12512 2010-03-08 21:24:12Z jomi.gregory $
 *
 * Takes network traffic traces of various formats, allows filtering and
 * packet manipulations to be performed and writes modified traces out in
 * various formats.
 */

/* dagconvert headers. */
#include "utils.h"
#include "inputs.h"
#include "outputs.h"


/* CVS Header */
static const char* const kCvsHeader __attribute__ ((unused))  = "$Id: dagconvert.c 12512 2010-03-08 21:24:12Z jomi.gregory $";
static const char* const kRevisionString = "$Revision: 12512 $";


/* Exported via inputs.h. */
uint64_t gRotateSize = 0;
//0 meens no limit  
int gFileNumber = 0;
int gFcsBits = 32;
int gErfOutputIface = -1;
int gErfOutputType = 4;
int gOutputDataLinkType = DLT_NULL;
pcap_record_t * gPcapRecord = NULL;
uint8_t gUseTempOutputFiles= 0;
/* Array to hold the input file names. */
char gInpArray[MAXCNT][100];

/* Array to hold the input files' pointers. */
FILE *gInputFiles[MAXCNT];

/* Array to hold the pcap packet capture descriptor's pointer. */
pcap_t *gPcapDescriptors[MAXCNT];

/* Pointer to hold list of dag records (usually first record) from each file. */
void *gInpRecord;

/* Counter variable to hold number of input files. */
int gFileCount = 0;


/*
 * enumerates the various I/O types currently supported 
 */
enum types
{
	NONE = 0,
	ERF,
	DAG,
	PCAP,
	PRT,
	ATM,
	ETH,
	POS,
	NUL
};

static const char * uTypeStrings[] =
{
	"NONE",
	"ERF",
	"DAG",
	"PCAP",
	"PRT",
	"ATM",
	"ETH",
	"POS",
	"NULL"
};


/* Types used to determine which io functions to call. */
static enum types uInputType = NONE;
static enum types uOutputType = NONE;

/* ERF type to be converted */
static int8_t uErfType = -1;

/* Seconds to spend capturing data - default (<= 0) is unlimited. */
static int uCaptureSeconds = 0;

/* I/O device specific variables. */
static int uSnapLength = NO_SNAP_LIMIT;
static int uVariableLength = -1;
static int uRecordAlignment;

/* The arguments passed to the DAG card. */
static char * uExtraArgs;

/* Which ERF errors should be filtered. */
static uint8_t uFilterOptions = 0;

/* Set by signal handler to indicate processing should be stopped. */
static int uStopProcessing = 0;

/* Masks for bit positions in ERF flags byte. */
#define FILTER_TRUNC_MASK 0x08
#define FILTER_RX_MASK 0x10
#define FILTER_DS_MASK 0x20

/* Which interfaces to accept packets from. */
static int uInterfacePermit = 0;

/* Each interface is a bit in uInterfacePermit. */
#define INTERFACE_A 1
#define INTERFACE_B 2
#define INTERFACE_C 4
#define INTERFACE_D 8

/* BPF filter. */
static struct bpf_insn *uBpfInstructions = NULL;
static char* uFilterString = NULL;

/* Counter variables used to hold statistics by the report function. */
static uint64_t uInCount;
static uint64_t uOutCount;
static uint64_t uByteCount;

static uint32_t uSeconds = 0;


/* A link to the report alarm handler if it exists. */
static void (*uAlarmHandler) (int);



/* Internal routines. */
static void print_version(void);
static void print_usage(void);
static void anysig(int sig);
static void alarm_second_sig(int sig);
static void set_type(enum types *type, enum types value);
static void set_types(char *type_string);
static void close_devices(void);


/* Implementation of internal routines. */
static void
print_version(void)
{
	printf("dagconvert (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
#ifdef HAVE_PCAP_LIB_VERSION
	printf("using: %s\n", pcap_lib_version());
#endif
}


static void
print_usage(void)
{
	print_version();

	/* FIXME: not yet implemented.
	   dagclarg_display_usage(clarg, stdout);
	*/
	
	printf("dagconvert - Endace DAG file conversion utility.\n");
	printf("Usage: dagconvert [options] [extra-args]\n");
	printf("    -d <device>            DAG device name\n");
	printf("    -h,--help,--usage      display help (this page)\n");
	printf("    -v,--verbose           increase verbosity\n");
	printf("    --version              display version information\n");
	printf("    -i <filename>          input file(s)\n");
	printf("                               The redirection operator '<' can also be used for single file\n");
	printf("                               input, provided the input is not of PCAP format.\n");
	printf("                               To select multiple input files, use this option repeatedly.\n");
	printf("    -o <filename>          output file\n");
	printf("    -r N[k|m|g|t]          change output file after N Bytes.\n");
	printf("                           k, m, g, t suffixes for kilobytes, megabytes, gigabytes, terabytes.\n");
	printf("    --fnum                 Maximum Files before the counter overlaps .\n");
	printf("    -s <snaplen>           output snap length\n");
	printf("    -t <seconds>           capture period in seconds\n");
	printf("    -T <in_fmt:out_fmt>    input and output format (see list of format below)\n");
	printf("    -e <type>              conversion erf type (ERF only ; see list of types below))\n");
	printf("    -A <int>               output record alignment (ERF only)\n");
	printf("    -V                     select variable length output (ERF only)\n");
	printf("    -F                     select fixed length output (ERF only)\n");
	printf("    -G                     specify GMT offset in seconds (pcap only)\n");
	printf("    -c 0|16|32             specify number of bits in FCS checksum (pcap only)\n");
	printf("    -f <list>              comma separated list of filters (see list of filters below)\n");
	printf("    -b <BPF>               specify a BPF style filter\n");
	printf("    -p 0|1|2|3             specify an interface to write into output ERF records\n");
	printf("    -u 4|6                 specify the output ERF type IPv4/IPv6 for DLT_RAW\n");
	printf("    -U                     Use temporary names to indicate current output file  \n");
	printf("    -y <DLT>               specify which DLT to output (pcap only ; see list of DLTs below)\n");
    	printf("    -a <api>               specify which API to use\n\t\t\t\t\t0: DAG 2.4 legacy API [dag_offset()]\n\t\t\t\t\t1: DAG 2.5 API [dag_advance_stream()](default value)\n\t\t\t\t\t2: DAG 2.5 API [dag_rx_stream_next_record()]\n");
	printf("\n");
	printf("Supported Input/Output format:\n");
	printf("    dag      ERF direct from DAG device (input only)\n");
	printf("    erf      ERF (extensible record format) file (input and output)\n");
	printf("    atm      legacy ATM file (input only)\n");
	printf("    eth      legacy Ethernet file (input only)\n");
	printf("    pos      legacy PoS file (input only)\n");
	printf("    null     produces no input or output\n");
	printf("    pcap     libpcap format file (input and output)\n");
	printf("    prt      ASCII text packet dump (output only)\n");
	printf("\n");
	printf("Supported filters:\n");
	printf("    rx       filter out rx errors (link layer)\n");
	printf("    ds       filter out ds errors (framing)\n");
	printf("    trunc    filter out truncated packets\n");
	printf("    a,b,c,d  filter on indicated interface(s)\n");
	printf("\n");
	printf("Supported conversion ERF types:\n");
	printf("    pos  	(eth  ->  pos)       \n");
	printf("    ipv4/ipv6	(eth  ->  ipv4/ipv6)  \n");
	printf("    eth  	(ipv4/ipv6 ->  eth) \n\n");
	printf("\n");
	printf("Supported DLT types (case insensitive):\n");
	printf("    EN10MB      Ethernet\n");
#if defined(DLT_DOCSIS)
	printf("    DOCSIS      Ethernet\n");
#endif
	printf("    CHDLC       HDLC\n");
	printf("    PPP_SERIAL  HDLC\n");
	printf("    FRELAY      HDLC\n");
#if defined(DLT_MTP2)
	printf("    MTP2        HDLC\n");
#endif
	printf("    ATM_RFC1483 ATM, AAL5\n");
	printf("    SUNATM      ATM, AAL5\n");
	printf("    RAW         IPV4, IPV6\n");
#if defined(DLT_IPV4)
	printf("    IPV4        IPV4\n");
#endif
#if defined(DLT_IPV6)
	printf("    IPV6        IPV6\n");
#endif
	printf("\n");
	printf("If the optional <extra-args> are present, they are passed through to the DAG card.\n");
}


static void
anysig(int sig)
{
	uStopProcessing = 1;
}


static void
alarm_second_sig(int sig)
{
	uSeconds++;
}

#if defined(_WIN32)
/*Windows does not have strcasecmp*/
int
strcasecmp(const char *s1, const char *s2)
{
	return stricmp(s1,s2);
}
#endif

/*
 * Private function used to set input and output types.  It tests to see that
 * a value has not already been set to some other type - if it has then a
 * panic message is displayed. 
 */
static void
set_type(enum types *type, enum types value)
{
	if (*type == NONE || *type == value)
	{
		*type = value;
	}
	else
	{
		dagutil_panic("Multiple %s types specified:\n"
			      "    Current type is %s, desired type is %s.\n"
			      "    When capturing from a DAG card, input type must be 'dag'.\n",
			      type == &uInputType ? "input" : "output", uTypeStrings[uInputType], uTypeStrings[value]);
	}
}

/*
 * Parses the given type_string obatined from the -T option and sets the input
 * and output types based on its contents.  A panic message is displayed if an
 * illegal/unknown type keyword is detected.
 */
static void
set_types(char *type_string)
{
	char * type_state = NULL;
	char * arg;
	char * input = NULL;
	char * output = NULL;

	if (type_string == NULL)
	{
		dagutil_panic("Type argument is NULL\n");
	}
	arg = strtok_r(type_string, ":", &type_state);
	if (arg == NULL)
	{
		dagutil_verbose("No input or output type given, using defaults.\n");
	}

	if (type_string[0] != ':')
	{
		input = arg;
		output = strtok_r(NULL, ":", &type_state);
	}
	else
	{
		output = arg;
	}

	if (input == NULL)
	{
		/* Do nothing. */
	}
	else if (strcasecmp(input, "ERF") == 0)
	{
		set_type(&uInputType, ERF);
	}
	else if (strcasecmp(input, "DAG") == 0)
	{
		set_type(&uInputType, DAG);
	}
	else if (strcasecmp(input, "PCAP") == 0)
	{
		set_type(&uInputType, PCAP);
	}
	else if (strcasecmp(input, "ATM") == 0)
	{
		set_type(&uInputType, ATM);
	}
	else if (strcasecmp(input, "ETH") == 0)
	{
		set_type(&uInputType, ETH);
	}
	else if (strcasecmp(input, "POS") == 0)
	{
		set_type(&uInputType, POS);
	}
	else if (strcasecmp(input, "NULL") == 0)
	{
		set_type(&uInputType, NUL);
	}
	else
	{
		dagutil_panic("Illegal input type '%s'.\n", input);
	}
	dagutil_verbose("Input type set to %s.\n", uTypeStrings[uInputType]);

	if (output == NULL)
	{
	}
	else if (strcasecmp(output, "ERF") == 0)
	{
		set_type(&uOutputType, ERF);
	}
	else if (strcasecmp(output, "PCAP") == 0)
	{
		set_type(&uOutputType, PCAP);
	}
	else if (strcasecmp(output, "PRT") == 0)
	{
		set_type(&uOutputType, PRT);
		dagutil_warning("PRT deprecated, use: dagbits -vv -f filename print OR dagbits -vv -d dag0 print\n");
	}
	else if (strcasecmp(output, "NULL") == 0)
	{
		set_type(&uOutputType, NUL);
	}
	else
	{
		dagutil_panic("Illegal output type '%s'.\n", output);
	}
	dagutil_verbose("Output type set to %s.\n", uTypeStrings[uOutputType]);
}

/*
 * Set filter options. Expects a comma separated
 * list of filters.
 */
static void
set_filters(char *filters)
{
	char * filter_state;
	char * arg;

	if (filters == NULL || strlen(filters) == 0)
		dagutil_panic("Bad input to set_filters\n");

	uFilterOptions = 0;

	arg = strtok_r(filters, ",", &filter_state);
	if (arg == NULL)
		return;

	while (arg)
	{
		if (strcasecmp(arg, "rx") == 0)
			uFilterOptions |= FILTER_RX_MASK;
		else if (strcasecmp(arg, "ds") == 0)
			uFilterOptions |= FILTER_DS_MASK;
		else if (strcasecmp(arg, "trunc") == 0)
			uFilterOptions |= FILTER_TRUNC_MASK;
		else if (strcasecmp(arg, "a") == 0)
			uInterfacePermit |= INTERFACE_A;
		else if (strcasecmp(arg, "b") == 0)
			uInterfacePermit |= INTERFACE_B;
		else if (strcasecmp(arg, "c") == 0)
			uInterfacePermit |= INTERFACE_C;
		else if (strcasecmp(arg, "d") == 0)
			uInterfacePermit |= INTERFACE_D;
		else
			dagutil_warning("unknown filter %s\n", arg);

		arg = strtok_r(NULL, ",", &filter_state);
	}

	dagutil_verbose("Filter mask set to %d\n", uFilterOptions);
	dagutil_verbose("Interface filter mask set to %d\n", uInterfacePermit);
}

/*
 * A post setup function that sets input and output types to a default value
 * if they have not been set already.
 */
static void
check_types_set(void)
{
	if (uInputType == NONE)
	{
		uInputType = ERF;
	}
	
	if (uOutputType == NONE)
	{
		uOutputType = ERF;
	}
}

/*
 * Parses the given length_string to an int and sets the snap length to that
 * value.
 */
static void
set_snap_length(char *length_string)
{
	char * end;
	int value = strtol(length_string, &end, 10);

	if (*end != '\0')
	{
		dagutil_panic("Could not convert '%s' to an integer.\n", length_string);
	}
	
	if (value < -1)
	{
		value = NO_SNAP_LIMIT;
	}
	uSnapLength = value;

	dagutil_verbose("Snap length set to %d\n", uSnapLength);
}

/*
 * Parses the given alignment_string to an int and sets the record alignment to that
 * value.
 */
static void
set_record_alignment(char *alignment_string)
{
	char* end;
	int value = strtol(alignment_string, &end, 10);

	if (*end != '\0')
	{
		dagutil_panic("Could not convert '%s' to an integer.\n", alignment_string);
	}
	
	if (value < 1)
	{
		value = 1;
	}
	
	uRecordAlignment = value;

	dagutil_verbose("Output record alignment set to %d\n", uRecordAlignment);
}

static void
set_dag_api_version(char *version_string)
{
    char* end;
    int value = strtol(version_string, &end, 10);
    
    if (*end != '\0')
    {
        dagutil_panic("Could not convert api version '%s' to an integer.\n", version_string);
    }
    
    if ( (value < 0 ) || ( value > 2 ) )
    {
        dagutil_panic("Input API: invalid (%d).Valid values are:\n\t\t\t\t\t0: DAG 2.4 legacy API [dag_offset()]\n\t\t\t\t\t1: DAG 2.5 API [dag_advance_stream()]\n\t\t\t\t\t2: DAG 2.5 API [dag_rx_stream_next_record()]\n", value);
    }
    
    set_capture_api_version(value);
    
    dagutil_verbose("Capture API version set to %d\n",value );
}
/*
 * Set the length of the FCS checksum.
 * value.
 */
static void
set_fcs_bits(char *fcs_string)
{
	char* end;
	int value = strtol(fcs_string, &end, 10);

	if (*end != '\0')
	{
		dagutil_panic("Could not convert '%s' to an integer.\n", fcs_string);
	}
	
	gFcsBits = value;
	if (value != 0 && value != 16 && value != 32)
		dagutil_warning("Strange CRC bit length specified, continuing anyway.\n");

	dagutil_verbose("FCS length set to %d\n", gFcsBits);
}

static void
set_capture_time(char *time_string)
{
	char* end;
	int value = strtol(time_string, &end, 10);

	if (*end != '\0')
	{
		dagutil_panic("Could not convert '%s' to an integer.\n", time_string);
	}
	if (value < 0)
	{
		value = 0;
	}
	uCaptureSeconds = value;

	dagutil_verbose("Capture time set to %d seconds.\n", uCaptureSeconds);
}

static void
set_erf_output_iface(int iface)
{
	gErfOutputIface = iface;

	dagutil_verbose("Output interface for ERF records will be set to %d.\n", gErfOutputIface);
}

static void
set_erf_output_type(int type)
{
	gErfOutputType = type;

	dagutil_verbose("Output type for ERF records will be set to IPv%d.\n", gErfOutputType);
}

/*
 * Opens (dag)input and output files if the input and output types are
 * appropriate.  If in_file_name is null then stdin is used.  If out_file_name
 * is null the stdout is used.
 */
static void
open_devices(char *in_file_name, int dagstream, char *out_file_name)
{
	if (in_file_name && strcmp(in_file_name, "-") == 0)
	{
		in_file_name = NULL;
	}

	if (uInputType == DAG) 
	{
		set_dag_input(in_file_name, dagstream, uExtraArgs);
		dagutil_verbose("Set input device to stdin.\n");
	}

	if (out_file_name && strcmp(out_file_name, "-") == 0)
	{
		out_file_name = NULL;
	}
	
	switch (uOutputType)
	{
	case ERF:
		set_erf_output(out_file_name);
		break;
		
	case PCAP:
		set_pcap_output(out_file_name);
		break;
		
	case PRT:
		set_prt_output(out_file_name);
		break;
		
	case NUL:
		set_null_output(out_file_name);
		break;
		
	default:
		dagutil_panic("No output type set.\n");
		break;
	}
	
	dagutil_verbose("Set output file to %s.\n", out_file_name == NULL ? "stdout" : out_file_name);
}

/*
 * Closes the input and output devices by calling appropriate device specific
 * functions.
 */
static void
close_devices(void)
{
	if (uInputType == DAG)
		close_dag_input(dagutil_get_verbosity());

	switch (uOutputType)
	{
	case ERF:
		close_erf_output();
		break;
			
	case PCAP: 
		close_pcap_output();
		/* multichannel dumpers are closed in dagconvert_main */
		break;
			
	case PRT:
		close_prt_output();
		break;
			
	case NUL:
		close_null_output();
		break;
			
	default:
		dagutil_panic("No output type set.\n");
		break;
	}

	dagutil_verbose("\nRecords Processed: %"PRIu64" (%"PRIu64") Bytes Processed: %"PRIu64"\n", uInCount, uOutCount, uByteCount);
	dagutil_verbose("Closed input and output.\n");
}


/*
 * Called before processing occurs to set any parameters for input and output
 * devices.
 */
static void
set_parameters(void)
{
	switch (uInputType)
	{
	case ERF:
	case DAG:
	case PCAP:
	case ATM:
	case ETH:
	case POS:
	case NUL:
		break;

	default:
		dagutil_panic("No input type set.\n");
		break;
	}

	switch (uOutputType)
	{
	case ERF:
		set_erf_snap_length(uSnapLength, uVariableLength);
		set_erf_record_alignment(uRecordAlignment);
		break;
		
	case PCAP:
		set_pcap_snap_length(uSnapLength);
		break;
		
	case PRT:
	case NUL:
		break;
		
	default:
		dagutil_panic("No output type set.\n");
		break;
	}
}

static void
set_output_erf_type(char *erftype)
{
	if(strcasecmp(erftype, "pos") == 0)
	{
		uErfType = ERF_TYPE_HDLC_POS;
	}
	else if(strcasecmp(erftype, "ipv6") == 0)
	{
		uErfType = ERF_TYPE_IPV6;
	}
	else if(strcasecmp(erftype, "ipv4") == 0)
	{
		uErfType = ERF_TYPE_IPV4;
	}	
	else if(strcasecmp(erftype, "eth") == 0)
	{
		uErfType = ERF_TYPE_ETH;
	}
	else
	{
		dagutil_error("unsupported ERF type \n");
		print_usage();
		return;
	}
}

static void
set_output_dlt_type(char *dlttype)
{
	if (dlttype == NULL)
	{
		/* Do nothing. */
	}
	else if (strcasecmp(dlttype, "EN10MB") == 0)
	{
		gOutputDataLinkType = DLT_EN10MB;
	}
#if defined(DLT_DOCSIS)
	else if (strcasecmp(dlttype, "DOCSIS") == 0)
	{
		gOutputDataLinkType = DLT_DOCSIS;
	}
#endif
	else if (strcasecmp(dlttype, "CHDLC") == 0)
	{
#if defined(__NetBSD__)
		gOutputDataLinkType = DLT_HDLC;
#else
		gOutputDataLinkType = DLT_CHDLC;
#endif
	}
	else if (strcasecmp(dlttype, "PPP_SERIAL") == 0)
	{
		gOutputDataLinkType = DLT_PPP_SERIAL;
	}
	else if (strcasecmp(dlttype, "FRELAY") == 0)
	{
		gOutputDataLinkType = DLT_FRELAY;
	}
#if defined(DLT_MTP2)
	else if (strcasecmp(dlttype, "MTP2") == 0)
	{
		gOutputDataLinkType = DLT_MTP2;
	}
#endif
	else if (strcasecmp(dlttype, "ATM_RFC1483") == 0)
	{
		gOutputDataLinkType = DLT_ATM_RFC1483;
	}
	else if (strcasecmp(dlttype, "SUNATM") == 0)
	{
		gOutputDataLinkType = DLT_SUNATM;
	}
	else if (strcasecmp(dlttype, "RAW") == 0)
	{
		gOutputDataLinkType = DLT_RAW;
	}
#if defined(DLT_IPV4)
	else if (strcasecmp(dlttype, "IPV4") == 0)
	{
		gOutputDataLinkType = DLT_IPV4;
	}
#endif
#if defined(DLT_IPV6)
	else if (strcasecmp(dlttype, "IPV6") == 0)
	{
		gOutputDataLinkType = DLT_IPV6;
	}
#endif
	else
	{
		dagutil_panic("Unrecognized output DLT '%s'.\n", dlttype);
	}
	dagutil_verbose("Output DLT set to %s.\n", dlttype);
}

/*
 * Generates a simple single line stats report to stderr about the packets
 * that have passed through the system.
 */
static void
report(void)
{
	static struct timeval last;
	static uint64_t last_in_count;
	static uint64_t last_out_count;
	static uint64_t last_byte_count;
	
	struct timeval now;
	struct timeval time_diff;
	double in_rate;
	double out_rate;
	double byte_rate = 0;
	double second;

	gettimeofday(&now, NULL);
	if (0 == timerisset(&last))
	{
		/*
		 * Initial call 
		 */
		last = now;
		return;
	}

	if (dagutil_get_verbosity() >= 1)
	{
		uint64_t in_count_now = uInCount;
		uint64_t out_count_now = uOutCount;
		uint64_t byte_count_now = uByteCount;

		timersub(&now, &last, &time_diff);

		second = (double) time_diff.tv_sec +  ((double) time_diff.tv_usec / 1000000.0);
		in_rate = (double) (in_count_now - last_in_count) / second;
		out_rate = (double) (in_count_now - last_in_count) / second;
		byte_rate = (double) (byte_count_now - last_byte_count) / second;

		fprintf(stderr, "Rate: %.1f (%.1f) rec/s %.1f MB/s Total: %"PRIu64" (%"PRIu64") recs %.1f MB     \r",
			in_rate, out_rate, byte_rate / 1048576.0, in_count_now,
			out_count_now,
			(double) byte_count_now / (1024.0 * 1024.0));

		last_in_count = in_count_now;
		last_out_count = out_count_now;
		last_byte_count = byte_count_now;
		last = now;
	}
}


static void
alarm_report_sig(int sig)
{
	/*
	 * call second alarm handler if it exists 
	 */
	if (uAlarmHandler && uAlarmHandler != SIG_DFL)
		uAlarmHandler(sig);
	
	report();
}

/*
 * Catches alarm signals (send by the one second timer) and decrements the
 * uCaptureSeconds.  When the capture time is less than or equal to zero the
 * _keep_going flag is reset.
 */
static void
alarm_capture_sig(int sig)
{
	/*
	 * call second alarm handler if it exists 
	 */
	if (uAlarmHandler && uAlarmHandler != SIG_DFL)
		uAlarmHandler(sig);
	
	report();

	uCaptureSeconds--;
	if (uCaptureSeconds <= 0)
	{
		uStopProcessing = 1;
	}
}

// Time signal handling
/* 
   Windows does not have a timer signal in the same form as 
   Unix (i.e. no SIGALRM) therefore a more Windows based 
   approach is required for setting up reporting and 
   termination after a given runtime
*/
#if defined(_WIN32)
VOID CALLBACK
time_handler(LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue){

	//	if (uAlarmHandler != NULL && uAlarmHandler != SIG_DFL)
	//		uAlarmHandler(sig);
	report();

	if (uCaptureSeconds > 0)
	{
		uCaptureSeconds--;
		if (uCaptureSeconds <= 0)
		{
			uStopProcessing = 1;
		}
	}
}
#endif /* _WIN32 */


/*
 * Adds the capture alarm handler.
 */
static void
add_alarm_handler(void)
{
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	struct sigaction sigact;
	struct sigaction sigactold;
        memset(&sigact, 0, sizeof(sigact));
	memset(&sigactold, 0, sizeof(sigact));

	sigact.sa_handler = (uCaptureSeconds >= 1) ? alarm_capture_sig : alarm_report_sig;

	sigact.sa_flags = SA_RESTART;
	if (sigaction(SIGALRM, &sigact, &sigactold) < 0)
		dagutil_panic("sigaction SIGALRM: %s\n", strerror(errno));
	/*
	 * set second handler 
	 */
	uAlarmHandler = sigactold.sa_handler;

#elif defined(_WIN32)

	/* Create a  timer. */
	if (SetTimer(NULL, 34, 1000, (TIMERPROC)time_handler)==0)
		dagutil_panic("Set Timer failed (%d)\n", GetLastError());

#endif /* Platform-specific code. */
}

/*
 * This function is called to perform BPF filtering on packet data.
 * If the supplied expression has not yet been compiled, then it
 * is compiled. The same compiled code is them applied on subsequent
 * calls. The return value is the result of calling the underlying
 * bpf_filter function.
 *
 * The input and output formats may not be pcap, so create pcap headers
 * if needed.
 *
 * This was mostly taken from dagfilter.c.
 */
static int
bpffilter(dag_record_t * header, void *payload)
{

	if (uFilterString && (NULL == uBpfInstructions))
	{
		struct bpf_program fcode;
		int snapshot = 0;
		int linktype = 0;
		pcap_t *p;
		
		dagutil_verbose("Compiling BPF Filter\n");

		/* Select compile parameters based on link type. */
		get_dlt(header, &linktype, &snapshot);

		p = pcap_open_dead(linktype, snapshot);
		if (p == NULL)
		{
			dagutil_panic("pcap_open_dead: out of memory\n");
		}

		/* Compile with optimization and no netmask. */
		if (pcap_compile(p, &fcode, uFilterString, 1, 0))
			dagutil_panic("bpf compilation error: %s\n", pcap_geterr(p));

		pcap_close(p);

		/* Store the compiled code. */
		uBpfInstructions = fcode.bf_insns;
	}

	/* The bpf filter needs a pcap format record.
	 * This can come directly from a pcap input file, otherwise we need to
	 * create one from the (possibly pseudo) ERF header info.
	 */
	if(gPcapRecord == NULL) {
		create_pcap_record(header, payload);
	}

	/*
	 * We currently ignore the value of the filter return code, this
	 * could be changed with future variable length packet structures.
	 */
	return bpf_filter(uBpfInstructions, gPcapRecord->pkt, gPcapRecord->pkthdr.len, gPcapRecord->pkthdr.caplen);
}


/*
 * The heart of dagconvert.  Reads ERF headers and payloads from an input
 * reader, applies any filtering and sends (possibly modified) ERF headers and
 * payloads to output writer.
 */
static void
process(void)
{
	dag_record_t * header;
	void * payload;
	void * new_payload;

	dag_record_t * (*get_next_header) ();
	void * (*get_payload) ();

	int (*write_record) (dag_record_t *, void *);

#if defined(_WIN32)
	MSG msg;
#endif /* _WIN32 */

	/* Choose output method based on output type. */
	switch (uInputType)
	{
	case ERF:
		get_next_header = &get_next_erf_header;
		get_payload = &get_erf_payload;
		break;
		
	case DAG:
		get_next_header = &get_next_dag_header;
		get_payload = &get_dag_payload;
		break;
		
	case PCAP:
		get_next_header = &get_next_pcap_header;
		get_payload = &get_pcap_payload;
		break;
		
	case ATM:
	case ETH:
	case POS:
		get_next_header = &get_next_legacy_header;
		get_payload = &get_legacy_payload;
		break;
		
	case NUL:
		get_next_header = &get_next_null_header;
		get_payload = &get_null_payload;
		break;
		
	default:
		dagutil_panic("No input type set.\n");
		break;
	}

	/* Choose output method based on output type. */
	switch (uOutputType)
	{
	case ERF:
		write_record = &write_erf_record;
		break;
		
	case PCAP:
		write_record = &write_pcap_record;
		break;
		
	case PRT:
		write_record = &write_prt_record;
		break;
		
	case NUL:
		write_record = &write_null_record;
		break;
		
	default:
		dagutil_panic("No output type set.\n");
		break;
	}
		
	uInCount = 0;
	uOutCount = 0; 
	uByteCount = 0;

	header = get_next_header(); 
   
    while (!uStopProcessing)
    {
        if (header) 
        {
            int rlen;
    
            payload = get_payload();
            uInCount++;
    
            rlen = ntohs(header->rlen);
            if (rlen == 20)
            {
                /* DS error truncates the packet, but the packet has effectively been padded to 28 bytes by the card. */
                rlen = 28;
            }
            uByteCount += rlen;
            /*
            * apply any filters here 
            */
    
            /*
            * filter based on ERF flags 
            * for efficiency the dag_header is cast to an anonymous
            * structure which replaces the bitfields of dag_header_t
            * with an u_char
            */
            if (uFilterOptions && (((struct {
                uint64_t ts;
                unsigned char type;
                unsigned char flags;}
                        *)header)->
                        flags & uFilterOptions))
            {
                /* Filter suggests packet should be discarded. */
            }
            else if (uFilterString && !bpffilter(header, payload))
            {
                /* Filter suggests packet should be discarded. */
            }
            else if ((0 == uInterfacePermit) || (uInterfacePermit & (1 << header->flags.iface)))
            {
                /*
                * Accept packet if no particular interface was
                * selected or if packet matches one of the selected
                * interfaces.
                */
                uOutCount++;
    
                if((uOutputType == ERF) && (uErfType >= 0))
                {
                    format_erf(header, payload, uErfType, &new_payload);
                }

		/* Due to the conversion from ipv4 to ethernet needs change 
 	         * the size of payload. the conditon checking is introduced 
		 * that write new ethernet header into the files.  
		 */
    		if((uOutputType == ERF) && (uErfType == ERF_TYPE_ETH))
		{	
			if (write_record(header, new_payload) == 0)
                    		dagutil_panic("Failed to write data to output.\n");

			if (new_payload)
				free(new_payload); 
		}
		else 
		{
                	if (write_record(header, payload) == 0)
                    		dagutil_panic("Failed to write data to output.\n");
		}
            }
          #if defined(_WIN32)
            /* Check for timer messages for reporting and timer controlled termination */
            while (PeekMessage(&msg, NULL, 0, 0, PM_QS_POSTMESSAGE | PM_REMOVE) != 0)
            {
                DispatchMessage(&msg);
            }
#endif /*_WIN32 */
        }
        else if ( uInputType != DAG)
        {
            /* if the Input is not DAG and if the header is NULL,it it could be assumed that the input 
             * data is finished. If input is DAG, we will run till the Ctrl+C is pressed,or capture period 
             * completed.
             */
            break;
        }
        
        gPcapRecord = NULL;
        header = get_next_header();
    }
    
}


/*
 * Parses command line arguments setting appropriate parameters in input and
 * output devices and the initiates the processing.
 */
int
dagconvert_main(int argc, char **argv)
{
	int dagstream = 0;
	int opt;
	int len;
	int i;
	int j;
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	int fd1;
#endif

	char dagname_buf[DAGNAME_BUFSIZE];
	char in_file_name[DAGNAME_BUFSIZE];
	char* out_file_name = NULL;
	char* more = NULL;
	char ebuf[100];

	dagutil_set_progname("dagconvert");

	while ((opt = getopt(argc, argv, "A:a:b:c:d:e:f:FG:hi:o:p:u:r:s:t:T:UVv-:y:")) != EOF)
	{
		switch (opt)
		{
		case 'A':
			set_record_alignment(optarg);
			break;
        case 'a':
			set_dag_api_version(optarg);
			break;
			
		case 'b':
			uFilterString = optarg;
			break;
			
		case 'c':
			set_fcs_bits(optarg);
			break;
			
		case 'd':
			set_type(&uInputType, DAG);
			strncpy(dagname_buf, optarg, DAGNAME_BUFSIZE);
			if (-1 == dag_parse_name(dagname_buf, in_file_name, DAGNAME_BUFSIZE, &dagstream))
			{
				dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
			}
			dagutil_verbose("device=%s, stream=%d\n", in_file_name, dagstream);
			break;

		case 'e':
			set_output_erf_type(optarg);
			break;
			
		case 'f':
			set_filters(optarg);
			break;
			
		case 'F':
			uVariableLength = 0;
			break;
			
		case 'G':
			/* GMT offset no longer supported */
			break;
			
		case '?':
		case 'h':
			print_usage();
			return EXIT_SUCCESS;
			break;
			
		case 'i':
			strncpy(in_file_name, optarg, DAGNAME_BUFSIZE);
			if (gFileCount < MAXCNT)
			{
				strcpy(gInpArray[gFileCount], in_file_name);
				gFileCount++;
			}
			break;
			
		case 'o':
			out_file_name = optarg;
			break;
			
		case 'p':
		{
			int output_iface = strtol(optarg, NULL, 0);
					
			if ((0 <= output_iface) && (output_iface <= 3))
			{
				set_erf_output_iface(output_iface);
			}
			else
			{
				dagutil_warning("unrecognised output interface %s\n", optarg);
			}
		}
		break;
			
		case 'u':
		{
			int output_type = strtol(optarg, NULL, 0);
					
			if ((4 == output_type) || (output_type == 6))
			{
				set_erf_output_type(output_type);
			}
			else
			{
				dagutil_warning("unrecognised output type %s\n", optarg);
			}
		}
		break;

		case 'r':
			gRotateSize = strtoul(optarg, &more, 0);
			switch (more[0])
			{
			case '\0':
				break;
						
			case 'T':
			case 't':
				gRotateSize *= ONE_TEBI;
				break;

			case 'G':
			case 'g':
				gRotateSize *= ONE_GIBI;
				break;
						
			case 'M':
			case 'm':
				gRotateSize *= ONE_MEBI;
				break;
						
			case 'K':
			case 'k':
				gRotateSize *= ONE_KIBI;
				break;
						
			default:
				dagutil_warning("unrecognized character '%c' after -r <N> specification\n", more[0]);
				break;
			}
				
			if ((more[0] != '\0') && (more[1] != '\0'))
				dagutil_warning("extra character '%c' after -r <N> specification\n", more[0]);
			break;
			
		case 's':
			set_snap_length(optarg);
			break;
			
		case 't':
			set_capture_time(optarg);
			break;
			
		case 'T':
			set_types(optarg);
			break;
			
		case 'V':
			uVariableLength = 1;
			break;
			
		case 'v':
			dagutil_inc_verbosity();
			dagutil_verbose("Verbose level set to %d\n", dagutil_get_verbosity());
			break;
			
		case 'y':
			set_output_dlt_type(optarg);
			break;
        case 'U':
            gUseTempOutputFiles = 1;
            break;
			
		case '-':
			if (strcmp(optarg, "help") == 0 || strcmp(optarg, "usage") == 0)
			{
				print_usage();
				return EXIT_SUCCESS;
			}
			else if (strcmp(optarg, "verbose") == 0)
			{
				dagutil_inc_verbosity();
				dagutil_verbose("Verbose level set to %d\n", dagutil_get_verbosity());
			}
			else if (strcmp(optarg, "version") == 0)
			{
				print_version();
				return EXIT_SUCCESS;
			}
			 else if (strncmp(optarg, "fnum", 4) == 0)
            {//
            if (strlen(optarg) < 5)
                    dagutil_panic("fnum incorrect. \n");
            if (optarg[4] =='=') 
            {
                gFileNumber = strtoul(optarg+5, &more, 0);
            } else
            {
                gFileNumber = strtoul(optarg+4, &more, 0);
            }
             break;
            }

			else
			{
				dagutil_panic("unknown option '%s', see -h for help on usage\n", optarg);
			}
			break;
			
		default:
			dagutil_panic("unknown option, see -h for help on usage\n");
			break;
		}
	}

	/* Send any extra arguments to DAG if it is the input device. */
	argc -= optind;
	argv += optind;
	len = 0;
	for (opt = 0; opt < argc; opt++)
	{
		len += (int) strlen(argv[opt]) + 1;
	}

	if(len)
	{
		uExtraArgs = (char *) dagutil_malloc(sizeof(char) * len + 1);
		memset(uExtraArgs, 0, sizeof(char) * len + 1);
		for (opt = 0; opt < argc; opt++)
		{
			strcat(uExtraArgs, argv[opt]);
			strcat(uExtraArgs, " ");
		}
		uExtraArgs[len-1] = '\0';
	

		if (strlen(uExtraArgs))
		{
			if (uInputType == DAG)
			{
				dagutil_verbose("Configuring DAG card with '%s'.\n", uExtraArgs);
			}
			else
			{
				dagutil_warning("Unused args: '%s'.\n", uExtraArgs);
			}
		}
	}

	/* Check that input/output types have been set to something. */
	check_types_set();

	switch (uInputType)
	{
	case ERF:
		gInpRecord = dagutil_malloc(sizeof(dag_record_t) * MAXCNT);
		break;
			
	case ATM:
	case ETH:
	case POS:
		gInpRecord = dagutil_malloc(sizeof(legacy_record_t) * MAXCNT);
		break;
		
	case DAG:
	case NUL:
		break;
		
	case PCAP:
		gInpRecord = dagutil_malloc(sizeof(pcap_record_t) * MAXCNT);
			
		for (i = 0; i < gFileCount; i++)
		{
			(((pcap_record_t*)gInpRecord) + i)->pkt = NULL;
		}
			
		if (0 == uRecordAlignment)
			uRecordAlignment = 4;
		break;
			
	default:
		dagutil_panic("No input type set.\n");
		break;
	}

	/* Setup input and output device parameters. */
	set_parameters();

	dagutil_set_signal_handler(anysig);

	/* Start timer that fires every 1 second. */
	dagutil_set_timer_handler(alarm_second_sig, 1);

	/* Add the capture and report alarms. */
	add_alarm_handler();

	/* Initialize the report counters. */
	report();

	if (uInputType == DAG)
	{
		open_devices(in_file_name, dagstream, out_file_name);
	}
	else
	{
		open_devices(NULL, 0, out_file_name);

		for (i = 0; i < gFileCount; i++)
		{
			if (uInputType != PCAP)
			{
#if defined(__linux__) || defined(__FreeBSD__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)

				fd1 = open(gInpArray[i], O_RDONLY|O_LARGEFILE);
			
				if (fd1 == -1) 
					dagutil_panic("Could not open %s for reading.\n", gInpArray[i]);

				gInputFiles[i] =  fdopen(fd1, "r");
			
#elif defined(_WIN32)

				gInputFiles[i] = fopen(gInpArray[i], "rb");

#endif /* Platform-specific code. */

				if (gInputFiles[i] == NULL) 
				{
					for (j = 0; j < i; j++)
					{
						fclose(gInputFiles[j]);
					}
					dagutil_panic("Could not open %s for reading.\n", gInpArray[i]);
				}
			}
			else
			{
				gPcapDescriptors[i] = pcap_open_offline(gInpArray[i], ebuf);
				if (gPcapDescriptors[i] == NULL)
				{
					for (j = 0; j < i; j++)
					{
						pcap_close(gPcapDescriptors[j]);
					}
					dagutil_panic("%s\n",ebuf);
				}
			}
		} /* for */

		if (0 == gFileCount)
		{
			if (uInputType != PCAP)
			{
				gInputFiles[0] = stdin;
				gFileCount = 1;
			}
		}
	}

	/* set up dumper for each channel (MC ERF) */
	alloc_pcap_dumper_table();

	/* Do the processing. */
	process();

	/* clean up dumpers (MC ERF) */
	cleanup_pcap_dumper_table();

	/* Close input and output files. */
	close_devices();

	/* Finished with the extra args at this point. */
	dagutil_free(uExtraArgs);
	dagutil_free(gInpRecord);

	sleep(1);

	return EXIT_SUCCESS;
}


#ifndef ENDACE_UNIT_TEST
int
main(int argc, const char* const * argv)
{
	return dagconvert_main(argc, (char**) argv);
}
#endif /* ENDACE_UNIT_TEST */


/*
 * Return the channel(connection) number of a multichannel ERF record,
 * or -1 if the record is not multichannel.
 */
int get_mc_channel(dag_record_t * header, void * payload) {
	int channel;
	uint32_t mcheader;

	mcheader = (uint32_t)ntohl(*(uint32_t*)payload);
            
	/* get the current channel in case the header is multichannel */
	switch (header->type) {
	case ERF_TYPE_MC_HDLC:
	case ERF_TYPE_COLOR_MC_HDLC_POS:
	case ERF_TYPE_MC_ATM:
	case ERF_TYPE_MC_RAW_CHANNEL:
	case ERF_TYPE_MC_AAL2:  /* not in 'man 5 erf', see
			     * dm_framer_read_mcaal2erf_XML instead */
		channel = mcheader & 0x03ff;
		break;
	case ERF_TYPE_MC_RAW:
		channel = mcheader & 0x000f;
		break;
	case ERF_TYPE_MC_AAL5:
		channel = mcheader & 0x07ff;
		break;
	case ERF_TYPE_AAL2:
		channel = mcheader & 0x00ff;
		break;

	default:
		channel = -1;
	}            

	return channel;
}
